first commit
This commit is contained in:
@@ -0,0 +1,547 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user