Skip to content

Commit 53045e7

Browse files
Surface modeling functionality (#1007)
* Thicken and extend nsided * Accept wires too * Implement project * Add distance(s), project and constructOn * Convert to VectorLike * Allow VectorLike everywhere * Implement Location ** and allow VectorLike * Additional tests for Location * Refactor interpPlate * Fix tests * Project and distance tests * More tests * More tests * Use Real for dispatch * Better coverage
1 parent c9d3f1e commit 53045e7

File tree

6 files changed

+457
-189
lines changed

6 files changed

+457
-189
lines changed

cadquery/cq.py

Lines changed: 39 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3745,10 +3745,12 @@ def _sweep(
37453745

37463746
def interpPlate(
37473747
self: T,
3748-
surf_edges: Union[Sequence[VectorLike], Sequence[Edge]],
3748+
surf_edges: Union[
3749+
Sequence[VectorLike], Sequence[Union[Edge, Wire]], "Workplane"
3750+
],
37493751
surf_pts: Sequence[VectorLike] = [],
37503752
thickness: float = 0,
3751-
combine: bool = False,
3753+
combine: CombineMode = False,
37523754
clean: bool = True,
37533755
degree: int = 3,
37543756
nbPtsOnCur: int = 15,
@@ -3797,42 +3799,47 @@ def interpPlate(
37973799
:type MaxSegments: Integer >= 2 (?)
37983800
"""
37993801

3800-
# If thickness is 0, only a 2D surface will be returned.
3801-
if thickness == 0:
3802-
combine = False
3802+
# convert points to edges if needed
3803+
edges: List[Union[Edge, Wire]] = []
3804+
points = []
3805+
3806+
if isinstance(surf_edges, Workplane):
3807+
edges.extend(cast(Edge, el) for el in surf_edges.edges().objects)
3808+
else:
3809+
for el in surf_edges:
3810+
if isinstance(el, (Edge, Wire)):
3811+
edges.append(el)
3812+
else:
3813+
points.append(el)
38033814

38043815
# Creates interpolated plate
3805-
p = Solid.interpPlate(
3806-
surf_edges,
3816+
f: Face = Face.makeNSidedSurface(
3817+
edges if not points else [Wire.makePolygon(points).close()],
38073818
surf_pts,
3808-
thickness,
3809-
degree,
3810-
nbPtsOnCur,
3811-
nbIter,
3812-
anisotropy,
3813-
tol2d,
3814-
tol3d,
3815-
tolAng,
3816-
tolCurv,
3817-
maxDeg,
3818-
maxSegments,
3819+
degree=degree,
3820+
nbPtsOnCur=nbPtsOnCur,
3821+
nbIter=nbIter,
3822+
anisotropy=anisotropy,
3823+
tol2d=tol2d,
3824+
tol3d=tol3d,
3825+
tolAng=tolAng,
3826+
tolCurv=tolCurv,
3827+
maxDeg=maxDeg,
3828+
maxSegments=maxSegments,
38193829
)
38203830

3821-
plates = self.eachpoint(lambda loc: p.moved(loc), True)
3831+
# thicken if needed
3832+
s = f.thicken(thickness) if thickness > 0 else f
38223833

3823-
# if combination is not desired, just return the created boxes
3824-
if not combine:
3825-
return plates
3826-
else:
3827-
return self.union(plates, clean=clean)
3834+
return self.eachpoint(lambda loc: s.moved(loc), True, combine)
38283835

38293836
def box(
38303837
self: T,
38313838
length: float,
38323839
width: float,
38333840
height: float,
38343841
centered: Union[bool, Tuple[bool, bool, bool]] = True,
3835-
combine: bool = True,
3842+
combine: CombineMode = True,
38363843
clean: bool = True,
38373844
) -> T:
38383845
"""
@@ -3894,14 +3901,7 @@ def box(
38943901

38953902
box = Solid.makeBox(length, width, height, offset)
38963903

3897-
boxes = self.eachpoint(lambda loc: box.moved(loc), True)
3898-
3899-
# if combination is not desired, just return the created boxes
3900-
if not combine:
3901-
return boxes
3902-
else:
3903-
# combine everything
3904-
return self.union(boxes, clean=clean)
3904+
return self.eachpoint(lambda loc: box.moved(loc), True, combine)
39053905

39063906
def sphere(
39073907
self: T,
@@ -3911,7 +3911,7 @@ def sphere(
39113911
angle2: float = 90,
39123912
angle3: float = 360,
39133913
centered: Union[bool, Tuple[bool, bool, bool]] = True,
3914-
combine: bool = True,
3914+
combine: CombineMode = True,
39153915
clean: bool = True,
39163916
) -> T:
39173917
"""
@@ -3965,13 +3965,7 @@ def sphere(
39653965
s = Solid.makeSphere(radius, offset, direct, angle1, angle2, angle3)
39663966

39673967
# We want a sphere for each point on the workplane
3968-
spheres = self.eachpoint(lambda loc: s.moved(loc), True)
3969-
3970-
# If we don't need to combine everything, just return the created spheres
3971-
if not combine:
3972-
return spheres
3973-
else:
3974-
return self.union(spheres, clean=clean)
3968+
return self.eachpoint(lambda loc: s.moved(loc), True, combine)
39753969

39763970
def cylinder(
39773971
self: T,
@@ -3980,7 +3974,7 @@ def cylinder(
39803974
direct: Vector = Vector(0, 0, 1),
39813975
angle: float = 360,
39823976
centered: Union[bool, Tuple[bool, bool, bool]] = True,
3983-
combine: bool = True,
3977+
combine: CombineMode = True,
39843978
clean: bool = True,
39853979
) -> T:
39863980
"""
@@ -4028,13 +4022,7 @@ def cylinder(
40284022
s = Solid.makeCylinder(radius, height, offset, direct, angle)
40294023

40304024
# We want a cylinder for each point on the workplane
4031-
cylinders = self.eachpoint(lambda loc: s.moved(loc), True)
4032-
4033-
# If we don't need to combine everything, just return the created cylinders
4034-
if not combine:
4035-
return cylinders
4036-
else:
4037-
return self.union(cylinders, clean=clean)
4025+
return self.eachpoint(lambda loc: s.moved(loc), True, combine)
40384026

40394027
def wedge(
40404028
self: T,
@@ -4048,7 +4036,7 @@ def wedge(
40484036
pnt: VectorLike = Vector(0, 0, 0),
40494037
dir: VectorLike = Vector(0, 0, 1),
40504038
centered: Union[bool, Tuple[bool, bool, bool]] = True,
4051-
combine: bool = True,
4039+
combine: CombineMode = True,
40524040
clean: bool = True,
40534041
) -> T:
40544042
"""
@@ -4104,13 +4092,7 @@ def wedge(
41044092
w = Solid.makeWedge(dx, dy, dz, xmin, zmin, xmax, zmax, offset, dir)
41054093

41064094
# We want a wedge for each point on the workplane
4107-
wedges = self.eachpoint(lambda loc: w.moved(loc), True)
4108-
4109-
# If we don't need to combine everything, just return the created wedges
4110-
if not combine:
4111-
return wedges
4112-
else:
4113-
return self.union(wedges, clean=clean)
4095+
return self.eachpoint(lambda loc: w.moved(loc), True, combine)
41144096

41154097
def clean(self: T) -> T:
41164098
"""

cadquery/occ_impl/geom.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@
2121
from OCP.TopoDS import TopoDS_Shape
2222
from OCP.TopLoc import TopLoc_Location
2323

24+
from ..types import Real
25+
2426
TOL = 1e-2
2527

28+
VectorLike = Union["Vector", Tuple[Real, Real], Tuple[Real, Real, Real]]
29+
2630

2731
class Vector(object):
2832
"""Create a 3-dimensional vector
@@ -928,7 +932,7 @@ def __init__(self) -> None:
928932
...
929933

930934
@overload
931-
def __init__(self, t: Vector) -> None:
935+
def __init__(self, t: VectorLike) -> None:
932936
"""Location with translation t with respect to the original location."""
933937
...
934938

@@ -938,7 +942,7 @@ def __init__(self, t: Plane) -> None:
938942
...
939943

940944
@overload
941-
def __init__(self, t: Plane, v: Vector) -> None:
945+
def __init__(self, t: Plane, v: VectorLike) -> None:
942946
"""Location corresponding to the angular location of the Plane t with translation v."""
943947
...
944948

@@ -953,7 +957,7 @@ def __init__(self, t: gp_Trsf) -> None:
953957
...
954958

955959
@overload
956-
def __init__(self, t: Vector, ax: Vector, angle: float) -> None:
960+
def __init__(self, t: VectorLike, ax: VectorLike, angle: float) -> None:
957961
"""Location with translation t and rotation around ax by angle
958962
with respect to the original location."""
959963
...
@@ -967,8 +971,8 @@ def __init__(self, *args):
967971
elif len(args) == 1:
968972
t = args[0]
969973

970-
if isinstance(t, Vector):
971-
T.SetTranslationPart(t.wrapped)
974+
if isinstance(t, (Vector, tuple)):
975+
T.SetTranslationPart(Vector(t).wrapped)
972976
elif isinstance(t, Plane):
973977
cs = gp_Ax3(t.origin.toPnt(), t.zDir.toDir(), t.xDir.toDir())
974978
T.SetTransformation(cs)
@@ -978,21 +982,19 @@ def __init__(self, *args):
978982
return
979983
elif isinstance(t, gp_Trsf):
980984
T = t
981-
elif isinstance(t, (tuple, list)):
982-
raise TypeError(
983-
"A tuple or list is not a valid parameter, use a Vector instead."
984-
)
985985
else:
986986
raise TypeError("Unexpected parameters")
987987
elif len(args) == 2:
988988
t, v = args
989-
cs = gp_Ax3(v.toPnt(), t.zDir.toDir(), t.xDir.toDir())
989+
cs = gp_Ax3(Vector(v).toPnt(), t.zDir.toDir(), t.xDir.toDir())
990990
T.SetTransformation(cs)
991991
T.Invert()
992992
else:
993993
t, ax, angle = args
994-
T.SetRotation(gp_Ax1(Vector().toPnt(), ax.toDir()), angle * math.pi / 180.0)
995-
T.SetTranslationPart(t.wrapped)
994+
T.SetRotation(
995+
gp_Ax1(Vector().toPnt(), Vector(ax).toDir()), angle * math.pi / 180.0
996+
)
997+
T.SetTranslationPart(Vector(t).wrapped)
996998

997999
self.wrapped = TopLoc_Location(T)
9981000

@@ -1005,6 +1007,10 @@ def __mul__(self, other: "Location") -> "Location":
10051007

10061008
return Location(self.wrapped * other.wrapped)
10071009

1010+
def __pow__(self, exponent: int) -> "Location":
1011+
1012+
return Location(self.wrapped.Powered(exponent))
1013+
10081014
def toTuple(self) -> Tuple[Tuple[float, float, float], Tuple[float, float, float]]:
10091015
"""Convert the location to a translation, rotation tuple."""
10101016

0 commit comments

Comments
 (0)