Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions src/build123d/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,10 @@
Edge,
Wire,
Shape,
downcast,
)
from build123d.build_common import UNITS_PER_METER
from build123d.topology.shape_core import _topods_entities

PathSegment: TypeAlias = PT.Line | PT.Arc | PT.QuadraticBezier | PT.CubicBezier
"""A type alias for the various path segment types in the svgpathtools library."""
Expand Down Expand Up @@ -1150,13 +1152,15 @@ def _add_single_shape(self, shape: Shape, layer: _Layer, reverse_wires: bool):
def _wire_edges(wire: Wire, reverse: bool) -> list[Edge]:
# Note that BRepTools_WireExplorer can return edges in a different order
# than the standard edges() method.
edges = []
explorer = BRepTools_WireExplorer(wire.wrapped)
while explorer.More():
topo_edge = explorer.Current()
edges.append(Edge(topo_edge))
explorer.Next()
# edges = wire.edges()

# edges = []
# explorer = BRepTools_WireExplorer(wire.wrapped)
# while explorer.More():
# topo_edge = explorer.Current()
# edges.append(Edge(topo_edge))
# explorer.Next()

edges = [Edge(downcast(i)) for i in _topods_entities(wire.wrapped, "Edge")]
if reverse:
edges.reverse()
return edges
Expand Down
19 changes: 11 additions & 8 deletions src/build123d/exporters3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
from build123d.build_enums import PrecisionMode, Unit
from build123d.geometry import Location
from build123d.topology import Compound, Curve, Part, Shape, Sketch
from build123d.topology.shape_core import _topods_entities


def _create_xde(to_export: Shape, unit: Unit = Unit.MM) -> TDocStd_Document:
Expand Down Expand Up @@ -122,20 +123,22 @@ def _create_xde(to_export: Shape, unit: Unit = Unit.MM) -> TDocStd_Document:
# object not just the Compound wrapper
sub_node_labels = []
if isinstance(node, Compound) and not node.children:
sub_nodes = []

if isinstance(node, Part):
explorer = TopExp_Explorer(node.wrapped, ta.TopAbs_SOLID)
sub_nodes = _topods_entities(node.wrapped, "Solid")
elif isinstance(node, Sketch):
explorer = TopExp_Explorer(node.wrapped, ta.TopAbs_FACE)
sub_nodes = _topods_entities(node.wrapped, "Face")
elif isinstance(node, Curve):
explorer = TopExp_Explorer(node.wrapped, ta.TopAbs_EDGE)
sub_nodes = _topods_entities(node.wrapped, "Edge")
else:
warnings.warn("Unknown Compound type, color not set", stacklevel=2)
explorer = TopExp_Explorer() # don't know what to look for
# TODO: extend Shape._topods_entities to be capable of enumerating all types

while explorer.More():
sub_nodes.append(explorer.Current())
explorer.Next()
sub_nodes = []
explorer = TopExp_Explorer() # don't know what to look for
while explorer.More():
sub_nodes.append(explorer.Current())
explorer.Next()

