You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
7.6 KiB
Python
219 lines
7.6 KiB
Python
import importlib
|
|
import json
|
|
import sys
|
|
import tempfile
|
|
import types
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
MODULE_DIR = REPO_ROOT / "src" / "Mod" / "FreeCADExchange"
|
|
if str(MODULE_DIR) not in sys.path:
|
|
sys.path.insert(0, str(MODULE_DIR))
|
|
|
|
|
|
def _install_fake_freecad():
|
|
class Vector:
|
|
def __init__(self, x=0.0, y=0.0, z=0.0):
|
|
self.x = float(x)
|
|
self.y = float(y)
|
|
self.z = float(z)
|
|
|
|
class Rotation:
|
|
def __init__(self, axis=None, angle=None):
|
|
self.axis = axis
|
|
self.angle = angle
|
|
|
|
class Placement:
|
|
def __init__(self, base=None, rotation=None):
|
|
self.Base = base
|
|
self.Rotation = rotation
|
|
|
|
fake_freecad = types.ModuleType("FreeCAD")
|
|
fake_freecad.Vector = Vector
|
|
fake_freecad.Rotation = Rotation
|
|
fake_freecad.Placement = Placement
|
|
fake_freecad.Console = types.SimpleNamespace(
|
|
PrintMessage=lambda *args, **kwargs: None,
|
|
PrintWarning=lambda *args, **kwargs: None,
|
|
PrintError=lambda *args, **kwargs: None,
|
|
)
|
|
fake_freecad.ActiveDocument = None
|
|
fake_freecad.newDocument = lambda name: types.SimpleNamespace(Name=name, Objects=[], getObject=lambda item: None)
|
|
sys.modules["FreeCAD"] = fake_freecad
|
|
|
|
fake_freecadgui = types.ModuleType("FreeCADGui")
|
|
fake_freecadgui.SendMsgToActiveView = lambda *args, **kwargs: None
|
|
fake_freecadgui.addCommand = lambda *args, **kwargs: None
|
|
fake_freecadgui.Selection = types.SimpleNamespace(getSelection=lambda: [])
|
|
sys.modules["FreeCADGui"] = fake_freecadgui
|
|
|
|
fake_importgui = types.ModuleType("ImportGui")
|
|
fake_importgui.insert = lambda *args, **kwargs: None
|
|
sys.modules["ImportGui"] = fake_importgui
|
|
|
|
|
|
def _reload_exchange_modules():
|
|
for name in [
|
|
"TerminalObjects",
|
|
"TemplateSemantics",
|
|
"DeviceImport",
|
|
"TerminalImport",
|
|
]:
|
|
sys.modules.pop(name, None)
|
|
template_semantics = importlib.import_module("TemplateSemantics")
|
|
terminal_import = importlib.import_module("TerminalImport")
|
|
return template_semantics, terminal_import
|
|
|
|
|
|
class TemplateSemanticsRotationTest(unittest.TestCase):
|
|
def test_terminal_hint_uses_template_slot_name_before_object_name(self):
|
|
_install_fake_freecad()
|
|
template_semantics, _ = _reload_exchange_modules()
|
|
|
|
fake_lcs = types.SimpleNamespace(
|
|
Name="Terminal_P1",
|
|
Label="端子一",
|
|
TypeId="Part::LocalCoordinateSystem",
|
|
Role="Terminal",
|
|
QetTemplateSlotName="P1",
|
|
Placement=types.SimpleNamespace(
|
|
Base=sys.modules["FreeCAD"].Vector(1, 2, 3),
|
|
Rotation=sys.modules["FreeCAD"].Rotation(),
|
|
),
|
|
)
|
|
container = types.SimpleNamespace(Group=[fake_lcs])
|
|
|
|
hints = template_semantics.collect_terminal_hints(container)
|
|
|
|
self.assertEqual("P1", hints[0]["name"])
|
|
|
|
def test_terminal_hint_keeps_source_object_rotation(self):
|
|
_install_fake_freecad()
|
|
template_semantics, _ = _reload_exchange_modules()
|
|
|
|
fake_lcs = types.SimpleNamespace(
|
|
Name="TerminalA1",
|
|
Label="Terminal A1",
|
|
TypeId="Part::LocalCoordinateSystem",
|
|
Role="Terminal",
|
|
Placement=types.SimpleNamespace(
|
|
Base=sys.modules["FreeCAD"].Vector(4, 5, 6),
|
|
Rotation=sys.modules["FreeCAD"].Rotation(
|
|
sys.modules["FreeCAD"].Vector(0, 1, 0),
|
|
37.5,
|
|
),
|
|
),
|
|
)
|
|
container = types.SimpleNamespace(Group=[fake_lcs])
|
|
|
|
hints = template_semantics.collect_terminal_hints(container)
|
|
|
|
self.assertEqual(1, len(hints))
|
|
self.assertIn("rotation", hints[0])
|
|
self.assertEqual(37.5, hints[0]["rotation"]["angle"])
|
|
self.assertEqual(0.0, hints[0]["rotation"]["axis"].x)
|
|
self.assertEqual(1.0, hints[0]["rotation"]["axis"].y)
|
|
self.assertEqual(0.0, hints[0]["rotation"]["axis"].z)
|
|
|
|
def test_sidecar_rotation_is_normalized_from_payload(self):
|
|
_install_fake_freecad()
|
|
template_semantics, _ = _reload_exchange_modules()
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
model_path = Path(temp_dir) / "Relay.step"
|
|
model_path.write_text("", encoding="utf-8")
|
|
sidecar_path = Path(temp_dir) / "Relay.qet_template.json"
|
|
sidecar_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"terminal_slots": [
|
|
{
|
|
"name": "A1",
|
|
"label": "A1",
|
|
"position": {"x": 10, "y": 20, "z": 30},
|
|
"rotation": {
|
|
"axis": {"x": 0, "y": 0, "z": 1},
|
|
"angle": 90,
|
|
},
|
|
}
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
slots = template_semantics.load_sidecar_terminal_slots(str(model_path))
|
|
|
|
self.assertEqual(1, len(slots))
|
|
self.assertIn("rotation", slots[0])
|
|
self.assertEqual(90.0, slots[0]["rotation"]["angle"])
|
|
self.assertEqual(0.0, slots[0]["rotation"]["axis"].x)
|
|
self.assertEqual(0.0, slots[0]["rotation"]["axis"].y)
|
|
self.assertEqual(1.0, slots[0]["rotation"]["axis"].z)
|
|
|
|
|
|
class TerminalSlotResolutionPolicyTest(unittest.TestCase):
|
|
def test_resolve_terminal_slots_returns_empty_when_model_has_no_template_slots(self):
|
|
_install_fake_freecad()
|
|
template_semantics, _ = _reload_exchange_modules()
|
|
|
|
container = types.SimpleNamespace(Group=[])
|
|
|
|
slots = template_semantics.resolve_terminal_slots(container, "", 2)
|
|
|
|
self.assertEqual([], slots)
|
|
|
|
def test_resolve_terminal_slots_does_not_pad_template_hints_with_bbox_fallback(self):
|
|
_install_fake_freecad()
|
|
template_semantics, _ = _reload_exchange_modules()
|
|
|
|
fake_lcs = types.SimpleNamespace(
|
|
Name="Terminal_P1",
|
|
Label="P1",
|
|
TypeId="Part::LocalCoordinateSystem",
|
|
Role="Terminal",
|
|
QetTemplateSlotName="P1",
|
|
Placement=types.SimpleNamespace(
|
|
Base=sys.modules["FreeCAD"].Vector(10, 20, 30),
|
|
Rotation=sys.modules["FreeCAD"].Rotation(),
|
|
),
|
|
)
|
|
container = types.SimpleNamespace(Group=[fake_lcs])
|
|
|
|
slots = template_semantics.resolve_terminal_slots(container, "", 2)
|
|
|
|
self.assertEqual(1, len(slots))
|
|
self.assertEqual("P1", slots[0]["name"])
|
|
self.assertNotEqual("fallback", slots[0].get("source"))
|
|
|
|
|
|
class TerminalPlacementTest(unittest.TestCase):
|
|
def test_slot_placement_uses_rotation_metadata(self):
|
|
_install_fake_freecad()
|
|
_, terminal_import = _reload_exchange_modules()
|
|
|
|
slot = {
|
|
"base": sys.modules["FreeCAD"].Vector(1, 2, 3),
|
|
"rotation": {
|
|
"axis": sys.modules["FreeCAD"].Vector(0, 0, 1),
|
|
"angle": 45.0,
|
|
},
|
|
}
|
|
|
|
placement = terminal_import._slot_placement(slot)
|
|
|
|
self.assertEqual(1.0, placement.Base.x)
|
|
self.assertEqual(2.0, placement.Base.y)
|
|
self.assertEqual(3.0, placement.Base.z)
|
|
self.assertIsNotNone(placement.Rotation)
|
|
self.assertEqual(45.0, placement.Rotation.angle)
|
|
self.assertEqual(0.0, placement.Rotation.axis.x)
|
|
self.assertEqual(0.0, placement.Rotation.axis.y)
|
|
self.assertEqual(1.0, placement.Rotation.axis.z)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|