cq_warehouse
cq_warehouse copied to clipboard
Make cq_object optional via sub-classing
By sub-classing all of the objects created with cq_warehouse the use of .cq_object
will become unnecessary and result in a more natural user interface. For example:
flanged_nut = HexNutWithFlange(size="M6-1", fastener_type="din1665")
...
show_object(flanged_nut)
will be possible.
The changes to do this are as follows:
- The class definition must specify the super-class: e.g.
class Nut(ABC,cq.Compound):
. Note that the Nut body is of typeSolid
but once the thread is added it becomes typeCompound
therefore the base class must be determined for all objects. - The
__init__
method must initialize the super class as follows:super().__init__(self._cq_object.wrapped)
- A deprecation warning needs to be placed with all the cq_object property definitions. e.g.
warn("cq_object will be deprecated.", DeprecationWarning, stacklevel=2)
- The appropriate documentation needs to be changed such that use of cq_object is deprecated.
Note that for derived classes, only the base class need to change.
Thread will require more change as currently the actual thread object isn't created until the cq_object property is accessed - for performance reasons when building threaded objects with the simple
option. A new optional parameter simple
may need to be introduced to thread which will create a null thread object.
This doesn't look to be feasible after all. Consider this example:
from cadquery import Solid, Vector
from cadquery.occ_impl.shapes import VectorLike
class ChamferBox(Solid):
def __init__(
self,
length: float,
width: float,
height: float,
chamfer_size: float,
pnt: VectorLike = Vector(0, 0, 0),
dir: VectorLike = Vector(0, 0, 1),
) -> Solid:
box = Solid.makeBox(length, width, height, pnt, dir)
box = box.chamfer(chamfer_size, None, box.Edges())
super().__init__(box.wrapped)
chamfer_box = ChamferBox(1, 1, 1, 0.1)
if "show_object" in locals():
show_object(chamfer_box)
print(
f"{type(chamfer_box)=},{isinstance(chamfer_box, Solid)=},{chamfer_box.isValid()=}"
)
which generates:
type(chamfer_box)=<class '__main__.ChamferBox'>,isinstance(chamfer_box, Solid)=True,chamfer_box.isValid()=True
However, if a one does translated_chamfer_box = chamfer_box.translate((1, 0, 0))
the following traceback is generated:
Traceback (most recent call last):
File "/home/roger/Documents/Bugs/subclass.py", line 27, in <module>
translated_chamfer_box = chamfer_box.translate((1, 0, 0))
File "/home/roger/anaconda3/envs/cqJuly/lib/python3.9/site-packages/cadquery/occ_impl/shapes.py", line 892, in translate
return self._apply_transform(T)
File "/home/roger/anaconda3/envs/cqJuly/lib/python3.9/site-packages/cadquery/occ_impl/shapes.py", line 852, in _apply_transform
return self.__class__(BRepBuilderAPI_Transform(self.wrapped, Tr, True).Shape())
TypeError: __init__() missing 3 required positional arguments: 'width', 'height', and 'chamfer_size'
The problem seems to be how _apply_transform()
(and many other methods) is/are implemented - it/they take apart the object and rebuild it with the object's Shape()
which works for the base classes but with a sub-classed object Shape()
doesn't provide sufficient information to recreate the object.
def _apply_transform(self: T, Tr: gp_Trsf) -> T:
return self.__class__(BRepBuilderAPI_Transform(self.wrapped, Tr, True).Shape())
In addition, there appears to be no way to subclass an Assembly.
It seems as though significant development in the cadquery direct API layer would need to be done to support sub-classing.