cqparts.utils package

Submodules

cqparts.utils.geometry module

class cqparts.utils.geometry.CoordSystem(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1))

Bases: cadquery.freecad_impl.geom.Plane

Defines the location, and rotation of an orthogonal 3 dimensional coordinate system.

__add__(other)

For A + B. Where A is this coordinate system, and B is other.

Raises:TypeError – if addition for the given type is not supported

Supported types:

A (CoordSystem) + B (CoordSystem):

Returns:world coordinates of B in A’s coordinates
Return type:CoordSystem

A (CoordSystem) + B (cadquery.Vector):

Returns:world coordinates of B represented in A’s coordinate system
Return type:cadquery.Vector

A (CoordSystem) + B (cadquery.CQ):

remember: cadquery.Workplane inherits from cadquery.CQ

Returns:content of B moved to A’s coordinate system
Return type:cadquery.Workplane
__sub__(other)

For A - B. Where A is this coordinate system, and B is other.

Raises:TypeError – if subtraction for the given type is not supported

Supported types:

A (CoordSystem) + B (CoordSystem):

Returns:local coordinate system of A from B’s coordinate system
Return type:CoordSystem
classmethod from_plane(plane)
Parameters:plane (cadquery.Plane) – cadquery plane instance to base coordinate system on
Returns:duplicate of the given plane, in this class
Return type:CoordSystem

usage example:

>>> import cadquery
>>> from cqparts.utils.geometry import CoordSystem
>>> obj = cadquery.Workplane('XY').circle(1).extrude(5)
>>> plane = obj.faces(">Z").workplane().plane
>>> isinstance(plane, cadquery.Plane)
True
>>> coord_sys = CoordSystem.from_plane(plane)
>>> isinstance(coord_sys, CoordSystem)
True
>>> coord_sys.origin.z
5.0
classmethod from_transform(matrix)
Parameters:matrix (FreeCAD.Matrix) – 4x4 3d affine transform matrix
Returns:a unit, zero offset coordinate system transformed by the given matrix
Return type:CoordSystem

Individual rotation & translation matricies are:

\[\begin{split}R_z & = \begin{bmatrix} cos(\alpha) & -sin(\alpha) & 0 & 0 \\ sin(\alpha) & cos(\alpha) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \qquad & R_y & = \begin{bmatrix} cos(\beta) & 0 & sin(\beta) & 0 \\ 0 & 1 & 0 & 0 \\ -sin(\beta) & 0 & cos(\beta) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \\ \\ R_x & = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & cos(\gamma) & -sin(\gamma) & 0 \\ 0 & sin(\gamma) & cos(\gamma) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \qquad & T_{\text{xyz}} & = \begin{bmatrix} 1 & 0 & 0 & \delta x \\ 0 & 1 & 0 & \delta y \\ 0 & 0 & 1 & \delta z \\ 0 & 0 & 0 & 1 \end{bmatrix}\end{split}\]

The transform is the combination of these:

\[\begin{split}transform = T_{\text{xyz}} \cdot R_z \cdot R_y \cdot R_x = \begin{bmatrix} a & b & c & \delta x \\ d & e & f & \delta y \\ g & h & i & \delta z \\ 0 & 0 & 0 & 1 \end{bmatrix}\end{split}\]

Where:

\[\begin{split}a & = cos(\alpha) cos(\beta) \\ b & = cos(\alpha) sin(\beta) sin(\gamma) - sin(\alpha) cos(\gamma) \\ c & = cos(\alpha) sin(\beta) cos(\gamma) + sin(\alpha) sin(\gamma) \\ d & = sin(\alpha) cos(\beta) \\ e & = sin(\alpha) sin(\beta) sin(\gamma) + cos(\alpha) cos(\gamma) \\ f & = sin(\alpha) sin(\beta) cos(\gamma) - cos(\alpha) sin(\gamma) \\ g & = -sin(\beta) \\ h & = cos(\beta) sin(\gamma) \\ i & = cos(\beta) cos(\gamma)\end{split}\]
local_to_world_transform
Returns:3d affine transform matrix to convert local coordinates to world coordinates.
Return type:cadquery.Matrix

For matrix structure, see from_transform().

classmethod random(span=1, seed=None)

Creates a randomized coordinate system.

Useful for confirming that an assembly does not rely on its origin coordinate system to remain intact.

For example, the CoordSysIndicator assembly aligns 3 boxes along each of the \(XYZ\) axes. Positioning it randomly by setting its world_coords shows that each box is always positioned orthogonally to the other two.

from cqparts_misc.basic.indicators import CoordSysIndicator
from cqparts.display import display
from cqparts.utils import CoordSystem

cs = CoordSysIndicator()
cs.world_coords = CoordSystem.random()

display(cs)  
Parameters:
  • span – origin of return will be \(\pm span\) per axis
  • seed (hashable object) – if supplied, return is psudorandom (repeatable)
Returns:

randomized coordinate system

Return type:

CoordSystem

world_to_local_transform
Returns:3d affine transform matrix to convert world coordinates to local coordinates.
Return type:cadquery.Matrix

For matrix structure, see from_transform().

cqparts.utils.misc module

cqparts.utils.misc.indicate_last(items)

iterate through list and indicate which item is the last, intended to assist tree displays of hierarchical content.

Returns:yielding (<bool>, <item>) where bool is True only on last entry
Return type:generator
cqparts.utils.misc.measure_time(*args, **kwds)
class cqparts.utils.misc.property_buffered(getter, name=None)

Bases: object

Buffer the result of a method on the class instance, similar to python builtin @property, but the result is kept in memory until it’s explicitly deleted.

>>> from cqparts.utils.misc import property_buffered
>>> class A(object):
...     @property_buffered
...     def x(self):
...         print("x called")
...         return 100
>>> a = A()
>>> a.x
x called
100
>>> a.x
100
>>> del a.x
>>> a.x
x called
100

Basis of class was sourced from the funkybob/antfarm project. thanks to @funkybob.

cqparts.utils.misc.working_dir(*args, **kwds)

Change working directory within a context:

>>> import os
>>> from cqparts.utils import working_dir

>>> print(os.getcwd())
/home/myuser/temp

>>> with working_dir('..'):
...     print(os.getcwd())
...
/home/myuser
Parameters:path (str) – working path to use while in context

cqparts.utils.sphinx module

This module is only to be referenced from your project’s sphinx autodoc configuration.

http://www.sphinx-doc.org/en/stable/ext/autodoc.html

cqparts.utils.sphinx.add_parametric_object_params(prepend=False, hide_private=True)

Add ParametricObject parameters in a list to the docstring.

This is only intended to be used with sphinx autodoc.

In your sphinx config.py file:

from cqparts.utils.sphinx import add_parametric_object_params
def setup(app):
    app.connect("autodoc-process-docstring", add_parametric_object_params())

Then, when documenting your Part or Assembly the ParametricObject parameters will also be documented in the output.

Parameters:
  • prepend (bool) – if True, parameters are added to the beginning of the docstring. otherwise, they’re appended at the end.
  • hide_private (bool) – if True, parameters with a _ prefix are not documented.
cqparts.utils.sphinx.add_search_index_criteria(prepend=False)

Add the search criteria used when calling register() on a Component as a table to the docstring.

This is only intended to be used with sphinx autodoc.

In your sphinx config.py file:

from cqparts.utils.sphinx import add_search_index_criteria
def setup(app):
    app.connect("autodoc-process-docstring", add_search_index_criteria())

Then, when documenting your Part or Assembly the search criteria will also be documented in the output.

Parameters:prepend (bool) – if True, table is added to the beginning of the docstring. otherwise, it’s appended at the end.
cqparts.utils.sphinx.skip_class_parameters()

Can be used with add_parametric_object_params(), this removes duplicate variables cluttering the sphinx docs.

