mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Compare commits
2 commits
6605b676a3
...
726a72a20b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
726a72a20b | ||
|
|
3871345dcd |
7 changed files with 109 additions and 89 deletions
|
|
@ -963,9 +963,9 @@ def split(
|
|||
for obj in object_list:
|
||||
bottom = None
|
||||
if keep == Keep.BOTH:
|
||||
top, bottom = obj.split(bisect_by, keep) # type: ignore[arg-type]
|
||||
top, bottom = obj.split(bisect_by, keep)
|
||||
else:
|
||||
top = obj.split(bisect_by, keep) # type: ignore[arg-type]
|
||||
top = obj.split(bisect_by, keep)
|
||||
for subpart in [top, bottom]:
|
||||
if isinstance(subpart, Iterable):
|
||||
new_objects.extend(subpart)
|
||||
|
|
|
|||
|
|
@ -600,7 +600,7 @@ class Compound(Mixin3D[TopoDS_Compound]):
|
|||
"""Return the Compound"""
|
||||
shape_list = self.compounds()
|
||||
entity_count = len(shape_list)
|
||||
if entity_count != 1:
|
||||
if entity_count > 1:
|
||||
warnings.warn(
|
||||
f"Found {entity_count} compounds, returning first",
|
||||
stacklevel=2,
|
||||
|
|
|
|||
|
|
@ -556,7 +556,7 @@ class Mixin1D(Shape[TOPODS]):
|
|||
|
||||
A curvature comb is a set of short line segments (“teeth”) erected
|
||||
perpendicular to the curve that visualize the signed curvature κ(u).
|
||||
Tooth length is proportional to \|κ\| and the direction encodes the sign
|
||||
Tooth length is proportional to |κ| and the direction encodes the sign
|
||||
(left normal for κ>0, right normal for κ<0). This is useful for inspecting
|
||||
fairness and continuity (C0/C1/C2) of edges and wires.
|
||||
|
||||
|
|
@ -684,30 +684,30 @@ class Mixin1D(Shape[TOPODS]):
|
|||
|
||||
return derivative
|
||||
|
||||
def edge(self) -> Edge | None:
|
||||
"""Return the Edge"""
|
||||
return Shape.get_single_shape(self, "Edge")
|
||||
# def edge(self) -> Edge | None:
|
||||
# """Return the Edge"""
|
||||
# return Shape.get_single_shape(self, "Edge")
|
||||
|
||||
def edges(self) -> ShapeList[Edge]:
|
||||
"""edges - all the edges in this Shape"""
|
||||
if isinstance(self, Wire) and self.wrapped is not None:
|
||||
# The WireExplorer is a tool to explore the edges of a wire in a connection order.
|
||||
explorer = BRepTools_WireExplorer(self.wrapped)
|
||||
# def edges(self) -> ShapeList[Edge]:
|
||||
# """edges - all the edges in this Shape"""
|
||||
# if isinstance(self, Wire) and self.wrapped is not None:
|
||||
# # The WireExplorer is a tool to explore the edges of a wire in a connection order.
|
||||
# explorer = BRepTools_WireExplorer(self.wrapped)
|
||||
|
||||
edge_list: ShapeList[Edge] = ShapeList()
|
||||
while explorer.More():
|
||||
next_edge = Edge(explorer.Current())
|
||||
next_edge.topo_parent = (
|
||||
self if self.topo_parent is None else self.topo_parent
|
||||
)
|
||||
edge_list.append(next_edge)
|
||||
explorer.Next()
|
||||
return edge_list
|
||||
# edge_list: ShapeList[Edge] = ShapeList()
|
||||
# while explorer.More():
|
||||
# next_edge = Edge(explorer.Current())
|
||||
# next_edge.topo_parent = (
|
||||
# self if self.topo_parent is None else self.topo_parent
|
||||
# )
|
||||
# edge_list.append(next_edge)
|
||||
# explorer.Next()
|
||||
# return edge_list
|
||||
|
||||
edge_list = Shape.get_shape_list(self, "Edge")
|
||||
return edge_list.filter_by(
|
||||
lambda e: BRep_Tool.Degenerated_s(e.wrapped), reverse=True
|
||||
)
|
||||
# edge_list = Shape.get_shape_list(self, "Edge")
|
||||
# return edge_list.filter_by(
|
||||
# lambda e: BRep_Tool.Degenerated_s(e.wrapped), reverse=True
|
||||
# )
|
||||
|
||||
def end_point(self) -> Vector:
|
||||
"""The end point of this edge.
|
||||
|
|
@ -1431,21 +1431,21 @@ class Mixin1D(Shape[TOPODS]):
|
|||
"""
|
||||
return self.derivative_at(position, 1, position_mode).normalized()
|
||||
|
||||
def vertex(self) -> Vertex | None:
|
||||
"""Return the Vertex"""
|
||||
return Shape.get_single_shape(self, "Vertex")
|
||||
# def vertex(self) -> Vertex | None:
|
||||
# """Return the Vertex"""
|
||||
# return Shape.get_single_shape(self, "Vertex")
|
||||
|
||||
def vertices(self) -> ShapeList[Vertex]:
|
||||
"""vertices - all the vertices in this Shape"""
|
||||
return Shape.get_shape_list(self, "Vertex")
|
||||
# def vertices(self) -> ShapeList[Vertex]:
|
||||
# """vertices - all the vertices in this Shape"""
|
||||
# return Shape.get_shape_list(self, "Vertex")
|
||||
|
||||
def wire(self) -> Wire | None:
|
||||
"""Return the Wire"""
|
||||
return Shape.get_single_shape(self, "Wire")
|
||||
# def wire(self) -> Wire | None:
|
||||
# """Return the Wire"""
|
||||
# return Shape.get_single_shape(self, "Wire")
|
||||
|
||||
def wires(self) -> ShapeList[Wire]:
|
||||
"""wires - all the wires in this Shape"""
|
||||
return Shape.get_shape_list(self, "Wire")
|
||||
# def wires(self) -> ShapeList[Wire]:
|
||||
# """wires - all the wires in this Shape"""
|
||||
# return Shape.get_shape_list(self, "Wire")
|
||||
|
||||
|
||||
class Edge(Mixin1D[TopoDS_Edge]):
|
||||
|
|
@ -3561,6 +3561,21 @@ class Wire(Mixin1D[TopoDS_Wire]):
|
|||
|
||||
return return_value
|
||||
|
||||
def edges(self) -> ShapeList[Edge]:
|
||||
"""edges - all the edges in this Shape"""
|
||||
# The WireExplorer is a tool to explore the edges of a wire in a connection order.
|
||||
explorer = BRepTools_WireExplorer(self.wrapped)
|
||||
|
||||
edge_list: ShapeList[Edge] = ShapeList()
|
||||
while explorer.More():
|
||||
next_edge = Edge(explorer.Current())
|
||||
next_edge.topo_parent = (
|
||||
self if self.topo_parent is None else self.topo_parent
|
||||
)
|
||||
edge_list.append(next_edge)
|
||||
explorer.Next()
|
||||
return edge_list
|
||||
|
||||
def fillet_2d(self, radius: float, vertices: Iterable[Vertex]) -> Wire:
|
||||
"""fillet_2d
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ from OCP.BRepGProp import BRepGProp, BRepGProp_Face
|
|||
from OCP.BRepIntCurveSurface import BRepIntCurveSurface_Inter
|
||||
from OCP.BRepMesh import BRepMesh_IncrementalMesh
|
||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakeHalfSpace
|
||||
from OCP.BRepTools import BRepTools
|
||||
from OCP.BRepTools import BRepTools, BRepTools_WireExplorer
|
||||
from OCP.gce import gce_MakeLin
|
||||
from OCP.Geom import Geom_Line
|
||||
from OCP.GeomAPI import GeomAPI_ProjectPointOnSurf
|
||||
|
|
@ -839,7 +839,9 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
|||
with a warning if count != 1."""
|
||||
shape_list = Shape.get_shape_list(shape, entity_type)
|
||||
entity_count = len(shape_list)
|
||||
if entity_count != 1:
|
||||
if entity_count == 0:
|
||||
return None
|
||||
elif entity_count > 1:
|
||||
warnings.warn(
|
||||
f"Found {entity_count} {entity_type.lower()}s, returning first",
|
||||
stacklevel=3,
|
||||
|
|
@ -1185,13 +1187,14 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
|||
|
||||
def edge(self) -> Edge | None:
|
||||
"""Return the Edge"""
|
||||
return None
|
||||
|
||||
# Note all sub-classes have vertices and vertex methods
|
||||
return Shape.get_single_shape(self, "Edge")
|
||||
|
||||
def edges(self) -> ShapeList[Edge]:
|
||||
"""edges - all the edges in this Shape - subclasses may override"""
|
||||
return ShapeList()
|
||||
edge_list = Shape.get_shape_list(self, "Edge")
|
||||
return edge_list.filter_by(
|
||||
lambda e: BRep_Tool.Degenerated_s(e.wrapped), reverse=True
|
||||
)
|
||||
|
||||
def entities(self, topo_type: Shapes) -> list[TopoDS_Shape]:
|
||||
"""Return all of the TopoDS sub entities of the given type"""
|
||||
|
|
@ -1201,11 +1204,11 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
|||
|
||||
def face(self) -> Face | None:
|
||||
"""Return the Face"""
|
||||
return None
|
||||
return Shape.get_single_shape(self, "Face")
|
||||
|
||||
def faces(self) -> ShapeList[Face]:
|
||||
"""faces - all the faces in this Shape"""
|
||||
return ShapeList()
|
||||
return Shape.get_shape_list(self, "Face")
|
||||
|
||||
def faces_intersected_by_axis(
|
||||
self,
|
||||
|
|
@ -1724,11 +1727,11 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
|||
|
||||
def shell(self) -> Shell | None:
|
||||
"""Return the Shell"""
|
||||
return None
|
||||
return Shape.get_single_shape(self, "Shell")
|
||||
|
||||
def shells(self) -> ShapeList[Shell]:
|
||||
"""shells - all the shells in this Shape"""
|
||||
return ShapeList()
|
||||
return Shape.get_shape_list(self, "Shell")
|
||||
|
||||
def show_topology(
|
||||
self,
|
||||
|
|
@ -1782,11 +1785,11 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
|||
|
||||
def solid(self) -> Solid | None:
|
||||
"""Return the Solid"""
|
||||
return None
|
||||
return Shape.get_single_shape(self, "Solid")
|
||||
|
||||
def solids(self) -> ShapeList[Solid]:
|
||||
"""solids - all the solids in this Shape"""
|
||||
return ShapeList()
|
||||
return Shape.get_shape_list(self, "Solid")
|
||||
|
||||
@overload
|
||||
def split(
|
||||
|
|
@ -1805,6 +1808,12 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
|||
]:
|
||||
"""split and keep inside and outside"""
|
||||
|
||||
@overload
|
||||
def split(
|
||||
self, tool: TrimmingTool, keep: Literal[Keep.INSIDE, Keep.OUTSIDE]
|
||||
) -> None:
|
||||
"""invalid split"""
|
||||
|
||||
@overload
|
||||
def split(self, tool: TrimmingTool) -> Self | list[Self] | None:
|
||||
"""split and keep inside (default)"""
|
||||
|
|
@ -1834,6 +1843,9 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
|||
if self._wrapped is None or not tool:
|
||||
raise ValueError("Can't split an empty edge/wire/tool")
|
||||
|
||||
if keep in [Keep.INSIDE, Keep.OUTSIDE]:
|
||||
raise ValueError(f"{keep} is invalid")
|
||||
|
||||
shape_list = TopTools_ListOfShape()
|
||||
shape_list.Append(self.wrapped)
|
||||
|
||||
|
|
@ -1924,7 +1936,6 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
|||
return top
|
||||
if keep == Keep.BOTTOM:
|
||||
return bottom
|
||||
return None
|
||||
|
||||
@overload
|
||||
def split_by_perimeter(
|
||||
|
|
@ -2227,11 +2238,11 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
|||
|
||||
def wire(self) -> Wire | None:
|
||||
"""Return the Wire"""
|
||||
return None
|
||||
return Shape.get_single_shape(self, "Wire")
|
||||
|
||||
def wires(self) -> ShapeList[Wire]:
|
||||
"""wires - all the wires in this Shape"""
|
||||
return ShapeList()
|
||||
return Shape.get_shape_list(self, "Wire")
|
||||
|
||||
def _apply_transform(self, transformation: gp_Trsf) -> Self:
|
||||
"""Private Apply Transform
|
||||
|
|
@ -2387,6 +2398,14 @@ class Shape(NodeMixin, Generic[TOPODS]):
|
|||
|
||||
return shape_to_html(self)._repr_html_()
|
||||
|
||||
def vertex(self) -> Vertex | None:
|
||||
"""Return the Vertex"""
|
||||
return Shape.get_single_shape(self, "Vertex")
|
||||
|
||||
def vertices(self) -> ShapeList[Vertex]:
|
||||
"""vertices - all the vertices in this Shape"""
|
||||
return Shape.get_shape_list(self, "Vertex")
|
||||
|
||||
|
||||
class Comparable(ABC):
|
||||
"""Abstract base class that requires comparison methods"""
|
||||
|
|
|
|||
|
|
@ -144,16 +144,6 @@ class Mixin3D(Shape[TOPODS]):
|
|||
project_to_viewport = Mixin1D.project_to_viewport
|
||||
find_intersection_points = Mixin2D.find_intersection_points
|
||||
|
||||
vertices = Mixin1D.vertices
|
||||
vertex = Mixin1D.vertex
|
||||
edges = Mixin1D.edges
|
||||
edge = Mixin1D.edge
|
||||
wires = Mixin1D.wires
|
||||
wire = Mixin1D.wire
|
||||
faces = Mixin2D.faces
|
||||
face = Mixin2D.face
|
||||
shells = Mixin2D.shells
|
||||
shell = Mixin2D.shell
|
||||
# ---- Properties ----
|
||||
|
||||
@property
|
||||
|
|
@ -725,13 +715,13 @@ class Mixin3D(Shape[TOPODS]):
|
|||
|
||||
return offset_solid
|
||||
|
||||
def solid(self) -> Solid | None:
|
||||
"""Return the Solid"""
|
||||
return Shape.get_single_shape(self, "Solid")
|
||||
# def solid(self) -> Solid | None:
|
||||
# """Return the Solid"""
|
||||
# return Shape.get_single_shape(self, "Solid")
|
||||
|
||||
def solids(self) -> ShapeList[Solid]:
|
||||
"""solids - all the solids in this Shape"""
|
||||
return Shape.get_shape_list(self, "Solid")
|
||||
# def solids(self) -> ShapeList[Solid]:
|
||||
# """solids - all the solids in this Shape"""
|
||||
# return Shape.get_shape_list(self, "Solid")
|
||||
|
||||
|
||||
class Solid(Mixin3D[TopoDS_Solid]):
|
||||
|
|
|
|||
|
|
@ -174,11 +174,6 @@ class Mixin2D(ABC, Shape[TOPODS]):
|
|||
|
||||
project_to_viewport = Mixin1D.project_to_viewport
|
||||
|
||||
vertices = Mixin1D.vertices
|
||||
vertex = Mixin1D.vertex
|
||||
edges = Mixin1D.edges
|
||||
edge = Mixin1D.edge
|
||||
wires = Mixin1D.wires
|
||||
# ---- Properties ----
|
||||
|
||||
@property
|
||||
|
|
@ -226,13 +221,13 @@ class Mixin2D(ABC, Shape[TOPODS]):
|
|||
|
||||
return new_surface
|
||||
|
||||
def face(self) -> Face | None:
|
||||
"""Return the Face"""
|
||||
return Shape.get_single_shape(self, "Face")
|
||||
# def face(self) -> Face | None:
|
||||
# """Return the Face"""
|
||||
# return Shape.get_single_shape(self, "Face")
|
||||
|
||||
def faces(self) -> ShapeList[Face]:
|
||||
"""faces - all the faces in this Shape"""
|
||||
return Shape.get_shape_list(self, "Face")
|
||||
# def faces(self) -> ShapeList[Face]:
|
||||
# """faces - all the faces in this Shape"""
|
||||
# return Shape.get_shape_list(self, "Face")
|
||||
|
||||
def find_intersection_points(
|
||||
self, other: Axis, tolerance: float = TOLERANCE
|
||||
|
|
@ -412,13 +407,13 @@ class Mixin2D(ABC, Shape[TOPODS]):
|
|||
"""Return a copy of self moved along the normal by amount"""
|
||||
return copy.deepcopy(self).moved(Location(self.normal_at() * amount))
|
||||
|
||||
def shell(self) -> Shell | None:
|
||||
"""Return the Shell"""
|
||||
return Shape.get_single_shape(self, "Shell")
|
||||
# def shell(self) -> Shell | None:
|
||||
# """Return the Shell"""
|
||||
# return Shape.get_single_shape(self, "Shell")
|
||||
|
||||
def shells(self) -> ShapeList[Shell]:
|
||||
"""shells - all the shells in this Shape"""
|
||||
return Shape.get_shape_list(self, "Shell")
|
||||
# def shells(self) -> ShapeList[Shell]:
|
||||
# """shells - all the shells in this Shape"""
|
||||
# return Shape.get_shape_list(self, "Shell")
|
||||
|
||||
def _wrap_edge(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -172,10 +172,11 @@ class TestShape(unittest.TestCase):
|
|||
self.assertEqual(len(top), 2)
|
||||
self.assertAlmostEqual(top[0].length, 3, 5)
|
||||
|
||||
def test_split_return_none(self):
|
||||
shape = Box(1, 1, 1) - Pos((0, 0, -0.25)) * Box(1, 0.5, 0.5)
|
||||
split_shape = shape.split(Plane.XY, keep=Keep.INSIDE)
|
||||
self.assertIsNone(split_shape)
|
||||
def test_split_invalid_keep(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Box(1, 1, 1).split(Plane.XY, keep=Keep.INSIDE)
|
||||
with self.assertRaises(ValueError):
|
||||
Box(1, 1, 1).split(Plane.XY, keep=Keep.OUTSIDE)
|
||||
|
||||
def test_split_by_perimeter(self):
|
||||
# Test 0 - extract a spherical cap
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue