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)
 | 
			
		||||
 | 
			
		||||
        vecNormal = text_flat.Faces()[0].normalAt() * height
 | 
			
		||||
        if height != 0:
 | 
			
		||||
            vecNormal = text_flat.Faces()[0].normalAt() * height
 | 
			
		||||
 | 
			
		||||
        text_3d = BRepPrimAPI_MakePrism(text_flat.wrapped, vecNormal.wrapped)
 | 
			
		||||
            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