Tracking points across transforms

55 views
Skip to first unread message

john.j...@gmail.com

unread,
Dec 5, 2024, 4:09:08 AM12/5/24
to CadQuery
Hi,

Something that I've been struggling with recently is how to handle relations between two separate objects. This came out of a model for the KiCad library, where we wanted to connect two "feet" to the ends of a coil:

coil conns.png
The coil is made by a function that returns it at (0,0,0) and is transformed up in Z, and the feet also are made centred at (0,0,0) and are rotated around the Z axis and translated into place.

We did it by manually re-constructing the transform for the expected points, which wasn't so bad as the transforms were simple (90 degree rotations and axis-aligned translations only), but if there were any non-trivial transformation, it would become very tricky.

For example, in the following toy example:

import cadquery as cq
class ConnectableWidget:
    """
    Some object that ends up with a circle at the top.
    """

    def __init__(self, height):
        self.height = height

    def make(self):

        cube = cq.Workplane("XY").box(1, 1, self.height)
        cylinder  = cq.Workplane("XY").circle(0.25).extrude(self.height)
        return cube.union(cylinder)

    def connection_point(self):
        """"
        The class knows where the connection point is based on its
        internal state
        """
        return cq.Vector(0, 0, self.height)


connectable1 = ConnectableWidget(2).make().translate((5, 0, 0))
connectable2 = ConnectableWidget(4).make().translate((0, 5, 0))

result = connectable1.union(connectable2)

# At this point, do I have any way to know the connection points of the two
# connectable widgets? I can manually perform parallel transformations on the
# results of connection_point() to get the connection points?

I.e. I'm looking for the post-transform positions of the tops of the items here. For example, I may want to construct a spline to make a solid swept wire to connect the two.

cq_example.png
In this case I could choose the >Z faces after the transform, but this is a toy example - in real life, the points might not have simple selectors, or the selector that works may vary based on transform (e.g. if you rotated 180 around x, you'd need <Z instead).

What is the (or a!) "right" way to track this kind of information across transforms?

Thank you,

John

Lorenz

unread,
Dec 6, 2024, 11:57:36 PM12/6/24
to CadQuery
I don't have a new solution.  I'd suggest you might not abandon the selector solution.  New unreleased features enable custom filtering and make this easier.


> I could choose the >Z faces after the transform, but this is a toy example - in real life, the points might not have simple selectors

Yes, but there are other methods of selecting the face aside from directional selectors.  Here is a rough example (requires master) that checks the expected area of the connector face, number of sibling faces, type of sibling face, length of sibling edge.



class ConnectableWidget:
    """
    Some object that ends up with a circle at the top.
    """

    def __init__(self, height):
        self.height = height

    def make(self):

        cube = cq.Workplane("XY").box(1, 1, self.height)
        cylinder = cq.Workplane("XY").circle(0.25).extrude(self.height)

        # tag the connector face - using this for the area test
        cylinder.faces(">Z").tag("conn")

        return cube.union(cylinder)

    def find_connector(self):
        """Return the connector face"""

        def callback(wp):

            obj = wp.val()

            def filter_face(f):
                """Test for for the connector face.
                Not all tests are not needed for the toy case."""

                # looking for PLANE faces
                if f.geomType() != "PLANE":
                    return False

                # check that number of siblings is 1
                if len(list(f.siblings(obj, "Edge"))) != 1:
                    return False

                # check that sibling face is CYLINDER
                if f.siblings(obj, "Edge").faces().geomType() != "CYLINDER":
                    return False

                # check sibling edge height - the edge parallel to connector face normal
                edges1 = (
                    f.siblings(obj, "Edge")
                    .edges(cq.selectors.ParallelDirSelector(f.normalAt()))
                    .Edges()
                )
                if len(edges1) != 1:  # expect single seam edge
                    return False
                # Vector for equality test with tolerance
                if cq.Vector(0, 0, edges1[0].Length()) != cq.Vector(
                    0, 0, self.height / 2.0
                ):
                    return False

                # check area of connector face is expected
                if abs(f.Area() - wp.faces(tag="conn").val().Area()) > 1e-3:
                    return False

                return True

            return wp.faces().filter(filter_face)

        return callback


connectable1_inst = ConnectableWidget(2)
connectable1 = connectable1_inst.make().translate((5, 0, 0))
face_conn1 = connectable1.invoke(connectable1_inst.find_connector())

connectable2_inst = ConnectableWidget(4)
connectable2 = connectable2_inst.make().translate((0, 5, 0))
face_conn2 = connectable2.invoke(connectable2_inst.find_connector())

Adam Urbanczyk

unread,
Dec 10, 2024, 2:15:48 AM12/10/24
to CadQuery
You'll need to apply the transform again:

import cadquery as cq

cube = cq.Workplane("XY").box(1, 1, 5)
cylinder  = cq.Workplane("XY").circle(0.25).extrude(5)
tmp =  cube.union(cylinder)

tmp.faces('>Z').tag('top')

pos = (1,2,3)

rv = tmp.translate(pos)
top = tmp.faces(tag='top').translate(pos)

BTW: I don't think that using cq.Wrokplane for building another API layer is a good idea. You'll be better off using the Shape or free-func API

john.j...@gmail.com

unread,
Dec 15, 2024, 2:58:15 PM12/15/24
to CadQuery
Thanks for the replies.

I'll keep working on using other APIs (free-function or Shape as suggested). I'm looking forward to refactoring some of our generators into some really easily reusable components - CadQuery is really helping a lot to build a great 3D library!

Thanks,

John
Reply all
Reply to author
Forward
0 new messages