Skip to content

Commit 1b2c7fd

Browse files
Add more trimming possibilities (#1833)
* Add more trimming possibilities * Expose edge * Black fix * Mypy fix * Add more trim/edge overloads * Correct handling of periodicity * Docstring fix * Add a test for edge() * Rename edge, add wireOn and extend tests * Black fix * Test additional overload * Fix corner case * Corner case test * Add hasPCurve() * Make uvBounds public * Fix Mixin1DProtocol * Add faceOn * Add some docs * Tweak parameters * Doc fix * Doc tweaks * Closed edge handling fix * faceOn test * Add kwargs to faceOn * Apply suggestions from code review * Apply suggestions from code review - docs Co-authored-by: Lorenz <[email protected]> * Fix the fix --------- Co-authored-by: adam-urbanczyk <[email protected]> Co-authored-by: Lorenz <[email protected]>
1 parent 8e67848 commit 1b2c7fd

File tree

5 files changed

+387
-26
lines changed

5 files changed

+387
-26
lines changed

cadquery/func.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
Solid,
1010
CompSolid,
1111
Compound,
12+
edgeOn,
13+
wireOn,
1214
wire,
1315
face,
1416
shell,
@@ -48,4 +50,5 @@
4850
closest,
4951
setThreads,
5052
project,
53+
faceOn,
5154
)

cadquery/occ_impl/shapes.py

Lines changed: 235 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from io import BytesIO
2121

22+
2223
from vtkmodules.vtkCommonDataModel import vtkPolyData
2324
from vtkmodules.vtkFiltersCore import vtkTriangleFilter, vtkPolyDataNormals
2425

@@ -59,7 +60,12 @@
5960
)
6061

6162
# Array of points (used for B-spline construction):
62-
from OCP.TColgp import TColgp_HArray1OfPnt, TColgp_HArray2OfPnt, TColgp_Array1OfPnt
63+
from OCP.TColgp import (
64+
TColgp_HArray1OfPnt,
65+
TColgp_HArray2OfPnt,
66+
TColgp_Array1OfPnt,
67+
TColgp_HArray1OfPnt2d,
68+
)
6369

6470
# Array of vectors (used for B-spline interpolation):
6571
from OCP.TColgp import TColgp_Array1OfVec
@@ -162,6 +168,8 @@
162168
)
163169
from OCP.Geom2d import Geom2d_Line
164170

171+
from OCP.Geom2dAPI import Geom2dAPI_Interpolate
172+
165173
from OCP.BRepLib import BRepLib, BRepLib_FindSurface
166174

