mirror of
https://github.com/gumyr/build123d.git
synced 2026-01-03 15:53:56 -08:00
Tests & Edge.intersections
This commit is contained in:
parent
b5de144819
commit
fcccedacb0
5 changed files with 235 additions and 139 deletions
|
|
@ -14,20 +14,21 @@ __all__ = [
|
||||||
"FT",
|
"FT",
|
||||||
# Enums
|
# Enums
|
||||||
"Align",
|
"Align",
|
||||||
"Select",
|
|
||||||
"Kind",
|
|
||||||
"Keep",
|
|
||||||
"Mode",
|
|
||||||
"Transition",
|
|
||||||
"FontStyle",
|
|
||||||
"Until",
|
|
||||||
"SortBy",
|
|
||||||
"GeomType",
|
|
||||||
"AngularDirection",
|
"AngularDirection",
|
||||||
"PositionMode",
|
|
||||||
"FrameMethod",
|
|
||||||
"Direction",
|
|
||||||
"CenterOf",
|
"CenterOf",
|
||||||
|
"Direction",
|
||||||
|
"FontStyle",
|
||||||
|
"FrameMethod",
|
||||||
|
"GeomType",
|
||||||
|
"Keep",
|
||||||
|
"Kind",
|
||||||
|
"LengthMode",
|
||||||
|
"Mode",
|
||||||
|
"PositionMode",
|
||||||
|
"Select",
|
||||||
|
"SortBy",
|
||||||
|
"Transition",
|
||||||
|
"Until",
|
||||||
# Classes
|
# Classes
|
||||||
"Rotation",
|
"Rotation",
|
||||||
"RotationLike",
|
"RotationLike",
|
||||||
|
|
|
||||||
|
|
@ -40,91 +40,42 @@ class Align(Enum):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
class Select(Enum):
|
class AngularDirection(Enum):
|
||||||
"""Selector scope - all or last operation"""
|
"""Angular rotation direction"""
|
||||||
|
|
||||||
ALL = auto()
|
CLOCKWISE = auto()
|
||||||
LAST = auto()
|
COUNTER_CLOCKWISE = auto()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
class Kind(Enum):
|
class CenterOf(Enum):
|
||||||
"""Offset corner transition"""
|
"""Center Options"""
|
||||||
|
|
||||||
ARC = auto()
|
GEOMETRY = auto()
|
||||||
INTERSECTION = auto()
|
MASS = auto()
|
||||||
TANGENT = auto()
|
BOUNDING_BOX = auto()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
class Keep(Enum):
|
class Direction(Enum):
|
||||||
"""Split options"""
|
"""Face direction"""
|
||||||
|
|
||||||
TOP = auto()
|
ALONG_AXIS = auto()
|
||||||
BOTTOM = auto()
|
OPPOSITE = auto()
|
||||||
BOTH = auto()
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
class Mode(Enum):
|
class FrameMethod(Enum):
|
||||||
"""Combination Mode"""
|
"""Moving frame calculation method"""
|
||||||
|
|
||||||
ADD = auto()
|
FRENET = auto()
|
||||||
SUBTRACT = auto()
|
CORRECTED = auto()
|
||||||
INTERSECT = auto()
|
|
||||||
REPLACE = auto()
|
|
||||||
PRIVATE = auto()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
|
||||||
|
|
||||||
|
|
||||||
class Transition(Enum):
|
|
||||||
"""Sweep discontinuity handling option"""
|
|
||||||
|
|
||||||
RIGHT = auto()
|
|
||||||
ROUND = auto()
|
|
||||||
TRANSFORMED = auto()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
|
||||||
|
|
||||||
|
|
||||||
class FontStyle(Enum):
|
|
||||||
"""Text Font Styles"""
|
|
||||||
|
|
||||||
REGULAR = auto()
|
|
||||||
BOLD = auto()
|
|
||||||
ITALIC = auto()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
|
||||||
|
|
||||||
|
|
||||||
class Until(Enum):
|
|
||||||
"""Extrude limit"""
|
|
||||||
|
|
||||||
NEXT = auto()
|
|
||||||
LAST = auto()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
|
||||||
|
|
||||||
|
|
||||||
class SortBy(Enum):
|
|
||||||
"""Sorting criteria"""
|
|
||||||
|
|
||||||
LENGTH = auto()
|
|
||||||
RADIUS = auto()
|
|
||||||
AREA = auto()
|
|
||||||
VOLUME = auto()
|
|
||||||
DISTANCE = auto()
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
@ -154,11 +105,58 @@ class GeomType(Enum):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
class AngularDirection(Enum):
|
class Keep(Enum):
|
||||||
"""Angular rotation direction"""
|
"""Split options"""
|
||||||
|
|
||||||
CLOCKWISE = auto()
|
TOP = auto()
|
||||||
COUNTER_CLOCKWISE = auto()
|
BOTTOM = auto()
|
||||||
|
BOTH = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
|
class Kind(Enum):
|
||||||
|
"""Offset corner transition"""
|
||||||
|
|
||||||
|
ARC = auto()
|
||||||
|
INTERSECTION = auto()
|
||||||
|
TANGENT = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
|
class Mode(Enum):
|
||||||
|
"""Combination Mode"""
|
||||||
|
|
||||||
|
ADD = auto()
|
||||||
|
SUBTRACT = auto()
|
||||||
|
INTERSECT = auto()
|
||||||
|
REPLACE = auto()
|
||||||
|
PRIVATE = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
|
class FontStyle(Enum):
|
||||||
|
"""Text Font Styles"""
|
||||||
|
|
||||||
|
REGULAR = auto()
|
||||||
|
BOLD = auto()
|
||||||
|
ITALIC = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
|
class LengthMode(Enum):
|
||||||
|
"""Method of specifying length along PolarLine"""
|
||||||
|
|
||||||
|
DIAGONAL = auto()
|
||||||
|
HORIZONTAL = auto()
|
||||||
|
VERTICAL = auto()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
@ -174,32 +172,45 @@ class PositionMode(Enum):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
class FrameMethod(Enum):
|
class Select(Enum):
|
||||||
"""Moving frame calculation method"""
|
"""Selector scope - all or last operation"""
|
||||||
|
|
||||||
FRENET = auto()
|
ALL = auto()
|
||||||
CORRECTED = auto()
|
LAST = auto()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
class Direction(Enum):
|
class SortBy(Enum):
|
||||||
"""Face direction"""
|
"""Sorting criteria"""
|
||||||
|
|
||||||
ALONG_AXIS = auto()
|
LENGTH = auto()
|
||||||
OPPOSITE = auto()
|
RADIUS = auto()
|
||||||
|
AREA = auto()
|
||||||
|
VOLUME = auto()
|
||||||
|
DISTANCE = auto()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
class CenterOf(Enum):
|
class Transition(Enum):
|
||||||
"""Center Options"""
|
"""Sweep discontinuity handling option"""
|
||||||
|
|
||||||
GEOMETRY = auto()
|
RIGHT = auto()
|
||||||
MASS = auto()
|
ROUND = auto()
|
||||||
BOUNDING_BOX = auto()
|
TRANSFORMED = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
|
class Until(Enum):
|
||||||
|
"""Extrude limit"""
|
||||||
|
|
||||||
|
NEXT = auto()
|
||||||
|
LAST = auto()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import copy
|
||||||
import inspect
|
import inspect
|
||||||
from math import sin, cos, radians, sqrt, copysign
|
from math import sin, cos, radians, sqrt, copysign
|
||||||
from typing import Union, Iterable
|
from typing import Union, Iterable
|
||||||
from build123d.build_enums import Select, Mode, AngularDirection
|
from build123d.build_enums import AngularDirection, LengthMode, Mode, Select
|
||||||
from build123d.direct_api import (
|
from build123d.direct_api import (
|
||||||
Axis,
|
Axis,
|
||||||
Edge,
|
Edge,
|
||||||
|
|
@ -569,6 +569,7 @@ class PolarLine(Edge):
|
||||||
length (float): line length
|
length (float): line length
|
||||||
angle (float, optional): angle from +v X axis. Defaults to None.
|
angle (float, optional): angle from +v X axis. Defaults to None.
|
||||||
direction (VectorLike, optional): vector direction. Defaults to None.
|
direction (VectorLike, optional): vector direction. Defaults to None.
|
||||||
|
length_mode (LengthMode, optional):
|
||||||
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
|
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -583,6 +584,7 @@ class PolarLine(Edge):
|
||||||
length: float,
|
length: float,
|
||||||
angle: float = None,
|
angle: float = None,
|
||||||
direction: VectorLike = None,
|
direction: VectorLike = None,
|
||||||
|
length_mode: LengthMode = LengthMode.DIAGONAL,
|
||||||
mode: Mode = Mode.ADD,
|
mode: Mode = Mode.ADD,
|
||||||
):
|
):
|
||||||
context: BuildLine = BuildLine._get_context(self)
|
context: BuildLine = BuildLine._get_context(self)
|
||||||
|
|
@ -590,6 +592,11 @@ class PolarLine(Edge):
|
||||||
|
|
||||||
start = WorkplaneList.localize(start)
|
start = WorkplaneList.localize(start)
|
||||||
|
|
||||||
|
if length_mode == LengthMode.HORIZONTAL:
|
||||||
|
length = length / cos(radians(angle))
|
||||||
|
elif length_mode == LengthMode.VERTICAL:
|
||||||
|
length = length / sin(radians(angle))
|
||||||
|
|
||||||
if angle is not None:
|
if angle is not None:
|
||||||
x_val = cos(radians(angle)) * length
|
x_val = cos(radians(angle)) * length
|
||||||
y_val = sin(radians(angle)) * length
|
y_val = sin(radians(angle)) * length
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ from OCP.Geom import (
|
||||||
Geom_Plane,
|
Geom_Plane,
|
||||||
Geom_Surface,
|
Geom_Surface,
|
||||||
)
|
)
|
||||||
from OCP.Geom2d import Geom2d_Line
|
from OCP.Geom2d import Geom2d_Line, Geom2d_Curve
|
||||||
from OCP.GeomAbs import GeomAbs_C0, GeomAbs_Intersection, GeomAbs_JoinType
|
from OCP.GeomAbs import GeomAbs_C0, GeomAbs_Intersection, GeomAbs_JoinType
|
||||||
from OCP.GeomAPI import (
|
from OCP.GeomAPI import (
|
||||||
GeomAPI_Interpolate,
|
GeomAPI_Interpolate,
|
||||||
|
|
@ -164,6 +164,7 @@ from OCP.GeomAPI import (
|
||||||
GeomAPI_PointsToBSplineSurface,
|
GeomAPI_PointsToBSplineSurface,
|
||||||
GeomAPI_ProjectPointOnSurf,
|
GeomAPI_ProjectPointOnSurf,
|
||||||
)
|
)
|
||||||
|
from OCP.Geom2dAPI import Geom2dAPI_InterCurveCurve
|
||||||
from OCP.GeomFill import (
|
from OCP.GeomFill import (
|
||||||
GeomFill_CorrectedFrenet,
|
GeomFill_CorrectedFrenet,
|
||||||
GeomFill_Frenet,
|
GeomFill_Frenet,
|
||||||
|
|
@ -203,7 +204,7 @@ from OCP.Precision import Precision
|
||||||
from OCP.Prs3d import Prs3d_IsoAspect
|
from OCP.Prs3d import Prs3d_IsoAspect
|
||||||
from OCP.Quantity import Quantity_Color, Quantity_ColorRGBA
|
from OCP.Quantity import Quantity_Color, Quantity_ColorRGBA
|
||||||
from OCP.RWStl import RWStl
|
from OCP.RWStl import RWStl
|
||||||
from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds
|
from OCP.ShapeAnalysis import ShapeAnalysis_Edge, ShapeAnalysis_FreeBounds
|
||||||
from OCP.ShapeFix import ShapeFix_Face, ShapeFix_Shape, ShapeFix_Solid
|
from OCP.ShapeFix import ShapeFix_Face, ShapeFix_Shape, ShapeFix_Solid
|
||||||
from OCP.ShapeUpgrade import ShapeUpgrade_UnifySameDomain
|
from OCP.ShapeUpgrade import ShapeUpgrade_UnifySameDomain
|
||||||
|
|
||||||
|
|
@ -4427,36 +4428,12 @@ class Compound(Shape, Mixin3D):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
# Calculate the size of the tree labels
|
|
||||||
size_tuples = [(node.height, len(node.label)) for node in self.descendants]
|
|
||||||
size_tuples.append((self.height, len(self.label)))
|
|
||||||
size_tuples_per_level = [
|
|
||||||
list(filter(lambda ll: ll[0] == l, size_tuples))
|
|
||||||
for l in range(self.height + 1)
|
|
||||||
]
|
|
||||||
max_sizes_per_level = [
|
|
||||||
max(4, max([l[1] for l in level])) for level in size_tuples_per_level
|
|
||||||
]
|
|
||||||
level_sizes_per_level = [
|
|
||||||
l + i * 4 for i, l in enumerate(reversed(max_sizes_per_level))
|
|
||||||
]
|
|
||||||
tree_label_width = max(level_sizes_per_level) + 1
|
|
||||||
|
|
||||||
result = ""
|
|
||||||
for pre, _fill, node in RenderTree(self):
|
|
||||||
treestr = "%s%s" % (pre, node.label)
|
|
||||||
result += (
|
|
||||||
f"{treestr.ljust(tree_label_width)}{node.__class__.__name__.ljust(8)} "
|
|
||||||
f"at {id(self):#x}, Location{repr(self.location)}\n"
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
"""Return Compound info as string"""
|
||||||
if hasattr(self, "label") and hasattr(self, "children"):
|
if hasattr(self, "label") and hasattr(self, "children"):
|
||||||
result = (
|
result = (
|
||||||
f"Compound at {id(self):#x}, label({self.label}), "
|
f"Compound at {id(self):#x}, label({self.label}), "
|
||||||
f"#children({len(self.children)})"
|
+ f"#children({len(self.children)})"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = f"Compound at {id(self):#x}"
|
result = f"Compound at {id(self):#x}"
|
||||||
|
|
@ -4509,7 +4486,7 @@ class Compound(Shape, Mixin3D):
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
elif center_of == CenterOf.BOUNDING_BOX:
|
elif center_of == CenterOf.BOUNDING_BOX:
|
||||||
middle = self.center(CenterOf.BOUNDING_BOX)
|
middle = self.bounding_box().center()
|
||||||
return middle
|
return middle
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -4519,18 +4496,17 @@ class Compound(Shape, Mixin3D):
|
||||||
shapes: Iterable[Shape]:
|
shapes: Iterable[Shape]:
|
||||||
Returns:
|
Returns:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return cls(cls._make_compound((s.wrapped for s in shapes)))
|
return cls(cls._make_compound((s.wrapped for s in shapes)))
|
||||||
|
|
||||||
def remove(self, shape: Shape) -> Compound:
|
def _remove(self, shape: Shape) -> Compound:
|
||||||
"""Remove the specified shape.
|
"""Return self with the specified shape removed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
shape: Shape:
|
shape: Shape:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
comp_builder = TopoDS_Builder()
|
comp_builder = TopoDS_Builder()
|
||||||
comp_builder.Remove(self.wrapped, shape.wrapped)
|
comp_builder.Remove(self.wrapped, shape.wrapped)
|
||||||
|
return self
|
||||||
|
|
||||||
def _post_detach(self, parent: Compound):
|
def _post_detach(self, parent: Compound):
|
||||||
"""Method call after detaching from `parent`."""
|
"""Method call after detaching from `parent`."""
|
||||||
|
|
@ -4960,6 +4936,68 @@ class Edge(Shape, Mixin1D):
|
||||||
|
|
||||||
return return_value
|
return return_value
|
||||||
|
|
||||||
|
def intersections(
|
||||||
|
self, plane: Plane, edge: Edge = None, tolerance: float = TOLERANCE
|
||||||
|
) -> list[Vector]:
|
||||||
|
"""intersections
|
||||||
|
|
||||||
|
Determine the points where a 2D edge crosses itself or another 2D edge
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plane (Plane): plane containing edge(s)
|
||||||
|
edge (Edge): curve to compare with
|
||||||
|
tolerance (float, optional): defines the precision of computing the intersection points.
|
||||||
|
Defaults to TOLERANCE.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[Vector]: list of intersection points
|
||||||
|
"""
|
||||||
|
# This will be updated by Geom_Surface to the edge location but isn't otherwise used
|
||||||
|
edge_location = TopLoc_Location()
|
||||||
|
|
||||||
|
# Check if self is on the plane
|
||||||
|
if not all([plane.contains(self.position_at(i / 7)) for i in range(8)]):
|
||||||
|
raise ValueError("self must be a 2D edge on the given plane")
|
||||||
|
|
||||||
|
edge_surface: Geom_Surface = Face.make_plane(plane)._geom_adaptor()
|
||||||
|
|
||||||
|
self_parameters = [
|
||||||
|
BRep_Tool.Parameter_s(self.vertices()[i].wrapped, self.wrapped)
|
||||||
|
for i in [0, 1]
|
||||||
|
]
|
||||||
|
self_2d_curve: Geom2d_Curve = BRep_Tool.CurveOnPlane_s(
|
||||||
|
self.wrapped,
|
||||||
|
edge_surface,
|
||||||
|
edge_location,
|
||||||
|
*self_parameters,
|
||||||
|
)
|
||||||
|
if edge:
|
||||||
|
# Check if edge is on the plane
|
||||||
|
if not all([plane.contains(edge.position_at(i / 7)) for i in range(8)]):
|
||||||
|
raise ValueError("edge must be a 2D edge on the given plane")
|
||||||
|
|
||||||
|
edge_parameters = [
|
||||||
|
BRep_Tool.Parameter_s(edge.vertices()[i].wrapped, edge.wrapped)
|
||||||
|
for i in [0, 1]
|
||||||
|
]
|
||||||
|
edge_2d_curve: Geom2d_Curve = BRep_Tool.CurveOnPlane_s(
|
||||||
|
edge.wrapped,
|
||||||
|
edge_surface,
|
||||||
|
edge_location,
|
||||||
|
*edge_parameters,
|
||||||
|
)
|
||||||
|
intersector = Geom2dAPI_InterCurveCurve(
|
||||||
|
self_2d_curve, edge_2d_curve, tolerance
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
intersector = Geom2dAPI_InterCurveCurve(self_2d_curve, tolerance)
|
||||||
|
|
||||||
|
crosses = [
|
||||||
|
Vector(intersector.Point(i + 1).X(), intersector.Point(i + 1).Y())
|
||||||
|
for i in range(intersector.NbPoints())
|
||||||
|
]
|
||||||
|
return crosses
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_bezier(cls, *cntl_pnts: VectorLike, weights: list[float] = None) -> Edge:
|
def make_bezier(cls, *cntl_pnts: VectorLike, weights: list[float] = None) -> Edge:
|
||||||
"""make_bezier
|
"""make_bezier
|
||||||
|
|
@ -6132,6 +6170,7 @@ class Face(Shape):
|
||||||
|
|
||||||
return cls.cast(face)
|
return cls.cast(face)
|
||||||
|
|
||||||
|
|
||||||
class Shell(Shape):
|
class Shell(Shape):
|
||||||
"""the outer boundary of a surface"""
|
"""the outer boundary of a surface"""
|
||||||
|
|
||||||
|
|
@ -7814,7 +7853,7 @@ class Joint(ABC):
|
||||||
self.connected_to = other
|
self.connected_to = other
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def relative_to(self, other:Joint, *args, **kwargs) -> Location:
|
def relative_to(self, other: Joint, *args, **kwargs) -> Location:
|
||||||
"""Return relative location to another joint"""
|
"""Return relative location to another joint"""
|
||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
|
|
||||||
|
|
@ -7854,7 +7893,7 @@ class RigidJoint(Joint):
|
||||||
to_part.joints[label] = self
|
to_part.joints[label] = self
|
||||||
super().__init__(label, to_part)
|
super().__init__(label, to_part)
|
||||||
|
|
||||||
def relative_to(self, other : Joint, **kwargs) -> Location:
|
def relative_to(self, other: Joint, **kwargs) -> Location:
|
||||||
"""relative_to
|
"""relative_to
|
||||||
|
|
||||||
Return the relative position to move the other.
|
Return the relative position to move the other.
|
||||||
|
|
@ -7867,6 +7906,7 @@ class RigidJoint(Joint):
|
||||||
|
|
||||||
return self.relative_location * other.relative_location.inverse()
|
return self.relative_location * other.relative_location.inverse()
|
||||||
|
|
||||||
|
|
||||||
class RevoluteJoint(Joint):
|
class RevoluteJoint(Joint):
|
||||||
"""RevoluteJoint
|
"""RevoluteJoint
|
||||||
|
|
||||||
|
|
@ -7948,7 +7988,12 @@ class RevoluteJoint(Joint):
|
||||||
z_dir=(0, 0, 1),
|
z_dir=(0, 0, 1),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return self.relative_axis.to_location() * rotation * other.relative_location.inverse()
|
return (
|
||||||
|
self.relative_axis.to_location()
|
||||||
|
* rotation
|
||||||
|
* other.relative_location.inverse()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LinearJoint(Joint):
|
class LinearJoint(Joint):
|
||||||
"""LinearJoint
|
"""LinearJoint
|
||||||
|
|
@ -8187,7 +8232,10 @@ class CylindricalJoint(Joint):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return joint_relative_position * joint_rotation * other.relative_location.inverse()
|
return (
|
||||||
|
joint_relative_position * joint_rotation * other.relative_location.inverse()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BallJoint(Joint):
|
class BallJoint(Joint):
|
||||||
"""BallJoint
|
"""BallJoint
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,12 @@ class TestAssembly(unittest.TestCase):
|
||||||
self.assertEqual(first, topos[i])
|
self.assertEqual(first, topos[i])
|
||||||
self.assertEqual(third, locs[i])
|
self.assertEqual(third, locs[i])
|
||||||
|
|
||||||
|
def test_remove_child(self):
|
||||||
|
assembly = TestAssembly.create_test_assembly()
|
||||||
|
self.assertEqual(len(assembly.children), 2)
|
||||||
|
assembly.children = list(assembly.children)[1:]
|
||||||
|
self.assertEqual(len(assembly.children), 1)
|
||||||
|
|
||||||
|
|
||||||
class TestAxis(unittest.TestCase):
|
class TestAxis(unittest.TestCase):
|
||||||
"""Test the Axis class"""
|
"""Test the Axis class"""
|
||||||
|
|
@ -526,12 +532,35 @@ class TestCompound(unittest.TestCase):
|
||||||
self.assertAlmostEqual(fuzzy.volume, 2, 5)
|
self.assertAlmostEqual(fuzzy.volume, 2, 5)
|
||||||
|
|
||||||
def test_remove(self):
|
def test_remove(self):
|
||||||
# Doesn't work
|
box1 = Solid.make_box(1, 1, 1)
|
||||||
# box1 = Solid.make_box(1, 1, 1)
|
box2 = Solid.make_box(1, 1, 1, Plane((2, 0, 0)))
|
||||||
# box2 = Solid.make_box(1, 1, 1, Plane((2, 0, 0)))
|
combined = Compound.make_compound([box1, box2])
|
||||||
# combined = Compound.make_compound([box1, box2])
|
self.assertTrue(len(combined._remove(box2).solids()), 1)
|
||||||
# self.assertTrue(len(combined.remove(box2).solids()), 1)
|
|
||||||
pass
|
def test_repr(self):
|
||||||
|
simple = Compound.make_compound([Solid.make_box(1, 1, 1)])
|
||||||
|
simple_str = repr(simple).split("0x")[0] + repr(simple).split(", ")[1]
|
||||||
|
self.assertEqual(simple_str, "Compound at label()")
|
||||||
|
|
||||||
|
assembly = Compound.make_compound([Solid.make_box(1, 1, 1)])
|
||||||
|
assembly.children = [Solid.make_box(1, 1, 1)]
|
||||||
|
assembly.label = "test"
|
||||||
|
assembly_str = repr(assembly).split("0x")[0] + repr(assembly).split(", l")[1]
|
||||||
|
self.assertEqual(assembly_str, "Compound at abel(test), #children(1)")
|
||||||
|
|
||||||
|
def test_center(self):
|
||||||
|
test_compound = Compound.make_compound(
|
||||||
|
[
|
||||||
|
Solid.make_box(2, 2, 2).locate(Location((-1, -1, -1))),
|
||||||
|
Solid.make_box(1, 1, 1).locate(Location((8.5, -0.5, -0.5))),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.assertTupleAlmostEquals(test_compound.center(CenterOf.MASS), (1, 0, 0), 5)
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
test_compound.center(CenterOf.BOUNDING_BOX), (4.25, 0, 0), 5
|
||||||
|
)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
test_compound.center(CenterOf.GEOMETRY)
|
||||||
|
|
||||||
|
|
||||||
class TestEdge(unittest.TestCase):
|
class TestEdge(unittest.TestCase):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue