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
This commit is contained in:
@ -3745,10 +3745,12 @@ class Workplane(object):
|
||||
|
||||
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,
|
||||
@ -3797,34 +3799,39 @@ class Workplane(object):
|
||||
: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,
|
||||
@ -3832,7 +3839,7 @@ class Workplane(object):
|
||||
width: float,
|
||||
height: float,
|
||||
centered: Union[bool, Tuple[bool, bool, bool]] = True,
|
||||
combine: bool = True,
|
||||
combine: CombineMode = True,
|
||||
clean: bool = True,
|
||||
) -> T:
|
||||
"""
|
||||
@ -3894,14 +3901,7 @@ class Workplane(object):
|
||||
|
||||
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,
|
||||
@ -3911,7 +3911,7 @@ class Workplane(object):
|
||||
angle2: float = 90,
|
||||
angle3: float = 360,
|
||||
centered: Union[bool, Tuple[bool, bool, bool]] = True,
|
||||
combine: bool = True,
|
||||
combine: CombineMode = True,
|
||||
clean: bool = True,
|
||||
) -> T:
|
||||
"""
|
||||
@ -3965,13 +3965,7 @@ class Workplane(object):
|
||||
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,
|
||||
@ -3980,7 +3974,7 @@ class Workplane(object):
|
||||
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:
|
||||
"""
|
||||
@ -4028,13 +4022,7 @@ class Workplane(object):
|
||||
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,
|
||||
@ -4048,7 +4036,7 @@ class Workplane(object):
|
||||
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:
|
||||
"""
|
||||
@ -4104,13 +4092,7 @@ class Workplane(object):
|
||||
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:
|
||||
"""
|
||||
|
||||
@ -21,8 +21,12 @@ from OCP.BRepMesh import BRepMesh_IncrementalMesh
|
||||
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
|
||||
@ -928,7 +932,7 @@ class Location(object):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(self, t: Vector) -> None:
|
||||
def __init__(self, t: VectorLike) -> None:
|
||||
"""Location with translation t with respect to the original location."""
|
||||
...
|
||||
|
||||
@ -938,7 +942,7 @@ class Location(object):
|
||||
...
|
||||
|
||||
@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."""
|
||||
...
|
||||
|
||||
@ -953,7 +957,7 @@ class Location(object):
|
||||
...
|
||||
|
||||
@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."""
|
||||
...
|
||||
@ -967,8 +971,8 @@ class Location(object):
|
||||
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)
|
||||
@ -978,21 +982,19 @@ class Location(object):
|
||||
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)
|
||||
|
||||
@ -1005,6 +1007,10 @@ class Location(object):
|
||||
|
||||
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."""
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
from typing import (
|
||||
Type,
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
@ -20,7 +19,8 @@ from io import BytesIO
|
||||
from vtkmodules.vtkCommonDataModel import vtkPolyData
|
||||
from vtkmodules.vtkFiltersCore import vtkTriangleFilter, vtkPolyDataNormals
|
||||
|
||||
from .geom import Vector, BoundBox, Plane, Location, Matrix
|
||||
|
||||
from .geom import Vector, VectorLike, BoundBox, Plane, Location, Matrix
|
||||
|
||||
from ..utils import cqmultimethod as multimethod
|
||||
|
||||
@ -205,7 +205,7 @@ from OCP.GeomAbs import (
|
||||
GeomAbs_JoinType,
|
||||
)
|
||||
from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeFilling
|
||||
from OCP.BRepOffset import BRepOffset_MakeOffset, BRepOffset_Skin
|
||||
from OCP.BRepOffset import BRepOffset_MakeOffset, BRepOffset_Mode
|
||||
|
||||
from OCP.BOPAlgo import BOPAlgo_GlueEnum
|
||||
|
||||
@ -224,6 +224,9 @@ from OCP.GeomFill import (
|
||||
GeomFill_TrihedronLaw,
|
||||
)
|
||||
|
||||
from OCP.BRepProj import BRepProj_Projection
|
||||
from OCP.BRepExtrema import BRepExtrema_DistShapeShape
|
||||
|
||||
from OCP.IVtkOCC import IVtkOCC_Shape, IVtkOCC_ShapeMesher
|
||||
from OCP.IVtkVTK import IVtkVTK_ShapeData
|
||||
|
||||
@ -232,9 +235,12 @@ from OCP.Standard import Standard_NoSuchObject, Standard_Failure
|
||||
|
||||
from OCP.Interface import Interface_Static
|
||||
|
||||
from math import pi, sqrt
|
||||
from math import pi, sqrt, inf
|
||||
|
||||
import warnings
|
||||
|
||||
from ..utils import deprecate
|
||||
|
||||
Real = Union[float, int]
|
||||
|
||||
TOLERANCE = 1e-6
|
||||
@ -338,7 +344,7 @@ Geoms = Literal[
|
||||
"HYPERBOLA",
|
||||
"PARABOLA",
|
||||
]
|
||||
VectorLike = Union[Vector, Tuple[float, float, float]]
|
||||
|
||||
T = TypeVar("T", bound="Shape")
|
||||
|
||||
|
||||
@ -846,7 +852,7 @@ class Shape(object):
|
||||
return self.__class__(BRepBuilderAPI_Transform(self.wrapped, Tr, True).Shape())
|
||||
|
||||
def rotate(
|
||||
self: T, startVector: Vector, endVector: Vector, angleDegrees: float
|
||||
self: T, startVector: VectorLike, endVector: VectorLike, angleDegrees: float
|
||||
) -> T:
|
||||
"""
|
||||
Rotates a shape around an axis.
|
||||
@ -866,22 +872,22 @@ class Shape(object):
|
||||
|
||||
Tr = gp_Trsf()
|
||||
Tr.SetRotation(
|
||||
gp_Ax1(startVector.toPnt(), (endVector - startVector).toDir()),
|
||||
gp_Ax1(
|
||||
Vector(startVector).toPnt(),
|
||||
(Vector(endVector) - Vector(startVector)).toDir(),
|
||||
),
|
||||
angleDegrees * DEG2RAD,
|
||||
)
|
||||
|
||||
return self._apply_transform(Tr)
|
||||
|
||||
def translate(self: T, vector: Vector) -> T:
|
||||
def translate(self: T, vector: VectorLike) -> T:
|
||||
"""
|
||||
Translates this shape through a transformation.
|
||||
"""
|
||||
|
||||
if type(vector) == tuple:
|
||||
vector = Vector(vector)
|
||||
|
||||
T = gp_Trsf()
|
||||
T.SetTranslation(vector.wrapped)
|
||||
T.SetTranslation(Vector(vector).wrapped)
|
||||
|
||||
return self._apply_transform(T)
|
||||
|
||||
@ -1147,6 +1153,27 @@ class Shape(object):
|
||||
|
||||
return self._bool_op((self,), splitters, split_op)
|
||||
|
||||
def distance(self, other: "Shape") -> float:
|
||||
"""
|
||||
Minimal distance between two shapes
|
||||
"""
|
||||
|
||||
return BRepExtrema_DistShapeShape(self.wrapped, other.wrapped).Value()
|
||||
|
||||
def distances(self, *others: "Shape") -> Iterator[float]:
|
||||
"""
|
||||
Minimal distances to between self and other shapes
|
||||
"""
|
||||
|
||||
dist_calc = BRepExtrema_DistShapeShape()
|
||||
dist_calc.LoadS1(self.wrapped)
|
||||
|
||||
for s in others:
|
||||
dist_calc.LoadS2(s.wrapped)
|
||||
dist_calc.Perform()
|
||||
|
||||
yield dist_calc.Value()
|
||||
|
||||
def mesh(self, tolerance: float, angularTolerance: float = 0.1):
|
||||
"""
|
||||
Generate triangulation if none exists.
|
||||
@ -1327,6 +1354,9 @@ class Mixin1DProtocol(ShapeProtocol, Protocol):
|
||||
...
|
||||
|
||||
|
||||
T1D = TypeVar("T1D", bound=Mixin1DProtocol)
|
||||
|
||||
|
||||
class Mixin1D(object):
|
||||
def _bounds(self: Mixin1DProtocol) -> Tuple[float, float]:
|
||||
|
||||
@ -1555,6 +1585,40 @@ class Mixin1D(object):
|
||||
|
||||
return [self.locationAt(d, mode, frame, planar) for d in ds]
|
||||
|
||||
def project(
|
||||
self: T1D, face: "Face", d: VectorLike, closest: bool = True
|
||||
) -> Union[T1D, List[T1D]]:
|
||||
"""
|
||||
Project onto a face along the specified direction
|
||||
"""
|
||||
|
||||
bldr = BRepProj_Projection(self.wrapped, face.wrapped, Vector(d).toDir())
|
||||
shapes = Compound(bldr.Shape())
|
||||
|
||||
# select the closest projection if requested
|
||||
rv: Union[T1D, List[T1D]]
|
||||
|
||||
if closest:
|
||||
|
||||
dist_calc = BRepExtrema_DistShapeShape()
|
||||
dist_calc.LoadS1(self.wrapped)
|
||||
|
||||
min_dist = inf
|
||||
|
||||
for el in shapes:
|
||||
dist_calc.LoadS2(el.wrapped)
|
||||
dist_calc.Perform()
|
||||
dist = dist_calc.Value()
|
||||
|
||||
if dist < min_dist:
|
||||
min_dist = dist
|
||||
rv = tcast(T1D, el)
|
||||
|
||||
else:
|
||||
rv = [tcast(T1D, el) for el in shapes]
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class Edge(Shape, Mixin1D):
|
||||
"""
|
||||
@ -1808,7 +1872,9 @@ class Edge(Shape, Mixin1D):
|
||||
return cls(BRepBuilderAPI_MakeEdge(spline_geom).Edge())
|
||||
|
||||
@classmethod
|
||||
def makeThreePointArc(cls, v1: Vector, v2: Vector, v3: Vector) -> "Edge":
|
||||
def makeThreePointArc(
|
||||
cls, v1: VectorLike, v2: VectorLike, v3: VectorLike
|
||||
) -> "Edge":
|
||||
"""
|
||||
Makes a three point arc through the provided points
|
||||
:param cls:
|
||||
@ -1817,12 +1883,14 @@ class Edge(Shape, Mixin1D):
|
||||
:param v3: end vector
|
||||
:return: an edge object through the three points
|
||||
"""
|
||||
circle_geom = GC_MakeArcOfCircle(v1.toPnt(), v2.toPnt(), v3.toPnt()).Value()
|
||||
circle_geom = GC_MakeArcOfCircle(
|
||||
Vector(v1).toPnt(), Vector(v2).toPnt(), Vector(v3).toPnt()
|
||||
).Value()
|
||||
|
||||
return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge())
|
||||
|
||||
@classmethod
|
||||
def makeTangentArc(cls, v1: Vector, v2: Vector, v3: Vector) -> "Edge":
|
||||
def makeTangentArc(cls, v1: VectorLike, v2: VectorLike, v3: VectorLike) -> "Edge":
|
||||
"""
|
||||
Makes a tangent arc from point v1, in the direction of v2 and ends at
|
||||
v3.
|
||||
@ -1832,19 +1900,23 @@ class Edge(Shape, Mixin1D):
|
||||
:param v3: end vector
|
||||
:return: an edge
|
||||
"""
|
||||
circle_geom = GC_MakeArcOfCircle(v1.toPnt(), v2.wrapped, v3.toPnt()).Value()
|
||||
circle_geom = GC_MakeArcOfCircle(
|
||||
Vector(v1).toPnt(), Vector(v2).wrapped, Vector(v3).toPnt()
|
||||
).Value()
|
||||
|
||||
return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge())
|
||||
|
||||
@classmethod
|
||||
def makeLine(cls, v1: Vector, v2: Vector) -> "Edge":
|
||||
def makeLine(cls, v1: VectorLike, v2: VectorLike) -> "Edge":
|
||||
"""
|
||||
Create a line between two points
|
||||
:param v1: Vector that represents the first point
|
||||
:param v2: Vector that represents the second point
|
||||
:return: A linear edge between the two provided points
|
||||
"""
|
||||
return cls(BRepBuilderAPI_MakeEdge(v1.toPnt(), v2.toPnt()).Edge())
|
||||
return cls(
|
||||
BRepBuilderAPI_MakeEdge(Vector(v1).toPnt(), Vector(v2).toPnt()).Edge()
|
||||
)
|
||||
|
||||
|
||||
class Wire(Shape, Mixin1D):
|
||||
@ -1937,7 +2009,9 @@ class Wire(Shape, Mixin1D):
|
||||
return cls(wire_builder.Wire())
|
||||
|
||||
@classmethod
|
||||
def makeCircle(cls, radius: float, center: Vector, normal: Vector) -> "Wire":
|
||||
def makeCircle(
|
||||
cls, radius: float, center: VectorLike, normal: VectorLike
|
||||
) -> "Wire":
|
||||
"""
|
||||
Makes a Circle centered at the provided point, having normal in the provided direction
|
||||
:param radius: floating point radius of the circle, must be > 0
|
||||
@ -1955,9 +2029,9 @@ class Wire(Shape, Mixin1D):
|
||||
cls,
|
||||
x_radius: float,
|
||||
y_radius: float,
|
||||
center: Vector,
|
||||
normal: Vector,
|
||||
xDir: Vector,
|
||||
center: VectorLike,
|
||||
normal: VectorLike,
|
||||
xDir: VectorLike,
|
||||
angle1: float = 360.0,
|
||||
angle2: float = 360.0,
|
||||
rotation_angle: float = 0.0,
|
||||
@ -1986,19 +2060,19 @@ class Wire(Shape, Mixin1D):
|
||||
w = cls.assembleEdges([ellipse_edge])
|
||||
|
||||
if rotation_angle != 0.0:
|
||||
w = w.rotate(center, center + normal, rotation_angle)
|
||||
w = w.rotate(center, Vector(center) + Vector(normal), rotation_angle)
|
||||
|
||||
return w
|
||||
|
||||
@classmethod
|
||||
def makePolygon(
|
||||
cls, listOfVertices: Iterable[Vector], forConstruction: bool = False,
|
||||
cls, listOfVertices: Iterable[VectorLike], forConstruction: bool = False,
|
||||
) -> "Wire":
|
||||
# convert list of tuples into Vectors.
|
||||
wire_builder = BRepBuilderAPI_MakePolygon()
|
||||
|
||||
for v in listOfVertices:
|
||||
wire_builder.Add(v.toPnt())
|
||||
wire_builder.Add(Vector(v).toPnt())
|
||||
|
||||
w = cls(wire_builder.Wire())
|
||||
w.forConstruction = forConstruction
|
||||
@ -2011,8 +2085,8 @@ class Wire(Shape, Mixin1D):
|
||||
pitch: float,
|
||||
height: float,
|
||||
radius: float,
|
||||
center: Vector = Vector(0, 0, 0),
|
||||
dir: Vector = Vector(0, 0, 1),
|
||||
center: VectorLike = Vector(0, 0, 0),
|
||||
dir: VectorLike = Vector(0, 0, 1),
|
||||
angle: float = 360.0,
|
||||
lefthand: bool = False,
|
||||
) -> "Wire":
|
||||
@ -2025,11 +2099,13 @@ class Wire(Shape, Mixin1D):
|
||||
# 1. build underlying cylindrical/conical surface
|
||||
if angle == 360.0:
|
||||
geom_surf: Geom_Surface = Geom_CylindricalSurface(
|
||||
gp_Ax3(center.toPnt(), dir.toDir()), radius
|
||||
gp_Ax3(Vector(center).toPnt(), Vector(dir).toDir()), radius
|
||||
)
|
||||
else:
|
||||
geom_surf = Geom_ConicalSurface(
|
||||
gp_Ax3(center.toPnt(), dir.toDir()), angle * DEG2RAD, radius
|
||||
gp_Ax3(Vector(center).toPnt(), Vector(dir).toDir()),
|
||||
angle * DEG2RAD,
|
||||
radius,
|
||||
)
|
||||
|
||||
# 2. construct an segment in the u,v domain
|
||||
@ -2170,8 +2246,8 @@ class Face(Shape):
|
||||
@classmethod
|
||||
def makeNSidedSurface(
|
||||
cls,
|
||||
edges: Iterable[Edge],
|
||||
points: Iterable[gp_Pnt],
|
||||
edges: Iterable[Union[Edge, Wire]],
|
||||
constraints: Iterable[Union[Edge, Wire, VectorLike, gp_Pnt]],
|
||||
continuity: GeomAbs_Shape = GeomAbs_C0,
|
||||
degree: int = 3,
|
||||
nbPtsOnCur: int = 15,
|
||||
@ -2186,8 +2262,8 @@ class Face(Shape):
|
||||
) -> "Face":
|
||||
"""
|
||||
Returns a surface enclosed by a closed polygon defined by 'edges' and going through 'points'.
|
||||
:param points
|
||||
:type points: list of gp_Pnt
|
||||
:param constraints
|
||||
:type points: list of constraints (points or edges)
|
||||
:param edges
|
||||
:type edges: list of Edge
|
||||
:param continuity=GeomAbs_C0
|
||||
@ -2226,12 +2302,36 @@ class Face(Shape):
|
||||
maxDeg,
|
||||
maxSegments,
|
||||
)
|
||||
for edge in edges:
|
||||
n_sided.Add(edge.wrapped, continuity)
|
||||
for pt in points:
|
||||
n_sided.Add(pt)
|
||||
|
||||
# outer edges
|
||||
for el in edges:
|
||||
if isinstance(el, Edge):
|
||||
n_sided.Add(el.wrapped, continuity)
|
||||
else:
|
||||
for el_edge in el.Edges():
|
||||
n_sided.Add(el_edge.wrapped, continuity)
|
||||
|
||||
# (inner) constraints
|
||||
for c in constraints:
|
||||
if isinstance(c, gp_Pnt):
|
||||
n_sided.Add(c)
|
||||
elif isinstance(c, Vector):
|
||||
n_sided.Add(c.toPnt())
|
||||
elif isinstance(c, tuple):
|
||||
n_sided.Add(Vector(c).toPnt())
|
||||
elif isinstance(c, Edge):
|
||||
n_sided.Add(c.wrapped, GeomAbs_C0, False)
|
||||
elif isinstance(c, Wire):
|
||||
for e in c.Edges():
|
||||
n_sided.Add(e.wrapped, GeomAbs_C0, False)
|
||||
else:
|
||||
raise ValueError(f"Invalid constraint {c}")
|
||||
|
||||
# build, fix and return
|
||||
n_sided.Build()
|
||||
|
||||
face = n_sided.Shape()
|
||||
|
||||
return Face(face).fix()
|
||||
|
||||
@classmethod
|
||||
@ -2407,6 +2507,45 @@ class Face(Shape):
|
||||
adaptor = BRepAdaptor_Surface(self.wrapped)
|
||||
return adaptor.Plane()
|
||||
|
||||
def thicken(self, thickness: float) -> "Solid":
|
||||
"""
|
||||
Return a thickened face
|
||||
"""
|
||||
|
||||
builder = BRepOffset_MakeOffset()
|
||||
|
||||
builder.Initialize(
|
||||
self.wrapped,
|
||||
thickness,
|
||||
1.0e-6,
|
||||
BRepOffset_Mode.BRepOffset_Skin,
|
||||
False,
|
||||
False,
|
||||
GeomAbs_Intersection,
|
||||
True,
|
||||
) # The last True is important to make solid
|
||||
|
||||
builder.MakeOffsetShape()
|
||||
|
||||
return Solid(builder.Shape())
|
||||
|
||||
@classmethod
|
||||
def constructOn(cls, f: "Face", outer: "Wire", *inner: "Wire") -> "Face":
|
||||
|
||||
bldr = BRepBuilderAPI_MakeFace(f._geomAdaptor(), outer.wrapped)
|
||||
|
||||
for w in inner:
|
||||
bldr.Add(TopoDS.Wire_s(w.wrapped.Reversed()))
|
||||
|
||||
return cls(bldr.Face()).fix()
|
||||
|
||||
def project(self, other: "Face", d: VectorLike) -> "Face":
|
||||
|
||||
outer_p = tcast(Wire, self.outerWire().project(other, d))
|
||||
inner_p = (tcast(Wire, w.project(other, d)) for w in self.innerWires())
|
||||
|
||||
return self.constructOn(other, outer_p, *inner_p)
|
||||
|
||||
|
||||
class Shell(Shape):
|
||||
"""
|
||||
@ -2631,6 +2770,7 @@ class Solid(Shape, Mixin3D):
|
||||
wrapped: TopoDS_Solid
|
||||
|
||||
@classmethod
|
||||
@deprecate()
|
||||
def interpPlate(
|
||||
cls,
|
||||
surf_edges,
|
||||
@ -2722,19 +2862,8 @@ class Solid(Shape, Mixin3D):
|
||||
if (
|
||||
abs(thickness) > 0
|
||||
): # abs() because negative values are allowed to set direction of thickening
|
||||
solid = BRepOffset_MakeOffset()
|
||||
solid.Initialize(
|
||||
face.wrapped,
|
||||
thickness,
|
||||
1.0e-5,
|
||||
BRepOffset_Skin,
|
||||
False,
|
||||
False,
|
||||
GeomAbs_Intersection,
|
||||
True,
|
||||
) # The last True is important to make solid
|
||||
solid.MakeOffsetShape()
|
||||
return cls(solid.Shape())
|
||||
return face.thicken(thickness)
|
||||
|
||||
else: # Return 2D surface only
|
||||
return face
|
||||
|
||||
@ -2761,8 +2890,8 @@ class Solid(Shape, Mixin3D):
|
||||
length: float,
|
||||
width: float,
|
||||
height: float,
|
||||
pnt: Vector = Vector(0, 0, 0),
|
||||
dir: Vector = Vector(0, 0, 1),
|
||||
pnt: VectorLike = Vector(0, 0, 0),
|
||||
dir: VectorLike = Vector(0, 0, 1),
|
||||
) -> "Solid":
|
||||
"""
|
||||
makeBox(length,width,height,[pnt,dir]) -- Make a box located in pnt with the dimensions (length,width,height)
|
||||
@ -2770,7 +2899,7 @@ class Solid(Shape, Mixin3D):
|
||||
"""
|
||||
return cls(
|
||||
BRepPrimAPI_MakeBox(
|
||||
gp_Ax2(pnt.toPnt(), dir.toDir()), length, width, height
|
||||
gp_Ax2(Vector(pnt).toPnt(), Vector(dir).toDir()), length, width, height
|
||||
).Shape()
|
||||
)
|
||||
|
||||
@ -2780,8 +2909,8 @@ class Solid(Shape, Mixin3D):
|
||||
radius1: float,
|
||||
radius2: float,
|
||||
height: float,
|
||||
pnt: Vector = Vector(0, 0, 0),
|
||||
dir: Vector = Vector(0, 0, 1),
|
||||
pnt: VectorLike = Vector(0, 0, 0),
|
||||
dir: VectorLike = Vector(0, 0, 1),
|
||||
angleDegrees: float = 360,
|
||||
) -> "Solid":
|
||||
"""
|
||||
@ -2791,7 +2920,7 @@ class Solid(Shape, Mixin3D):
|
||||
"""
|
||||
return cls(
|
||||
BRepPrimAPI_MakeCone(
|
||||
gp_Ax2(pnt.toPnt(), dir.toDir()),
|
||||
gp_Ax2(Vector(pnt).toPnt(), Vector(dir).toDir()),
|
||||
radius1,
|
||||
radius2,
|
||||
height,
|
||||
@ -2804,8 +2933,8 @@ class Solid(Shape, Mixin3D):
|
||||
cls,
|
||||
radius: float,
|
||||
height: float,
|
||||
pnt: Vector = Vector(0, 0, 0),
|
||||
dir: Vector = Vector(0, 0, 1),
|
||||
pnt: VectorLike = Vector(0, 0, 0),
|
||||
dir: VectorLike = Vector(0, 0, 1),
|
||||
angleDegrees: float = 360,
|
||||
) -> "Solid":
|
||||
"""
|
||||
@ -2815,7 +2944,10 @@ class Solid(Shape, Mixin3D):
|
||||
"""
|
||||
return cls(
|
||||
BRepPrimAPI_MakeCylinder(
|
||||
gp_Ax2(pnt.toPnt(), dir.toDir()), radius, height, angleDegrees * DEG2RAD
|
||||
gp_Ax2(Vector(pnt).toPnt(), Vector(dir).toDir()),
|
||||
radius,
|
||||
height,
|
||||
angleDegrees * DEG2RAD,
|
||||
).Shape()
|
||||
)
|
||||
|
||||
@ -2824,8 +2956,8 @@ class Solid(Shape, Mixin3D):
|
||||
cls,
|
||||
radius1: float,
|
||||
radius2: float,
|
||||
pnt: Vector = Vector(0, 0, 0),
|
||||
dir: Vector = Vector(0, 0, 1),
|
||||
pnt: VectorLike = Vector(0, 0, 0),
|
||||
dir: VectorLike = Vector(0, 0, 1),
|
||||
angleDegrees1: float = 0,
|
||||
angleDegrees2: float = 360,
|
||||
) -> "Solid":
|
||||
@ -2837,7 +2969,7 @@ class Solid(Shape, Mixin3D):
|
||||
"""
|
||||
return cls(
|
||||
BRepPrimAPI_MakeTorus(
|
||||
gp_Ax2(pnt.toPnt(), dir.toDir()),
|
||||
gp_Ax2(Vector(pnt).toPnt(), Vector(dir).toDir()),
|
||||
radius1,
|
||||
radius2,
|
||||
angleDegrees1 * DEG2RAD,
|
||||
@ -2874,8 +3006,8 @@ class Solid(Shape, Mixin3D):
|
||||
zmin: float,
|
||||
xmax: float,
|
||||
zmax: float,
|
||||
pnt: Vector = Vector(0, 0, 0),
|
||||
dir: Vector = Vector(0, 0, 1),
|
||||
pnt: VectorLike = Vector(0, 0, 0),
|
||||
dir: VectorLike = Vector(0, 0, 1),
|
||||
) -> "Solid":
|
||||
"""
|
||||
Make a wedge located in pnt
|
||||
@ -2884,7 +3016,14 @@ class Solid(Shape, Mixin3D):
|
||||
|
||||
return cls(
|
||||
BRepPrimAPI_MakeWedge(
|
||||
gp_Ax2(pnt.toPnt(), dir.toDir()), dx, dy, dz, xmin, zmin, xmax, zmax
|
||||
gp_Ax2(Vector(pnt).toPnt(), Vector(dir).toDir()),
|
||||
dx,
|
||||
dy,
|
||||
dz,
|
||||
xmin,
|
||||
zmin,
|
||||
xmax,
|
||||
zmax,
|
||||
).Solid()
|
||||
)
|
||||
|
||||
@ -2892,8 +3031,8 @@ class Solid(Shape, Mixin3D):
|
||||
def makeSphere(
|
||||
cls,
|
||||
radius: float,
|
||||
pnt: Vector = Vector(0, 0, 0),
|
||||
dir: Vector = Vector(0, 0, 1),
|
||||
pnt: VectorLike = Vector(0, 0, 0),
|
||||
dir: VectorLike = Vector(0, 0, 1),
|
||||
angleDegrees1: float = 0,
|
||||
angleDegrees2: float = 90,
|
||||
angleDegrees3: float = 360,
|
||||
@ -2904,7 +3043,7 @@ class Solid(Shape, Mixin3D):
|
||||
"""
|
||||
return cls(
|
||||
BRepPrimAPI_MakeSphere(
|
||||
gp_Ax2(pnt.toPnt(), dir.toDir()),
|
||||
gp_Ax2(Vector(pnt).toPnt(), Vector(dir).toDir()),
|
||||
radius,
|
||||
angleDegrees1 * DEG2RAD,
|
||||
angleDegrees2 * DEG2RAD,
|
||||
@ -2931,8 +3070,8 @@ class Solid(Shape, Mixin3D):
|
||||
cls,
|
||||
outerWire: Wire,
|
||||
innerWires: List[Wire],
|
||||
vecCenter: Vector,
|
||||
vecNormal: Vector,
|
||||
vecCenter: VectorLike,
|
||||
vecNormal: VectorLike,
|
||||
angleDegrees: Real,
|
||||
) -> "Solid":
|
||||
"""
|
||||
@ -2986,7 +3125,11 @@ class Solid(Shape, Mixin3D):
|
||||
@classmethod
|
||||
@extrudeLinearWithRotation.register
|
||||
def extrudeLinearWithRotation(
|
||||
cls, face: Face, vecCenter: Vector, vecNormal: Vector, angleDegrees: Real,
|
||||
cls,
|
||||
face: Face,
|
||||
vecCenter: VectorLike,
|
||||
vecNormal: VectorLike,
|
||||
angleDegrees: Real,
|
||||
) -> "Solid":
|
||||
|
||||
return cls.extrudeLinearWithRotation(
|
||||
@ -2998,7 +3141,7 @@ class Solid(Shape, Mixin3D):
|
||||
cls,
|
||||
outerWire: Wire,
|
||||
innerWires: List[Wire],
|
||||
vecNormal: Vector,
|
||||
vecNormal: VectorLike,
|
||||
taper: Real = 0,
|
||||
) -> "Solid":
|
||||
"""
|
||||
@ -3033,11 +3176,13 @@ class Solid(Shape, Mixin3D):
|
||||
|
||||
@classmethod
|
||||
@extrudeLinear.register
|
||||
def extrudeLinear(cls, face: Face, vecNormal: Vector, taper: Real = 0,) -> "Solid":
|
||||
def extrudeLinear(
|
||||
cls, face: Face, vecNormal: VectorLike, taper: Real = 0,
|
||||
) -> "Solid":
|
||||
|
||||
if taper == 0:
|
||||
prism_builder: Any = BRepPrimAPI_MakePrism(
|
||||
face.wrapped, vecNormal.wrapped, True
|
||||
face.wrapped, Vector(vecNormal).wrapped, True
|
||||
)
|
||||
else:
|
||||
faceNormal = face.normalAt()
|
||||
@ -3054,8 +3199,8 @@ class Solid(Shape, Mixin3D):
|
||||
outerWire: Wire,
|
||||
innerWires: List[Wire],
|
||||
angleDegrees: Real,
|
||||
axisStart: Vector,
|
||||
axisEnd: Vector,
|
||||
axisStart: VectorLike,
|
||||
axisEnd: VectorLike,
|
||||
) -> "Solid":
|
||||
"""
|
||||
Attempt to revolve the list of wires into a solid in the provided direction
|
||||
@ -3088,7 +3233,7 @@ class Solid(Shape, Mixin3D):
|
||||
@classmethod
|
||||
@revolve.register
|
||||
def revolve(
|
||||
cls, face: Face, angleDegrees: Real, axisStart: Vector, axisEnd: Vector,
|
||||
cls, face: Face, angleDegrees: Real, axisStart: VectorLike, axisEnd: VectorLike,
|
||||
) -> "Solid":
|
||||
|
||||
v1 = Vector(axisStart)
|
||||
@ -3360,11 +3505,15 @@ class Compound(Shape, Mixin3D):
|
||||
|
||||
text_flat = text_flat.translate(t)
|
||||
|
||||
if height != 0:
|
||||
vecNormal = text_flat.Faces()[0].normalAt() * height
|
||||
|
||||
text_3d = BRepPrimAPI_MakePrism(text_flat.wrapped, vecNormal.wrapped)
|
||||
rv = cls(text_3d.Shape()).transformShape(position.rG)
|
||||
else:
|
||||
rv = text_flat.transformShape(position.rG)
|
||||
|
||||
return cls(text_3d.Shape()).transformShape(position.rG)
|
||||
return rv
|
||||
|
||||
def __iter__(self) -> Iterator[Shape]:
|
||||
"""
|
||||
|
||||
@ -4,8 +4,8 @@ import cadquery as cq
|
||||
# TEST_1
|
||||
# example from PythonOCC core_geometry_geomplate.py, use of thickness = 0 returns 2D surface.
|
||||
thickness = 0
|
||||
edge_points = [[0.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 10.0, 10.0], [0.0, 0.0, 10.0]]
|
||||
surface_points = [[5.0, 5.0, 5.0]]
|
||||
edge_points = [(0.0, 0.0, 0.0), (0.0, 10.0, 0.0), (0.0, 10.0, 10.0), (0.0, 0.0, 10.0)]
|
||||
surface_points = [(5.0, 5.0, 5.0)]
|
||||
plate_0 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness)
|
||||
print("plate_0.val().Volume() = ", plate_0.val().Volume())
|
||||
plate_0 = plate_0.translate((0, 6 * 12, 0))
|
||||
@ -15,11 +15,11 @@ show_object(plate_0)
|
||||
# Plate with 5 sides and 2 bumps, one side is not co-planar with the other sides
|
||||
thickness = 0.1
|
||||
edge_points = [
|
||||
[-7.0, -7.0, 0.0],
|
||||
[-3.0, -10.0, 3.0],
|
||||
[7.0, -7.0, 0.0],
|
||||
[7.0, 7.0, 0.0],
|
||||
[-7.0, 7.0, 0.0],
|
||||
(-7.0, -7.0, 0.0),
|
||||
(-3.0, -10.0, 3.0),
|
||||
(7.0, -7.0, 0.0),
|
||||
(7.0, 7.0, 0.0),
|
||||
(-7.0, 7.0, 0.0),
|
||||
]
|
||||
edge_wire = cq.Workplane("XY").polyline(
|
||||
[(-7.0, -7.0), (7.0, -7.0), (7.0, 7.0), (-7.0, 7.0)]
|
||||
@ -32,7 +32,7 @@ edge_wire = edge_wire.add(
|
||||
.transformed(offset=cq.Vector(0, 0, -7), rotate=cq.Vector(45, 0, 0))
|
||||
.spline([(-7.0, 0.0), (3, -3), (7.0, 0.0)])
|
||||
)
|
||||
surface_points = [[-3.0, -3.0, -3.0], [3.0, 3.0, 3.0]]
|
||||
surface_points = [(-3.0, -3.0, -3.0), (3.0, 3.0, 3.0)]
|
||||
plate_1 = cq.Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
|
||||
# plate_1 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness) # list of (x,y,z) points instead of wires for edges
|
||||
print("plate_1.val().Volume() = ", plate_1.val().Volume())
|
||||
@ -45,16 +45,16 @@ r2 = 10.0
|
||||
fn = 6
|
||||
thickness = 0.1
|
||||
edge_points = [
|
||||
[r1 * cos(i * pi / fn), r1 * sin(i * pi / fn)]
|
||||
(r1 * cos(i * pi / fn), r1 * sin(i * pi / fn))
|
||||
if i % 2 == 0
|
||||
else [r2 * cos(i * pi / fn), r2 * sin(i * pi / fn)]
|
||||
else (r2 * cos(i * pi / fn), r2 * sin(i * pi / fn))
|
||||
for i in range(2 * fn + 1)
|
||||
]
|
||||
edge_wire = cq.Workplane("XY").polyline(edge_points)
|
||||
r2 = 4.5
|
||||
surface_points = [
|
||||
[r2 * cos(i * pi / fn), r2 * sin(i * pi / fn), 1.0] for i in range(2 * fn)
|
||||
] + [[0.0, 0.0, -2.0]]
|
||||
(r2 * cos(i * pi / fn), r2 * sin(i * pi / fn), 1.0) for i in range(2 * fn)
|
||||
] + [(0.0, 0.0, -2.0)]
|
||||
plate_2 = cq.Workplane("XY").interpPlate(
|
||||
edge_wire,
|
||||
surface_points,
|
||||
@ -106,20 +106,20 @@ pts = [
|
||||
thickness = 0.1
|
||||
fn = 6
|
||||
edge_points = [
|
||||
[
|
||||
(
|
||||
r1 * cos(i * 2 * pi / fn + 30 * pi / 180),
|
||||
r1 * sin(i * 2 * pi / fn + 30 * pi / 180),
|
||||
]
|
||||
)
|
||||
for i in range(fn + 1)
|
||||
]
|
||||
surface_points = [
|
||||
[
|
||||
(
|
||||
r1 / 4 * cos(i * 2 * pi / fn + 30 * pi / 180),
|
||||
r1 / 4 * sin(i * 2 * pi / fn + 30 * pi / 180),
|
||||
0.75,
|
||||
]
|
||||
)
|
||||
for i in range(fn + 1)
|
||||
] + [[0, 0, 2]]
|
||||
] + [(0, 0, 2)]
|
||||
edge_wire = cq.Workplane("XY").polyline(edge_points)
|
||||
plate_3 = (
|
||||
cq.Workplane("XY")
|
||||
@ -168,7 +168,7 @@ for i in range(len(edge_points) - 1):
|
||||
.workplane(offset=-offset_list[i + 1])
|
||||
.spline(edge_points[i + 1])
|
||||
)
|
||||
surface_points = [[0, 0, 0]]
|
||||
surface_points = [(0, 0, 0)]
|
||||
plate_4 = cq.Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
|
||||
print("plate_4.val().Volume() = ", plate_4.val().Volume())
|
||||
plate_4 = plate_4.translate((0, 5 * 12, 0))
|
||||
|
||||
@ -594,6 +594,12 @@ class TestCadObjects(BaseTest):
|
||||
|
||||
def testLocation(self):
|
||||
|
||||
# Tuple
|
||||
loc0 = Location((0, 0, 1))
|
||||
|
||||
T = loc0.wrapped.Transformation().TranslationPart()
|
||||
self.assertTupleAlmostEquals((T.X(), T.Y(), T.Z()), (0, 0, 1), 6)
|
||||
|
||||
# Vector
|
||||
loc1 = Location(Vector(0, 0, 1))
|
||||
|
||||
@ -621,9 +627,34 @@ class TestCadObjects(BaseTest):
|
||||
self.assertTupleAlmostEquals(loc4.toTuple()[0], (0, 0, 0), 7)
|
||||
self.assertTupleAlmostEquals(loc4.toTuple()[1], (0, 0, 0), 7)
|
||||
|
||||
# Test composition
|
||||
loc4 = Location((0, 0, 0), Vector(0, 0, 1), 15)
|
||||
|
||||
loc5 = loc1 * loc4
|
||||
loc6 = loc4 * loc4
|
||||
loc7 = loc4 ** 2
|
||||
|
||||
T = loc5.wrapped.Transformation().TranslationPart()
|
||||
self.assertTupleAlmostEquals((T.X(), T.Y(), T.Z()), (0, 0, 1), 6)
|
||||
|
||||
angle5 = (
|
||||
loc5.wrapped.Transformation().GetRotation().GetRotationAngle() * RAD2DEG
|
||||
)
|
||||
self.assertAlmostEqual(15, angle5)
|
||||
|
||||
angle6 = (
|
||||
loc6.wrapped.Transformation().GetRotation().GetRotationAngle() * RAD2DEG
|
||||
)
|
||||
self.assertAlmostEqual(30, angle6)
|
||||
|
||||
angle7 = (
|
||||
loc7.wrapped.Transformation().GetRotation().GetRotationAngle() * RAD2DEG
|
||||
)
|
||||
self.assertAlmostEqual(30, angle7)
|
||||
|
||||
# Test error handling on creation
|
||||
with self.assertRaises(TypeError):
|
||||
Location((0, 0, 1))
|
||||
Location([0, 0, 1])
|
||||
with self.assertRaises(TypeError):
|
||||
Location("xy_plane")
|
||||
|
||||
|
||||
@ -4187,12 +4187,12 @@ class TestCadQuery(BaseTest):
|
||||
# example from PythonOCC core_geometry_geomplate.py, use of thickness = 0 returns 2D surface.
|
||||
thickness = 0
|
||||
edge_points = [
|
||||
[0.0, 0.0, 0.0],
|
||||
[0.0, 10.0, 0.0],
|
||||
[0.0, 10.0, 10.0],
|
||||
[0.0, 0.0, 10.0],
|
||||
(0.0, 0.0, 0.0),
|
||||
(0.0, 10.0, 0.0),
|
||||
(0.0, 10.0, 10.0),
|
||||
(0.0, 0.0, 10.0),
|
||||
]
|
||||
surface_points = [[5.0, 5.0, 5.0]]
|
||||
surface_points = [(5.0, 5.0, 5.0)]
|
||||
plate_0 = Workplane("XY").interpPlate(edge_points, surface_points, thickness)
|
||||
self.assertTrue(plate_0.val().isValid())
|
||||
self.assertAlmostEqual(plate_0.val().Area(), 141.218823892, 1)
|
||||
@ -4200,11 +4200,11 @@ class TestCadQuery(BaseTest):
|
||||
# Plate with 5 sides and 2 bumps, one side is not co-planar with the other sides
|
||||
thickness = 0.1
|
||||
edge_points = [
|
||||
[-7.0, -7.0, 0.0],
|
||||
[-3.0, -10.0, 3.0],
|
||||
[7.0, -7.0, 0.0],
|
||||
[7.0, 7.0, 0.0],
|
||||
[-7.0, 7.0, 0.0],
|
||||
(-7.0, -7.0, 0.0),
|
||||
(-3.0, -10.0, 3.0),
|
||||
(7.0, -7.0, 0.0),
|
||||
(7.0, 7.0, 0.0),
|
||||
(-7.0, 7.0, 0.0),
|
||||
]
|
||||
edge_wire = Workplane("XY").polyline(
|
||||
[(-7.0, -7.0), (7.0, -7.0), (7.0, 7.0), (-7.0, 7.0)]
|
||||
@ -4217,7 +4217,7 @@ class TestCadQuery(BaseTest):
|
||||
.transformed(offset=Vector(0, 0, -7), rotate=Vector(45, 0, 0))
|
||||
.spline([(-7.0, 0.0), (3, -3), (7.0, 0.0)])
|
||||
)
|
||||
surface_points = [[-3.0, -3.0, -3.0], [3.0, 3.0, 3.0]]
|
||||
surface_points = [(-3.0, -3.0, -3.0), (3.0, 3.0, 3.0)]
|
||||
plate_1 = Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
|
||||
self.assertTrue(plate_1.val().isValid())
|
||||
self.assertAlmostEqual(plate_1.val().Volume(), 26.124970206, 2)
|
||||
@ -4228,17 +4228,17 @@ class TestCadQuery(BaseTest):
|
||||
fn = 6
|
||||
thickness = 0.1
|
||||
edge_points = [
|
||||
[r1 * math.cos(i * math.pi / fn), r1 * math.sin(i * math.pi / fn)]
|
||||
(r1 * math.cos(i * math.pi / fn), r1 * math.sin(i * math.pi / fn))
|
||||
if i % 2 == 0
|
||||
else [r2 * math.cos(i * math.pi / fn), r2 * math.sin(i * math.pi / fn)]
|
||||
else (r2 * math.cos(i * math.pi / fn), r2 * math.sin(i * math.pi / fn))
|
||||
for i in range(2 * fn + 1)
|
||||
]
|
||||
edge_wire = Workplane("XY").polyline(edge_points)
|
||||
r2 = 4.5
|
||||
surface_points = [
|
||||
[r2 * math.cos(i * math.pi / fn), r2 * math.sin(i * math.pi / fn), 1.0]
|
||||
(r2 * math.cos(i * math.pi / fn), r2 * math.sin(i * math.pi / fn), 1.0)
|
||||
for i in range(2 * fn)
|
||||
] + [[0.0, 0.0, -2.0]]
|
||||
] + [(0.0, 0.0, -2.0)]
|
||||
plate_2 = Workplane("XY").interpPlate(
|
||||
edge_wire,
|
||||
surface_points,
|
||||
@ -4287,20 +4287,20 @@ class TestCadQuery(BaseTest):
|
||||
thickness = 0.1
|
||||
fn = 6
|
||||
edge_points = [
|
||||
[
|
||||
(
|
||||
r1 * math.cos(i * 2 * math.pi / fn + 30 * math.pi / 180),
|
||||
r1 * math.sin(i * 2 * math.pi / fn + 30 * math.pi / 180),
|
||||
]
|
||||
)
|
||||
for i in range(fn + 1)
|
||||
]
|
||||
surface_points = [
|
||||
[
|
||||
(
|
||||
r1 / 4 * math.cos(i * 2 * math.pi / fn + 30 * math.pi / 180),
|
||||
r1 / 4 * math.sin(i * 2 * math.pi / fn + 30 * math.pi / 180),
|
||||
0.75,
|
||||
]
|
||||
)
|
||||
for i in range(fn + 1)
|
||||
] + [[0, 0, 2]]
|
||||
] + [(0, 0, 2)]
|
||||
edge_wire = Workplane("XY").polyline(edge_points)
|
||||
plate_3 = (
|
||||
Workplane("XY")
|
||||
@ -4349,11 +4349,22 @@ class TestCadQuery(BaseTest):
|
||||
.workplane(offset=-offset_list[i + 1])
|
||||
.spline(edge_points[i + 1])
|
||||
)
|
||||
surface_points = [[0, 0, 0]]
|
||||
surface_points = [(0, 0, 0)]
|
||||
plate_4 = Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
|
||||
self.assertTrue(plate_4.val().isValid())
|
||||
self.assertAlmostEqual(plate_4.val().Volume(), 7.760559490, 2)
|
||||
|
||||
plate_5 = Workplane().interpPlate(Workplane().slot2D(2, 1).vals())
|
||||
|
||||
assert plate_5.val().isValid()
|
||||
|
||||
plate_6 = Solid.interpPlate(
|
||||
[(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)], [], thickness=1
|
||||
)
|
||||
|
||||
assert plate_6.isValid()
|
||||
self.assertAlmostEqual(plate_6.Volume(), 1, 2)
|
||||
|
||||
def testTangentArcToPoint(self):
|
||||
|
||||
# create a simple shape with tangents of straight edges and see if it has the correct area
|
||||
@ -5315,3 +5326,92 @@ class TestCadQuery(BaseTest):
|
||||
repr(wp.plane)
|
||||
== "Plane(origin=(0.0, 0.0, 0.0), xDir=(1.0, 0.0, 0.0), normal=(0.0, 0.0, 1.0))"
|
||||
)
|
||||
|
||||
def test_distance(self):
|
||||
|
||||
w1 = Face.makePlane(2, 2).Wires()[0]
|
||||
w2 = Face.makePlane(1, 1).Wires()[0]
|
||||
w3 = Face.makePlane(3, 3).Wires()[0]
|
||||
|
||||
d12 = w1.distance(w2)
|
||||
|
||||
assert d12 == approx(0.5)
|
||||
|
||||
d12, d13 = w1.distances(w2, w3)
|
||||
|
||||
assert d12 == approx(0.5)
|
||||
assert d13 == approx(0.5)
|
||||
|
||||
def test_project(self):
|
||||
|
||||
# project a single letter
|
||||
t = Compound.makeText("T", 5, 0).Faces()[0]
|
||||
f = Workplane("XZ", origin=(0, 0, -7)).sphere(6).faces("not %PLANE").val()
|
||||
|
||||
res = t.project(f, (0, 0, -1))
|
||||
|
||||
assert res.isValid()
|
||||
assert len(res.Edges()) == 8
|
||||
assert t.distance(res) == approx(1)
|
||||
|
||||
# extrude it
|
||||
res_ex = Solid.extrudeLinear(t.project(f, (0, 0, -1)), (0.0, 0.0, 0.5))
|
||||
|
||||
assert res_ex.isValid()
|
||||
assert len(res_ex.Faces()) == 10
|
||||
|
||||
# project a wire
|
||||
w = t.outerWire()
|
||||
|
||||
res_w = w.project(f, (0, 0, -1))
|
||||
|
||||
assert len(res_w.Edges()) == 8
|
||||
assert res_w.isValid()
|
||||
|
||||
res_w1, res_w2 = w.project(f, (0, 0, -1), False)
|
||||
|
||||
assert len(res_w1.Edges()) == 8
|
||||
assert len(res_w2.Edges()) == 8
|
||||
|
||||
# project a single letter with openings
|
||||
o = Compound.makeText("O", 5, 0).Faces()[0]
|
||||
f = Workplane("XZ", origin=(0, 0, -7)).sphere(6).faces("not %PLANE").val()
|
||||
|
||||
res_o = o.project(f, (0, 0, -1))
|
||||
|
||||
assert res_o.isValid()
|
||||
|
||||
# extrude it
|
||||
res_o_ex = Solid.extrudeLinear(o.project(f, (0, 0, -1)), (0.0, 0.0, 0.5))
|
||||
|
||||
assert res_o_ex.isValid()
|
||||
|
||||
def test_makeNSidedSurface(self):
|
||||
|
||||
# inner edge/wire constraint
|
||||
outer_w = Workplane().slot2D(2, 1).wires().vals()
|
||||
|
||||
inner_e1 = (
|
||||
Workplane(origin=(0, 0, 1)).moveTo(-0.5, 0).lineTo(0.5, 0.0).edges().vals()
|
||||
)
|
||||
inner_e2 = (
|
||||
Workplane(origin=(0, 0, 1)).moveTo(0, -0.2).lineTo(0, 0.2).edges().vals()
|
||||
)
|
||||
inner_w = Workplane(origin=(0, 0, 1)).ellipse(0.5, 0.2).vals()
|
||||
|
||||
f1 = Face.makeNSidedSurface(outer_w, inner_e1 + inner_e2 + inner_w)
|
||||
|
||||
assert f1.isValid()
|
||||
assert len(f1.Edges()) == 4
|
||||
|
||||
# inner points
|
||||
f2 = Face.makeNSidedSurface(
|
||||
outer_w, [Vector(-0.4, 0, 1).toPnt(), Vector(0.4, 0, 1)]
|
||||
)
|
||||
|
||||
assert f2.isValid()
|
||||
assert len(f2.Edges()) == 4
|
||||
|
||||
# exception on invalid constraint
|
||||
with raises(ValueError):
|
||||
Face.makeNSidedSurface(outer_w, [[0, 0, 1]])
|
||||
|
||||
Reference in New Issue
Block a user