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.
LightWork3D/tests/python/freecad_exchange_template_s...

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()