Skip to content

Commit

Permalink
Surface modeling functionality (#1007)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
adam-urbanczyk authored Jul 8, 2022
1 parent c9d3f1e commit 53045e7
Show file tree
Hide file tree
Showing 6 changed files with 457 additions and 189 deletions.
96 changes: 39 additions & 57 deletions cadquery/cq.py
Original file line number Diff line number Diff line change
Expand Up @@ -3745,10 +3745,12 @@ def _sweep(

def interpPlate(
self: T,
surf_edges: Union[Sequence[VectorLike], Sequence[Edge]],
surf_edges: Union[
Sequence[VectorLike], Sequence[Union[Edge, Wire]], "Workplane"
],
surf_pts: Sequence[VectorLike] = [],
thickness: float = 0,
combine: bool = False,
combine: CombineMode = False,
clean: bool = True,
degree: int = 3,
nbPtsOnCur: int = 15,
Expand Down Expand Up @@ -3797,42 +3799,47 @@ def interpPlate(
:type MaxSegments: Integer >= 2 (?)
"""

# If thickness is 0, only a 2D surface will be returned.
if thickness == 0:
combine = False
# convert points to edges if needed
edges: List[Union[Edge, Wire]] = []
points = []

if isinstance(surf_edges, Workplane):
edges.extend(cast(Edge, el) for el in surf_edges.edges().objects)
else:
for el in surf_edges:
if isinstance(el, (Edge, Wire)):
edges.append(el)
else:
points.append(el)

# Creates interpolated plate
p = Solid.interpPlate(
surf_edges,
f: Face = Face.makeNSidedSurface(
edges if not points else [Wire.makePolygon(points).close()],
surf_pts,
thickness,
degree,
nbPtsOnCur,
nbIter,
anisotropy,
tol2d,
tol3d,
tolAng,
tolCurv,
maxDeg,
maxSegments,
degree=degree,
nbPtsOnCur=nbPtsOnCur,
nbIter=nbIter,
anisotropy=anisotropy,
tol2d=tol2d,
tol3d=tol3d,
tolAng=tolAng,
tolCurv=tolCurv,
maxDeg=maxDeg,
maxSegments=maxSegments,
)

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

# if combination is not desired, just return the created boxes
if not combine:
return plates
else:
return self.union(plates, clean=clean)
return self.eachpoint(lambda loc: s.moved(loc), True, combine)

def box(
self: T,
length: float,
width: float,
height: float,
centered: Union[bool, Tuple[bool, bool, bool]] = True,
combine: bool = True,
combine: CombineMode = True,
clean: bool = True,
) -> T:
"""
Expand Down Expand Up @@ -3894,14 +3901,7 @@ def box(

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

boxes = self.eachpoint(lambda loc: box.moved(loc), True)

# if combination is not desired, just return the created boxes
if not combine:
return boxes
else:
# combine everything
return self.union(boxes, clean=clean)
return self.eachpoint(lambda loc: box.moved(loc), True, combine)

def sphere(
self: T,
Expand All @@ -3911,7 +3911,7 @@ def sphere(
angle2: float = 90,
angle3: float = 360,
centered: Union[bool, Tuple[bool, bool, bool]] = True,
combine: bool = True,
combine: CombineMode = True,
clean: bool = True,
) -> T:
"""
Expand Down Expand Up @@ -3965,13 +3965,7 @@ def sphere(
s = Solid.makeSphere(radius, offset, direct, angle1, angle2, angle3)

# We want a sphere for each point on the workplane
spheres = self.eachpoint(lambda loc: s.moved(loc), True)

# If we don't need to combine everything, just return the created spheres
if not combine:
return spheres
else:
return self.union(spheres, clean=clean)
return self.eachpoint(lambda loc: s.moved(loc), True, combine)

def cylinder(
self: T,
Expand All @@ -3980,7 +3974,7 @@ def cylinder(
direct: Vector = Vector(0, 0, 1),
angle: float = 360,
centered: Union[bool, Tuple[bool, bool, bool]] = True,
combine: bool = True,
combine: CombineMode = True,
clean: bool = True,
) -> T:
"""
Expand Down Expand Up @@ -4028,13 +4022,7 @@ def cylinder(
s = Solid.makeCylinder(radius, height, offset, direct, angle)

# We want a cylinder for each point on the workplane
cylinders = self.eachpoint(lambda loc: s.moved(loc), True)

# If we don't need to combine everything, just return the created cylinders
if not combine:
return cylinders
else:
return self.union(cylinders, clean=clean)
return self.eachpoint(lambda loc: s.moved(loc), True, combine)

def wedge(
self: T,
Expand All @@ -4048,7 +4036,7 @@ def wedge(
pnt: VectorLike = Vector(0, 0, 0),
dir: VectorLike = Vector(0, 0, 1),
centered: Union[bool, Tuple[bool, bool, bool]] = True,
combine: bool = True,
combine: CombineMode = True,
clean: bool = True,
) -> T:
"""
Expand Down Expand Up @@ -4104,13 +4092,7 @@ def wedge(
w = Solid.makeWedge(dx, dy, dz, xmin, zmin, xmax, zmax, offset, dir)

# We want a wedge for each point on the workplane
wedges = self.eachpoint(lambda loc: w.moved(loc), True)

# If we don't need to combine everything, just return the created wedges
if not combine:
return wedges
else:
return self.union(wedges, clean=clean)
return self.eachpoint(lambda loc: w.moved(loc), True, combine)

def clean(self: T) -> T:
"""
Expand Down
30 changes: 18 additions & 12 deletions cadquery/occ_impl/geom.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@
from OCP.TopoDS import TopoDS_Shape
from OCP.TopLoc import TopLoc_Location

from ..types import Real

TOL = 1e-2

VectorLike = Union["Vector", Tuple[Real, Real], Tuple[Real, Real, Real]]


class Vector(object):
"""Create a 3-dimensional vector
Expand Down Expand Up @@ -928,7 +932,7 @@ def __init__(self) -> None:
...

@overload
def __init__(self, t: Vector) -> None:
def __init__(self, t: VectorLike) -> None:
"""Location with translation t with respect to the original location."""
...

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

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

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

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

if isinstance(t, Vector):
T.SetTranslationPart(t.wrapped)
if isinstance(t, (Vector, tuple)):
T.SetTranslationPart(Vector(t).wrapped)
elif isinstance(t, Plane):
cs = gp_Ax3(t.origin.toPnt(), t.zDir.toDir(), t.xDir.toDir())
T.SetTransformation(cs)
Expand All @@ -978,21 +982,19 @@ def __init__(self, *args):
return
elif isinstance(t, gp_Trsf):
T = t
elif isinstance(t, (tuple, list)):
raise TypeError(
"A tuple or list is not a valid parameter, use a Vector instead."
)
else:
raise TypeError("Unexpected parameters")
elif len(args) == 2:
t, v = args
cs = gp_Ax3(v.toPnt(), t.zDir.toDir(), t.xDir.toDir())
cs = gp_Ax3(Vector(v).toPnt(), t.zDir.toDir(), t.xDir.toDir())
T.SetTransformation(cs)
T.Invert()
else:
t, ax, angle = args
T.SetRotation(gp_Ax1(Vector().toPnt(), ax.toDir()), angle * math.pi / 180.0)
T.SetTranslationPart(t.wrapped)
T.SetRotation(
gp_Ax1(Vector().toPnt(), Vector(ax).toDir()), angle * math.pi / 180.0
)
T.SetTranslationPart(Vector(t).wrapped)

self.wrapped = TopLoc_Location(T)

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

return Location(self.wrapped * other.wrapped)

def __pow__(self, exponent: int) -> "Location":

return Location(self.wrapped.Powered(exponent))

def toTuple(self) -> Tuple[Tuple[float, float, float], Tuple[float, float, float]]:
"""Convert the location to a translation, rotation tuple."""

Expand Down
Loading

0 comments on commit 53045e7

Please sign in to comment.