Skip to content

Commit 1c0e747

Browse files
Final shape / free func tweaks (#1731)
* Add sample * Mypy fix * Isoline constructon * Add isolines and return params when sampling * Extend check * Test extended check output * Mypy fix * Func API * Use the official import in the docs * Add face * Handle trimming and all possible geometries * Initial tests * test _adaptor_curve_to_edge * Fix test * Be consistent with Sketch * Punctuation * Update cadquery/occ_impl/shapes.py Co-authored-by: Jeremy Wright <[email protected]> --------- Co-authored-by: Jeremy Wright <[email protected]>
1 parent f1bf901 commit 1c0e747

File tree

6 files changed

+250
-14
lines changed

6 files changed

+250
-14
lines changed

cadquery/func.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from .occ_impl.geom import Vector, Plane, Location
2+
from .occ_impl.shapes import (
3+
Shape,
4+
Vertex,
5+
Edge,
6+
Wire,
7+
Face,
8+
Shell,
9+
Solid,
10+
CompSolid,
11+
Compound,
12+
wire,
13+
face,
14+
shell,
15+
solid,
16+
compound,
17+
vertex,
18+
segment,
19+
polyline,
20+
polygon,
21+
rect,
22+
spline,
23+
circle,
24+
ellipse,
25+
plane,
26+
box,
27+
cylinder,
28+
sphere,
29+
torus,
30+
cone,
31+
text,
32+
fuse,
33+
cut,
34+
intersect,
35+
split,
36+
fill,
37+
clean,
38+
cap,
39+
fillet,
40+
chamfer,
41+
extrude,
42+
revolve,
43+
offset,
44+
sweep,
45+
loft,
46+
check,
47+
)

cadquery/occ_impl/geom.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from math import pi, radians, degrees
22

3-
from typing import overload, Sequence, Union, Tuple, Type, Optional
3+
from typing import overload, Sequence, Union, Tuple, Type, Optional, Iterator
44

55
from OCP.gp import (
66
gp_Vec,
@@ -236,6 +236,10 @@ def __eq__(self, other: "Vector") -> bool: # type: ignore[override]
236236
else False
237237
)
238238

239+
def __iter__(self) -> Iterator[float]:
240+
241+
yield from (self.x, self.y, self.z)
242+
239243
def toPnt(self) -> gp_Pnt:
240244

241245
return gp_Pnt(self.wrapped.XYZ())

cadquery/occ_impl/shapes.py

Lines changed: 123 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@
231231
GeomAbs_C2,
232232
GeomAbs_Intersection,
233233
GeomAbs_JoinType,
234+
GeomAbs_IsoType,
235+
GeomAbs_CurveType,
234236
)
235237
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeFilling
236238
from OCP.BRepOffset import BRepOffset_MakeOffset, BRepOffset_Mode
@@ -244,7 +246,11 @@
244246
from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds, ShapeAnalysis_Wire
245247
from OCP.TopTools import TopTools_HSequenceOfShape
246248

247-
from OCP.GCPnts import GCPnts_AbscissaPoint
249+
from OCP.GCPnts import (
250+
GCPnts_AbscissaPoint,
251+
GCPnts_QuasiUniformAbscissa,
252+
GCPnts_QuasiUniformDeflection,
253+
)
248254