sub_node_labels = [
shape_tool.FindShape(sub_node, findInstance=False)
Expand Down
31 changes: 15 additions & 16 deletions src/build123d/mesher.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,14 @@
from OCP.gp import gp_Pnt
from OCP.GProp import GProp_GProps
from OCP.TopAbs import TopAbs_ShapeEnum
from OCP.TopExp import TopExp_Explorer
from OCP.TopLoc import TopLoc_Location
from OCP.TopoDS import TopoDS_Compound
from lib3mf import Lib3MF

from build123d.build_enums import MeshType, Unit
from build123d.geometry import TOLERANCE, Color
from build123d.topology import Compound, Shape, Shell, Solid, downcast
from build123d.topology.shape_core import _topods_entities


class Mesher:
Expand Down Expand Up @@ -312,42 +312,43 @@ def _create_3mf_mesh(
# Round off the vertices to avoid vertices within tolerance being
# considered as different vertices
digits = -int(round(math.log(TOLERANCE, 10), 1))

# Create vertex to index mapping directly
vertex_to_idx = {}
next_idx = 0
vert_table = {}

# First pass - create mapping
for i, (x, y, z) in enumerate(ocp_mesh_vertices):
key = (round(x, digits), round(y, digits), round(z, digits))
if key not in vertex_to_idx:
vertex_to_idx[key] = next_idx
next_idx += 1
vert_table[i] = vertex_to_idx[key]

# Create vertices array in one shot
vertices_3mf = [
Lib3MF.Position((ctypes.c_float * 3)(*v))
for v in vertex_to_idx.keys()
Lib3MF.Position((ctypes.c_float * 3)(*v)) for v in vertex_to_idx.keys()
]

# Pre-allocate triangles array and process in bulk
c_uint3 = ctypes.c_uint * 3
triangles_3mf = []

# Process triangles in bulk
for tri in triangles:
# Map indices directly without list comprehension
a, b, c = tri[0], tri[1], tri[2]
mapped_a = vert_table[a]
mapped_b = vert_table[b]
mapped_c = vert_table[c]

# Quick degenerate check without set creation
if mapped_a != mapped_b and mapped_b != mapped_c and mapped_c != mapped_a:
triangles_3mf.append(Lib3MF.Triangle(c_uint3(mapped_a, mapped_b, mapped_c)))

triangles_3mf.append(
Lib3MF.Triangle(c_uint3(mapped_a, mapped_b, mapped_c))
)

return (vertices_3mf, triangles_3mf)

def _add_color(self, b3d_shape: Shape, mesh_3mf: Lib3MF.MeshObject):
Expand Down Expand Up @@ -477,11 +478,9 @@ def _get_shape(self, mesh_3mf: Lib3MF.MeshObject) -> Shape:
occ_sewed_shape = downcast(shell_builder.SewedShape())

if isinstance(occ_sewed_shape, TopoDS_Compound):
occ_shells = []
explorer = TopExp_Explorer(occ_sewed_shape, TopAbs_ShapeEnum.TopAbs_SHELL)
while explorer.More():
occ_shells.append(downcast(explorer.Current()))
explorer.Next()
occ_shells = [
downcast(i) for i in _topods_entities(occ_sewed_shape, "Shell")
]
else:
occ_shells = [occ_sewed_shape]

Expand Down
18 changes: 5 additions & 13 deletions src/build123d/topology/one_d.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@
shapetype,
topods_dim,
unwrap_topods_compound,
_topods_entities,
_topods_entities_nodict_downcast,
)
from .utils import (
_extrude_topods_shape,
Expand Down Expand Up @@ -936,19 +938,8 @@ def project_to_viewport(
tuple[ShapeList[Edge],ShapeList[Edge]]: visible & hidden Edges
"""

def extract_edges(compound):
edges = [] # List to store the extracted edges

# Create a TopExp_Explorer to traverse the sub-shapes of the compound
explorer = TopExp_Explorer(compound, TopAbs_ShapeEnum.TopAbs_EDGE)

# Loop through the sub-shapes and extract edges
while explorer.More():
edge = downcast(explorer.Current())
edges.append(edge)
explorer.Next()

return edges
def extract_edges(compound) -> list[TopoDS_Edge]:
return [downcast(i) for i in _topods_entities(compound, "Edge")]

# Setup the projector
hidden_line_removal = HLRBRep_Algo()
Expand Down Expand Up @@ -3257,6 +3248,7 @@ def topo_explore_connected_edges(
common_topods_vertex: Vertex | None = topo_explore_common_vertex(
given_topods_edge, topods_edge
)

if common_topods_vertex is not None:
# shared_vertex is the TopoDS_Vertex common to edge1 and edge2
u1 = BRep_Tool.Parameter_s(common_topods_vertex.wrapped, given_topods_edge)
Expand Down
90 changes: 64 additions & 26 deletions src/build123d/topology/shape_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@
from OCP.TopAbs import TopAbs_Orientation, TopAbs_ShapeEnum
from OCP.TopExp import TopExp, TopExp_Explorer
from OCP.TopLoc import TopLoc_Location
from OCP.TopTools import (
TopTools_IndexedDataMapOfShapeListOfShape,
TopTools_IndexedMapOfShape,
TopTools_ListOfShape,
TopTools_SequenceOfShape,
)
from OCP.TopoDS import (
TopoDS,
TopoDS_Compound,
Expand All @@ -126,11 +132,6 @@
TopoDS_Vertex,
TopoDS_Wire,
)
from OCP.TopTools import (
TopTools_IndexedDataMapOfShapeListOfShape,
TopTools_ListOfShape,
TopTools_SequenceOfShape,
)
from typing_extensions import Self

from build123d.build_enums import CenterOf, GeomType, Keep, SortBy, Transition
Expand Down Expand Up @@ -208,7 +209,7 @@ class Shape(NodeMixin, Generic[TOPODS]):

inverse_shape_LUT = {v: k for k, v in shape_LUT.items()}

downcast_LUT = {
downcast_LUT: [ta.TopAbs_SHAPE, TopoDS_Shape] = {
ta.TopAbs_VERTEX: TopoDS.Vertex_s,
ta.TopAbs_EDGE: TopoDS.Edge_s,
ta.TopAbs_WIRE: TopoDS.Wire_s,
Expand Down Expand Up @@ -797,7 +798,12 @@ def get_shape_list(
if shape.wrapped is None:
return ShapeList()
shape_list = ShapeList(
[shape.__class__.cast(i) for i in shape.entities(entity_type)]
[
shape.__class__.cast(i)
for i in _topods_entities_nodict_downcast(
shape.wrapped, Shape.inverse_shape_LUT[entity_type]
)
]
)
for item in shape_list:
item.topo_parent = shape if shape.topo_parent is None else shape.topo_parent
Expand Down Expand Up @@ -2195,17 +2201,14 @@ def _ocp_section(
# Get the resulting shapes from the intersection
intersection_shape: TopoDS_Shape = section.Shape()

vertices: list[Vertex] = []
# Iterate through the intersection shape to find intersection points/edges
explorer = TopExp_Explorer(intersection_shape, TopAbs_ShapeEnum.TopAbs_VERTEX)
while explorer.More():
vertices.append(self.__class__.cast(downcast(explorer.Current())))
explorer.Next()
edges: ShapeList[Edge] = ShapeList()
explorer = TopExp_Explorer(intersection_shape, TopAbs_ShapeEnum.TopAbs_EDGE)
while explorer.More():
edges.append(self.__class__.cast(downcast(explorer.Current())))
explorer.Next()
vertices = [
self.__class__.cast(downcast(i))
for i in _topods_entities(intersection_shape, "Vertex")
]
edges = [
self.__class__.cast(downcast(i))
for i in _topods_entities(intersection_shape, "Edge")
]

return (ShapeList(set(vertices)), edges)

Expand Down Expand Up @@ -2970,18 +2973,53 @@ def _sew_topods_faces(faces: Iterable[TopoDS_Face]) -> TopoDS_Shape:
return downcast(shell_builder.SewedShape())


def _topods_entities_nodict(shape: TopoDS_Shape, topo_type: ta) -> list[TopoDS_Shape]:
"""Return the TopoDS_Shapes of topo_type from this TopoDS_Shape"""
shape_set = TopTools_IndexedMapOfShape()
TopExp.MapShapes_s(shape, topo_type, shape_set)
return [shape_set.FindKey(i) for i in range(1, shape_set.Size() + 1)]


def _topods_entities_nodict_downcast(
shape: TopoDS_Shape, topo_type: ta
) -> ShapeList[TopoDS_Shape]:
"""Return the TopoDS_Shapes of topo_type from this TopoDS_Shape"""
shape_set = TopTools_IndexedMapOfShape()
TopExp.MapShapes_s(shape, topo_type, shape_set)
return [downcast(shape_set.FindKey(i)) for i in range(1, shape_set.Size() + 1)]


def _topods_entities(shape: TopoDS_Shape, topo_type: Shapes) -> list[TopoDS_Shape]:
"""Return the TopoDS_Shapes of topo_type from this TopoDS_Shape"""
out = {} # using dict to prevent duplicates
shape_set = TopTools_IndexedMapOfShape()
TopExp.MapShapes_s(shape, Shape.inverse_shape_LUT[topo_type], shape_set)
return [shape_set.FindKey(i) for i in range(1, shape_set.Size() + 1)]


def _topods_entities_downcast(
shape: TopoDS_Shape, topo_type: Shapes
) -> list[TopoDS_Shape]:
"""Return the specialized type TopoDS_Shapes of topo_type from this TopoDS_Shape"""
shape_set = TopTools_IndexedMapOfShape()
TopExp.MapShapes_s(shape, Shape.inverse_shape_LUT[topo_type], shape_set)
return [downcast(shape_set.FindKey(i)) for i in range(1, shape_set.Size() + 1)]


explorer = TopExp_Explorer(shape, Shape.inverse_shape_LUT[topo_type])
def _topods_entities_downcast_generator(
shape: TopoDS_Shape, topo_type: Shapes
) -> list[Shape]:
"""Return the TopoDS_Shapes of topo_type from this TopoDS_Shape"""
shape_set = TopTools_IndexedMapOfShape()
TopExp.MapShapes_s(shape, Shape.inverse_shape_LUT[topo_type], shape_set)
yield from (downcast(shape_set.FindKey(i)) for i in range(1, shape_set.Size() + 1))

while explorer.More():
item = explorer.Current()
out[hash(item)] = item # needed to avoid pseudo-duplicate entities
explorer.Next()
# for i in range(1, shape_set.Size() + 1):
# yield downcast(shape_set.FindKey(i))

return list(out.values())
# return [
# Shape.__class__.cast(downcast(shape_set.FindKey(i)))
# for i in range(1, shape_set.Size() + 1)
# ]


def _topods_face_normal_at(face: TopoDS_Face, surface_point: gp_Pnt) -> Vector:
Expand Down Expand Up @@ -3009,7 +3047,7 @@ def downcast(obj: TopoDS_Shape) -> TopoDS_Shape:

"""

f_downcast: Any = Shape.downcast_LUT[shapetype(obj)]
f_downcast: TopoDS_Shape = Shape.downcast_LUT[shapetype(obj)]
return_value = f_downcast(obj)

return return_value
Expand Down
29 changes: 15 additions & 14 deletions src/build123d/topology/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
from OCP.BRepPrimAPI import BRepPrimAPI_MakePrism
from OCP.ShapeFix import ShapeFix_Face, ShapeFix_Shape
from OCP.TopAbs import TopAbs_ShapeEnum
from OCP.TopExp import TopExp_Explorer
from OCP.TopTools import TopTools_ListOfShape
from OCP.TopoDS import (
TopoDS,
Expand All @@ -85,7 +84,14 @@
)
from build123d.geometry import TOLERANCE, BoundBox, Vector, VectorLike

from .shape_core import Shape, ShapeList, downcast, shapetype, unwrap_topods_compound
from .shape_core import (
Shape,
ShapeList,
downcast,
shapetype,
unwrap_topods_compound,
_topods_entities,
)


if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -125,11 +131,7 @@ def _extrude_topods_shape(obj: TopoDS_Shape, direction: VectorLike) -> TopoDS_Sh
extrusion = downcast(prism_builder.Shape())
shape_type = extrusion.ShapeType()
if shape_type == TopAbs_ShapeEnum.TopAbs_COMPSOLID:
solids = []
explorer = TopExp_Explorer(extrusion, TopAbs_ShapeEnum.TopAbs_SOLID)
while explorer.More():
solids.append(downcast(explorer.Current()))
explorer.Next()
solids = [downcast(i) for i in _topods_entities(extrusion, "Solid")]
extrusion = _make_topods_compound_from_shapes(solids)
return extrusion

Expand Down Expand Up @@ -390,13 +392,12 @@ def new_edges(*objects: Shape, combined: Shape) -> ShapeList[Edge]:
operation.SetRunParallel(True)
operation.Build()

edges = []
explorer = TopExp_Explorer(operation.Shape(), TopAbs_ShapeEnum.TopAbs_EDGE)
while explorer.More():
found_edge = combined.__class__.cast(downcast(explorer.Current()))
found_edge.topo_parent = combined
edges.append(found_edge)
explorer.Next()
edges = [
combined.__class__.cast(downcast(i))
for i in _topods_entities(operation.Shape(), "Edge")
]
for edge in edges:
edge.topo_parent = combined

return ShapeList(edges)

Expand Down
Loading
Loading