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())