Beginner Questions about Querying Geometry

662 views
Skip to first unread message

Jason Ross

unread,
Sep 9, 2021, 3:00:08 PM9/9/21
to CadQuery

Hello,

I'm interested in using this library to construct geometry and then query it later. Here's an example to get started:

-----

import cadquery as cq
import numpy as np


wp = cq.Workplane("front")
thickness = 1.0
height = 1.0
width = 1.0

pline = (
    wp
    .threePointArc((thickness/2, -thickness/2), (thickness, 0))
    .line(0, height)
    .tangentArcPoint((-thickness, thickness))
    .line(-width, 0)
    .tangentArcPoint((0, -thickness))
    .line(width, 0)
    .close()
)

points = 10 * (np.random.rand(20, 2) - 0.5)

------

I'd like to be able to do the following:
  1. For each point in `points`, find if it is inside of `pline`
  2. For each point in `points`, find the nearest point on `pline`
  3. For each point in `points`, find the nearest segment of `pline`
  4. For each point in `points`, find the parametric position of the nearest point on `pline`
  5. Given a segment of `pline`, say the first arc, find its geometric properties (center, direction, etc.). I do know how to get the radius, but I don't know how to find the center of a circular arc.
Can someone help me with these?

Thanks

Roger Maitland

unread,
Sep 10, 2021, 11:45:01 AM9/10/21
to CadQuery
Here is something to get you started:

To work with and see the points, create Vector and Vertex versions of them:

# Convert the numpy array into cadquery vectors so they can be used as locations
random_vectors = [cq.Vector(*tuple(p), 0) for p in points]
# Convert the numpy array into cadquery vertices so they can be seen in cq-editor
random_vertices = [cq.Vertex.makeVertex(*tuple(p), 0) for p in points]

There are likely more efficient ways to find if a point is inside a wire but this might be enough for you:

test_sphere_radius = 0.1
test_sphere = cq.Workplane("XY").sphere(test_sphere_radius)
test_sphere_volume = test_sphere.val().Volume()
pline_offset_solid = (
    pline.toPending()
    .offset2D(test_sphere_radius)
    .extrude(test_sphere_radius, both=True)
    .val()
)
within = []
for p in random_vectors:
    within_shape = test_sphere.translate(p).intersect(pline_offset_solid)
    within.append(within_shape.val().Volume() == test_sphere_volume)


If the random point is within pline, the intersection of the test sphere at the location of each random point with a solid version of pline should result in a complete sphere.

There are cadquery selectors that can be helpful:

# Use a selector to find the closest vertex in pline to each point in points
nearest_pline_vertices = [
    pline.vertices(cq.NearestToPointSelector(tuple(p))) for p in points
]

This will find the nearest vertices. Custom selectors can be created if there isn't one that already fits.

The pline Workplane object can be converted back to Edge objects where a bunch of useful methods are available, such as:

pline_edges = pline.edges().vals()
arc_centers = []
for e in pline_edges:
    print(e.geomType(), e.positionAt(0), e.positionAt(1))
    if e.geomType() == "CIRCLE":
        arc_radius = e.radius()
        chord_center = (e.positionAt(1) + e.positionAt(0)) * 0.5
        arc_midpoint = e.positionAt(0.5)
        radial_direction = (chord_center - arc_midpoint).normalized()
        arc_center = arc_midpoint + radial_direction * arc_radius
        arc_centers.append(cq.Vertex.makeVertex(*arc_center.toTuple()))


`arc_centers` is a vertex at the center of the circle for each of the arcs in pline. By knowing if the edge is a CIRCLE (with end points, center and radius) or LINE (with end points), the remainder of your questions can be answered with some calculations.

Cheers,
Roger

Jason Ross

unread,
Sep 13, 2021, 12:28:26 PM9/13/21
to CadQuery
Hi Roger,

Thank you very much for the detailed explanation. I'm still not sure how to get positions along the curve the way I described.

This code
    # Use a selector to find the closest vertex in pline to each point in points
    nearest_pline_vertices = [
        pline.vertices(cq.NearestToPointSelector(tuple(p))) for p in points
    ]
returns vertices of `pline`, which are points that are already defined. What I'm looking for is the parametric position of the closest point on `pline` to each `p` in `points`, so if `p` is closest to the midpoint of `pline`, I'd like to get 0.5 back, rather than some vertex of `pline`.

As a side note, I found that this code
    test_sphere_radius = 0.1
    test_sphere = cq.Workplane("XY").sphere(test_sphere_radius)
    test_sphere_volume = test_sphere.val().Volume()
    pline_offset_solid = (
        pline.toPending()
        .offset2D(test_sphere_radius)
        .extrude(test_sphere_radius, both=True)
        .val()
    )
    within = []
    for p in random_vectors:
        within_shape = test_sphere.translate(p).intersect(pline_offset_solid)
        within.append(within_shape.val().Volume() == test_sphere_volume)
can be replaced by this code:
    pline_offset_solid = (
        pline.toPending()
        .extrude(0.01, both=True)
        .val()
    )
    within = [pline_offset_solid.isInside(v) for v in random_vectors]
and runs a lot faster. Is there any reason not to do it this way?

Thanks!
Jason

Roger Maitland

unread,
Sep 13, 2021, 5:10:21 PM9/13/21
to Jason Ross, CadQuery
As I said Jason, "There are likely more efficient ways to find if a point is inside a wire" and you found it - excellent, I didn't know about the `isInside()` method.

I had a similar requirement to convert a Vector along a line back to a floating point position along the line between 0 and 1 and used a recursive binary search as follows:
def positiontOnEdge(
edge: cq.Edge,
target: cq.Vector,
window_start: float = 0.0,
window_end: float = 1.0,
tol: float = 0.00001,
) -> float:
"""
Search an edge for the position along it closest to target
"""
window_mid = (window_start + window_end) / 2
distance_to_start = (edge.positionAt(window_start) - target).Length
distance_to_mid = (edge.positionAt(window_mid) - target).Length
distance_to_end = (edge.positionAt(window_end) - target).Length

# Close enough to quit?
if distance_to_mid <= tol:
return window_mid

# Prepare for another try
if distance_to_start > distance_to_end:
window_start = window_mid
else:
window_end = window_mid
return positiontOnEdge(edge, target, window_start, window_end, tol)
which seems to work:
print(positiontOnEdge(pline_edges[0], pline_edges[0].positionAt(0.35)))
0.350006103515625
Again, there may be a more efficient way to do this but I don't know of one. I've found the binary search didn't need many loops to find a sufficiently accurate value for what I was doing.

Give the following a try to find the closest edge:
nearest_pline_edges = [pline.edges(cq.NearestToPointSelector(tuple(p))) for p in points]

If the closest edge is a CIRCLE, the same chord method used to find the center of the arcs for the circular edges, something like:
arc_closest = (
arc_center_vectors[0]
+ (random_vectors[0] - arc_center_vectors[0]).normalized() * arc_radii[0]
)
For LINE edges, you might try to use the Vector.getAngle() method to calculate the angles at each end and then solving the triangles to find the closest point.

I hope this helps.

Cheers,
Roger

--
cadquery home: https://github.com/CadQuery/cadquery
post issues at https://github.com/CadQuery/cadquery/issues
run it at home at : https://github.com/CadQuery/CQ-editor
---
You received this message because you are subscribed to the Google Groups "CadQuery" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cadquery+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cadquery/d304e7e1-f8b4-4efa-a2f4-aecfd724399bn%40googlegroups.com.

Jason Ross

unread,
Sep 13, 2021, 5:53:26 PM9/13/21
to CadQuery
Thanks, Roger. I think I have enough to work with.

Since I have your ear can I ask how you get syntax highlighting in google groups posts?

Thanks,
Jason

Roger Maitland

unread,
Sep 13, 2021, 6:34:42 PM9/13/21
to Jason Ross, CadQuery
When replying to Gmail I just copy/paste from Visual Studio Code and the HTML sticks around. I couldn't get this to work directly from Google Groups though.

Cheers,
Roger

Jeremy Wright

unread,
Sep 14, 2021, 6:52:40 AM9/14/21
to CadQuery
I'm testing the use of an HTML pre tag for basic code separation to see if it works. https://www.w3schools.com/tags/tag_pre.asp

<pre>
import cadquery as cq

result = cq.Workplane().box(10, 10, 10)

show_object(result);
</pre>

Jeremy Wright

unread,
Sep 14, 2021, 6:54:04 AM9/14/21
to CadQuery
I'm not sure. Before they redisgned Google Groups there was an option for code under text formatting, now embedding HTML directly through the Groups UI doesn't even seem to work.
Reply all
Reply to author
Forward
0 new messages