167175
from OCP.BRepOffsetAPI import (
@@ -260,6 +268,7 @@
260268

261269
from OCP.ShapeAnalysis import (
262270
ShapeAnalysis_FreeBounds,
271+
ShapeAnalysis_Edge,
263272
ShapeAnalysis_Wire,
264273
ShapeAnalysis_Surface,
265274
)
@@ -1827,6 +1836,9 @@ def _curve_and_param(
18271836
) -> Tuple[Union[BRepAdaptor_Curve, BRepAdaptor_CompCurve], float]:
18281837
...
18291838

1839+
def bounds(self) -> Tuple[float, float]:
1840+
...
1841+
18301842
def paramAt(self, d: float) -> float:
18311843
...
18321844

@@ -1857,6 +1869,13 @@ def paramsLength(self, locations: Iterable[float]) -> List[float]:
18571869
class Mixin1D(object):
18581870
def _bounds(self: Mixin1DProtocol) -> Tuple[float, float]:
18591871

1872+
return self.bounds()
1873+
1874+
def bounds(self: Mixin1DProtocol) -> Tuple[float, float]:
1875+
"""
1876+
Parametric bounds of the curve.
1877+
"""
1878+
18601879
curve = self._geomAdaptor()
18611880
return curve.FirstParameter(), curve.LastParameter()
18621881

@@ -2370,6 +2389,13 @@ def trim(self, u0: Real, u1: Real) -> "Edge":
23702389

23712390
return self.__class__(bldr.Shape())
23722391

2392+
def hasPCurve(self, f: "Face") -> bool:
2393+
"""
2394+
Check if self has a pcurve defined on f.
2395+
"""
2396+
2397+
return ShapeAnalysis_Edge().HasPCurve(self.wrapped, f.wrapped)
2398+
23732399
@classmethod
23742400
def makeCircle(
23752401
cls,
@@ -3043,6 +3069,13 @@ def _geomAdaptor(self) -> Geom_Surface:
30433069

30443070
def _uvBounds(self) -> Tuple[float, float, float, float]:
30453071

3072+
return self.uvBounds()
3073+
3074+
def uvBounds(self) -> Tuple[float, float, float, float]:
3075+
"""
3076+
Parametric bounds (u_min, u_max, v_min, v_max).
3077+
"""
3078+
30463079
return BRepTools.UVBounds_s(self.wrapped)
30473080

30483081
def paramAt(self, pt: VectorLike) -> Tuple[float, float]:
@@ -3494,14 +3527,9 @@ def thicken(self, thickness: float) -> "Solid":
34943527
return Solid(builder.Shape())
34953528

34963529
@classmethod
3497-
def constructOn(cls, f: "Face", outer: "Wire", *inner: "Wire") -> "Face":
3498-
3499-
bldr = BRepBuilderAPI_MakeFace(f._geomAdaptor(), outer.wrapped)
3500-
3501-
for w in inner:
3502-
bldr.Add(TopoDS.Wire_s(w.wrapped))
3530+
def constructOn(cls, f: "Face", outer: "Wire", *inner: "Wire") -> Self:
35033531

3504-
return cls(bldr.Face()).fix()
3532+
return f.trim(outer, *inner)
35053533

35063534
def project(self, other: "Face", d: VectorLike) -> "Face":
35073535

@@ -3519,9 +3547,10 @@ def toArcs(self, tolerance: float = 1e-3) -> "Face":
35193547

35203548
return self.__class__(BRepAlgo.ConvertFace_s(self.wrapped, tolerance))
35213549

3522-
def trim(self, u0: Real, u1: Real, v0: Real, v1: Real, tol: Real = 1e-6) -> "Face":
3550+
@multimethod
3551+
def trim(self, u0: Real, u1: Real, v0: Real, v1: Real, tol: Real = 1e-6) -> Self:
35233552
"""
3524-
Trim the face in the parametric space to (u0, u1).
3553+
Trim the face in the (u,v) space to (u0, u1)x(v1, v2).
35253554
35263555
NB: this operation is done on the base geometry.
35273556
"""
@@ -3530,6 +3559,59 @@ def trim(self, u0: Real, u1: Real, v0: Real, v1: Real, tol: Real = 1e-6) -> "Fac
35303559

35313560
return self.__class__(bldr.Shape())
35323561

3562+
@trim.register
3563+
def _(
3564+
self,
3565+
pt1: Tuple[Real, Real],
3566+
pt2: Tuple[Real, Real],
3567+
pt3: Tuple[Real, Real],
3568+
*pts: Tuple[Real, Real],
3569+
) -> Self:
3570+
"""
3571+
Trim the face using a polyline defined in the (u,v) space.
3572+
"""
3573+
3574+
segs_uv = []
3575+
geom = self._geomAdaptor()
3576+
3577+
# build (u,v) segments
3578+
for el1, el2 in zip((pt1, pt2, pt3, *pts), (pt2, pt3, *pts, pt1)):
3579+
segs_uv.append(GCE2d_MakeSegment(gp_Pnt2d(*el1), gp_Pnt2d(*el2)).Value())
3580+
3581+
# convert to edges
3582+
edges = []
3583+
3584+
for seg in segs_uv:
3585+
edges.append(BRepBuilderAPI_MakeEdge(seg, geom).Edge())
3586+
3587+
# convert to a wire
3588+
builder = BRepBuilderAPI_MakeWire()
3589+
3590+
tmp = TopTools_ListOfShape()
3591+
for edge in edges:
3592+
tmp.Append(edge)
3593+
3594+
builder.Add(tmp)
3595+
3596+
w = builder.Wire()
3597+
BRepLib.BuildCurves3d_s(w)
3598+
3599+
# construct the final trimmed face
3600+
return self.constructOn(self, Wire(w))
3601+
3602+
@trim.register
3603+
def _(self, outer: Wire, *inner: Wire) -> Self:
3604+
"""
3605+
Trim using wires. The provided wires need to have a pcurve on self.
3606+
"""
3607+
3608+
bldr = BRepBuilderAPI_MakeFace(self._geomAdaptor(), outer.wrapped)
3609+
3610+
for w in inner:
3611+
bldr.Add(TopoDS.Wire_s(w.wrapped))
3612+
3613+
return self.__class__(bldr.Face()).fix()
3614+
35333615
def isoline(self, param: Real, direction: Literal["u", "v"] = "v") -> Edge:
35343616
"""
35353617
Construct an isoline.
@@ -4875,22 +4957,23 @@ def _get_wires(s: Shape) -> Iterable[Shape]:
48754957
raise ValueError(f"Required type(s): Edge, Wire; encountered {t}")
48764958

48774959

4878-
def _get_edges(s: Shape) -> Iterable[Shape]:
4960+
def _get_edges(*shapes: Shape) -> Iterable[Shape]:
48794961
"""
4880-
Get wires or wires from edges.
4962+
Get edges or edges from wires.
48814963
"""
48824964

4883-
t = s.ShapeType()
4884-
4885-
if t == "Edge":
4886-
yield s
4887-
elif t == "Wire":
4888-
yield from _get_edges(s.edges())
4889-
elif t == "Compound":
4890-
for el in s:
4891-
yield from _get_edges(el)
4892-
else:
4893-
raise ValueError(f"Required type(s): Edge, Wire; encountered {t}")
4965+
for s in shapes:
4966+
t = s.ShapeType()
4967+
4968+
if t == "Edge":
4969+
yield s
4970+
elif t == "Wire":
4971+
yield from _get_edges(s.edges())
4972+
elif t == "Compound":
4973+
for el in s:
4974+
yield from _get_edges(el)
4975+
else:
4976+
raise ValueError(f"Required type(s): Edge, Wire; encountered {t}")
48944977

48954978

48964979
def _get_wire_lists(s: Sequence[Shape]) -> List[List[Union[Wire, Vertex]]]:
@@ -5017,7 +5100,7 @@ def _compound_or_shape(s: Union[TopoDS_Shape, List[TopoDS_Shape]]) -> Shape:
50175100

50185101
def _pts_to_harray(pts: Sequence[VectorLike]) -> TColgp_HArray1OfPnt:
50195102
"""
5020-
Convert a sequence of Vecotor to a TColgp harray (OCCT specific).
5103+
Convert a sequence of Vector to a TColgp harray (OCCT specific).
50215104
"""
50225105

50235106
rv = TColgp_HArray1OfPnt(1, len(pts))
@@ -5028,6 +5111,19 @@ def _pts_to_harray(pts: Sequence[VectorLike]) -> TColgp_HArray1OfPnt:
50285111
return rv
50295112

50305113

5114+
def _pts_to_harray2D(pts: Sequence[Tuple[Real, Real]]) -> TColgp_HArray1OfPnt2d:
5115+
"""
5116+
Convert a sequence of 2d points to a TColgp harray (OCCT specific).
5117+
"""
5118+
5119+
rv = TColgp_HArray1OfPnt2d(1, len(pts))
5120+
5121+
for i, p in enumerate(pts):
5122+
rv.SetValue(i + 1, gp_Pnt2d(*p))
5123+
5124+
return rv
5125+
5126+
50315127
def _floats_to_harray(vals: Sequence[float]) -> TColStd_HArray1OfReal:
50325128
"""
50335129
Convert a sequence of floats to a TColstd harray (OCCT specific).
@@ -5129,6 +5225,91 @@ def _adaptor_curve_to_edge(crv: Adaptor3d_Curve, p1: float, p2: float) -> TopoDS
51295225
ShapeHistory = Dict[Union[Shape, str], Shape]
51305226

51315227

5228+
@multimethod
5229+
def edgeOn(
5230+
base: Shape,
5231+
pts: Sequence[Tuple[Real, Real]],
5232+
periodic: bool = False,
5233+
tol: float = 1e-6,
5234+
) -> Shape:
5235+
"""
5236+
Build an edge on a face from points in (u,v) space.
5237+
"""
5238+
5239+
f = _get_one(base, "Face")
5240+
5241+
# interpolate the u,v points
5242+
spline_bldr = Geom2dAPI_Interpolate(_pts_to_harray2D(pts), periodic, tol)
5243+
spline_bldr.Perform()
5244+
5245+
# build the final edge
5246+
rv = BRepBuilderAPI_MakeEdge(spline_bldr.Curve(), f._geomAdaptor()).Edge()
5247+
BRepLib.BuildCurves3d_s(rv)
5248+
5249+
return _compound_or_shape(rv)
5250+
5251+
5252+
@edgeOn.register
5253+
def _(
5254+
fbase: Shape, edg: Shape, *edgs: Shape, tol: float = 1e-6, N: int = 20,
5255+
):
5256+
"""
5257+
Map one or more edges onto a base face in the u,v space.
5258+
"""
5259+
5260+
f = _get_one(fbase, "Face")
5261+
5262+
rvs: List[TopoDS_Shape] = []
5263+
5264+
for el in _get_edges(edg, *edgs):
5265+
5266+
# sample the original curve
5267+
pts3D, params = el.sample(N)
5268+
5269+
# convert to 2D points ignoring the z coord
5270+
pts = [(el.x, el.y) for el in pts3D]
5271+
5272+
# handle periodicity
5273+
t0, t1 = el._bounds()
5274+
el_crv = el._geomAdaptor()
5275+
5276+
periodic = False
5277+
5278+
# periodic (and closed)
5279+
if el_crv.IsPeriodic() and el_crv.IsClosed():
5280+
periodic = True
5281+
params.append(t0 + el_crv.Period())
5282+
5283+
# only closed
5284+
elif el_crv.IsClosed():
5285+
pts.append(pts[0])
5286+
params.append(t1)
5287+
5288+
# interpolate the u,v points
5289+
spline_bldr = Geom2dAPI_Interpolate(
5290+
_pts_to_harray2D(pts), _floats_to_harray(params), periodic, tol
5291+
)
5292+
spline_bldr.Perform()
5293+
5294+
# build the final edge
5295+
rv = BRepBuilderAPI_MakeEdge(spline_bldr.Curve(), f._geomAdaptor()).Edge()
5296+
BRepLib.BuildCurves3d_s(rv)
5297+
5298+
rvs.append(rv)
5299+
5300+
return _compound_or_shape(rvs)
5301+
5302+
5303+
def wireOn(base: Shape, w: Shape, tol=1e-6, N=20) -> Shape:
5304+
"""
5305+
Map a wire onto a base face in the u,v space.
5306+
"""
5307+
5308+
rvs = [edgeOn(base, e, tol=tol, N=N) for e in w.Edges()]
5309+
5310+
return wire(rvs)
5311+
5312+
51325313
@multimethod
51335314
def wire(*s: Shape) -> Shape:
51345315
"""
@@ -5177,6 +5358,36 @@ def face(s: Sequence[Shape]) -> Shape:
51775358
return face(*s)
51785359

51795360

5361+
def faceOn(base: Shape, *fcs: Shape, tol=1e-6, N=20) -> Shape:
5362+
"""
5363+
Build face(s) on base by mapping planar face(s) onto the (u,v) space of base.
5364+
"""
5365+
5366+
rv: Shape
5367+
rvs = []
5368+
5369+
# get a face
5370+
fbase = _get_one(base, "Face")
5371+
5372+
# iterate over all faces
5373+
for el in fcs:
5374+
for fc in el.Faces():
5375+
# construct pcurves and trim in one go
5376+
rvs.append(
5377+
fbase.trim(
5378+
wireOn(fbase, fc.outerWire(), tol=tol, N=N),
5379+
*(wireOn(fbase, w, tol=tol, N=N) for w in fc.innerWires()),
5380+
)
5381+
)
5382+
5383+
if len(rvs) == 1:
5384+
rv = rvs[0]
5385+
else:
5386+
rv = compound(rvs)
5387+
5388+
return rv
5389+
5390+
51805391
def _process_sewing_history(
51815392
builder: BRepBuilderAPI_Sewing, faces: List[Face], history: Optional[ShapeHistory],
51825393
):

0 commit comments

Comments
 (0)