163 lines
5.3 KiB
Python
163 lines
5.3 KiB
Python
import cadquery
|
|
import six
|
|
from copy import copy
|
|
|
|
from .params import Boolean
|
|
from .display.material import (
|
|
RenderParam,
|
|
TEMPLATE as RENDER_TEMPLATE,
|
|
)
|
|
from .errors import MakeError, ParameterError, AssemblyFindError
|
|
from .constraint import Constraint, Mate
|
|
|
|
from .utils.geometry import CoordSystem
|
|
|
|
import logging
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
from .component import Component
|
|
|
|
class Part(Component):
|
|
|
|
# Parameters common to every Part
|
|
_simple = Boolean(False, doc="if set, simplified geometry is built")
|
|
_render = RenderParam(RENDER_TEMPLATE['default'], doc="render properties")
|
|
|
|
def __init__(self, *largs, **kwargs):
|
|
super(Part, self).__init__(*largs, **kwargs)
|
|
|
|
# Initializing Instance State
|
|
self._local_obj = None
|
|
self._world_obj = None
|
|
|
|
def make(self):
|
|
"""
|
|
Create and return solid part
|
|
|
|
:return: cadquery.Workplane of the part in question
|
|
:rtype: subclass of :class:`cadquery.CQ`, usually a :class:`cadquery.Workplane`
|
|
|
|
.. important::
|
|
This must be overridden in your ``Part``
|
|
|
|
The outcome of this function should be accessed via cqparts.Part.object
|
|
"""
|
|
raise NotImplementedError("make function not implemented")
|
|
|
|
def make_simple(self):
|
|
"""
|
|
Create and return *simplified* solid part.
|
|
|
|
The simplified representation of a ``Part`` is to lower the export
|
|
quality of an ``Assembly`` or ``Part`` for rendering.
|
|
|
|
Overriding this is optional, but highly recommended.
|
|
|
|
The default behaviour returns the full complexity object's bounding box.
|
|
But to do this, theh full complexity object must be generated first.
|
|
|
|
There are 2 main problems with this:
|
|
|
|
#. building the full complexity part is not efficient.
|
|
#. a bounding box may not be a good representation of the part.
|
|
|
|
**Bolts**
|
|
|
|
A good example of this is a bolt.
|
|
|
|
* building a bolt's thread is not a trivial task;
|
|
it can take some time to generate.
|
|
* a box is not a good visual representation of a bolt
|
|
|
|
So for the ``Fastener`` parts, all ``make_simple`` methods are overridden
|
|
to provide 2 cylinders, one for the bolt's head, and another for the thread.
|
|
"""
|
|
complex_obj = self.make()
|
|
bb = complex_obj.findSolid().BoundingBox()
|
|
simple_obj = cadquery.Workplane('XY', origin=(bb.xmin, bb.ymin, bb.zmin)) \
|
|
.box(bb.xlen, bb.ylen, bb.zlen, centered=(False, False, False))
|
|
return simple_obj
|
|
|
|
def build(self, recursive=False):
|
|
"""
|
|
Building a part buffers the ``local_obj`` attribute.
|
|
|
|
Running ``.build()`` is optional, it's mostly used to test that
|
|
there aren't any critical runtime issues with it's construction.
|
|
|
|
:param recursive: (:class:`Part` has no children, parameter ignored)
|
|
"""
|
|
self.local_obj # force object's construction, but don't do anything with it
|
|
|
|
# ----- Local Object
|
|
@property
|
|
def local_obj(self):
|
|
"""
|
|
Buffered result of :meth:`make` which is (probably) a
|
|
:class:`cadquery.Workplane` instance. If ``_simple`` is ``True``, then
|
|
:meth:`make_simple` is returned instead.
|
|
|
|
.. note::
|
|
This is usually the correct way to get your part's object
|
|
for rendering, exporting, or measuring.
|
|
|
|
Only call :meth:`cqparts.Part.make` directly if you explicitly intend
|
|
to re-generate the model from scratch, then dispose of it.
|
|
"""
|
|
if self._local_obj is None:
|
|
# Simplified or Complex
|
|
if self._simple:
|
|
value = self.make_simple()
|
|
else:
|
|
value = self.make()
|
|
# Verify type
|
|
if not isinstance(value, cadquery.CQ):
|
|
raise MakeError("invalid object type returned by make(): %r" % value)
|
|
# Buffer object
|
|
self._local_obj = value
|
|
return self._local_obj
|
|
|
|
@local_obj.setter
|
|
def local_obj(self, value):
|
|
self._local_obj = value
|
|
self._world_obj = None
|
|
|
|
# ----- World Object
|
|
@property
|
|
def world_obj(self):
|
|
"""
|
|
The :meth:`local_obj <local_obj>` object in the
|
|
:meth:`world_coords <Component.world_coords>` coordinate system.
|
|
|
|
.. note::
|
|
|
|
This is automatically generated when called, and
|
|
:meth:`world_coords <Component.world_coords>` is not ``Null``.
|
|
"""
|
|
if self._world_obj is None:
|
|
local_obj = self.local_obj
|
|
world_coords = self.world_coords
|
|
if (local_obj is not None) and (world_coords is not None):
|
|
# Copy local object, apply transform to move to its new home.
|
|
self._world_obj = world_coords + local_obj
|
|
return self._world_obj
|
|
|
|
@world_obj.setter
|
|
def world_obj(self, value):
|
|
# implemented just for this helpful message
|
|
raise ValueError("can't set world_obj directly, set local_obj instead")
|
|
|
|
@property
|
|
def bounding_box(self):
|
|
"""
|
|
Generate a bounding box based on the full complexity part.
|
|
|
|
:return: bounding box of part
|
|
:rtype: cadquery.BoundBox
|
|
"""
|
|
return self.local_obj.findSolid().BoundingBox()
|
|
|
|
def _placement_changed(self):
|
|
self._world_obj = None
|