235 lines
8.5 KiB
Python
235 lines
8.5 KiB
Python
from __future__ import annotations
|
|
|
|
import sys
|
|
import types
|
|
import unittest
|
|
from contextlib import contextmanager
|
|
|
|
from cadpy.assembly import AssemblyHelper, MateTarget, label_shape, label_text, target
|
|
from cadpy.step_export import _collect_assembly_mates
|
|
|
|
|
|
class FakeLocation:
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
def __mul__(self, other):
|
|
return FakeLocation(("mul", self.value, other.value))
|
|
|
|
|
|
class FakePart:
|
|
def __init__(self):
|
|
self.joints = {}
|
|
self.label = None
|
|
|
|
|
|
class FakeJoint:
|
|
def __init__(self, *, label, to_part, joint_location=None, **options):
|
|
self.label = label
|
|
self.to_part = to_part
|
|
self.location = joint_location
|
|
self.options = options
|
|
self.connections = []
|
|
to_part.joints[label] = self
|
|
|
|
def connect_to(self, other, **options):
|
|
self.connections.append((other, options))
|
|
|
|
|
|
class FakeCompound:
|
|
def __init__(self, *, label, children):
|
|
self.label = label
|
|
self.children = tuple(children)
|
|
|
|
|
|
@contextmanager
|
|
def fake_build123d():
|
|
module = types.SimpleNamespace(
|
|
BallJoint=FakeJoint,
|
|
Compound=FakeCompound,
|
|
CylindricalJoint=FakeJoint,
|
|
LinearJoint=FakeJoint,
|
|
Location=FakeLocation,
|
|
RevoluteJoint=FakeJoint,
|
|
RigidJoint=FakeJoint,
|
|
)
|
|
original = sys.modules.get("build123d")
|
|
sys.modules["build123d"] = module
|
|
try:
|
|
yield module
|
|
finally:
|
|
if original is None:
|
|
sys.modules.pop("build123d", None)
|
|
else:
|
|
sys.modules["build123d"] = original
|
|
|
|
|
|
class AssemblyHelperTests(unittest.TestCase):
|
|
def test_label_text_normalizes_tokens(self) -> None:
|
|
self.assertEqual(
|
|
"base_plate:left_side",
|
|
label_text("base plate", "left:side"),
|
|
)
|
|
|
|
def test_label_shape_sets_native_label_and_color(self) -> None:
|
|
shape = types.SimpleNamespace()
|
|
color = object()
|
|
|
|
returned = label_shape(shape, "m3 standoff", "front left", color=color)
|
|
|
|
self.assertIs(returned, shape)
|
|
self.assertEqual("m3_standoff:front_left", shape.label)
|
|
self.assertIs(color, shape.color)
|
|
|
|
def test_helper_connects_fixed_joint_to_moving_joint(self) -> None:
|
|
with fake_build123d():
|
|
assembly = AssemblyHelper("enclosure")
|
|
base = assembly.add(FakePart(), "base")
|
|
lid = assembly.add(FakePart(), "lid")
|
|
base_frame = assembly.rigid_frame(base, "lid_seat", FakeLocation("base_frame"))
|
|
lid_frame = assembly.rigid_frame(lid, "underside", FakeLocation("lid_frame"))
|
|
|
|
relation = assembly.face_to_face(base_frame, lid_frame)
|
|
|
|
fixed_joint = base.joints["lid_seat"]
|
|
moving_joint = lid.joints["underside"]
|
|
self.assertEqual("face_to_face", relation.relation)
|
|
self.assertEqual("lid_seat", relation.fixed)
|
|
self.assertEqual("underside", relation.moving)
|
|
self.assertEqual({"part": "base", "frame": "lid_seat"}, relation.fixed_endpoint)
|
|
self.assertEqual({"part": "lid", "frame": "underside"}, relation.moving_endpoint)
|
|
self.assertEqual([(moving_joint, {})], fixed_joint.connections)
|
|
|
|
def test_build_records_mate_endpoint_payloads(self) -> None:
|
|
with fake_build123d():
|
|
assembly = AssemblyHelper("enclosure")
|
|
base = assembly.add(FakePart(), "base")
|
|
lid = assembly.add(FakePart(), "lid")
|
|
base_frame = assembly.rigid_frame(base, "lid_seat", FakeLocation("base_frame"))
|
|
lid_frame = assembly.rigid_frame(lid, "underside", FakeLocation("lid_frame"))
|
|
|
|
assembly.face_to_face(base_frame, lid_frame, label="lid_mate")
|
|
compound = assembly.build()
|
|
|
|
self.assertEqual(
|
|
[
|
|
{
|
|
"id": "m1",
|
|
"label": "m1",
|
|
"sourceLabel": "lid_mate",
|
|
"type": "face_to_face",
|
|
"relation": "face_to_face",
|
|
"fixed": "lid_seat",
|
|
"moving": "underside",
|
|
"parameters": {},
|
|
"fixedEndpoint": {"part": "base", "frame": "lid_seat"},
|
|
"movingEndpoint": {"part": "lid", "frame": "underside"},
|
|
}
|
|
],
|
|
compound.assembly_mates,
|
|
)
|
|
|
|
def test_export_collection_renumbers_mates_globally(self) -> None:
|
|
with fake_build123d():
|
|
first = AssemblyHelper("first")
|
|
first_base = first.add(FakePart(), "first_base")
|
|
first_lid = first.add(FakePart(), "first_lid")
|
|
first_base_frame = first.rigid_frame(first_base, "seat", FakeLocation("first_base"))
|
|
first_lid_frame = first.rigid_frame(first_lid, "underside", FakeLocation("first_lid"))
|
|
first.face_to_face(first_base_frame, first_lid_frame, label="first_mate")
|
|
first_compound = first.build()
|
|
|
|
second = AssemblyHelper("second")
|
|
second_base = second.add(FakePart(), "second_base")
|
|
second_lid = second.add(FakePart(), "second_lid")
|
|
second_base_frame = second.rigid_frame(second_base, "seat", FakeLocation("second_base"))
|
|
second_lid_frame = second.rigid_frame(second_lid, "underside", FakeLocation("second_lid"))
|
|
second.face_to_face(second_base_frame, second_lid_frame, label="second_mate")
|
|
second_compound = second.build()
|
|
|
|
root = FakeCompound(label="root", children=[first_compound, second_compound])
|
|
|
|
mates = _collect_assembly_mates(root)
|
|
|
|
self.assertEqual(["m1", "m2"], [mate["id"] for mate in mates])
|
|
self.assertEqual(["m1", "m2"], [mate["label"] for mate in mates])
|
|
self.assertEqual(["first_mate", "second_mate"], [mate["sourceLabel"] for mate in mates])
|
|
|
|
def test_helper_accepts_existing_native_joint_labels(self) -> None:
|
|
with fake_build123d():
|
|
assembly = AssemblyHelper("hinge")
|
|
frame = FakePart()
|
|
leaf = FakePart()
|
|
fixed_joint = FakeJoint(
|
|
label="hinge_axis",
|
|
to_part=frame,
|
|
joint_location=FakeLocation("frame_axis"),
|
|
)
|
|
moving_joint = FakeJoint(
|
|
label="leaf_axis",
|
|
to_part=leaf,
|
|
joint_location=FakeLocation("leaf_axis"),
|
|
)
|
|
|
|
relation = assembly.revolute(
|
|
(frame, "hinge_axis"),
|
|
(leaf, "leaf_axis"),
|
|
angle=45,
|
|
)
|
|
|
|
self.assertEqual("revolute", relation.relation)
|
|
self.assertEqual({"angle": 45}, relation.parameters)
|
|
self.assertEqual([(moving_joint, {"angle": 45})], fixed_joint.connections)
|
|
|
|
def test_axis_frames_use_native_joint_axis_argument(self) -> None:
|
|
with fake_build123d():
|
|
assembly = AssemblyHelper("hinge")
|
|
frame = FakePart()
|
|
|
|
frame_target = assembly.revolute_frame(
|
|
frame,
|
|
"hinge_axis",
|
|
"Axis.Z",
|
|
angular_range=(-90, 90),
|
|
)
|
|
|
|
self.assertEqual(MateTarget(frame, "hinge_axis"), frame_target)
|
|
joint = frame.joints["hinge_axis"]
|
|
self.assertIsNone(joint.location)
|
|
self.assertEqual({"axis": "Axis.Z", "angular_range": (-90, 90)}, joint.options)
|
|
|
|
def test_offset_target_creates_temporary_native_joint(self) -> None:
|
|
with fake_build123d():
|
|
assembly = AssemblyHelper("offset")
|
|
base = FakePart()
|
|
lid = FakePart()
|
|
base_frame = assembly.rigid_frame(base, "seat", FakeLocation("base"))
|
|
lid_frame = assembly.rigid_frame(lid, "underside", FakeLocation("lid"))
|
|
|
|
relation = assembly.face_to_face(base_frame, lid_frame, offset=0.5)
|
|
|
|
offset_joint = base.joints["seat:offset"]
|
|
self.assertIsInstance(offset_joint.location, FakeLocation)
|
|
self.assertEqual(("mul", "base", (0.0, 0.0, 0.5)), offset_joint.location.value)
|
|
self.assertEqual("seat:offset", relation.fixed)
|
|
|
|
def test_build_returns_labeled_compound(self) -> None:
|
|
with fake_build123d():
|
|
assembly = AssemblyHelper("robot arm")
|
|
base = assembly.add(FakePart(), "base")
|
|
arm = assembly.add(FakePart(), "arm")
|
|
|
|
compound = assembly.build()
|
|
|
|
self.assertEqual("robot_arm", compound.label)
|
|
self.assertEqual((base, arm), compound.children)
|
|
|
|
def test_target_tuple_is_available_for_call_sites(self) -> None:
|
|
part = object()
|
|
|
|
self.assertEqual(MateTarget(part, "axis"), target(part, "axis"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|