Skip to content

Commit

Permalink
Merge pull request #812 from gumyr/refactor_topology
Browse files Browse the repository at this point in the history
Refactored topology.py ready to split into multiple modules
  • Loading branch information
gumyr authored Jan 5, 2025
2 parents c0728e0 + 93513b1 commit 6621e3b
Show file tree
Hide file tree
Showing 17 changed files with 5,193 additions and 4,194 deletions.
12 changes: 0 additions & 12 deletions docs/direct_api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,6 @@ Import/Export
*************
Methods and functions specific to exporting and importing build123d objects are defined below.

.. py:module:: topology
:noindex:

.. automethod:: Shape.export_brep
:noindex:
.. automethod:: Shape.export_stl
:noindex:
.. automethod:: Shape.export_step
:noindex:
.. automethod:: Shape.export_stl
:noindex:

.. py:module:: importers
:noindex:

Expand Down
12 changes: 11 additions & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Global options:

[mypy]
no_implicit_optional=False

[mypy-OCP.*]
ignore_missing_imports = True

[mypy-anytree.*]
ignore_missing_imports = True
Expand All @@ -12,3 +14,11 @@ ignore_missing_imports = True
[mypy-vtkmodules.*]
ignore_missing_imports = True

[mypy-build123d.topology.jupyter_tools.*]
ignore_missing_imports = True

[mypy-IPython.lib.pretty.*]
ignore_missing_imports = True

[mypy-svgpathtools.*]
ignore_missing_imports = True
19 changes: 13 additions & 6 deletions src/build123d/build_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,23 +429,30 @@ def _add_to_context(
if mode == Mode.ADD:
if self._obj is None:
if len(typed[self._shape]) == 1:
self._obj = typed[self._shape][0]
combined = typed[self._shape][0]
else:
self._obj = (
combined = (
typed[self._shape].pop().fuse(*typed[self._shape])
)
else:
self._obj = self._obj.fuse(*typed[self._shape])
combined = self._obj.fuse(*typed[self._shape])
elif mode == Mode.SUBTRACT:
if self._obj is None:
raise RuntimeError("Nothing to subtract from")
self._obj = self._obj.cut(*typed[self._shape])
combined = self._obj.cut(*typed[self._shape])
elif mode == Mode.INTERSECT:
if self._obj is None:
raise RuntimeError("Nothing to intersect with")
self._obj = self._obj.intersect(*typed[self._shape])
combined = self._obj.intersect(*typed[self._shape])
elif mode == Mode.REPLACE:
self._obj = Compound(list(typed[self._shape]))
combined = self._sub_class(list(typed[self._shape]))

# If the boolean operation created a list, convert back
self._obj = (
self._sub_class(combined)
if isinstance(combined, list)
else combined
)

if self._obj is not None and clean:
self._obj = self._obj.clean()
Expand Down
25 changes: 15 additions & 10 deletions src/build123d/drafting.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
from build123d.objects_sketch import BaseSketchObject, Polygon, Text
from build123d.operations_generic import fillet, mirror, sweep
from build123d.operations_sketch import make_face, trace
from build123d.topology import Compound, Edge, Sketch, Vertex, Wire
from build123d.topology import Compound, Curve, Edge, Sketch, Vertex, Wire


class ArrowHead(BaseSketchObject):
Expand Down Expand Up @@ -439,23 +439,25 @@ def __init__(
overage = shaft_length + draft.pad_around_text + label_length / 2
label_u_values = [0.5, -overage / path_length, 1 + overage / path_length]

# d_lines = Sketch(children=arrows[0])
d_lines = {}
# for arrow_pair in arrow_shapes:
for u_value in label_u_values:
d_line = Sketch()
for add_arrow, arrow_shape in zip(arrows, arrow_shapes):
if add_arrow:
d_line += arrow_shape
select_arrow_shapes = [
arrow_shape
for add_arrow, arrow_shape in zip(arrows, arrow_shapes)
if add_arrow
]
d_line = Sketch(select_arrow_shapes)
flip_label = path_obj.tangent_at(u_value).get_angle(Vector(1, 0, 0)) >= 180
loc = Draft._sketch_location(path_obj, u_value, flip_label)
placed_label = label_shape.located(loc)
self_intersection = d_line.intersect(placed_label).area
self_intersection = Sketch.intersect(d_line, placed_label).area
d_line += placed_label
bbox_size = d_line.bounding_box().size

# Minimize size while avoiding intersections
common_area = 0.0 if sketch is None else d_line.intersect(sketch).area
common_area = (
0.0 if sketch is None else Sketch.intersect(d_line, sketch).area
)
common_area += self_intersection
score = (d_line.area - 10 * common_area) / bbox_size.X
d_lines[d_line] = score
Expand Down Expand Up @@ -702,7 +704,10 @@ def __init__(
)
bf_pnt3 = box_frame_curve.edges().sort_by(Axis.X)[0] @ (1 / 3)
bf_pnt4 = box_frame_curve.edges().sort_by(Axis.X)[0] @ (2 / 3)
box_frame_curve += Edge.make_line(bf_pnt3, (bf_pnt2.X, bf_pnt3.Y))
box_frame_curve = Curve() + [
box_frame_curve,
Edge.make_line(bf_pnt3, (bf_pnt2.X, bf_pnt3.Y)),
]
box_frame_curve += Edge.make_line(bf_pnt4, (bf_pnt2.X, bf_pnt4.Y))
bf_pnt5 = box_frame_curve.edges().sort_by(Axis.Y)[-1] @ (1 / 3)
bf_pnt6 = box_frame_curve.edges().sort_by(Axis.Y)[-1] @ (2 / 3)
Expand Down
39 changes: 19 additions & 20 deletions src/build123d/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,17 @@
from OCP.HLRBRep import HLRBRep_Algo, HLRBRep_HLRToShape # type: ignore
from OCP.TopAbs import TopAbs_Orientation, TopAbs_ShapeEnum # type: ignore
from OCP.TopExp import TopExp_Explorer # type: ignore
from OCP.TopoDS import TopoDS
from typing_extensions import Self

from build123d.build_enums import Unit
from build123d.geometry import TOLERANCE, Color
from build123d.build_enums import Unit, GeomType
from build123d.geometry import TOLERANCE, Color, Vector, VectorLike
from build123d.topology import (
BoundBox,
Compound,
Edge,
Wire,
GeomType,
Shape,
Vector,
VectorLike,
)
from build123d.build_common import UNITS_PER_METER

Expand Down Expand Up @@ -682,7 +680,7 @@ def _convert_line(self, edge: Edge, attribs: dict):

def _convert_circle(self, edge: Edge, attribs: dict):
"""Converts a Circle object into a DXF circle entity."""
curve = edge._geom_adaptor()
curve = edge.geom_adaptor()
circle = curve.Circle()
center = self._convert_point(circle.Location())
radius = circle.Radius()
Expand Down Expand Up @@ -710,7 +708,7 @@ def _convert_circle(self, edge: Edge, attribs: dict):

def _convert_ellipse(self, edge: Edge, attribs: dict):
"""Converts an Ellipse object into a DXF ellipse entity."""
geom = edge._geom_adaptor()
geom = edge.geom_adaptor()
ellipse = geom.Ellipse()
minor_radius = ellipse.MinorRadius()
major_radius = ellipse.MajorRadius()
Expand Down Expand Up @@ -743,7 +741,7 @@ def _convert_bspline(self, edge: Edge, attribs):

# This pulls the underlying Geom_BSplineCurve out of the Edge.
# The adaptor also supplies a parameter range for the curve.
adaptor = edge._geom_adaptor()
adaptor = edge.geom_adaptor()
curve = adaptor.Curve().Curve()
u1 = adaptor.FirstParameter()
u2 = adaptor.LastParameter()
Expand Down Expand Up @@ -1063,7 +1061,7 @@ def _add_single_shape(self, shape: Shape, layer: _Layer, reverse_wires: bool):
)
while explorer.More():
topo_wire = explorer.Current()
loose_wires.append(Wire(topo_wire))
loose_wires.append(Wire(TopoDS.Wire_s(topo_wire)))
explorer.Next()
# print(f"{len(loose_wires)} loose wires")
for wire in loose_wires:
Expand Down Expand Up @@ -1100,12 +1098,13 @@ def _add_single_shape(self, shape: Shape, layer: _Layer, reverse_wires: bool):

@staticmethod
def _wire_edges(wire: Wire, reverse: bool) -> List[Edge]:
edges = []
explorer = BRepTools_WireExplorer(wire.wrapped)
while explorer.More():
topo_edge = explorer.Current()
edges.append(Edge(topo_edge))
explorer.Next()
# edges = []
# explorer = BRepTools_WireExplorer(wire.wrapped)
# while explorer.More():
# topo_edge = explorer.Current()
# edges.append(Edge(topo_edge))
# explorer.Next()
edges = wire.edges()
if reverse:
edges.reverse()
return edges
Expand Down Expand Up @@ -1157,7 +1156,7 @@ def _path_point(self, pt: Union[gp_Pnt, Vector]) -> complex:
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

def _line_segment(self, edge: Edge, reverse: bool) -> PT.Line:
curve = edge._geom_adaptor()
curve = edge.geom_adaptor()
fp = curve.FirstParameter()
lp = curve.LastParameter()
(u0, u1) = (lp, fp) if reverse else (fp, lp)
Expand Down Expand Up @@ -1187,7 +1186,7 @@ def _line_element(self, edge: Edge) -> ET.Element:

def _circle_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
# pylint: disable=too-many-locals
curve = edge._geom_adaptor()
curve = edge.geom_adaptor()
circle = curve.Circle()
radius = circle.Radius()
x_axis = circle.XAxis().Direction()
Expand Down Expand Up @@ -1215,7 +1214,7 @@ def _circle_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
def _circle_element(self, edge: Edge) -> ET.Element:
"""Converts a Circle object into an SVG circle element."""
if edge.is_closed:
curve = edge._geom_adaptor()
curve = edge.geom_adaptor()
circle = curve.Circle()
radius = circle.Radius()
center = circle.Location()
Expand All @@ -1233,7 +1232,7 @@ def _circle_element(self, edge: Edge) -> ET.Element:

def _ellipse_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:
# pylint: disable=too-many-locals
curve = edge._geom_adaptor()
curve = edge.geom_adaptor()
ellipse = curve.Ellipse()
minor_radius = ellipse.MinorRadius()
major_radius = ellipse.MajorRadius()
Expand Down Expand Up @@ -1276,7 +1275,7 @@ def _bspline_segments(self, edge: Edge, reverse: bool) -> list[PathSegment]:

# This pulls the underlying Geom_BSplineCurve out of the Edge.
# The adaptor also supplies a parameter range for the curve.
adaptor = edge._geom_adaptor()
adaptor = edge.geom_adaptor()
spline = adaptor.Curve().Curve()
u1 = adaptor.FirstParameter()
u2 = adaptor.LastParameter()
Expand Down
21 changes: 14 additions & 7 deletions src/build123d/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ def __eq__(self, other: object) -> bool:

def __hash__(self) -> int:
"""Hash of Vector"""
return hash(self.X) + hash(self.Y) + hash(self.Z)
return hash((round(self.X, 6), round(self.Y, 6), round(self.Z, 6)))

def __copy__(self) -> Vector:
"""Return copy of self"""
Expand Down Expand Up @@ -542,7 +542,7 @@ def intersect(self, *args, **kwargs):

#:TypeVar("VectorLike"): Tuple of float or Vector defining a position in space
VectorLike = Union[
Vector, tuple[float, float], tuple[float, float, float], Iterable[float]
Vector, tuple[float, float], tuple[float, float, float], Sequence[float]
]


Expand Down Expand Up @@ -861,15 +861,21 @@ class BoundBox:
"""A BoundingBox for a Shape"""

def __init__(self, bounding_box: Bnd_Box) -> None:
self.wrapped: Bnd_Box = bounding_box
x_min, y_min, z_min, x_max, y_max, z_max = bounding_box.Get()
if bounding_box.IsVoid():
self.wrapped = None
x_min, y_min, z_min, x_max, y_max, z_max = (0,) * 6
else:
self.wrapped: Bnd_Box = bounding_box
x_min, y_min, z_min, x_max, y_max, z_max = bounding_box.Get()
self.min = Vector(x_min, y_min, z_min) #: location of minimum corner
self.max = Vector(x_max, y_max, z_max) #: location of maximum corner
self.size = Vector(x_max - x_min, y_max - y_min, z_max - z_min) #: overall size

@property
def diagonal(self) -> float:
"""body diagonal length (i.e. object maximum size)"""
if self.wrapped is None:
return 0.0
return self.wrapped.SquareExtent() ** 0.5

def __repr__(self):
Expand Down Expand Up @@ -914,13 +920,14 @@ def add(

tmp = Bnd_Box()
tmp.SetGap(tol)
tmp.Add(self.wrapped)
if self.wrapped is not None:
tmp.Add(self.wrapped)

if isinstance(obj, tuple):
tmp.Update(*obj)
elif isinstance(obj, Vector):
tmp.Update(*obj.to_tuple())
elif isinstance(obj, BoundBox):
elif isinstance(obj, BoundBox) and obj.wrapped is not None:
tmp.Add(obj.wrapped)

return BoundBox(tmp)
Expand Down Expand Up @@ -963,7 +970,7 @@ def find_outside_box_2d(bb1: BoundBox, bb2: BoundBox) -> Optional[BoundBox]:
return result

@classmethod
def _from_topo_ds(
def from_topo_ds(
cls,
shape: TopoDS_Shape,
tolerance: float = None,
Expand Down
2 changes: 1 addition & 1 deletion src/build123d/importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def import_brep(file_name: Union[PathLike, str, bytes]) -> Shape:
if shape.IsNull():
raise ValueError(f"Could not import {file_name}")

return Shape.cast(shape)
return Compound.cast(shape)


def import_step(filename: Union[PathLike, str, bytes]) -> Compound:
Expand Down
2 changes: 1 addition & 1 deletion src/build123d/objects_sketch.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
Vector,
VectorLike,
to_align_offset,
TOLERANCE,
)
from build123d.topology import (
Compound,
Expand All @@ -52,7 +53,6 @@
Sketch,
Wire,
tuplify,
TOLERANCE,
topo_explore_common_vertex,
)

Expand Down
13 changes: 11 additions & 2 deletions src/build123d/operations_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,7 @@ def scale(

def split(
objects: Union[SplitType, Iterable[SplitType]] = None,
bisect_by: Union[Plane, Face] = Plane.XZ,
bisect_by: Union[Plane, Face, Shell] = Plane.XZ,
keep: Keep = Keep.TOP,
mode: Mode = Mode.REPLACE,
):
Expand Down Expand Up @@ -937,7 +937,16 @@ def split(

new_objects = []
for obj in object_list:
new_objects.append(obj.split(bisect_by, keep))
bottom = None
if keep == Keep.BOTH:
top, bottom = obj.split(bisect_by, keep)
else:
top = obj.split(bisect_by, keep)
for subpart in [top, bottom]:
if isinstance(subpart, Iterable):
new_objects.extend(subpart)
elif subpart is not None:
new_objects.append(subpart)

if context is not None:
context._add_to_context(*new_objects, mode=mode)
Expand Down
Loading

0 comments on commit 6621e3b

Please sign in to comment.