This is only intended to be used with sphinx autodoc

In your sphinx config.py file:

from cqparts.utils.sphinx import skip_class_parameters
def setup(app):
    app.connect("autodoc-skip-member", skip_class_parameters())

cqparts.utils.test module

class cqparts.utils.test.CatalogueTest(methodName='runTest')

Bases: cqparts.utils.test.ComponentTest

catalogue = None
classmethod create_from(catalogue, add_to={}, id_mangler=None, include_cond=None, exclude_cond=None)

Create a testcase class that will run generic tests on each item in the given Catalogue.

Parameters:
  • catalogue (Catalogue) – catalogue to generatea tests from
  • add_to (dict) – dict to add resulting class to (usually globals().
  • id_mangler (function) – convert item id to a valid python method name
  • include_cond (function) – returns true if item should be tested
  • exclude_cond (function) – returns true if item should not be tested
Returns:

a testcase class to be discovered and run by unittest

Return type:

unittest.TestCase sub-class (a class, not an instance)

To create a test-case, and add the class with the catalogue’s name to the globals() namespace:

from cqparts.utils.test import CatalogueTest
from cqparts.catalogue import JSONCatalogue

catalogue = JSONCatalogue('my_catalogue.json')
CatalogueTest.create_from(catalogue, add_to=globals())

Alternatively, to control your class name a bit more traditionally:

# alternatively
MyTestCase = CatalogueTest.create_from(catalogue)

Test Names / Catalogue IDs

Each test is named for its item’s id. By default, to translate the ids into valid python method names, this is done by replacing any non-alpha-numeric characters with a _.

To illustrate with some examples:

id mangled id test name
abc123 abc123 (same) test_abc123
3.14159 3_14159 test_3_14159
%$#*_yeah! _____yeah_ test______yeah_
_(@@)yeah& _____yeah_ test______yeah_

So you can see why a python method name of test_%$#*_yeah! might be a problem, which is why this is done. But you may also spot that the last 2, although their IDs are unique, the test method names are the same.

To change the way ID’s are mangled into test method names, set the id_mangler parameter:

def mangle_id(id_str):
    return id_str.replace('a', 'X')

CatalogueTest.create_from(
    catalogue,  # as defined in the previous example
    add_to=globals(),
    id_mangler=mangle_id,
)

That would change the first test name to test_Xbc123.

Include / Exclude Items

If you intend on including or excluding certain items from the testlist, you can employ the include_cond and/or exclude_cond parameters:

def include_item(item):
    # include item if it has a specific id
    return item.get('id') in ['a', 'b', 'c']

def exclude_item(item):
    # exclude everything with a width > 100
    return item.get('obj').get('params').get('width', 0) > 100

CatalogueTest.create_from(
    catalogue,  # as defined in the previous example
    add_to=globals(),
    include_cond=include_item,
    exclude_cond=exclude_item,
)

Tests will be created if the following conditions are met:

excluded included test case generated?
n/a n/a Yes : tests are generated if no include/exclude methods are set
n/a True Yes
n/a False No
True n/a No
False n/a Yes
False False No : inclusion take precedence (or lack thereof)
False True Yes
True False No
True True Yes : inclusion take precedence
class cqparts.utils.test.ComponentTest(methodName='runTest')

Bases: unittest.case.TestCase

Generic testcase with utilities for testing Part and Assembly instances.

For example:

import cqparts
import cadquery
from cqparts.utils.test import ComponentTest

class Box(cqparts.Part):
    def make(self):
        return cadquery.Workplane('XY').box(1,1,1)

class BoxTest(ComponentTest):
    def test_box(self):
        box = Box()
        self.assertComponent(box)
assertAssembly(obj)

Assert criteria common to any fully formed Assembly.

Parameters:obj (Assembly) – assembly under test
assertAssembyHasComponents(obj)
assertComponent(obj, recursive=True, _depth=0)

Assert criteria common to any fully formed Component.

Parameters:
  • obj (Component) – component under test
  • recursive (bool) – if True sub-components will also be tested
assertPart(obj)

Assert criteria common to any fully formed Part.

Parameters:obj (Part) – part under test
assertPartBoundingBox(obj)
assertPartHasVolume(obj)

cqparts.utils.wrappers module

cqparts.utils.wrappers.as_part(func)

Converts a function to a Part instance.

So the conventionally defined part:

import cadquery
from cqparts import Part
from cqparts.params import Float

class Box(Part):
    x = Float(1)
    y = Float(2)
    z = Float(4)
    def make(self):
        return cadquery.Workplane('XY').box(self.x, self.y, self.z)

box = Box(x=6, y=3, z=1)

May also be written as:

import cadquery
from cqparts.utils.wrappers import as_part

@as_part
def make_box(x=1, y=2, z=4):
    return cadquery.Workplane('XY').box(x, y, z)

box = make_box(x=6, y=3, z=1)

In both cases, box is a Part instance.

Module contents

class cqparts.utils.CoordSystem(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1))

Bases: cadquery.freecad_impl.geom.Plane

Defines the location, and rotation of an orthogonal 3 dimensional coordinate system.

__add__(other)

For A + B. Where A is this coordinate system, and B is other.

Raises:TypeError – if addition for the given type is not supported

Supported types:

A (CoordSystem) + B (CoordSystem):

Returns:world coordinates of B in A’s coordinates
Return type:CoordSystem

A (CoordSystem) + B (cadquery.Vector):

Returns:world coordinates of B represented in A’s coordinate system
Return type:cadquery.Vector

A (CoordSystem) + B (cadquery.CQ):

remember: cadquery.Workplane inherits from cadquery.CQ

Returns:content of B moved to A’s coordinate system
Return type:cadquery.Workplane
__sub__(other)

For A - B. Where A is this coordinate system, and B is other.

Raises:TypeError – if subtraction for the given type is not supported

Supported types:

A (CoordSystem) + B (CoordSystem):

Returns:local coordinate system of A from B’s coordinate system
Return type:CoordSystem
classmethod from_plane(plane)
Parameters:plane (cadquery.Plane) – cadquery plane instance to base coordinate system on
Returns:duplicate of the given plane, in this class
Return type:CoordSystem

usage example:

>>> import cadquery
>>> from cqparts.utils.geometry import CoordSystem
>>> obj = cadquery.Workplane('XY').circle(1).extrude(5)
>>> plane = obj.faces(">Z").workplane().plane
>>> isinstance(plane, cadquery.Plane)
True
>>> coord_sys = CoordSystem.from_plane(plane)
>>> isinstance(coord_sys, CoordSystem)
True
>>> coord_sys.origin.z
5.0
classmethod from_transform(matrix)
Parameters:matrix (FreeCAD.Matrix) – 4x4 3d affine transform matrix
Returns:a unit, zero offset coordinate system transformed by the given matrix
Return type:CoordSystem

Individual rotation & translation matricies are:

\[\begin{split}R_z & = \begin{bmatrix} cos(\alpha) & -sin(\alpha) & 0 & 0 \\ sin(\alpha) & cos(\alpha) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \qquad & R_y & = \begin{bmatrix} cos(\beta) & 0 & sin(\beta) & 0 \\ 0 & 1 & 0 & 0 \\ -sin(\beta) & 0 & cos(\beta) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \\ \\ R_x & = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & cos(\gamma) & -sin(\gamma) & 0 \\ 0 & sin(\gamma) & cos(\gamma) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \qquad & T_{\text{xyz}} & = \begin{bmatrix} 1 & 0 & 0 & \delta x \\ 0 & 1 & 0 & \delta y \\ 0 & 0 & 1 & \delta z \\ 0 & 0 & 0 & 1 \end{bmatrix}\end{split}\]

The transform is the combination of these:

\[\begin{split}transform = T_{\text{xyz}} \cdot R_z \cdot R_y \cdot R_x = \begin{bmatrix} a & b & c & \delta x \\ d & e & f & \delta y \\ g & h & i & \delta z \\ 0 & 0 & 0 & 1 \end{bmatrix}\end{split}\]

Where:

\[\begin{split}a & = cos(\alpha) cos(\beta) \\ b & = cos(\alpha) sin(\beta) sin(\gamma) - sin(\alpha) cos(\gamma) \\ c & = cos(\alpha) sin(\beta) cos(\gamma) + sin(\alpha) sin(\gamma) \\ d & = sin(\alpha) cos(\beta) \\ e & = sin(\alpha) sin(\beta) sin(\gamma) + cos(\alpha) cos(\gamma) \\ f & = sin(\alpha) sin(\beta) cos(\gamma) - cos(\alpha) sin(\gamma) \\ g & = -sin(\beta) \\ h & = cos(\beta) sin(\gamma) \\ i & = cos(\beta) cos(\gamma)\end{split}\]
local_to_world_transform
Returns:3d affine transform matrix to convert local coordinates to world coordinates.
Return type:cadquery.Matrix

For matrix structure, see from_transform().

classmethod random(span=1, seed=None)

Creates a randomized coordinate system.

Useful for confirming that an assembly does not rely on its origin coordinate system to remain intact.

For example, the CoordSysIndicator assembly aligns 3 boxes along each of the \(XYZ\) axes. Positioning it randomly by setting its world_coords shows that each box is always positioned orthogonally to the other two.

from cqparts_misc.basic.indicators import CoordSysIndicator
from cqparts.display import display
from cqparts.utils import CoordSystem

cs = CoordSysIndicator()
cs.world_coords = CoordSystem.random()

display(cs)  
Parameters:
  • span – origin of return will be \(\pm span\) per axis
  • seed (hashable object) – if supplied, return is psudorandom (repeatable)
Returns:

randomized coordinate system

Return type:

CoordSystem

world_to_local_transform
Returns:3d affine transform matrix to convert world coordinates to local coordinates.
Return type:cadquery.Matrix

For matrix structure, see from_transform().

class cqparts.utils.property_buffered(getter, name=None)

Bases: object

Buffer the result of a method on the class instance, similar to python builtin @property, but the result is kept in memory until it’s explicitly deleted.

>>> from cqparts.utils.misc import property_buffered
>>> class A(object):
...     @property_buffered
...     def x(self):
...         print("x called")
...         return 100
>>> a = A()
>>> a.x
x called
100
>>> a.x
100
>>> del a.x
>>> a.x
x called
100

Basis of class was sourced from the funkybob/antfarm project. thanks to @funkybob.

cqparts.utils.indicate_last(items)

iterate through list and indicate which item is the last, intended to assist tree displays of hierarchical content.

Returns:yielding (<bool>, <item>) where bool is True only on last entry
Return type:generator
cqparts.utils.working_dir(*args, **kwds)

Change working directory within a context:

>>> import os
>>> from cqparts.utils import working_dir

>>> print(os.getcwd())
/home/myuser/temp

>>> with working_dir('..'):
...     print(os.getcwd())
...
/home/myuser
Parameters:path (str) – working path to use while in context
cqparts.utils.measure_time(*args, **kwds)
cqparts.utils.as_part(func)

Converts a function to a Part instance.

So the conventionally defined part:

import cadquery
from cqparts import Part
from cqparts.params import Float

class Box(Part):
    x = Float(1)
    y = Float(2)
    z = Float(4)
    def make(self):
        return cadquery.Workplane('XY').box(self.x, self.y, self.z)

box = Box(x=6, y=3, z=1)

May also be written as:

import cadquery
from cqparts.utils.wrappers import as_part

@as_part
def make_box(x=1, y=2, z=4):
    return cadquery.Workplane('XY').box(x, y, z)

box = make_box(x=6, y=3, z=1)

In both cases, box is a Part instance.