249255
from OCP.GeomFill import (
250256
GeomFill_Frenet,
@@ -281,6 +287,10 @@
281287

282288
from OCP.BinTools import BinTools
283289

290+
from OCP.Adaptor3d import Adaptor3d_IsoCurve, Adaptor3d_Curve
291+
292+
from OCP.GeomAdaptor import GeomAdaptor_Surface
293+
284294
from math import pi, sqrt, inf, radians, cos
285295

286296
import warnings
@@ -1900,7 +1910,8 @@ def _curve_and_param(
19001910
def positionAt(
19011911
self: Mixin1DProtocol, d: float, mode: ParamMode = "length",
19021912
) -> Vector:
1903-
"""Generate a position along the underlying curve.
1913+
"""
1914+
Generate a position along the underlying curve.
19041915
19051916
:param d: distance or parameter value
19061917
:param mode: position calculation mode (default: length)
@@ -1914,7 +1925,8 @@ def positionAt(
19141925
def positions(
19151926
self: Mixin1DProtocol, ds: Iterable[float], mode: ParamMode = "length",
19161927
) -> List[Vector]:
1917-
"""Generate positions along the underlying curve
1928+
"""
1929+
Generate positions along the underlying curve.
19181930
19191931
:param ds: distance or parameter values
19201932
:param mode: position calculation mode (default: length)
@@ -1923,14 +1935,44 @@ def positions(
19231935

19241936
return [self.positionAt(d, mode) for d in ds]
19251937

1938+
def sample(
1939+
self: Mixin1DProtocol, n: Union[int, float]
1940+
) -> Tuple[List[Vector], List[float]]:
1941+
"""
1942+
Sample a curve based on a number of points or deflection.
1943+
1944+
:param n: Number of positions or deflection
1945+
:return: A list of Vectors and a list of parameters.
1946+
"""
1947+
1948+
gcpnts: Union[GCPnts_QuasiUniformAbscissa, GCPnts_QuasiUniformDeflection]
1949+
1950+
if isinstance(n, int):
1951+
crv = self._geomAdaptor()
1952+
gcpnts = GCPnts_QuasiUniformAbscissa(crv, n + 1 if crv.IsClosed() else n)
1953+
else:
1954+
crv = self._geomAdaptor()
1955+
gcpnts = GCPnts_QuasiUniformDeflection(crv, n)
1956+
1957+
N_pts = gcpnts.NbPoints()
1958+
1959+
params = [
1960+
gcpnts.Parameter(i)
1961+
for i in range(1, N_pts if crv.IsClosed() else N_pts + 1)
1962+
]
1963+
pnts = [Vector(crv.Value(p)) for p in params]
1964+
1965+
return pnts, params
1966+
19261967
def locationAt(
19271968
self: Mixin1DProtocol,
19281969
d: float,
19291970
mode: ParamMode = "length",
19301971
frame: FrameMode = "frenet",
19311972
planar: bool = False,
19321973
) -> Location:
1933-
"""Generate a location along the underlying curve.
1974+
"""
1975+
Generate a location along the underlying curve.
19341976
19351977
:param d: distance or parameter value
19361978
:param mode: position calculation mode (default: length)
@@ -1973,7 +2015,8 @@ def locations(
19732015
frame: FrameMode = "frenet",
19742016
planar: bool = False,
19752017
) -> List[Location]:
1976-
"""Generate location along the curve
2018+
"""
2019+
Generate locations along the curve.
19772020
19782021
:param ds: distance or parameter values
19792022
:param mode: position calculation mode (default: length)
@@ -3188,6 +3231,35 @@ def trim(self, u0: Real, u1: Real, v0: Real, v1: Real, tol: Real = 1e-6) -> "Fac
31883231

31893232
return self.__class__(bldr.Shape())
31903233

3234+
def isoline(self, param: Real, direction: Literal["u", "v"] = "v") -> Edge:
3235+
"""
3236+
Construct an isoline.
3237+
"""
3238+
3239+
u1, u2, v1, v2 = self._uvBounds()
3240+
3241+
if direction == "u":
3242+
iso = GeomAbs_IsoType.GeomAbs_IsoU
3243+
p1, p2 = v1, v2
3244+
else:
3245+
iso = GeomAbs_IsoType.GeomAbs_IsoV
3246+
p1, p2 = u1, u2
3247+
3248+
adaptor = Adaptor3d_IsoCurve(
3249+
GeomAdaptor_Surface(self._geomAdaptor()), iso, param
3250+
)
3251+
3252+
return Edge(_adaptor_curve_to_edge(adaptor, p1, p2))
3253+
3254+
def isolines(
3255+
self, params: Iterable[Real], direction: Literal["u", "v"] = "v"
3256+
) -> List[Edge]:
3257+
"""
3258+
Construct multiple isolines.
3259+
"""
3260+
3261+
return [self.isoline(p, direction) for p in params]
3262+
31913263

31923264
class Shell(Shape):
31933265
"""
@@ -4622,6 +4694,14 @@ def _shapes_to_toptools_list(s: Iterable[Shape]) -> TopTools_ListOfShape:
46224694
return rv
46234695

46244696

4697+
def _toptools_list_to_shapes(tl: TopTools_ListOfShape) -> List[Shape]:
4698+
"""
4699+
Convert a TopTools list (OCCT specific) to a compound.
4700+
"""
4701+
4702+
return [_normalize(Shape.cast(el)) for el in tl]
4703+
4704+
46254705
_geomabsshape_dict = dict(
46264706
C0=GeomAbs_Shape.GeomAbs_C0,
46274707
C1=GeomAbs_Shape.GeomAbs_C1,
@@ -4656,6 +4736,34 @@ def _to_parametrization(name: str) -> Approx_ParametrizationType:
46564736
return _parametrization_dict[name.lower()]
46574737

46584738

4739+
def _adaptor_curve_to_edge(crv: Adaptor3d_Curve, p1: float, p2: float) -> TopoDS_Edge:
4740+
4741+
GCT = GeomAbs_CurveType
4742+
4743+
t = crv.GetType()
4744+
4745+
if t == GCT.GeomAbs_BSplineCurve:
4746+
bldr = BRepBuilderAPI_MakeEdge(crv.BSpline(), p1, p2)
4747+
elif t == GCT.GeomAbs_BezierCurve:
4748+
bldr = BRepBuilderAPI_MakeEdge(crv.Bezier(), p1, p2)
4749+
elif t == GCT.GeomAbs_Circle:
4750+
bldr = BRepBuilderAPI_MakeEdge(crv.Circle(), p1, p2)
4751+
elif t == GCT.GeomAbs_Line:
4752+
bldr = BRepBuilderAPI_MakeEdge(crv.Line(), p1, p2)
4753+
elif t == GCT.GeomAbs_Ellipse:
4754+
bldr = BRepBuilderAPI_MakeEdge(crv.Ellipse(), p1, p2)
4755+
elif t == GCT.GeomAbs_Hyperbola:
4756+
bldr = BRepBuilderAPI_MakeEdge(crv.Hyperbola(), p1, p2)
4757+
elif t == GCT.GeomAbs_Parabola:
4758+
bldr = BRepBuilderAPI_MakeEdge(crv.Parabola(), p1, p2)
4759+
elif t == GCT.GeomAbs_OffsetCurve:
4760+
bldr = BRepBuilderAPI_MakeEdge(crv.OffsetCurve(), p1, p2)
4761+
else:
4762+
raise ValueError(r"{t} is not a supported curve type")
4763+
4764+
return bldr.Edge()
4765+
4766+
46594767
#%% alternative constructors
46604768

46614769

@@ -5675,7 +5783,7 @@ def loft(
56755783
#%% diagnotics
56765784

56775785

5678-
def check(s: Shape) -> bool:
5786+
def check(s: Shape, results: Optional[List[Tuple[List[Shape], Any]]] = None) -> bool:
56795787
"""
56805788
Check if a shape is valid.
56815789
"""
@@ -5688,4 +5796,13 @@ def check(s: Shape) -> bool:
56885796

56895797
rv = analyzer.IsValid()
56905798

5799+
# output detailed results if requested
5800+
if results is not None:
5801+
results.clear()
5802+
5803+
for r in analyzer.Result():
5804+
results.append(
5805+
(_toptools_list_to_shapes(r.GetFaultyShapes1()), r.GetCheckStatus())
5806+
)
5807+
56915808
return rv

doc/free-func.rst

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The purpose of this section is to demonstrate how to construct Shape objects usi
2121
.. cadquery::
2222
:height: 600px
2323

24-
from cadquery.occ_impl.shapes import *
24+
from cadquery.func import *
2525

2626
dh = 2
2727
r = 1
@@ -97,7 +97,7 @@ Various 1D, 2D and 3D primitives are supported.
9797

9898
.. cadquery::
9999

100-
from cadquery.occ_impl.shapes import *
100+
from cadquery.func import *
101101

102102
e = segment((0,0), (0,1))
103103

@@ -119,7 +119,7 @@ One can for example union multiple solids at once by first combining them into a
119119

120120
.. cadquery::
121121

122-
from cadquery.occ_impl.shapes import *
122+
from cadquery.func import *
123123

124124
c1 = cylinder(1, 2)
125125
c2 = cylinder(0.5, 3)
@@ -158,7 +158,7 @@ Constructing complex shapes from simple shapes is possible in various contexts.
158158

159159
.. cadquery::
160160

161-
from cadquery.occ_impl.shapes import *
161+
from cadquery.func import *
162162

163163
e1 = segment((0,0), (1,0))
164164
e2 = segment((1,0), (1,1))
@@ -196,7 +196,7 @@ Free function API currently supports :meth:`~cadquery.occ_impl.shapes.extrude`,
196196

197197
.. cadquery::
198198

199-
from cadquery.occ_impl.shapes import *
199+
from cadquery.func import *
200200

201201
r = rect(1,0.5)
202202
f = face(r, circle(0.2).moved(0.2), rect(0.2, 0.4).moved(-0.2))
@@ -229,7 +229,7 @@ Placement and creation of arrays is possible using :meth:`~cadquery.Shape.move`
229229

230230
.. cadquery::
231231

232-
from cadquery.occ_impl.shapes import *
232+
from cadquery.func import *
233233

234234
locs = [(0,-1,0), (0,1,0)]
235235

@@ -246,7 +246,7 @@ The free function API has extensive text creation capabilities including text on
246246

247247
.. cadquery::
248248

249-
from cadquery.occ_impl.shapes import *
249+
from cadquery.func import *
250250

251251
from math import pi
252252

0 commit comments

Comments
 (0)