CadQuery / cadquery

A python parametric CAD scripting framework based on OCCT
https://cadquery.readthedocs.io
Other
2.93k stars 276 forks source link

Constrain points #1613

Closed lenianiva closed 1 day ago

lenianiva commented 5 days ago

If I have the same set of points on two different surfaces, how can I write a constraint so the locations of the points match? Specifically, consider this example:

import cadquery as Cq
points = [
    (5, 5),
    (-5, 5),
    (5, -5)
]
box = (
    Cq.Workplane('XY')
    .box(20, 20, 2, centered=[True, True, False])
    .faces('>Z')
    .workplane()
    .pushPoints(points)
    .hole(2)
    .tag("mate")
)
cylinder = (
    Cq.Workplane('XY')
    .cylinder(2, 10, centered=[True, True, False])
    .faces('>Z')
    .workplane()
    .pushPoints(points)
    .cboreHole(3, cboreDiameter=5, cboreDepth=1)
    .tag("mate")
)
result = (
    Cq.Assembly()
    .add(box, name="box")
    .add(cylinder, name="cyl")
    .constrain("box?mate", "cyl?mate", "Point")
)
show_object(result)

In this case the two geometries overlap, but I want the <Z side of the cylinder to mate onto the >Z side of the box. I cannot simply create a new workspace and call pushPoints because points are not supported for the Point constraint.

lenianiva commented 5 days ago

If there's just 1 or 2 points I could use the example here, but

  1. There may be multiple "cylinder" objects that mate onto one "box" object
  2. Each "cylinder" object has more than 2 holes
lenianiva commented 5 days ago

This partial solution puts down phantom circles, but it ends up mating the centroid of the 3 holes instead of the holes themselves:

points = [
    (5, 5),
    (-5, 5),
    (5, -5)
]
box = (
    Cq.Workplane('XY')
    .box(20, 20, 2, centered=[True, True, False])
    .faces('>Z')
    .workplane()
    .pushPoints(points)
    .hole(2)
    .tag("mate")
)
(
    box
    .faces(">Z")
    .pushPoints(points)
    .circle(1, forConstruction=True)
    .tag("mate")
)
cylinder = (
    Cq.Workplane('XY')
    .cylinder(2, 25, centered=[True, True, False])
    .faces('>Z')
    .workplane()
    .pushPoints(points)
    .cboreHole(3, cboreDiameter=5, cboreDepth=1)
)
(
    cylinder
    .faces(">Z")
    .workplane(offset=-2)
    .pushPoints(points)
    .circle(1, forConstruction=True)
    .tag("mate")
)
result = (
    Cq.Assembly()
    .add(box, name="box")
    .add(cylinder, name="cyl")
    .constrain("box?mate", "cyl?mate", "Point")
    .solve()
)
show_object(result)
lenianiva commented 5 days ago

The main reason I want to avoid simply using selectors in this case, is that the product will have lots of points (i.e. multiple cylinder objects on the same box, or multiple boxes on the cylinder). In this example, the two holes on the panel are selected by

rv.faces(">Y").edges("%CIRCLE").edges(">Z").tag("hole1")
rv.faces(">Y").edges("%CIRCLE").edges("<Z").tag("hole2")

and it is not clear how would I do this with 3, 5, or 10 different holes.

adam-urbanczyk commented 4 days ago

3 non-degenerate points constraints will fully constrain your assy. There is no way around specifying which 3 points out of your N points need to be constrained. IDK the use case, but likely you'll need to tag, write a specialized selector or wait for #1514 and write an ad-hoc selector.

You could tag like so:

for i,p in enumerate(points[:3]):
    box.pushPoints((p,)).circle(1, forConstruction=True).tag(f"mate_{i}")
lenianiva commented 4 days ago

3 non-degenerate points constraints will fully constrain your assy. There is no way around specifying which 3 points out of your N points need to be constrained. IDK the use case, but likely you'll need to tag, write a specialized selector or wait for #1514 and write an ad-hoc selector.

You could tag like so:

for i,p in enumerate(points[:3]):
    box.pushPoints((p,)).circle(1, forConstruction=True).tag(f"mate_{i}")

so it has to use a dummy circle object? I was wondering if its possible to associate a relative position on the surface and use that as a constraint.

adam-urbanczyk commented 4 days ago

You'll need something, does that look better?

from cadquery.occ_impl.shapes import vertex

for i,p in enumerate(points[:3]):
    box.pushPoints((p,)).eachpoint(vertex(0,0,0)).tag(f"mate_{i}")
adam-urbanczyk commented 4 days ago

You could also use the 3rd overload of constrain and not use tags at all.

lenianiva commented 1 day ago

You'll need something, does that look better?

from cadquery.occ_impl.shapes import vertex

for i,p in enumerate(points[:3]):
    box.pushPoints((p,)).eachpoint(vertex(0,0,0)).tag(f"mate_{i}")

Thanks! This is what I need