mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-15 15:20:37 -08:00
Covering Face.axes_of_symmetry corner cases
This commit is contained in:
parent
b64ab34407
commit
f22f54af5f
2 changed files with 245 additions and 6 deletions
|
|
@ -57,7 +57,7 @@ from __future__ import annotations
|
|||
|
||||
import copy
|
||||
import warnings
|
||||
from typing import Any, Tuple, Union, overload, TYPE_CHECKING
|
||||
from typing import Any, overload, TYPE_CHECKING
|
||||
|
||||
from collections.abc import Iterable, Sequence
|
||||
|
||||
|
|
@ -93,6 +93,7 @@ from OCP.TopTools import TopTools_IndexedDataMapOfShapeListOfShape
|
|||
from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape, TopoDS_Shell, TopoDS_Solid
|
||||
from OCP.gce import gce_MakeLin
|
||||
from OCP.gp import gp_Pnt, gp_Vec
|
||||
|
||||
from build123d.build_enums import CenterOf, GeomType, Keep, SortBy, Transition
|
||||
from build123d.geometry import (
|
||||
TOLERANCE,
|
||||
|
|
@ -406,7 +407,6 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
|||
ValueError: If the face or its underlying representation is empty.
|
||||
ValueError: If the face is not planar.
|
||||
"""
|
||||
|
||||
if self.wrapped is None:
|
||||
raise ValueError("Can't determine axes_of_symmetry of empty face")
|
||||
|
||||
|
|
@ -452,12 +452,42 @@ class Face(Mixin2D, Shape[TopoDS_Face]):
|
|||
x_dir=cross_dir,
|
||||
z_dir=cross_dir.cross(normal),
|
||||
)
|
||||
# Split by plane
|
||||
top, bottom = self.split(split_plane, keep=Keep.BOTH)
|
||||
top_flipped = top.mirror(split_plane)
|
||||
|
||||
if type(top) != type(bottom): # exit early if not same
|
||||
continue
|
||||
|
||||
if top is None or bottom is None: # Impossible to actually happen?
|
||||
continue
|
||||
|
||||
top_list = ShapeList(top if isinstance(top, list) else [top])
|
||||
bottom_list = ShapeList(bottom if isinstance(top, list) else [bottom])
|
||||
|
||||
if len(top_list) != len(bottom_list): # exit early unequal length
|
||||
continue
|
||||
|
||||
bottom_list = bottom_list.sort_by(Axis(cog, cross_dir))
|
||||
top_flipped_list = ShapeList(
|
||||
f.mirror(split_plane) for f in top_list
|
||||
).sort_by(Axis(cog, cross_dir))
|
||||
|
||||
bottom_area = sum(f.area for f in bottom_list)
|
||||
intersect_area = 0.0
|
||||
for flipped_face, bottom_face in zip(top_flipped_list, bottom_list):
|
||||
intersection = flipped_face.intersect(bottom_face)
|
||||
if intersection is None or isinstance(intersection, list):
|
||||
intersect_area = -1.0
|
||||
break
|
||||
else:
|
||||
assert isinstance(intersection, Face)
|
||||
intersect_area += intersection.area
|
||||
|
||||
if intersect_area == -1.0:
|
||||
continue
|
||||
|
||||
# Are the top/bottom the same?
|
||||
if abs(bottom.intersect(top_flipped).area - bottom.area) < TOLERANCE:
|
||||
# If this axis isn't in the set already add it
|
||||
if abs(intersect_area - bottom_area) < TOLERANCE:
|
||||
if not symmetry_dirs:
|
||||
symmetry_dirs.add(cross_dir)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ from build123d.build_sketch import BuildSketch
|
|||
from build123d.exporters3d import export_stl
|
||||
from build123d.geometry import Axis, Location, Plane, Pos, Vector
|
||||
from build123d.importers import import_stl
|
||||
from build123d.objects_curve import Polyline
|
||||
from build123d.objects_curve import Line, Polyline, Spline, ThreePointArc
|
||||
from build123d.objects_part import Box, Cylinder
|
||||
from build123d.objects_sketch import (
|
||||
Circle,
|
||||
|
|
@ -547,6 +547,215 @@ class TestFace(unittest.TestCase):
|
|||
self.assertTrue(all(a == t) for a, t in zip(axes_dirs, target_dirs))
|
||||
self.assertTrue(all(a.position == cog) for a in axes)
|
||||
|
||||
# Fast abort code paths
|
||||
s1 = Spline(
|
||||
(0.0293923441471, 1.9478225275438),
|
||||
(0.0293923441471, 1.2810839877038),
|
||||
(0, -0.0521774724562),
|
||||
(0.0293923441471, -1.3158620329962),
|
||||
(0.0293923441471, -1.9478180575162),
|
||||
)
|
||||
l1 = Line(s1 @ 1, s1 @ 0)
|
||||
self.assertEqual(len(Face(Wire([s1, l1])).axes_of_symmetry), 0)
|
||||
|
||||
with BuildSketch() as skt:
|
||||
with BuildLine():
|
||||
Line(
|
||||
(-13.186467340991, 2.3737403364651),
|
||||
(-5.1864673409911, 2.3737403364651),
|
||||
)
|
||||
Line(
|
||||
(-13.186467340991, 2.3737403364651),
|
||||
(-13.186467340991, -2.4506956262169),
|
||||
)
|
||||
ThreePointArc(
|
||||
(-13.186467340991, -2.4506956262169),
|
||||
(-13.479360559805, -3.1578024074034),
|
||||
(-14.186467340991, -3.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(-17.186467340991, -3.4506956262169),
|
||||
(-14.186467340991, -3.4506956262169),
|
||||
)
|
||||
ThreePointArc(
|
||||
(-17.186467340991, -3.4506956262169),
|
||||
(-17.893574122178, -3.1578024074034),
|
||||
(-18.186467340991, -2.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(-18.186467340991, 7.6644400497781),
|
||||
(-18.186467340991, -2.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(-51.186467340991, 7.6644400497781),
|
||||
(-18.186467340991, 7.6644400497781),
|
||||
)
|
||||
Line(
|
||||
(-51.186467340991, 7.6644400497781),
|
||||
(-51.186467340991, -5.5182296356389),
|
||||
)
|
||||
Line(
|
||||
(-51.186467340991, -5.5182296356389),
|
||||
(-33.186467340991, -5.5182296356389),
|
||||
)
|
||||
Line(
|
||||
(-33.186467340991, -5.5182296356389),
|
||||
(-33.186467340991, -5.3055423052429),
|
||||
)
|
||||
Line(
|
||||
(-33.186467340991, -5.3055423052429),
|
||||
(53.813532659009, -5.3055423052429),
|
||||
)
|
||||
Line(
|
||||
(53.813532659009, -5.3055423052429),
|
||||
(53.813532659009, -5.7806956262169),
|
||||
)
|
||||
Line(
|
||||
(66.813532659009, -5.7806956262169),
|
||||
(53.813532659009, -5.7806956262169),
|
||||
)
|
||||
Line(
|
||||
(66.813532659009, -2.7217530775369),
|
||||
(66.813532659009, -5.7806956262169),
|
||||
)
|
||||
Line(
|
||||
(54.813532659009, -2.7217530775369),
|
||||
(66.813532659009, -2.7217530775369),
|
||||
)
|
||||
Line(
|
||||
(54.813532659009, 7.6644400497781),
|
||||
(54.813532659009, -2.7217530775369),
|
||||
)
|
||||
Line(
|
||||
(38.813532659009, 7.6644400497781),
|
||||
(54.813532659009, 7.6644400497781),
|
||||
)
|
||||
Line(
|
||||
(38.813532659009, 7.6644400497781),
|
||||
(38.813532659009, -2.4506956262169),
|
||||
)
|
||||
ThreePointArc(
|
||||
(38.813532659009, -2.4506956262169),
|
||||
(38.520639440195, -3.1578024074034),
|
||||
(37.813532659009, -3.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(37.813532659009, -3.4506956262169),
|
||||
(34.813532659009, -3.4506956262169),
|
||||
)
|
||||
ThreePointArc(
|
||||
(34.813532659009, -3.4506956262169),
|
||||
(34.106425877822, -3.1578024074034),
|
||||
(33.813532659009, -2.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(33.813532659009, 2.3737403364651),
|
||||
(33.813532659009, -2.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(25.813532659009, 2.3737403364651),
|
||||
(33.813532659009, 2.3737403364651),
|
||||
)
|
||||
Line(
|
||||
(25.813532659009, 2.3737403364651),
|
||||
(25.813532659009, -2.4506956262169),
|
||||
)
|
||||
ThreePointArc(
|
||||
(25.813532659009, -2.4506956262169),
|
||||
(25.520639440195, -3.1578024074034),
|
||||
(24.813532659009, -3.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(24.813532659009, -3.4506956262169),
|
||||
(21.813532659009, -3.4506956262169),
|
||||
)
|
||||
ThreePointArc(
|
||||
(21.813532659009, -3.4506956262169),
|
||||
(21.106425877822, -3.1578024074034),
|
||||
(20.813532659009, -2.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(20.813532659009, 2.3737403364651),
|
||||
(20.813532659009, -2.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(12.813532659009, 2.3737403364651),
|
||||
(20.813532659009, 2.3737403364651),
|
||||
)
|
||||
Line(
|
||||
(12.813532659009, 2.3737403364651),
|
||||
(12.813532659009, -2.4506956262169),
|
||||
)
|
||||
ThreePointArc(
|
||||
(12.813532659009, -2.4506956262169),
|
||||
(12.520639440195, -3.1578024074034),
|
||||
(11.813532659009, -3.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(8.8135326590089, -3.4506956262169),
|
||||
(11.813532659009, -3.4506956262169),
|
||||
)
|
||||
ThreePointArc(
|
||||
(8.8135326590089, -3.4506956262169),
|
||||
(8.1064258778223, -3.1578024074034),
|
||||
(7.8135326590089, -2.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(7.8135326590089, 2.3737403364651),
|
||||
(7.8135326590089, -2.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(-0.1864673409911, 2.3737403364651),
|
||||
(7.8135326590089, 2.3737403364651),
|
||||
)
|
||||
Line(
|
||||
(-0.1864673409911, 2.3737403364651),
|
||||
(-0.1864673409911, -2.4506956262169),
|
||||
)
|
||||
ThreePointArc(
|
||||
(-0.1864673409911, -2.4506956262169),
|
||||
(-0.4793605598046, -3.1578024074034),
|
||||
(-1.1864673409911, -3.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(-4.1864673409911, -3.4506956262169),
|
||||
(-1.1864673409911, -3.4506956262169),
|
||||
)
|
||||
ThreePointArc(
|
||||
(-4.1864673409911, -3.4506956262169),
|
||||
(-4.8935741221777, -3.1578024074034),
|
||||
(-5.1864673409911, -2.4506956262169),
|
||||
)
|
||||
Line(
|
||||
(-5.1864673409911, 2.3737403364651),
|
||||
(-5.1864673409911, -2.4506956262169),
|
||||
)
|
||||
make_face()
|
||||
self.assertEqual(len(skt.face().axes_of_symmetry), 0)
|
||||
|
||||
|
||||
class TestAxesOfSymmetrySplitNone(unittest.TestCase):
|
||||
def test_split_returns_none(self):
|
||||
# Create a rectangle face for testing.
|
||||
rect = Rectangle(10, 5).face()
|
||||
|
||||
# Monkey-patch the split method to simulate the degenerate case:
|
||||
# Force split to return (None, rect) for any splitting plane.
|
||||
original_split = Face.split # Save the original split method.
|
||||
Face.split = lambda self, plane, keep: (None, None)
|
||||
|
||||
# Call axes_of_symmetry. With our patch, every candidate axis is skipped,
|
||||
# so we expect no symmetry axes to be found.
|
||||
axes = rect.axes_of_symmetry
|
||||
|
||||
# Verify that the result is an empty list.
|
||||
self.assertEqual(
|
||||
axes, [], "Expected no symmetry axes when split returns None for one half."
|
||||
)
|
||||
|
||||
# Restore the original split method (cleanup).
|
||||
Face.split = original_split
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue