mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Merge branch 'dev' into intersections-2d
This commit is contained in:
commit
5ea2dab174
14 changed files with 173 additions and 148 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="build123d logo" src="docs/assets/build123d_logo/logo-banner.svg">
|
<img alt="build123d logo" src="https://github.com/gumyr/build123d/raw/dev/docs/assets/build123d_logo/logo-banner.svg">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://build123d.readthedocs.io/en/latest/?badge=latest)
|
[](https://build123d.readthedocs.io/en/latest/?badge=latest)
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ development = [
|
||||||
"black",
|
"black",
|
||||||
"mypy",
|
"mypy",
|
||||||
"pylint",
|
"pylint",
|
||||||
"pytest",
|
"pytest==8.4.2", # TODO: unpin on resolution of pytest-dev/pytest-xdist/issues/1273
|
||||||
"pytest-benchmark",
|
"pytest-benchmark",
|
||||||
"pytest-cov",
|
"pytest-cov",
|
||||||
"pytest-xdist",
|
"pytest-xdist",
|
||||||
|
|
|
||||||
|
|
@ -758,7 +758,7 @@ class ExportDXF(Export2D):
|
||||||
)
|
)
|
||||||
|
|
||||||
# need to apply the transform on the geometry level
|
# need to apply the transform on the geometry level
|
||||||
if edge.wrapped is None or edge.location is None:
|
if not edge or edge.location is None:
|
||||||
raise ValueError(f"Edge is empty {edge}.")
|
raise ValueError(f"Edge is empty {edge}.")
|
||||||
t = edge.location.wrapped.Transformation()
|
t = edge.location.wrapped.Transformation()
|
||||||
spline.Transform(t)
|
spline.Transform(t)
|
||||||
|
|
@ -1345,7 +1345,7 @@ class ExportSVG(Export2D):
|
||||||
u2 = adaptor.LastParameter()
|
u2 = adaptor.LastParameter()
|
||||||
|
|
||||||
# Apply the shape location to the geometry.
|
# Apply the shape location to the geometry.
|
||||||
if edge.wrapped is None or edge.location is None:
|
if not edge or edge.location is None:
|
||||||
raise ValueError(f"Edge is empty {edge}.")
|
raise ValueError(f"Edge is empty {edge}.")
|
||||||
t = edge.location.wrapped.Transformation()
|
t = edge.location.wrapped.Transformation()
|
||||||
spline.Transform(t)
|
spline.Transform(t)
|
||||||
|
|
@ -1411,7 +1411,7 @@ class ExportSVG(Export2D):
|
||||||
}
|
}
|
||||||
|
|
||||||
def _edge_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
|
def _edge_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
|
||||||
if edge.wrapped is None:
|
if not edge:
|
||||||
raise ValueError(f"Edge is empty {edge}.")
|
raise ValueError(f"Edge is empty {edge}.")
|
||||||
edge_reversed = edge.wrapped.Orientation() == TopAbs_Orientation.TopAbs_REVERSED
|
edge_reversed = edge.wrapped.Orientation() == TopAbs_Orientation.TopAbs_REVERSED
|
||||||
geom_type = edge.geom_type
|
geom_type = edge.geom_type
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,7 @@ class Mesher:
|
||||||
ocp_mesh_vertices.append(pnt)
|
ocp_mesh_vertices.append(pnt)
|
||||||
|
|
||||||
# Store the triangles from the triangulated faces
|
# Store the triangles from the triangulated faces
|
||||||
if facet.wrapped is None:
|
if not facet:
|
||||||
continue
|
continue
|
||||||
facet_reversed = facet.wrapped.Orientation() == ta.TopAbs_REVERSED
|
facet_reversed = facet.wrapped.Orientation() == ta.TopAbs_REVERSED
|
||||||
order = [1, 3, 2] if facet_reversed else [1, 2, 3]
|
order = [1, 3, 2] if facet_reversed else [1, 2, 3]
|
||||||
|
|
|
||||||
|
|
@ -365,7 +365,7 @@ def chamfer(
|
||||||
|
|
||||||
if target._dim == 1:
|
if target._dim == 1:
|
||||||
if isinstance(target, BaseLineObject):
|
if isinstance(target, BaseLineObject):
|
||||||
if target.wrapped is None:
|
if not target:
|
||||||
target = Wire([]) # empty wire
|
target = Wire([]) # empty wire
|
||||||
else:
|
else:
|
||||||
target = Wire(target.wrapped)
|
target = Wire(target.wrapped)
|
||||||
|
|
@ -465,7 +465,7 @@ def fillet(
|
||||||
|
|
||||||
if target._dim == 1:
|
if target._dim == 1:
|
||||||
if isinstance(target, BaseLineObject):
|
if isinstance(target, BaseLineObject):
|
||||||
if target.wrapped is None:
|
if not target:
|
||||||
target = Wire([]) # empty wire
|
target = Wire([]) # empty wire
|
||||||
else:
|
else:
|
||||||
target = Wire(target.wrapped)
|
target = Wire(target.wrapped)
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ from .utils import (
|
||||||
from .zero_d import Vertex
|
from .zero_d import Vertex
|
||||||
|
|
||||||
|
|
||||||
class Compound(Mixin3D, Shape[TopoDS_Compound]):
|
class Compound(Mixin3D[TopoDS_Compound]):
|
||||||
"""A Compound in build123d is a topological entity representing a collection of
|
"""A Compound in build123d is a topological entity representing a collection of
|
||||||
geometric shapes grouped together within a single structure. It serves as a
|
geometric shapes grouped together within a single structure. It serves as a
|
||||||
container for organizing diverse shapes like edges, faces, or solids. This
|
container for organizing diverse shapes like edges, faces, or solids. This
|
||||||
|
|
@ -453,7 +453,7 @@ class Compound(Mixin3D, Shape[TopoDS_Compound]):
|
||||||
will be a Wire, otherwise a Shape.
|
will be a Wire, otherwise a Shape.
|
||||||
"""
|
"""
|
||||||
if self._dim == 1:
|
if self._dim == 1:
|
||||||
curve = Curve() if self.wrapped is None else Curve(self.wrapped)
|
curve = Curve() if self._wrapped is None else Curve(self.wrapped)
|
||||||
sum1d: Edge | Wire | ShapeList[Edge] = curve + other
|
sum1d: Edge | Wire | ShapeList[Edge] = curve + other
|
||||||
if isinstance(sum1d, ShapeList):
|
if isinstance(sum1d, ShapeList):
|
||||||
result1d: Curve | Wire = Curve(sum1d)
|
result1d: Curve | Wire = Curve(sum1d)
|
||||||
|
|
@ -515,7 +515,7 @@ class Compound(Mixin3D, Shape[TopoDS_Compound]):
|
||||||
Check if empty.
|
Check if empty.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return TopoDS_Iterator(self.wrapped).More()
|
return self._wrapped is not None and TopoDS_Iterator(self.wrapped).More()
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[Shape]:
|
def __iter__(self) -> Iterator[Shape]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -532,7 +532,7 @@ class Compound(Mixin3D, Shape[TopoDS_Compound]):
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
"""Return the number of subshapes"""
|
"""Return the number of subshapes"""
|
||||||
count = 0
|
count = 0
|
||||||
if self.wrapped is not None:
|
if self._wrapped is not None:
|
||||||
for _ in self:
|
for _ in self:
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
@ -600,7 +600,7 @@ class Compound(Mixin3D, Shape[TopoDS_Compound]):
|
||||||
|
|
||||||
def compounds(self) -> ShapeList[Compound]:
|
def compounds(self) -> ShapeList[Compound]:
|
||||||
"""compounds - all the compounds in this Shape"""
|
"""compounds - all the compounds in this Shape"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return ShapeList()
|
return ShapeList()
|
||||||
if isinstance(self.wrapped, TopoDS_Compound):
|
if isinstance(self.wrapped, TopoDS_Compound):
|
||||||
# pylint: disable=not-an-iterable
|
# pylint: disable=not-an-iterable
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ def _as_gcc_arg(obj: Edge | Vector, constaint: Tangency) -> tuple[
|
||||||
- Edge -> (QualifiedCurve, h2d, first, last, True)
|
- Edge -> (QualifiedCurve, h2d, first, last, True)
|
||||||
- Vector -> (CartesianPoint, None, None, None, False)
|
- Vector -> (CartesianPoint, None, None, None, False)
|
||||||
"""
|
"""
|
||||||
if obj.wrapped is None:
|
if not obj:
|
||||||
raise TypeError("Can't create a qualified curve from empty edge")
|
raise TypeError("Can't create a qualified curve from empty edge")
|
||||||
|
|
||||||
if isinstance(obj.wrapped, TopoDS_Edge):
|
if isinstance(obj.wrapped, TopoDS_Edge):
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,7 @@ from build123d.geometry import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from .shape_core import (
|
from .shape_core import (
|
||||||
|
TOPODS,
|
||||||
Shape,
|
Shape,
|
||||||
ShapeList,
|
ShapeList,
|
||||||
SkipClean,
|
SkipClean,
|
||||||
|
|
@ -249,7 +250,7 @@ if TYPE_CHECKING: # pragma: no cover
|
||||||
from .two_d import Face, Shell # pylint: disable=R0801
|
from .two_d import Face, Shell # pylint: disable=R0801
|
||||||
|
|
||||||
|
|
||||||
class Mixin1D(Shape):
|
class Mixin1D(Shape[TOPODS]):
|
||||||
"""Methods to add to the Edge and Wire classes"""
|
"""Methods to add to the Edge and Wire classes"""
|
||||||
|
|
||||||
# ---- Properties ----
|
# ---- Properties ----
|
||||||
|
|
@ -262,14 +263,14 @@ class Mixin1D(Shape):
|
||||||
@property
|
@property
|
||||||
def is_closed(self) -> bool:
|
def is_closed(self) -> bool:
|
||||||
"""Are the start and end points equal?"""
|
"""Are the start and end points equal?"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't determine if empty Edge or Wire is closed")
|
raise ValueError("Can't determine if empty Edge or Wire is closed")
|
||||||
return BRep_Tool.IsClosed_s(self.wrapped)
|
return BRep_Tool.IsClosed_s(self.wrapped)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_forward(self) -> bool:
|
def is_forward(self) -> bool:
|
||||||
"""Does the Edge/Wire loop forward or reverse"""
|
"""Does the Edge/Wire loop forward or reverse"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't determine direction of empty Edge or Wire")
|
raise ValueError("Can't determine direction of empty Edge or Wire")
|
||||||
return self.wrapped.Orientation() == TopAbs_Orientation.TopAbs_FORWARD
|
return self.wrapped.Orientation() == TopAbs_Orientation.TopAbs_FORWARD
|
||||||
|
|
||||||
|
|
@ -387,8 +388,7 @@ class Mixin1D(Shape):
|
||||||
shape
|
shape
|
||||||
# for o in (other if isinstance(other, (list, tuple)) else [other])
|
# for o in (other if isinstance(other, (list, tuple)) else [other])
|
||||||
for o in ([other] if isinstance(other, Shape) else other)
|
for o in ([other] if isinstance(other, Shape) else other)
|
||||||
if o is not None
|
for shape in get_top_level_topods_shapes(o.wrapped if o else None)
|
||||||
for shape in get_top_level_topods_shapes(o.wrapped)
|
|
||||||
]
|
]
|
||||||
# If there is nothing to add return the original object
|
# If there is nothing to add return the original object
|
||||||
if not topods_summands:
|
if not topods_summands:
|
||||||
|
|
@ -403,7 +403,7 @@ class Mixin1D(Shape):
|
||||||
)
|
)
|
||||||
summand_edges = [e for summand in summands for e in summand.edges()]
|
summand_edges = [e for summand in summands for e in summand.edges()]
|
||||||
|
|
||||||
if self.wrapped is None: # an empty object
|
if self._wrapped is None: # an empty object
|
||||||
if len(summands) == 1:
|
if len(summands) == 1:
|
||||||
sum_shape: Edge | Wire | ShapeList[Edge] = summands[0]
|
sum_shape: Edge | Wire | ShapeList[Edge] = summands[0]
|
||||||
else:
|
else:
|
||||||
|
|
@ -451,7 +451,7 @@ class Mixin1D(Shape):
|
||||||
Returns:
|
Returns:
|
||||||
Vector: center
|
Vector: center
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't find center of empty edge/wire")
|
raise ValueError("Can't find center of empty edge/wire")
|
||||||
|
|
||||||
if center_of == CenterOf.GEOMETRY:
|
if center_of == CenterOf.GEOMETRY:
|
||||||
|
|
@ -577,7 +577,7 @@ class Mixin1D(Shape):
|
||||||
>>> show(my_wire, Curve(comb))
|
>>> show(my_wire, Curve(comb))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't create curvature_comb for empty curve")
|
raise ValueError("Can't create curvature_comb for empty curve")
|
||||||
pln = self.common_plane()
|
pln = self.common_plane()
|
||||||
if pln is None or not isclose(abs(pln.z_dir.Z), 1.0, abs_tol=TOLERANCE):
|
if pln is None or not isclose(abs(pln.z_dir.Z), 1.0, abs_tol=TOLERANCE):
|
||||||
|
|
@ -971,7 +971,7 @@ class Mixin1D(Shape):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't find normal of empty edge/wire")
|
raise ValueError("Can't find normal of empty edge/wire")
|
||||||
|
|
||||||
curve = self.geom_adaptor()
|
curve = self.geom_adaptor()
|
||||||
|
|
@ -1205,7 +1205,7 @@ class Mixin1D(Shape):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None or face.wrapped is None:
|
if self._wrapped is None or not face:
|
||||||
raise ValueError("Can't project an empty Edge or Wire onto empty Face")
|
raise ValueError("Can't project an empty Edge or Wire onto empty Face")
|
||||||
|
|
||||||
bldr = BRepProj_Projection(
|
bldr = BRepProj_Projection(
|
||||||
|
|
@ -1277,7 +1277,7 @@ class Mixin1D(Shape):
|
||||||
|
|
||||||
return edges
|
return edges
|
||||||
|
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't project empty edge/wire")
|
raise ValueError("Can't project empty edge/wire")
|
||||||
|
|
||||||
# Setup the projector
|
# Setup the projector
|
||||||
|
|
@ -1380,7 +1380,7 @@ class Mixin1D(Shape):
|
||||||
- **Keep.BOTH**: Returns a tuple `(inside, outside)` where each element is
|
- **Keep.BOTH**: Returns a tuple `(inside, outside)` where each element is
|
||||||
either a `Self` or `list[Self]`, or `None` if no corresponding part is found.
|
either a `Self` or `list[Self]`, or `None` if no corresponding part is found.
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None or tool.wrapped is None:
|
if self._wrapped is None or not tool:
|
||||||
raise ValueError("Can't split an empty edge/wire/tool")
|
raise ValueError("Can't split an empty edge/wire/tool")
|
||||||
|
|
||||||
shape_list = TopTools_ListOfShape()
|
shape_list = TopTools_ListOfShape()
|
||||||
|
|
@ -1546,7 +1546,7 @@ class Mixin1D(Shape):
|
||||||
return Shape.get_shape_list(self, "Wire")
|
return Shape.get_shape_list(self, "Wire")
|
||||||
|
|
||||||
|
|
||||||
class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
class Edge(Mixin1D[TopoDS_Edge]):
|
||||||
"""An Edge in build123d is a fundamental element in the topological data structure
|
"""An Edge in build123d is a fundamental element in the topological data structure
|
||||||
representing a one-dimensional geometric entity within a 3D model. It encapsulates
|
representing a one-dimensional geometric entity within a 3D model. It encapsulates
|
||||||
information about a curve, which could be a line, arc, or other parametrically
|
information about a curve, which could be a line, arc, or other parametrically
|
||||||
|
|
@ -1627,7 +1627,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
Returns:
|
Returns:
|
||||||
Edge: extruded shape
|
Edge: extruded shape
|
||||||
"""
|
"""
|
||||||
if obj.wrapped is None:
|
if not obj:
|
||||||
raise ValueError("Can't extrude empty vertex")
|
raise ValueError("Can't extrude empty vertex")
|
||||||
return Edge(TopoDS.Edge_s(_extrude_topods_shape(obj.wrapped, direction)))
|
return Edge(TopoDS.Edge_s(_extrude_topods_shape(obj.wrapped, direction)))
|
||||||
|
|
||||||
|
|
@ -2518,7 +2518,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
extension_factor: float = 0.1,
|
extension_factor: float = 0.1,
|
||||||
):
|
):
|
||||||
"""Helper method to slightly extend an edge that is bound to a surface"""
|
"""Helper method to slightly extend an edge that is bound to a surface"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't extend empty spline")
|
raise ValueError("Can't extend empty spline")
|
||||||
if self.geom_type != GeomType.BSPLINE:
|
if self.geom_type != GeomType.BSPLINE:
|
||||||
raise TypeError("_extend_spline only works with splines")
|
raise TypeError("_extend_spline only works with splines")
|
||||||
|
|
@ -2575,7 +2575,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
Returns:
|
Returns:
|
||||||
ShapeList[Vector]: list of intersection points
|
ShapeList[Vector]: list of intersection points
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't find intersections of empty edge")
|
raise ValueError("Can't find intersections of empty edge")
|
||||||
|
|
||||||
# Convert an Axis into an edge at least as large as self and Axis start point
|
# Convert an Axis into an edge at least as large as self and Axis start point
|
||||||
|
|
@ -2703,7 +2703,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
|
|
||||||
def geom_adaptor(self) -> BRepAdaptor_Curve:
|
def geom_adaptor(self) -> BRepAdaptor_Curve:
|
||||||
"""Return the Geom Curve from this Edge"""
|
"""Return the Geom Curve from this Edge"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't find adaptor for empty edge")
|
raise ValueError("Can't find adaptor for empty edge")
|
||||||
return BRepAdaptor_Curve(self.wrapped)
|
return BRepAdaptor_Curve(self.wrapped)
|
||||||
|
|
||||||
|
|
@ -2791,7 +2791,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
float: Normalized parameter in [0.0, 1.0] corresponding to the point's
|
float: Normalized parameter in [0.0, 1.0] corresponding to the point's
|
||||||
closest location on the edge.
|
closest location on the edge.
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't find param on empty edge")
|
raise ValueError("Can't find param on empty edge")
|
||||||
|
|
||||||
pnt = Vector(point)
|
pnt = Vector(point)
|
||||||
|
|
@ -2925,7 +2925,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
Returns:
|
Returns:
|
||||||
Edge: reversed
|
Edge: reversed
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("An empty edge can't be reversed")
|
raise ValueError("An empty edge can't be reversed")
|
||||||
|
|
||||||
assert isinstance(self.wrapped, TopoDS_Edge)
|
assert isinstance(self.wrapped, TopoDS_Edge)
|
||||||
|
|
@ -3005,7 +3005,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
# if start_u >= end_u:
|
# if start_u >= end_u:
|
||||||
# raise ValueError(f"start ({start_u}) must be less than end ({end_u})")
|
# raise ValueError(f"start ({start_u}) must be less than end ({end_u})")
|
||||||
|
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't trim empty edge")
|
raise ValueError("Can't trim empty edge")
|
||||||
|
|
||||||
self_copy = copy.deepcopy(self)
|
self_copy = copy.deepcopy(self)
|
||||||
|
|
@ -3040,7 +3040,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
Returns:
|
Returns:
|
||||||
Edge: trimmed edge
|
Edge: trimmed edge
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't trim empty edge")
|
raise ValueError("Can't trim empty edge")
|
||||||
|
|
||||||
start_u = Mixin1D._to_param(self, start, "start")
|
start_u = Mixin1D._to_param(self, start, "start")
|
||||||
|
|
@ -3069,7 +3069,7 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
return Edge(new_edge)
|
return Edge(new_edge)
|
||||||
|
|
||||||
|
|
||||||
class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
class Wire(Mixin1D[TopoDS_Wire]):
|
||||||
"""A Wire in build123d is a topological entity representing a connected sequence
|
"""A Wire in build123d is a topological entity representing a connected sequence
|
||||||
of edges forming a continuous curve or path in 3D space. Wires are essential
|
of edges forming a continuous curve or path in 3D space. Wires are essential
|
||||||
components in modeling complex objects, defining boundaries for surfaces or
|
components in modeling complex objects, defining boundaries for surfaces or
|
||||||
|
|
@ -3603,7 +3603,7 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
Returns:
|
Returns:
|
||||||
Wire: chamfered wire
|
Wire: chamfered wire
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't chamfer empty wire")
|
raise ValueError("Can't chamfer empty wire")
|
||||||
|
|
||||||
reference_edge = edge
|
reference_edge = edge
|
||||||
|
|
@ -3618,7 +3618,7 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
)
|
)
|
||||||
|
|
||||||
for v in vertices:
|
for v in vertices:
|
||||||
if v.wrapped is None:
|
if not v:
|
||||||
continue
|
continue
|
||||||
edge_list = vertex_edge_map.FindFromKey(v.wrapped)
|
edge_list = vertex_edge_map.FindFromKey(v.wrapped)
|
||||||
|
|
||||||
|
|
@ -3675,7 +3675,7 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
Returns:
|
Returns:
|
||||||
Wire: filleted wire
|
Wire: filleted wire
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't fillet an empty wire")
|
raise ValueError("Can't fillet an empty wire")
|
||||||
|
|
||||||
# Create a face to fillet
|
# Create a face to fillet
|
||||||
|
|
@ -3703,7 +3703,7 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
Returns:
|
Returns:
|
||||||
Wire: fixed wire
|
Wire: fixed wire
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't fix an empty edge")
|
raise ValueError("Can't fix an empty edge")
|
||||||
|
|
||||||
sf_w = ShapeFix_Wireframe(self.wrapped)
|
sf_w = ShapeFix_Wireframe(self.wrapped)
|
||||||
|
|
@ -3715,7 +3715,7 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
|
|
||||||
def geom_adaptor(self) -> BRepAdaptor_CompCurve:
|
def geom_adaptor(self) -> BRepAdaptor_CompCurve:
|
||||||
"""Return the Geom Comp Curve for this Wire"""
|
"""Return the Geom Comp Curve for this Wire"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't get geom adaptor of empty wire")
|
raise ValueError("Can't get geom adaptor of empty wire")
|
||||||
|
|
||||||
return BRepAdaptor_CompCurve(self.wrapped)
|
return BRepAdaptor_CompCurve(self.wrapped)
|
||||||
|
|
@ -3759,7 +3759,7 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
float: Normalized parameter in [0.0, 1.0] representing the relative
|
float: Normalized parameter in [0.0, 1.0] representing the relative
|
||||||
position of the projected point along the wire.
|
position of the projected point along the wire.
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't find point on empty wire")
|
raise ValueError("Can't find point on empty wire")
|
||||||
|
|
||||||
point_on_curve = Vector(point)
|
point_on_curve = Vector(point)
|
||||||
|
|
@ -3912,7 +3912,7 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
if self.wrapped is None or target_object.wrapped is None:
|
if self._wrapped is None or not target_object:
|
||||||
raise ValueError("Can't project empty Wires or to empty Shapes")
|
raise ValueError("Can't project empty Wires or to empty Shapes")
|
||||||
|
|
||||||
if direction is not None and center is None:
|
if direction is not None and center is None:
|
||||||
|
|
@ -4001,7 +4001,7 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
Returns:
|
Returns:
|
||||||
Wire: stitched wires
|
Wire: stitched wires
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None or other.wrapped is None:
|
if self._wrapped is None or not other:
|
||||||
raise ValueError("Can't stitch empty wires")
|
raise ValueError("Can't stitch empty wires")
|
||||||
|
|
||||||
wire_builder = BRepBuilderAPI_MakeWire()
|
wire_builder = BRepBuilderAPI_MakeWire()
|
||||||
|
|
@ -4045,7 +4045,7 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
"""
|
"""
|
||||||
# Build a single Geom_BSplineCurve from the wire, in *topological order*
|
# Build a single Geom_BSplineCurve from the wire, in *topological order*
|
||||||
builder = GeomConvert_CompCurveToBSplineCurve()
|
builder = GeomConvert_CompCurveToBSplineCurve()
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't convert an empty wire")
|
raise ValueError("Can't convert an empty wire")
|
||||||
wire_explorer = BRepTools_WireExplorer(self.wrapped)
|
wire_explorer = BRepTools_WireExplorer(self.wrapped)
|
||||||
|
|
||||||
|
|
@ -4197,9 +4197,9 @@ def topo_explore_connected_edges(
|
||||||
parent = parent if parent is not None else edge.topo_parent
|
parent = parent if parent is not None else edge.topo_parent
|
||||||
if parent is None:
|
if parent is None:
|
||||||
raise ValueError("edge has no valid parent")
|
raise ValueError("edge has no valid parent")
|
||||||
given_topods_edge = edge.wrapped
|
if not edge:
|
||||||
if given_topods_edge is None:
|
|
||||||
raise ValueError("edge is empty")
|
raise ValueError("edge is empty")
|
||||||
|
given_topods_edge = edge.wrapped
|
||||||
connected_edges = set()
|
connected_edges = set()
|
||||||
|
|
||||||
# Find all the TopoDS_Edges for this Shape
|
# Find all the TopoDS_Edges for this Shape
|
||||||
|
|
@ -4242,11 +4242,11 @@ def topo_explore_connected_faces(
|
||||||
) -> list[TopoDS_Face]:
|
) -> list[TopoDS_Face]:
|
||||||
"""Given an edge extracted from a Shape, return the topods_faces connected to it"""
|
"""Given an edge extracted from a Shape, return the topods_faces connected to it"""
|
||||||
|
|
||||||
if edge.wrapped is None:
|
if not edge:
|
||||||
raise ValueError("Can't explore from an empty edge")
|
raise ValueError("Can't explore from an empty edge")
|
||||||
|
|
||||||
parent = parent if parent is not None else edge.topo_parent
|
parent = parent if parent is not None else edge.topo_parent
|
||||||
if parent is None or parent.wrapped is None:
|
if not parent:
|
||||||
raise ValueError("edge has no valid parent")
|
raise ValueError("edge has no valid parent")
|
||||||
|
|
||||||
# make a edge --> faces mapping
|
# make a edge --> faces mapping
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
color: ColorLike | None = None,
|
color: ColorLike | None = None,
|
||||||
parent: Compound | None = None,
|
parent: Compound | None = None,
|
||||||
):
|
):
|
||||||
self.wrapped: TOPODS | None = (
|
self._wrapped: TOPODS | None = (
|
||||||
tcast(Optional[TOPODS], downcast(obj)) if obj is not None else None
|
tcast(Optional[TOPODS], downcast(obj)) if obj is not None else None
|
||||||
)
|
)
|
||||||
self.for_construction = False
|
self.for_construction = False
|
||||||
|
|
@ -304,6 +304,18 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes, too-many-public-methods
|
# pylint: disable=too-many-instance-attributes, too-many-public-methods
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wrapped(self):
|
||||||
|
assert self._wrapped
|
||||||
|
return self._wrapped
|
||||||
|
|
||||||
|
@wrapped.setter
|
||||||
|
def wrapped(self, shape: TOPODS):
|
||||||
|
self._wrapped = shape
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return self._wrapped is not None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _dim(self) -> int | None:
|
def _dim(self) -> int | None:
|
||||||
|
|
@ -312,7 +324,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
@property
|
@property
|
||||||
def area(self) -> float:
|
def area(self) -> float:
|
||||||
"""area -the surface area of all faces in this Shape"""
|
"""area -the surface area of all faces in this Shape"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return 0.0
|
return 0.0
|
||||||
properties = GProp_GProps()
|
properties = GProp_GProps()
|
||||||
BRepGProp.SurfaceProperties_s(self.wrapped, properties)
|
BRepGProp.SurfaceProperties_s(self.wrapped, properties)
|
||||||
|
|
@ -351,7 +363,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
GeomType: The geometry type of the shape
|
GeomType: The geometry type of the shape
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot determine geometry type of an empty shape")
|
raise ValueError("Cannot determine geometry type of an empty shape")
|
||||||
|
|
||||||
shape: TopAbs_ShapeEnum = shapetype(self.wrapped)
|
shape: TopAbs_ShapeEnum = shapetype(self.wrapped)
|
||||||
|
|
@ -380,7 +392,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
bool: is the shape manifold or water tight
|
bool: is the shape manifold or water tight
|
||||||
"""
|
"""
|
||||||
# Extract one or more (if a Compound) shape from self
|
# Extract one or more (if a Compound) shape from self
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return False
|
return False
|
||||||
shape_stack = get_top_level_topods_shapes(self.wrapped)
|
shape_stack = get_top_level_topods_shapes(self.wrapped)
|
||||||
|
|
||||||
|
|
@ -431,12 +443,12 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
underlying shape with the potential to be given a location and an
|
underlying shape with the potential to be given a location and an
|
||||||
orientation.
|
orientation.
|
||||||
"""
|
"""
|
||||||
return self.wrapped is None or self.wrapped.IsNull()
|
return self._wrapped is None or self.wrapped.IsNull()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_planar_face(self) -> bool:
|
def is_planar_face(self) -> bool:
|
||||||
"""Is the shape a planar face even though its geom_type may not be PLANE"""
|
"""Is the shape a planar face even though its geom_type may not be PLANE"""
|
||||||
if self.wrapped is None or not isinstance(self.wrapped, TopoDS_Face):
|
if self._wrapped is None or not isinstance(self.wrapped, TopoDS_Face):
|
||||||
return False
|
return False
|
||||||
surface = BRep_Tool.Surface_s(self.wrapped)
|
surface = BRep_Tool.Surface_s(self.wrapped)
|
||||||
is_face_planar = GeomLib_IsPlanarSurface(surface, TOLERANCE)
|
is_face_planar = GeomLib_IsPlanarSurface(surface, TOLERANCE)
|
||||||
|
|
@ -448,7 +460,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
subshapes. See the OCCT docs on BRepCheck_Analyzer::IsValid for a full
|
subshapes. See the OCCT docs on BRepCheck_Analyzer::IsValid for a full
|
||||||
description of what is checked.
|
description of what is checked.
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return True
|
return True
|
||||||
chk = BRepCheck_Analyzer(self.wrapped)
|
chk = BRepCheck_Analyzer(self.wrapped)
|
||||||
chk.SetParallel(True)
|
chk.SetParallel(True)
|
||||||
|
|
@ -474,7 +486,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
@property
|
@property
|
||||||
def location(self) -> Location:
|
def location(self) -> Location:
|
||||||
"""Get this Shape's Location"""
|
"""Get this Shape's Location"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't find the location of an empty shape")
|
raise ValueError("Can't find the location of an empty shape")
|
||||||
return Location(self.wrapped.Location())
|
return Location(self.wrapped.Location())
|
||||||
|
|
||||||
|
|
@ -518,7 +530,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
- It is commonly used in structural analysis, mechanical simulations,
|
- It is commonly used in structural analysis, mechanical simulations,
|
||||||
and physics-based motion calculations.
|
and physics-based motion calculations.
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't calculate matrix for empty shape")
|
raise ValueError("Can't calculate matrix for empty shape")
|
||||||
properties = GProp_GProps()
|
properties = GProp_GProps()
|
||||||
BRepGProp.VolumeProperties_s(self.wrapped, properties)
|
BRepGProp.VolumeProperties_s(self.wrapped, properties)
|
||||||
|
|
@ -546,7 +558,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
@property
|
@property
|
||||||
def position(self) -> Vector:
|
def position(self) -> Vector:
|
||||||
"""Get the position component of this Shape's Location"""
|
"""Get the position component of this Shape's Location"""
|
||||||
if self.wrapped is None or self.location is None:
|
if self._wrapped is None or self.location is None:
|
||||||
raise ValueError("Can't find the position of an empty shape")
|
raise ValueError("Can't find the position of an empty shape")
|
||||||
return self.location.position
|
return self.location.position
|
||||||
|
|
||||||
|
|
@ -575,7 +587,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
(Vector(0, 1, 0), 1000.0),
|
(Vector(0, 1, 0), 1000.0),
|
||||||
(Vector(0, 0, 1), 300.0)]
|
(Vector(0, 0, 1), 300.0)]
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't calculate properties for empty shape")
|
raise ValueError("Can't calculate properties for empty shape")
|
||||||
|
|
||||||
properties = GProp_GProps()
|
properties = GProp_GProps()
|
||||||
|
|
@ -615,7 +627,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
(150.0, 200.0, 50.0)
|
(150.0, 200.0, 50.0)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't calculate moments for empty shape")
|
raise ValueError("Can't calculate moments for empty shape")
|
||||||
|
|
||||||
properties = GProp_GProps()
|
properties = GProp_GProps()
|
||||||
|
|
@ -785,7 +797,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if obj.wrapped is None:
|
if not obj:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
properties = GProp_GProps()
|
properties = GProp_GProps()
|
||||||
|
|
@ -805,7 +817,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
],
|
],
|
||||||
) -> ShapeList:
|
) -> ShapeList:
|
||||||
"""Helper to extract entities of a specific type from a shape."""
|
"""Helper to extract entities of a specific type from a shape."""
|
||||||
if shape.wrapped is None:
|
if not shape:
|
||||||
return ShapeList()
|
return ShapeList()
|
||||||
shape_list = ShapeList(
|
shape_list = ShapeList(
|
||||||
[shape.__class__.cast(i) for i in shape.entities(entity_type)]
|
[shape.__class__.cast(i) for i in shape.entities(entity_type)]
|
||||||
|
|
@ -859,7 +871,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
if not all(summand._dim == addend_dim for summand in summands):
|
if not all(summand._dim == addend_dim for summand in summands):
|
||||||
raise ValueError("Only shapes with the same dimension can be added")
|
raise ValueError("Only shapes with the same dimension can be added")
|
||||||
|
|
||||||
if self.wrapped is None: # an empty object
|
if self._wrapped is None: # an empty object
|
||||||
if len(summands) == 1:
|
if len(summands) == 1:
|
||||||
sum_shape = summands[0]
|
sum_shape = summands[0]
|
||||||
else:
|
else:
|
||||||
|
|
@ -876,7 +888,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
"""intersect shape with self operator &"""
|
"""intersect shape with self operator &"""
|
||||||
others = other if isinstance(other, (list, tuple)) else [other]
|
others = other if isinstance(other, (list, tuple)) else [other]
|
||||||
|
|
||||||
if self.wrapped is None or (isinstance(other, Shape) and other.wrapped is None):
|
if not self or (isinstance(other, Shape) and not other):
|
||||||
raise ValueError("Cannot intersect shape with empty compound")
|
raise ValueError("Cannot intersect shape with empty compound")
|
||||||
new_shape = self.intersect(*others)
|
new_shape = self.intersect(*others)
|
||||||
|
|
||||||
|
|
@ -948,7 +960,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
"""Return hash code"""
|
"""Return hash code"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return 0
|
return 0
|
||||||
return hash(self.wrapped)
|
return hash(self.wrapped)
|
||||||
|
|
||||||
|
|
@ -966,7 +978,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
def __sub__(self, other: None | Shape | Iterable[Shape]) -> Self | ShapeList[Self]:
|
def __sub__(self, other: None | Shape | Iterable[Shape]) -> Self | ShapeList[Self]:
|
||||||
"""cut shape from self operator -"""
|
"""cut shape from self operator -"""
|
||||||
|
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot subtract shape from empty compound")
|
raise ValueError("Cannot subtract shape from empty compound")
|
||||||
|
|
||||||
# Convert `other` to list of base objects and filter out None values
|
# Convert `other` to list of base objects and filter out None values
|
||||||
|
|
@ -1014,7 +1026,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
BoundBox: A box sized to contain this Shape
|
BoundBox: A box sized to contain this Shape
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return BoundBox(Bnd_Box())
|
return BoundBox(Bnd_Box())
|
||||||
tolerance = TOLERANCE if tolerance is None else tolerance
|
tolerance = TOLERANCE if tolerance is None else tolerance
|
||||||
return BoundBox.from_topo_ds(self.wrapped, tolerance=tolerance, optimal=optimal)
|
return BoundBox.from_topo_ds(self.wrapped, tolerance=tolerance, optimal=optimal)
|
||||||
|
|
@ -1033,7 +1045,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
Shape: Original object with extraneous internal edges removed
|
Shape: Original object with extraneous internal edges removed
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return self
|
return self
|
||||||
upgrader = ShapeUpgrade_UnifySameDomain(self.wrapped, True, True, True)
|
upgrader = ShapeUpgrade_UnifySameDomain(self.wrapped, True, True, True)
|
||||||
upgrader.AllowInternalEdges(False)
|
upgrader.AllowInternalEdges(False)
|
||||||
|
|
@ -1112,7 +1124,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None or other.wrapped is None:
|
if self._wrapped is None or not other:
|
||||||
raise ValueError("Cannot calculate distance to or from an empty shape")
|
raise ValueError("Cannot calculate distance to or from an empty shape")
|
||||||
|
|
||||||
return BRepExtrema_DistShapeShape(self.wrapped, other.wrapped).Value()
|
return BRepExtrema_DistShapeShape(self.wrapped, other.wrapped).Value()
|
||||||
|
|
@ -1125,7 +1137,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
self, other: Shape | VectorLike
|
self, other: Shape | VectorLike
|
||||||
) -> tuple[float, Vector, Vector]:
|
) -> tuple[float, Vector, Vector]:
|
||||||
"""Minimal distance between two shapes and the points on each shape"""
|
"""Minimal distance between two shapes and the points on each shape"""
|
||||||
if self.wrapped is None or (isinstance(other, Shape) and other.wrapped is None):
|
if self._wrapped is None or (isinstance(other, Shape) and not other):
|
||||||
raise ValueError("Cannot calculate distance to or from an empty shape")
|
raise ValueError("Cannot calculate distance to or from an empty shape")
|
||||||
|
|
||||||
if isinstance(other, Shape):
|
if isinstance(other, Shape):
|
||||||
|
|
@ -1155,14 +1167,14 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot calculate distance to or from an empty shape")
|
raise ValueError("Cannot calculate distance to or from an empty shape")
|
||||||
|
|
||||||
dist_calc = BRepExtrema_DistShapeShape()
|
dist_calc = BRepExtrema_DistShapeShape()
|
||||||
dist_calc.LoadS1(self.wrapped)
|
dist_calc.LoadS1(self.wrapped)
|
||||||
|
|
||||||
for other_shape in others:
|
for other_shape in others:
|
||||||
if other_shape.wrapped is None:
|
if not other_shape:
|
||||||
raise ValueError("Cannot calculate distance to or from an empty shape")
|
raise ValueError("Cannot calculate distance to or from an empty shape")
|
||||||
dist_calc.LoadS2(other_shape.wrapped)
|
dist_calc.LoadS2(other_shape.wrapped)
|
||||||
dist_calc.Perform()
|
dist_calc.Perform()
|
||||||
|
|
@ -1181,7 +1193,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
|
|
||||||
def entities(self, topo_type: Shapes) -> list[TopoDS_Shape]:
|
def entities(self, topo_type: Shapes) -> list[TopoDS_Shape]:
|
||||||
"""Return all of the TopoDS sub entities of the given type"""
|
"""Return all of the TopoDS sub entities of the given type"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return []
|
return []
|
||||||
return _topods_entities(self.wrapped, topo_type)
|
return _topods_entities(self.wrapped, topo_type)
|
||||||
|
|
||||||
|
|
@ -1209,7 +1221,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
list[Face]: A list of intersected faces sorted by distance from axis.position
|
list[Face]: A list of intersected faces sorted by distance from axis.position
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return ShapeList()
|
return ShapeList()
|
||||||
|
|
||||||
line = gce_MakeLin(axis.wrapped).Value()
|
line = gce_MakeLin(axis.wrapped).Value()
|
||||||
|
|
@ -1239,7 +1251,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
|
|
||||||
def fix(self) -> Self:
|
def fix(self) -> Self:
|
||||||
"""fix - try to fix shape if not valid"""
|
"""fix - try to fix shape if not valid"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return self
|
return self
|
||||||
if not self.is_valid:
|
if not self.is_valid:
|
||||||
shape_copy: Shape = copy.deepcopy(self, None)
|
shape_copy: Shape = copy.deepcopy(self, None)
|
||||||
|
|
@ -1281,7 +1293,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
# self, child_type: Shapes, parent_type: Shapes
|
# self, child_type: Shapes, parent_type: Shapes
|
||||||
# ) -> Dict[Shape, list[Shape]]:
|
# ) -> Dict[Shape, list[Shape]]:
|
||||||
# """This function is very slow on M1 macs and is currently unused"""
|
# """This function is very slow on M1 macs and is currently unused"""
|
||||||
# if self.wrapped is None:
|
# if self._wrapped is None:
|
||||||
# return {}
|
# return {}
|
||||||
|
|
||||||
# res = TopTools_IndexedDataMapOfShapeListOfShape()
|
# res = TopTools_IndexedDataMapOfShapeListOfShape()
|
||||||
|
|
@ -1319,7 +1331,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
(e.g., edges, vertices) and other compounds, the method returns a list
|
(e.g., edges, vertices) and other compounds, the method returns a list
|
||||||
of only the simple shapes directly contained at the top level.
|
of only the simple shapes directly contained at the top level.
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return ShapeList()
|
return ShapeList()
|
||||||
return ShapeList(
|
return ShapeList(
|
||||||
self.__class__.cast(s) for s in get_top_level_topods_shapes(self.wrapped)
|
self.__class__.cast(s) for s in get_top_level_topods_shapes(self.wrapped)
|
||||||
|
|
@ -1398,7 +1410,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None or other.wrapped is None:
|
if self._wrapped is None or not other:
|
||||||
return False
|
return False
|
||||||
return self.wrapped.IsEqual(other.wrapped)
|
return self.wrapped.IsEqual(other.wrapped)
|
||||||
|
|
||||||
|
|
@ -1413,7 +1425,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None or other.wrapped is None:
|
if self._wrapped is None or not other:
|
||||||
return False
|
return False
|
||||||
return self.wrapped.IsSame(other.wrapped)
|
return self.wrapped.IsSame(other.wrapped)
|
||||||
|
|
||||||
|
|
@ -1426,7 +1438,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot locate an empty shape")
|
raise ValueError("Cannot locate an empty shape")
|
||||||
if loc.wrapped is None:
|
if loc.wrapped is None:
|
||||||
raise ValueError("Cannot locate a shape at an empty location")
|
raise ValueError("Cannot locate a shape at an empty location")
|
||||||
|
|
@ -1445,7 +1457,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
Shape: copy of Shape at location
|
Shape: copy of Shape at location
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot locate an empty shape")
|
raise ValueError("Cannot locate an empty shape")
|
||||||
if loc.wrapped is None:
|
if loc.wrapped is None:
|
||||||
raise ValueError("Cannot locate a shape at an empty location")
|
raise ValueError("Cannot locate a shape at an empty location")
|
||||||
|
|
@ -1463,7 +1475,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot mesh an empty shape")
|
raise ValueError("Cannot mesh an empty shape")
|
||||||
|
|
||||||
if not BRepTools.Triangulation_s(self.wrapped, tolerance):
|
if not BRepTools.Triangulation_s(self.wrapped, tolerance):
|
||||||
|
|
@ -1484,7 +1496,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
if not mirror_plane:
|
if not mirror_plane:
|
||||||
mirror_plane = Plane.XY
|
mirror_plane = Plane.XY
|
||||||
|
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return self
|
return self
|
||||||
transformation = gp_Trsf()
|
transformation = gp_Trsf()
|
||||||
transformation.SetMirror(
|
transformation.SetMirror(
|
||||||
|
|
@ -1502,7 +1514,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot move an empty shape")
|
raise ValueError("Cannot move an empty shape")
|
||||||
if loc.wrapped is None:
|
if loc.wrapped is None:
|
||||||
raise ValueError("Cannot move a shape at an empty location")
|
raise ValueError("Cannot move a shape at an empty location")
|
||||||
|
|
@ -1522,7 +1534,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
Shape: copy of Shape moved to relative location
|
Shape: copy of Shape moved to relative location
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot move an empty shape")
|
raise ValueError("Cannot move an empty shape")
|
||||||
if loc.wrapped is None:
|
if loc.wrapped is None:
|
||||||
raise ValueError("Cannot move a shape at an empty location")
|
raise ValueError("Cannot move a shape at an empty location")
|
||||||
|
|
@ -1536,7 +1548,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
OrientedBoundBox: A box oriented and sized to contain this Shape
|
OrientedBoundBox: A box oriented and sized to contain this Shape
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return OrientedBoundBox(Bnd_OBB())
|
return OrientedBoundBox(Bnd_OBB())
|
||||||
return OrientedBoundBox(self)
|
return OrientedBoundBox(self)
|
||||||
|
|
||||||
|
|
@ -1638,7 +1650,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
- The radius of gyration is computed based on the shape’s mass properties.
|
- The radius of gyration is computed based on the shape’s mass properties.
|
||||||
- It is useful for evaluating structural stability and rotational behavior.
|
- It is useful for evaluating structural stability and rotational behavior.
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't calculate radius of gyration for empty shape")
|
raise ValueError("Can't calculate radius of gyration for empty shape")
|
||||||
|
|
||||||
properties = GProp_GProps()
|
properties = GProp_GProps()
|
||||||
|
|
@ -1657,7 +1669,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot relocate an empty shape")
|
raise ValueError("Cannot relocate an empty shape")
|
||||||
if loc.wrapped is None:
|
if loc.wrapped is None:
|
||||||
raise ValueError("Cannot relocate a shape at an empty location")
|
raise ValueError("Cannot relocate a shape at an empty location")
|
||||||
|
|
@ -1852,7 +1864,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
"keep must be one of Keep.INSIDE, Keep.OUTSIDE, or Keep.BOTH"
|
"keep must be one of Keep.INSIDE, Keep.OUTSIDE, or Keep.BOTH"
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot split an empty shape")
|
raise ValueError("Cannot split an empty shape")
|
||||||
|
|
||||||
# Process the perimeter
|
# Process the perimeter
|
||||||
|
|
@ -1860,7 +1872,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
raise ValueError("perimeter must be a closed Wire or Edge")
|
raise ValueError("perimeter must be a closed Wire or Edge")
|
||||||
perimeter_edges = TopTools_SequenceOfShape()
|
perimeter_edges = TopTools_SequenceOfShape()
|
||||||
for perimeter_edge in perimeter.edges():
|
for perimeter_edge in perimeter.edges():
|
||||||
if perimeter_edge.wrapped is None:
|
if not perimeter_edge:
|
||||||
continue
|
continue
|
||||||
perimeter_edges.Append(perimeter_edge.wrapped)
|
perimeter_edges.Append(perimeter_edge.wrapped)
|
||||||
|
|
||||||
|
|
@ -1868,7 +1880,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
lefts: list[Shell] = []
|
lefts: list[Shell] = []
|
||||||
rights: list[Shell] = []
|
rights: list[Shell] = []
|
||||||
for target_shell in self.shells():
|
for target_shell in self.shells():
|
||||||
if target_shell.wrapped is None:
|
if not target_shell:
|
||||||
continue
|
continue
|
||||||
constructor = BRepFeat_SplitShape(target_shell.wrapped)
|
constructor = BRepFeat_SplitShape(target_shell.wrapped)
|
||||||
constructor.Add(perimeter_edges)
|
constructor.Add(perimeter_edges)
|
||||||
|
|
@ -1897,7 +1909,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
self, tolerance: float, angular_tolerance: float = 0.1
|
self, tolerance: float, angular_tolerance: float = 0.1
|
||||||
) -> tuple[list[Vector], list[tuple[int, int, int]]]:
|
) -> tuple[list[Vector], list[tuple[int, int, int]]]:
|
||||||
"""General triangulated approximation"""
|
"""General triangulated approximation"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot tessellate an empty shape")
|
raise ValueError("Cannot tessellate an empty shape")
|
||||||
|
|
||||||
self.mesh(tolerance, angular_tolerance)
|
self.mesh(tolerance, angular_tolerance)
|
||||||
|
|
@ -1959,7 +1971,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
Self: Approximated shape
|
Self: Approximated shape
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot approximate an empty shape")
|
raise ValueError("Cannot approximate an empty shape")
|
||||||
|
|
||||||
params = ShapeCustom_RestrictionParameters()
|
params = ShapeCustom_RestrictionParameters()
|
||||||
|
|
@ -1996,7 +2008,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
Shape: a copy of the object, but with geometry transformed
|
Shape: a copy of the object, but with geometry transformed
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return self
|
return self
|
||||||
new_shape = copy.deepcopy(self, None)
|
new_shape = copy.deepcopy(self, None)
|
||||||
transformed = downcast(
|
transformed = downcast(
|
||||||
|
|
@ -2019,7 +2031,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
Shape: copy of transformed shape with all objects keeping their type
|
Shape: copy of transformed shape with all objects keeping their type
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return self
|
return self
|
||||||
new_shape = copy.deepcopy(self, None)
|
new_shape = copy.deepcopy(self, None)
|
||||||
transformed = downcast(
|
transformed = downcast(
|
||||||
|
|
@ -2092,7 +2104,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
Shape: copy of transformed Shape
|
Shape: copy of transformed Shape
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return self
|
return self
|
||||||
shape_copy: Shape = copy.deepcopy(self, None)
|
shape_copy: Shape = copy.deepcopy(self, None)
|
||||||
transformed_shape = BRepBuilderAPI_Transform(
|
transformed_shape = BRepBuilderAPI_Transform(
|
||||||
|
|
@ -2201,7 +2213,7 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
||||||
Returns:
|
Returns:
|
||||||
tuple[ShapeList[Vertex], ShapeList[Edge]]: section results
|
tuple[ShapeList[Vertex], ShapeList[Edge]]: section results
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None or other.wrapped is None:
|
if self._wrapped is None or not other:
|
||||||
return (ShapeList(), ShapeList())
|
return (ShapeList(), ShapeList())
|
||||||
|
|
||||||
section = BRepAlgoAPI_Section(self.wrapped, other.wrapped)
|
section = BRepAlgoAPI_Section(self.wrapped, other.wrapped)
|
||||||
|
|
@ -2702,15 +2714,16 @@ class ShapeList(list[T]):
|
||||||
tol_digits,
|
tol_digits,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif hasattr(group_by, "wrapped"):
|
elif not group_by:
|
||||||
if group_by.wrapped is None:
|
raise ValueError("Cannot group by an empty object")
|
||||||
raise ValueError("Cannot group by an empty object")
|
|
||||||
|
|
||||||
if isinstance(group_by.wrapped, (TopoDS_Edge, TopoDS_Wire)):
|
elif hasattr(group_by, "wrapped") and isinstance(
|
||||||
|
group_by.wrapped, (TopoDS_Edge, TopoDS_Wire)
|
||||||
|
):
|
||||||
|
|
||||||
def key_f(obj):
|
def key_f(obj):
|
||||||
pnt1, _pnt2 = group_by.closest_points(obj.center())
|
pnt1, _pnt2 = group_by.closest_points(obj.center())
|
||||||
return round(group_by.param_at_point(pnt1), tol_digits)
|
return round(group_by.param_at_point(pnt1), tol_digits)
|
||||||
|
|
||||||
elif isinstance(group_by, SortBy):
|
elif isinstance(group_by, SortBy):
|
||||||
if group_by == SortBy.LENGTH:
|
if group_by == SortBy.LENGTH:
|
||||||
|
|
@ -2816,22 +2829,22 @@ class ShapeList(list[T]):
|
||||||
).position.Z,
|
).position.Z,
|
||||||
reverse=reverse,
|
reverse=reverse,
|
||||||
)
|
)
|
||||||
elif hasattr(sort_by, "wrapped"):
|
elif not sort_by:
|
||||||
if sort_by.wrapped is None:
|
raise ValueError("Cannot sort by an empty object")
|
||||||
raise ValueError("Cannot sort by an empty object")
|
elif hasattr(sort_by, "wrapped") and isinstance(
|
||||||
|
sort_by.wrapped, (TopoDS_Edge, TopoDS_Wire)
|
||||||
|
):
|
||||||
|
|
||||||
if isinstance(sort_by.wrapped, (TopoDS_Edge, TopoDS_Wire)):
|
def u_of_closest_center(obj) -> float:
|
||||||
|
"""u-value of closest point between object center and sort_by"""
|
||||||
|
assert not isinstance(sort_by, SortBy)
|
||||||
|
pnt1, _pnt2 = sort_by.closest_points(obj.center())
|
||||||
|
return sort_by.param_at_point(pnt1)
|
||||||
|
|
||||||
def u_of_closest_center(obj) -> float:
|
# pylint: disable=unnecessary-lambda
|
||||||
"""u-value of closest point between object center and sort_by"""
|
objects = sorted(
|
||||||
assert not isinstance(sort_by, SortBy)
|
self, key=lambda o: u_of_closest_center(o), reverse=reverse
|
||||||
pnt1, _pnt2 = sort_by.closest_points(obj.center())
|
)
|
||||||
return sort_by.param_at_point(pnt1)
|
|
||||||
|
|
||||||
# pylint: disable=unnecessary-lambda
|
|
||||||
objects = sorted(
|
|
||||||
self, key=lambda o: u_of_closest_center(o), reverse=reverse
|
|
||||||
)
|
|
||||||
|
|
||||||
elif isinstance(sort_by, SortBy):
|
elif isinstance(sort_by, SortBy):
|
||||||
if sort_by == SortBy.LENGTH:
|
if sort_by == SortBy.LENGTH:
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ from build123d.geometry import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from .one_d import Edge, Wire, Mixin1D
|
from .one_d import Edge, Wire, Mixin1D
|
||||||
from .shape_core import Shape, ShapeList, Joint, downcast, shapetype
|
from .shape_core import TOPODS, Shape, ShapeList, Joint, downcast, shapetype
|
||||||
from .two_d import sort_wires_by_build_order, Mixin2D, Face, Shell
|
from .two_d import sort_wires_by_build_order, Mixin2D, Face, Shell
|
||||||
from .utils import (
|
from .utils import (
|
||||||
_extrude_topods_shape,
|
_extrude_topods_shape,
|
||||||
|
|
@ -122,7 +122,7 @@ if TYPE_CHECKING: # pragma: no cover
|
||||||
from .composite import Compound, Curve, Sketch, Part # pylint: disable=R0801
|
from .composite import Compound, Curve, Sketch, Part # pylint: disable=R0801
|
||||||
|
|
||||||
|
|
||||||
class Mixin3D(Shape):
|
class Mixin3D(Shape[TOPODS]):
|
||||||
"""Additional methods to add to 3D Shape classes"""
|
"""Additional methods to add to 3D Shape classes"""
|
||||||
|
|
||||||
project_to_viewport = Mixin1D.project_to_viewport
|
project_to_viewport = Mixin1D.project_to_viewport
|
||||||
|
|
@ -714,7 +714,7 @@ class Mixin3D(Shape):
|
||||||
return Shape.get_shape_list(self, "Solid")
|
return Shape.get_shape_list(self, "Solid")
|
||||||
|
|
||||||
|
|
||||||
class Solid(Mixin3D, Shape[TopoDS_Solid]):
|
class Solid(Mixin3D[TopoDS_Solid]):
|
||||||
"""A Solid in build123d represents a three-dimensional solid geometry
|
"""A Solid in build123d represents a three-dimensional solid geometry
|
||||||
in a topological structure. A solid is a closed and bounded volume, enclosing
|
in a topological structure. A solid is a closed and bounded volume, enclosing
|
||||||
a region in 3D space. It comprises faces, edges, and vertices connected in a
|
a region in 3D space. It comprises faces, edges, and vertices connected in a
|
||||||
|
|
@ -1393,7 +1393,7 @@ class Solid(Mixin3D, Shape[TopoDS_Solid]):
|
||||||
outer_wire = section
|
outer_wire = section
|
||||||
inner_wires = inner_wires if inner_wires else []
|
inner_wires = inner_wires if inner_wires else []
|
||||||
|
|
||||||
shapes = []
|
shapes: list[Mixin3D[TopoDS_Shape]] = []
|
||||||
for wire in [outer_wire] + inner_wires:
|
for wire in [outer_wire] + inner_wires:
|
||||||
builder = BRepOffsetAPI_MakePipeShell(Wire(path).wrapped)
|
builder = BRepOffsetAPI_MakePipeShell(Wire(path).wrapped)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,7 @@ from build123d.geometry import (
|
||||||
|
|
||||||
from .one_d import Edge, Mixin1D, Wire
|
from .one_d import Edge, Mixin1D, Wire
|
||||||
from .shape_core import (
|
from .shape_core import (
|
||||||
|
TOPODS,
|
||||||
Shape,
|
Shape,
|
||||||
ShapeList,
|
ShapeList,
|
||||||
SkipClean,
|
SkipClean,
|
||||||
|
|
@ -165,7 +166,7 @@ if TYPE_CHECKING: # pragma: no cover
|
||||||
T = TypeVar("T", Edge, Wire, "Face")
|
T = TypeVar("T", Edge, Wire, "Face")
|
||||||
|
|
||||||
|
|
||||||
class Mixin2D(ABC, Shape):
|
class Mixin2D(ABC, Shape[TOPODS]):
|
||||||
"""Additional methods to add to Face and Shell class"""
|
"""Additional methods to add to Face and Shell class"""
|
||||||
|
|
||||||
project_to_viewport = Mixin1D.project_to_viewport
|
project_to_viewport = Mixin1D.project_to_viewport
|
||||||
|
|
@ -213,7 +214,7 @@ class Mixin2D(ABC, Shape):
|
||||||
|
|
||||||
def __neg__(self) -> Self:
|
def __neg__(self) -> Self:
|
||||||
"""Reverse normal operator -"""
|
"""Reverse normal operator -"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Invalid Shape")
|
raise ValueError("Invalid Shape")
|
||||||
new_surface = copy.deepcopy(self)
|
new_surface = copy.deepcopy(self)
|
||||||
new_surface.wrapped = downcast(self.wrapped.Complemented())
|
new_surface.wrapped = downcast(self.wrapped.Complemented())
|
||||||
|
|
@ -244,7 +245,7 @@ class Mixin2D(ABC, Shape):
|
||||||
Returns:
|
Returns:
|
||||||
list[tuple[Vector, Vector]]: Point and normal of intersection
|
list[tuple[Vector, Vector]]: Point and normal of intersection
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
intersection_line = gce_MakeLin(other.wrapped).Value()
|
intersection_line = gce_MakeLin(other.wrapped).Value()
|
||||||
|
|
@ -470,7 +471,7 @@ class Mixin2D(ABC, Shape):
|
||||||
world_point, world_point - target_object_center
|
world_point, world_point - target_object_center
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't wrap around an empty face")
|
raise ValueError("Can't wrap around an empty face")
|
||||||
|
|
||||||
# Initial setup
|
# Initial setup
|
||||||
|
|
@ -531,7 +532,7 @@ class Mixin2D(ABC, Shape):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Length error of {length_error:.6f} exceeds tolerance {tolerance}"
|
f"Length error of {length_error:.6f} exceeds tolerance {tolerance}"
|
||||||
)
|
)
|
||||||
if wrapped_edge.wrapped is None or not wrapped_edge.is_valid:
|
if not wrapped_edge or not wrapped_edge.is_valid:
|
||||||
raise RuntimeError("Wrapped edge is invalid")
|
raise RuntimeError("Wrapped edge is invalid")
|
||||||
|
|
||||||
if not snap_to_face:
|
if not snap_to_face:
|
||||||
|
|
@ -554,7 +555,7 @@ class Mixin2D(ABC, Shape):
|
||||||
return projected_edge
|
return projected_edge
|
||||||
|
|
||||||
|
|
||||||
class Face(Mixin2D, Shape[TopoDS_Face]):
|
class Face(Mixin2D[TopoDS_Face]):
|
||||||
"""A Face in build123d represents a 3D bounded surface within the topological data
|
"""A Face in build123d represents a 3D bounded surface within the topological data
|
||||||
structure. It encapsulates geometric information, defining a face of a 3D shape.
|
structure. It encapsulates geometric information, defining a face of a 3D shape.
|
||||||
These faces are integral components of complex structures, such as solids and
|
These faces are integral components of complex structures, such as solids and
|
||||||
|
|
@ -569,7 +570,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
@overload
|
@overload
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
obj: TopoDS_Face,
|
obj: TopoDS_Face | Plane,
|
||||||
label: str = "",
|
label: str = "",
|
||||||
color: Color | None = None,
|
color: Color | None = None,
|
||||||
parent: Compound | None = None,
|
parent: Compound | None = None,
|
||||||
|
|
@ -577,7 +578,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
"""Build a Face from an OCCT TopoDS_Shape/TopoDS_Face
|
"""Build a Face from an OCCT TopoDS_Shape/TopoDS_Face
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj (TopoDS_Shape, optional): OCCT Face.
|
obj (TopoDS_Shape | Plane, optional): OCCT Face or Plane.
|
||||||
label (str, optional): Defaults to ''.
|
label (str, optional): Defaults to ''.
|
||||||
color (Color, optional): Defaults to None.
|
color (Color, optional): Defaults to None.
|
||||||
parent (Compound, optional): assembly parent. Defaults to None.
|
parent (Compound, optional): assembly parent. Defaults to None.
|
||||||
|
|
@ -607,7 +608,9 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
|
|
||||||
if args:
|
if args:
|
||||||
l_a = len(args)
|
l_a = len(args)
|
||||||
if isinstance(args[0], TopoDS_Shape):
|
if isinstance(args[0], Plane):
|
||||||
|
obj = args[0]
|
||||||
|
elif isinstance(args[0], TopoDS_Shape):
|
||||||
obj, label, color, parent = args[:4] + (None,) * (4 - l_a)
|
obj, label, color, parent = args[:4] + (None,) * (4 - l_a)
|
||||||
elif isinstance(args[0], Wire):
|
elif isinstance(args[0], Wire):
|
||||||
outer_wire, inner_wires, label, color, parent = args[:5] + (None,) * (
|
outer_wire, inner_wires, label, color, parent = args[:5] + (None,) * (
|
||||||
|
|
@ -636,6 +639,9 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
color = kwargs.get("color", color)
|
color = kwargs.get("color", color)
|
||||||
parent = kwargs.get("parent", parent)
|
parent = kwargs.get("parent", parent)
|
||||||
|
|
||||||
|
if isinstance(obj, Plane):
|
||||||
|
obj = BRepBuilderAPI_MakeFace(obj.wrapped).Face()
|
||||||
|
|
||||||
if outer_wire is not None:
|
if outer_wire is not None:
|
||||||
inner_topods_wires = (
|
inner_topods_wires = (
|
||||||
[w.wrapped for w in inner_wires] if inner_wires is not None else []
|
[w.wrapped for w in inner_wires] if inner_wires is not None else []
|
||||||
|
|
@ -665,7 +671,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
float: The total surface area, including the area of holes. Returns 0.0 if
|
float: The total surface area, including the area of holes. Returns 0.0 if
|
||||||
the face is empty.
|
the face is empty.
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
return self.without_holes().area
|
return self.without_holes().area
|
||||||
|
|
@ -725,7 +731,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
ValueError: If the face or its underlying representation is empty.
|
ValueError: If the face or its underlying representation is empty.
|
||||||
ValueError: If the face is not planar.
|
ValueError: If the face is not planar.
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Can't determine axes_of_symmetry of empty face")
|
raise ValueError("Can't determine axes_of_symmetry of empty face")
|
||||||
|
|
||||||
if not self.is_planar_face:
|
if not self.is_planar_face:
|
||||||
|
|
@ -989,7 +995,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
Returns:
|
Returns:
|
||||||
Face: extruded shape
|
Face: extruded shape
|
||||||
"""
|
"""
|
||||||
if obj.wrapped is None:
|
if not obj:
|
||||||
raise ValueError("Can't extrude empty object")
|
raise ValueError("Can't extrude empty object")
|
||||||
return Face(TopoDS.Face_s(_extrude_topods_shape(obj.wrapped, direction)))
|
return Face(TopoDS.Face_s(_extrude_topods_shape(obj.wrapped, direction)))
|
||||||
|
|
||||||
|
|
@ -1099,7 +1105,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
)
|
)
|
||||||
return single_point_curve
|
return single_point_curve
|
||||||
|
|
||||||
if shape.wrapped is None:
|
if not shape:
|
||||||
raise ValueError("input Edge cannot be empty")
|
raise ValueError("input Edge cannot be empty")
|
||||||
|
|
||||||
adaptor = BRepAdaptor_Curve(shape.wrapped)
|
adaptor = BRepAdaptor_Curve(shape.wrapped)
|
||||||
|
|
@ -1133,6 +1139,12 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
plane: Plane = Plane.XY,
|
plane: Plane = Plane.XY,
|
||||||
) -> Face:
|
) -> Face:
|
||||||
"""Create a unlimited size Face aligned with plane"""
|
"""Create a unlimited size Face aligned with plane"""
|
||||||
|
warnings.warn(
|
||||||
|
"The 'make_plane' method is deprecated and will be removed in a future version.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
|
||||||
pln_shape = BRepBuilderAPI_MakeFace(plane.wrapped).Face()
|
pln_shape = BRepBuilderAPI_MakeFace(plane.wrapped).Face()
|
||||||
return cls(pln_shape)
|
return cls(pln_shape)
|
||||||
|
|
||||||
|
|
@ -1222,7 +1234,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
raise ValueError("exterior must be a Wire or list of Edges")
|
raise ValueError("exterior must be a Wire or list of Edges")
|
||||||
|
|
||||||
for edge in outside_edges:
|
for edge in outside_edges:
|
||||||
if edge.wrapped is None:
|
if not edge:
|
||||||
raise ValueError("exterior contains empty edges")
|
raise ValueError("exterior contains empty edges")
|
||||||
surface.Add(edge.wrapped, GeomAbs_C0)
|
surface.Add(edge.wrapped, GeomAbs_C0)
|
||||||
|
|
||||||
|
|
@ -1253,7 +1265,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
if interior_wires:
|
if interior_wires:
|
||||||
makeface_object = BRepBuilderAPI_MakeFace(surface_face.wrapped)
|
makeface_object = BRepBuilderAPI_MakeFace(surface_face.wrapped)
|
||||||
for wire in interior_wires:
|
for wire in interior_wires:
|
||||||
if wire.wrapped is None:
|
if not wire:
|
||||||
raise ValueError("interior_wires contain an empty wire")
|
raise ValueError("interior_wires contain an empty wire")
|
||||||
makeface_object.Add(wire.wrapped)
|
makeface_object.Add(wire.wrapped)
|
||||||
try:
|
try:
|
||||||
|
|
@ -1447,7 +1459,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
result = result.fix()
|
result = result.fix()
|
||||||
if not result.is_valid or result.wrapped is None:
|
if not result.is_valid or not result:
|
||||||
raise RuntimeError("Non planar face is invalid")
|
raise RuntimeError("Non planar face is invalid")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
@ -2058,7 +2070,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot approximate an empty shape")
|
raise ValueError("Cannot approximate an empty shape")
|
||||||
|
|
||||||
return self.__class__.cast(BRepAlgo.ConvertFace_s(self.wrapped, tolerance))
|
return self.__class__.cast(BRepAlgo.ConvertFace_s(self.wrapped, tolerance))
|
||||||
|
|
@ -2071,7 +2083,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
Returns:
|
Returns:
|
||||||
Face: A new Face instance identical to the original but without any holes.
|
Face: A new Face instance identical to the original but without any holes.
|
||||||
"""
|
"""
|
||||||
if self.wrapped is None:
|
if self._wrapped is None:
|
||||||
raise ValueError("Cannot remove holes from an empty face")
|
raise ValueError("Cannot remove holes from an empty face")
|
||||||
|
|
||||||
if not (inner_wires := self.inner_wires()):
|
if not (inner_wires := self.inner_wires()):
|
||||||
|
|
@ -2445,7 +2457,7 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
||||||
return wrapped_wire
|
return wrapped_wire
|
||||||
|
|
||||||
|
|
||||||
class Shell(Mixin2D, Shape[TopoDS_Shell]):
|
class Shell(Mixin2D[TopoDS_Shell]):
|
||||||
"""A Shell is a fundamental component in build123d's topological data structure
|
"""A Shell is a fundamental component in build123d's topological data structure
|
||||||
representing a connected set of faces forming a closed surface in 3D space. As
|
representing a connected set of faces forming a closed surface in 3D space. As
|
||||||
part of a geometric model, it defines a watertight enclosure, commonly encountered
|
part of a geometric model, it defines a watertight enclosure, commonly encountered
|
||||||
|
|
@ -2477,7 +2489,7 @@ class Shell(Mixin2D, Shape[TopoDS_Shell]):
|
||||||
obj = obj_list[0]
|
obj = obj_list[0]
|
||||||
|
|
||||||
if isinstance(obj, Face):
|
if isinstance(obj, Face):
|
||||||
if obj.wrapped is None:
|
if not obj:
|
||||||
raise ValueError(f"Can't create a Shell from empty Face")
|
raise ValueError(f"Can't create a Shell from empty Face")
|
||||||
builder = BRep_Builder()
|
builder = BRep_Builder()
|
||||||
shell = TopoDS_Shell()
|
shell = TopoDS_Shell()
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ def to_vtk_poly_data(
|
||||||
if not HAS_VTK:
|
if not HAS_VTK:
|
||||||
warnings.warn("VTK not supported", stacklevel=2)
|
warnings.warn("VTK not supported", stacklevel=2)
|
||||||
|
|
||||||
if obj.wrapped is None:
|
if not obj:
|
||||||
raise ValueError("Cannot convert an empty shape")
|
raise ValueError("Cannot convert an empty shape")
|
||||||
|
|
||||||
vtk_shape = IVtkOCC_Shape(obj.wrapped)
|
vtk_shape = IVtkOCC_Shape(obj.wrapped)
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,8 @@ class TestFace(unittest.TestCase):
|
||||||
distance=1, distance2=2, vertices=[vertex], edge=other_edge
|
distance=1, distance2=2, vertices=[vertex], edge=other_edge
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_make_rect(self):
|
def test_plane_as_face(self):
|
||||||
test_face = Face.make_plane()
|
test_face = Face(Plane.XY)
|
||||||
self.assertAlmostEqual(test_face.normal_at(), (0, 0, 1), 5)
|
self.assertAlmostEqual(test_face.normal_at(), (0, 0, 1), 5)
|
||||||
|
|
||||||
def test_length_width(self):
|
def test_length_width(self):
|
||||||
|
|
|
||||||
|
|
@ -476,7 +476,7 @@ class TestShape(unittest.TestCase):
|
||||||
self.assertAlmostEqual(Vector(verts[0]), (1, 2, 0), 5)
|
self.assertAlmostEqual(Vector(verts[0]), (1, 2, 0), 5)
|
||||||
self.assertListEqual(edges, [])
|
self.assertListEqual(edges, [])
|
||||||
|
|
||||||
verts, edges = Vertex(1, 2, 0)._ocp_section(Face.make_plane(Plane.XY))
|
verts, edges = Vertex(1, 2, 0)._ocp_section(Face(Plane.XY))
|
||||||
self.assertAlmostEqual(Vector(verts[0]), (1, 2, 0), 5)
|
self.assertAlmostEqual(Vector(verts[0]), (1, 2, 0), 5)
|
||||||
self.assertListEqual(edges, [])
|
self.assertListEqual(edges, [])
|
||||||
|
|
||||||
|
|
@ -494,7 +494,7 @@ class TestShape(unittest.TestCase):
|
||||||
self.assertEqual(len(edges1), 1)
|
self.assertEqual(len(edges1), 1)
|
||||||
self.assertAlmostEqual(edges1[0].length, 20, 5)
|
self.assertAlmostEqual(edges1[0].length, 20, 5)
|
||||||
|
|
||||||
vertices2, edges2 = cylinder._ocp_section(Face.make_plane(pln))
|
vertices2, edges2 = cylinder._ocp_section(Face(pln))
|
||||||
self.assertEqual(len(vertices2), 1)
|
self.assertEqual(len(vertices2), 1)
|
||||||
self.assertEqual(len(edges2), 1)
|
self.assertEqual(len(edges2), 1)
|
||||||
self.assertAlmostEqual(Vector(vertices2[0]), (5, 0, 0), 5)
|
self.assertAlmostEqual(Vector(vertices2[0]), (5, 0, 0), 5)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue