548 lines
22 KiB
Python
548 lines
22 KiB
Python
"""Focused tests for canonical 2.0 model JSON export."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import unittest
|
|
|
|
import simplecadapi as scad
|
|
from simplecadapi.graph import GraphSession
|
|
|
|
|
|
class TestModelJson(unittest.TestCase):
|
|
def test_model_json_contains_graph_and_expression_graph(self):
|
|
r = scad.var("r", 2.0)
|
|
with GraphSession() as session:
|
|
scad.make_circle_rface((0, 0, 0), r)
|
|
|
|
payload = scad.import_model_json(scad.export_model_json(session))
|
|
|
|
self.assertIn("graph", payload)
|
|
self.assertIn("expression_graph", payload)
|
|
self.assertIn("canonical_contract", payload)
|
|
self.assertGreaterEqual(payload["graph"].node_count, 1)
|
|
self.assertGreaterEqual(payload["expression_graph"].node_count, 1)
|
|
|
|
def test_model_json_declares_canonical_contract_and_graph_roles(self):
|
|
with GraphSession() as session:
|
|
scad.make_box_rsolid(1.0, 2.0, 3.0)
|
|
|
|
payload = scad.import_model_json(scad.export_model_json(session))
|
|
|
|
self.assertIn("canonical_contract", payload)
|
|
contract = payload["canonical_contract"]
|
|
self.assertEqual(contract["contract_version"], "2.0")
|
|
self.assertEqual(contract["graph_roles"]["graph"], "canonical_low_level_graph")
|
|
self.assertEqual(contract["graph_roles"]["leaf_ids"], "explicit_result_set")
|
|
self.assertEqual(contract["replay_policy"]["preferred_graph"], "graph")
|
|
self.assertIn("core_op_set", contract)
|
|
self.assertGreaterEqual(len(contract["core_op_set"]), 1)
|
|
self.assertEqual(contract["replay_policy"]["default_mode"], "strict")
|
|
self.assertEqual(
|
|
contract["replay_policy"]["permissive_mode"], "explicit_opt_in"
|
|
)
|
|
|
|
def test_model_json_includes_geometry_and_delta_registries(self):
|
|
with GraphSession() as session:
|
|
box = scad.make_box_rsolid(2.0, 3.0, 4.0)
|
|
scad.translate_shape(box, (1.0, 0.0, 0.0))
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
|
|
self.assertIn("geometry_registry", payload)
|
|
self.assertIn("semantic_delta_log", payload)
|
|
self.assertIn("topology_delta_log", payload)
|
|
self.assertGreaterEqual(len(payload["geometry_registry"]), 1)
|
|
self.assertGreaterEqual(len(payload["semantic_delta_log"]), 1)
|
|
|
|
def test_model_json_includes_sketch_registry_without_assembly_fields(self):
|
|
with GraphSession() as session:
|
|
face = scad.make_circle_rface((0, 0, 0), scad.var("r", 2.0))
|
|
scad.extrude_rsolid(face, (0, 0, 1), 3.0)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
|
|
self.assertIn("sketch_profile_registry", payload)
|
|
self.assertGreaterEqual(len(payload["sketch_profile_registry"]), 1)
|
|
self.assertNotIn("assembly", payload)
|
|
self.assertNotIn("assembly_registry", payload)
|
|
self.assertNotIn("constraint_registry", payload)
|
|
|
|
def test_model_json_import_preserves_registry_payloads(self):
|
|
with GraphSession() as session:
|
|
box = scad.make_box_rsolid(2.0, 3.0, 4.0)
|
|
scad.translate_shape(box, (1.0, 0.0, 0.0))
|
|
|
|
payload = scad.import_model_json(scad.export_model_json(session))
|
|
|
|
self.assertIn("geometry_registry", payload)
|
|
self.assertIn("canonical_contract", payload)
|
|
self.assertIn("semantic_delta_log", payload)
|
|
self.assertIn("topology_delta_log", payload)
|
|
self.assertGreaterEqual(len(payload["geometry_registry"]), 1)
|
|
|
|
def test_model_json_import_preserves_supported_extended_registries(self):
|
|
with GraphSession() as session:
|
|
face = scad.make_circle_rface((0, 0, 0), 2.0)
|
|
scad.extrude_rsolid(face, (0, 0, 1), 3.0)
|
|
|
|
payload = scad.import_model_json(scad.export_model_json(session))
|
|
|
|
self.assertIn("sketch_profile_registry", payload)
|
|
self.assertGreaterEqual(len(payload["sketch_profile_registry"]), 1)
|
|
self.assertNotIn("assembly", payload)
|
|
self.assertNotIn("assembly_registry", payload)
|
|
self.assertNotIn("constraint_registry", payload)
|
|
|
|
def test_model_json_graph_contains_only_low_level_ops(self):
|
|
with GraphSession() as session:
|
|
profile = scad.make_rectangle_rwire(0.4, 0.2)
|
|
scad.helical_sweep_rsolid(profile, pitch=1.0, height=2.0, radius=1.0)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
|
|
self.assertIn("graph", payload)
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
self.assertIn("make_helix_redge", core_ops)
|
|
self.assertIn("make_wire_from_edges_rwire", core_ops)
|
|
self.assertIn("make_sweep_rsolid", core_ops)
|
|
self.assertNotIn("helical_sweep", core_ops)
|
|
|
|
def test_model_json_can_replay_without_graph_json_api(self):
|
|
with GraphSession() as session:
|
|
face = scad.make_circle_rface((0, 0, 0), 1.2)
|
|
original = scad.extrude_rsolid(face, (0, 0, 1), 2.5)
|
|
|
|
replayed = scad.replay_model_json(scad.export_model_json(session))
|
|
|
|
self.assertEqual(len(replayed), 1)
|
|
self.assertIsInstance(replayed[0], scad.Solid)
|
|
self.assertAlmostEqual(
|
|
replayed[0].get_volume(), original.get_volume(), places=5
|
|
)
|
|
|
|
def test_model_json_replay_uses_single_low_level_graph_for_macro_ops(self):
|
|
with GraphSession() as session:
|
|
profile = scad.make_rectangle_rwire(0.4, 0.2)
|
|
original = scad.helical_sweep_rsolid(
|
|
profile, pitch=1.0, height=2.0, radius=1.0
|
|
)
|
|
|
|
replayed = scad.replay_model_json(scad.export_model_json(session))
|
|
|
|
self.assertEqual(len(replayed), 1)
|
|
self.assertIsInstance(replayed[0], scad.Solid)
|
|
self.assertAlmostEqual(
|
|
replayed[0].get_volume(), original.get_volume(), places=4
|
|
)
|
|
|
|
def test_graph_records_make_box_as_direct_primitive(self):
|
|
with GraphSession() as session:
|
|
scad.make_box_rsolid(2.0, 3.0, 4.0, bottom_face_center=(1.0, 2.0, 3.0))
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertEqual(core_ops, ["make_box_rsolid"])
|
|
|
|
def test_graph_lowers_make_circle_face_to_wire_plus_face(self):
|
|
with GraphSession() as session:
|
|
scad.make_circle_rface((0.0, 0.0, 0.0), 2.0)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertIn("make_circle_redge", core_ops)
|
|
self.assertIn("make_wire_from_edges_rwire", core_ops)
|
|
self.assertIn("make_face_from_wire_rface", core_ops)
|
|
self.assertNotIn("make_circle_face", core_ops)
|
|
|
|
def test_graph_records_make_cylinder_as_direct_primitive(self):
|
|
with GraphSession() as session:
|
|
scad.make_cylinder_rsolid(1.0, 3.0, bottom_face_center=(0.0, 0.0, 0.0))
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertEqual(core_ops, ["make_cylinder_rsolid"])
|
|
|
|
def test_graph_box_primitive_does_not_emit_rectangle_profile(self):
|
|
with GraphSession() as session:
|
|
scad.make_box_rsolid(2.0, 3.0, 4.0)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertEqual(core_ops, ["make_box_rsolid"])
|
|
self.assertNotIn("make_rectangle_face", core_ops)
|
|
|
|
def test_graph_records_make_sphere_as_direct_primitive(self):
|
|
with GraphSession() as session:
|
|
scad.make_sphere_rsolid(2.0, center=(0.0, 0.0, 0.0))
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertEqual(core_ops, ["make_sphere_rsolid"])
|
|
|
|
def test_graph_records_make_cone_as_direct_primitive(self):
|
|
with GraphSession() as session:
|
|
scad.make_cone_rsolid(2.0, 4.0, top_radius=0.5)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertEqual(core_ops, ["make_cone_rsolid"])
|
|
|
|
def test_graph_lowers_rectangle_wire_to_lines_plus_wire_assembly(self):
|
|
with GraphSession() as session:
|
|
scad.make_rectangle_rwire(2.0, 3.0)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertIn("make_line_redge", core_ops)
|
|
self.assertIn("make_wire_from_edges_rwire", core_ops)
|
|
self.assertNotIn("make_rectangle_wire", core_ops)
|
|
|
|
def test_graph_lowers_circle_wire_to_edge_plus_wire_assembly(self):
|
|
with GraphSession() as session:
|
|
scad.make_circle_rwire((0.0, 0.0, 0.0), 2.0)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertIn("make_circle_redge", core_ops)
|
|
self.assertIn("make_wire_from_edges_rwire", core_ops)
|
|
self.assertNotIn("make_circle_wire", core_ops)
|
|
|
|
def test_graph_lowers_linear_pattern_to_explicit_transforms(self):
|
|
with GraphSession() as session:
|
|
box = scad.make_box_rsolid(1.0, 1.0, 1.0)
|
|
scad.linear_pattern_rsolidlist(box, (1.0, 0.0, 0.0), 4, 1.5)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertIn("make_translate_rshape", core_ops)
|
|
self.assertNotIn("linear_pattern", core_ops)
|
|
|
|
def test_graph_lowers_radial_pattern_to_explicit_rotates(self):
|
|
with GraphSession() as session:
|
|
box = scad.make_box_rsolid(1.0, 1.0, 1.0)
|
|
scad.radial_pattern_rsolidlist(
|
|
box, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), 4, 360.0
|
|
)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertIn("make_rotate_rshape", core_ops)
|
|
self.assertNotIn("radial_pattern", core_ops)
|
|
|
|
def test_graph_lowers_remaining_wire_convenience_ops(self):
|
|
with GraphSession() as session:
|
|
scad.make_polyline_rwire(
|
|
[(0.0, 0.0, 0.0), (1.0, 0.2, 0.0), (2.0, 0.0, 0.0)]
|
|
)
|
|
scad.make_segment_rwire((0.0, 0.0, 0.0), (1.0, 0.0, 0.0))
|
|
scad.make_three_point_arc_rwire(
|
|
(0.0, 0.0, 0.0), (1.0, 1.0, 0.0), (2.0, 0.0, 0.0)
|
|
)
|
|
scad.make_angle_arc_rwire((0.0, 0.0, 0.0), 1.0, 0.0, 1.57)
|
|
scad.make_spline_rwire(
|
|
control_points=[
|
|
(0.0, 0.0, 0.0),
|
|
(0.6, 1.0, 0.0),
|
|
(1.4, 1.0, 0.0),
|
|
(2.0, 0.0, 0.0),
|
|
]
|
|
)
|
|
scad.make_helix_rwire(1.0, 2.0, 0.8)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertIn("make_line_redge", core_ops)
|
|
self.assertIn("make_three_point_arc_redge", core_ops)
|
|
self.assertIn("make_angle_arc_redge", core_ops)
|
|
self.assertIn("make_spline_redge", core_ops)
|
|
self.assertIn("make_helix_redge", core_ops)
|
|
self.assertIn("make_wire_from_edges_rwire", core_ops)
|
|
self.assertNotIn("make_polyline_wire", core_ops)
|
|
self.assertNotIn("make_segment_wire", core_ops)
|
|
self.assertNotIn("make_three_point_arc_wire", core_ops)
|
|
self.assertNotIn("make_angle_arc_wire", core_ops)
|
|
self.assertNotIn("make_spline_wire", core_ops)
|
|
self.assertNotIn("make_helix_wire", core_ops)
|
|
|
|
def test_graph_lowers_spline_wire_to_exact_spline_edge_plus_wire_assembly(self):
|
|
with GraphSession() as session:
|
|
scad.make_spline_rwire(
|
|
control_points=[
|
|
(0.0, 0.0, 0.0),
|
|
(1.0, 0.0, 0.0),
|
|
(1.0, 1.0, 0.0),
|
|
(0.0, 1.0, 0.0),
|
|
],
|
|
periodic=True,
|
|
)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
core_ops = [node["op"] for node in payload["graph"]["nodes"]]
|
|
|
|
self.assertIn("make_spline_redge", core_ops)
|
|
self.assertIn("make_wire_from_edges_rwire", core_ops)
|
|
self.assertNotIn("make_spline_wire", core_ops)
|
|
|
|
def test_graph_preserves_explicit_selected_refs_for_detail_features(self):
|
|
with GraphSession() as session:
|
|
box = scad.make_box_rsolid(4.0, 4.0, 4.0)
|
|
scad.fillet_rsolid(box, [box.get_edges(i) for i in range(2)], 0.3)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
fillet_nodes = [
|
|
node
|
|
for node in payload["graph"]["nodes"]
|
|
if node["op"] == "make_fillet_rsolid"
|
|
]
|
|
|
|
self.assertEqual(len(fillet_nodes), 1)
|
|
fillet_params = fillet_nodes[0]["params"]
|
|
self.assertIn("selected_edges", fillet_params)
|
|
self.assertGreaterEqual(len(fillet_params["selected_edges"]), 1)
|
|
self.assertIn("topo_id", fillet_params["selected_edges"][0])
|
|
|
|
def test_graph_ops_stay_within_declared_canonical_op_set(self):
|
|
with GraphSession() as session:
|
|
scad.make_box_rsolid(2.0, 3.0, 4.0)
|
|
scad.make_cylinder_rsolid(1.0, 3.0)
|
|
scad.make_sphere_rsolid(1.5)
|
|
profile = scad.make_rectangle_rwire(0.4, 0.2)
|
|
scad.helical_sweep_rsolid(profile, pitch=1.0, height=2.0, radius=1.0)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
contract = payload["canonical_contract"]
|
|
core_ops = {node["op"] for node in payload["graph"]["nodes"]}
|
|
|
|
self.assertTrue(core_ops.issubset(set(contract["core_op_set"])))
|
|
self.assertIn("make_box_rsolid", core_ops)
|
|
self.assertIn("make_cylinder_rsolid", core_ops)
|
|
self.assertIn("make_sphere_rsolid", core_ops)
|
|
self.assertNotIn("helical_sweep", core_ops)
|
|
self.assertNotIn("make_polyline_wire", core_ops)
|
|
|
|
def test_sketch_profile_registry_uses_canonical_op_names(self):
|
|
with GraphSession() as session:
|
|
scad.make_circle_rface((0.0, 0.0, 0.0), 1.0)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
registry_ops = {entry["op"] for entry in payload["sketch_profile_registry"]}
|
|
canonical_ops = set(payload["canonical_contract"]["core_op_set"])
|
|
|
|
self.assertTrue(registry_ops)
|
|
self.assertTrue(registry_ops.issubset(canonical_ops))
|
|
|
|
def test_model_json_import_rejects_legacy_graph_op(self):
|
|
payload = {
|
|
"schema_version": "2.0",
|
|
"canonical_contract": {"contract_version": "2.0"},
|
|
"graph": {
|
|
"schema_version": "2.0",
|
|
"graph_id": "test",
|
|
"nodes": [
|
|
{
|
|
"node_id": "n1",
|
|
"op": "make_legacy_box",
|
|
"params": {"w": 1, "h": 1, "d": 1},
|
|
"inputs": [],
|
|
"output_count": 1,
|
|
"tags": [],
|
|
}
|
|
],
|
|
"edges": [],
|
|
},
|
|
"leaf_ids": ["n1"],
|
|
"expression_graph": {"nodes": []},
|
|
"frame_graph": {"nodes": []},
|
|
}
|
|
|
|
with self.assertRaises(ValueError):
|
|
scad.import_model_json(json.dumps(payload))
|
|
|
|
def test_graph_selection_refs_follow_declared_schema(self):
|
|
with GraphSession() as session:
|
|
box = scad.make_box_rsolid(4.0, 4.0, 4.0)
|
|
scad.fillet_rsolid(box, [box.get_edges(i) for i in range(2)], 0.3)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
contract = payload["canonical_contract"]
|
|
selection_schema = contract["selection_ref_schema"]
|
|
fillet_node = next(
|
|
node
|
|
for node in payload["graph"]["nodes"]
|
|
if node["op"] == "make_fillet_rsolid"
|
|
)
|
|
selected_edge_ref = fillet_node["params"]["selected_edges"][0]
|
|
|
|
self.assertEqual(
|
|
selection_schema["replay_resolution_order"],
|
|
[
|
|
"geo_select_nodes",
|
|
"selection_query",
|
|
"explicit_topo_refs",
|
|
"stable_indices",
|
|
"selector_hint",
|
|
],
|
|
)
|
|
self.assertEqual(selection_schema["edge_param"], "selected_edges")
|
|
self.assertEqual(selection_schema["face_param"], "selected_faces")
|
|
self.assertTrue(
|
|
set(selection_schema["required_topo_ref_fields"]).issubset(
|
|
selected_edge_ref.keys()
|
|
)
|
|
)
|
|
self.assertEqual(selected_edge_ref["kind"], "EDGE")
|
|
self.assertIn("selector_hint", selected_edge_ref)
|
|
|
|
def test_model_json_replay_prefers_geo_select_nodes_over_selected_refs(self):
|
|
with GraphSession() as session:
|
|
box = scad.make_box_rsolid(4.0, 4.0, 4.0)
|
|
original = scad.fillet_rsolid(box, scad.ql.edges().take(1).exactly(1), 0.2)
|
|
|
|
payload = json.loads(scad.export_model_json(session))
|
|
fillet_node = next(
|
|
node
|
|
for node in payload["graph"]["nodes"]
|
|
if node["op"] == "make_fillet_rsolid"
|
|
)
|
|
self.assertNotIn("selection_query", fillet_node["params"])
|
|
self.assertEqual(len(fillet_node["params"]["selected_edge_node_ids"]), 1)
|
|
fillet_node["params"]["selected_edges"] = []
|
|
fillet_node["params"]["selected_edge_indices"] = []
|
|
fillet_node["params"]["edge_count"] = 1
|
|
|
|
replayed = scad.replay_model_json(json.dumps(payload))
|
|
|
|
self.assertEqual(len(replayed), 1)
|
|
self.assertIsInstance(replayed[0], scad.Solid)
|
|
self.assertAlmostEqual(
|
|
replayed[0].get_volume(), original.get_volume(), places=5
|
|
)
|
|
|
|
def test_model_json_replay_preserves_linear_pattern_multi_output(self):
|
|
with GraphSession() as session:
|
|
box = scad.make_box_rsolid(1.0, 1.0, 1.0)
|
|
originals = scad.linear_pattern_rsolidlist(box, (1.0, 0.0, 0.0), 4, 1.5)
|
|
|
|
replayed = scad.replay_model_json(scad.export_model_json(session))
|
|
|
|
self.assertEqual(len(replayed), 4)
|
|
self.assertAlmostEqual(
|
|
sum(shape.get_volume() for shape in replayed),
|
|
sum(shape.get_volume() for shape in originals),
|
|
places=5,
|
|
)
|
|
|
|
def test_model_json_replay_preserves_radial_pattern_multi_output(self):
|
|
with GraphSession() as session:
|
|
box = scad.make_box_rsolid(1.0, 1.0, 1.0)
|
|
originals = scad.radial_pattern_rsolidlist(
|
|
box, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), 4, 360.0
|
|
)
|
|
|
|
replayed = scad.replay_model_json(scad.export_model_json(session))
|
|
|
|
self.assertEqual(len(replayed), 4)
|
|
self.assertAlmostEqual(
|
|
sum(shape.get_volume() for shape in replayed),
|
|
sum(shape.get_volume() for shape in originals),
|
|
places=5,
|
|
)
|
|
|
|
|
|
class TestOperationGraphDeltaSerialization(unittest.TestCase):
|
|
def test_graph_json_roundtrip_preserves_semantic_delta(self):
|
|
with GraphSession() as session:
|
|
scad.make_box_rsolid(1.0, 1.0, 1.0)
|
|
|
|
restored = scad.import_graph_json(scad.export_graph_json(session.graph))
|
|
leaf = restored.leaf_nodes()[0]
|
|
|
|
self.assertIsNotNone(leaf.semantic_delta)
|
|
self.assertGreaterEqual(len(leaf.semantic_delta.created), 1)
|
|
|
|
def test_graph_json_roundtrip_preserves_topology_delta(self):
|
|
with GraphSession() as session:
|
|
body = scad.make_box_rsolid(4.0, 4.0, 4.0)
|
|
tool = scad.make_cylinder_rsolid(
|
|
0.75, 6.0, bottom_face_center=(0.0, 0.0, -1.0)
|
|
)
|
|
scad.cut_rsolid(body, tool)
|
|
|
|
restored = scad.import_graph_json(scad.export_graph_json(session.graph))
|
|
leaf = restored.leaf_nodes()[0]
|
|
|
|
self.assertIsNotNone(leaf.topo_delta)
|
|
self.assertGreaterEqual(
|
|
len(leaf.topo_delta.modified)
|
|
+ len(leaf.topo_delta.generated)
|
|
+ len(leaf.topo_delta.deleted),
|
|
1,
|
|
)
|
|
|
|
def test_multi_tool_cut_topology_delta_keeps_step_chain(self):
|
|
with GraphSession() as session:
|
|
body = scad.make_box_rsolid(4.0, 4.0, 4.0)
|
|
tool_a = scad.make_box_rsolid(
|
|
1.0, 1.0, 5.0, bottom_face_center=(-0.75, 0.0, -0.5)
|
|
)
|
|
tool_b = scad.make_box_rsolid(
|
|
1.0, 1.0, 5.0, bottom_face_center=(0.75, 0.0, -0.5)
|
|
)
|
|
scad.cut_rsolid(body, tool_a, tool_b)
|
|
|
|
restored = scad.import_graph_json(scad.export_graph_json(session.graph))
|
|
leaf = restored.leaf_nodes()[0]
|
|
|
|
self.assertEqual(leaf.op, "make_cut_rsolid")
|
|
self.assertIsNotNone(leaf.topo_delta)
|
|
self.assertEqual(len(leaf.topo_delta.raw_event["steps"]), 2)
|
|
|
|
def test_multi_tool_intersect_topology_delta_keeps_step_chain(self):
|
|
with GraphSession() as session:
|
|
body = scad.make_box_rsolid(4.0, 4.0, 4.0)
|
|
tool_a = scad.make_box_rsolid(
|
|
4.0, 4.0, 4.0, bottom_face_center=(1.0, 0.0, 0.0)
|
|
)
|
|
tool_b = scad.make_box_rsolid(
|
|
4.0, 4.0, 4.0, bottom_face_center=(0.0, 1.0, 0.0)
|
|
)
|
|
scad.intersect_rsolid(body, tool_a, tool_b)
|
|
|
|
restored = scad.import_graph_json(scad.export_graph_json(session.graph))
|
|
leaf = restored.leaf_nodes()[0]
|
|
|
|
self.assertEqual(leaf.op, "make_intersect_rsolid")
|
|
self.assertIsNotNone(leaf.topo_delta)
|
|
self.assertEqual(len(leaf.topo_delta.raw_event["steps"]), 2)
|
|
|
|
def test_semantic_delta_created_refs_are_bound_to_real_graph_and_node_ids(self):
|
|
with GraphSession() as session:
|
|
scad.make_box_rsolid(1.0, 1.0, 1.0)
|
|
|
|
leaf = session.graph.leaf_nodes()[0]
|
|
self.assertIsNotNone(leaf.semantic_delta)
|
|
self.assertGreaterEqual(len(leaf.semantic_delta.created), 1)
|
|
|
|
for ref in leaf.semantic_delta.created:
|
|
with self.subTest(ref=ref):
|
|
self.assertEqual(ref.graph_id, session.graph.graph_id)
|
|
self.assertEqual(ref.node_id, leaf.node_id)
|
|
self.assertNotEqual(ref.graph_id, "pending")
|
|
self.assertNotEqual(ref.node_id, "pending")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|