"""Tests for Phase 5 (QL sugar) and Phase 6 (DAG session recorder).""" import unittest import simplecadapi as scad from simplecadapi import ql as Q from simplecadapi.topology import OperationGraph, OperationNode from simplecadapi.graph import GraphSession, record_operation, get_active_session from simplecadapi.tracking import tracked_cut, tracked_union from simplecadapi.autotag import apply_tracking_tags_to_delta class TestQLSugar(unittest.TestCase): """Test QL sugar helpers for tracking-based queries.""" def test_op_predicate(self): pred = Q.op("cut", "generated") obj = type("Obj", (), {"_tags": {"op.cut.generated"}})() self.assertTrue(pred(obj)) def test_op_predicate_wildcard(self): pred = Q.op("cut") obj = type("Obj", (), {"_tags": {"op.cut.modified"}})() self.assertTrue(pred(obj)) def test_origin_predicate(self): pred = Q.origin("tool") obj = type("Obj", (), {"_tags": {"origin.tool"}})() self.assertTrue(pred(obj)) def test_origin_predicate_no_match(self): pred = Q.origin("tool") obj = type("Obj", (), {"_tags": {"origin.body"}})() self.assertFalse(pred(obj)) def test_role_predicate(self): pred = Q.role("section") obj = type("Obj", (), {"_tags": {"role.section.face"}})() self.assertTrue(pred(obj)) def test_select_faces_by_op(self): body = scad.make_box_rsolid(10, 10, 10) body.auto_tag_faces("box") tool = scad.make_cylinder_rsolid(2.0, 15.0, bottom_face_center=(3, 3, -2.5)) result = tracked_cut(body, tool) tagged = apply_tracking_tags_to_delta( result.solid, result.delta, result.delta_entries, op="cut" ) modified = Q.select(tagged.get_faces()).where(Q.op("cut", "modified")).all() self.assertGreater(len(modified), 0) def test_select_faces_by_origin(self): body = scad.make_box_rsolid(10, 10, 10) tool = scad.make_cylinder_rsolid(2.0, 15.0, bottom_face_center=(3, 3, -2.5)) result = tracked_cut(body, tool) tagged = apply_tracking_tags_to_delta( result.solid, result.delta, result.delta_entries, op="cut" ) tool_faces = Q.select(tagged.get_faces()).where(Q.origin("tool")).all() self.assertGreater(len(tool_faces), 0) class TestGraphSession(unittest.TestCase): """Test the DAG session recorder.""" def test_session_lifecycle(self): session = GraphSession() session.start() self.assertIsNotNone(get_active_session()) session.stop() self.assertIsNone(get_active_session()) def test_record_primitive(self): session = GraphSession() session.start() node = record_operation( "make_line_redge", {"start": (0, 0, 0), "end": (10, 0, 0)} ) self.assertEqual(node.op, "make_line_redge") self.assertEqual(session.graph.node_count, 1) session.stop() def test_record_with_inputs(self): session = GraphSession() session.start() n1 = record_operation( "make_line_redge", {"start": (0, 0, 0), "end": (10, 0, 0)} ) n2 = record_operation( "make_line_redge", {"start": (10, 0, 0), "end": (10, 10, 0)} ) n3 = record_operation( "make_wire_from_edges_rwire", {"edge_count": 2}, inputs=[n1, n2] ) self.assertEqual(session.graph.node_count, 3) self.assertEqual(len(n3.inputs), 2) session.stop() def test_record_with_topo_delta(self): body = scad.make_box_rsolid(10, 10, 10) tool = scad.make_cylinder_rsolid(2.0, 15.0, bottom_face_center=(3, 3, -2.5)) result = tracked_cut(body, tool) session = GraphSession() session.start() body_node = record_operation( "make_extrude_rsolid", {"direction": (0, 0, 1), "distance": 10.0}, ) tool_node = record_operation( "make_extrude_rsolid", {"direction": (0, 0, 1), "distance": 15.0}, ) cut_node = record_operation( "make_cut_rsolid", {}, inputs=[body_node, tool_node], topo_delta=result.delta, ) self.assertIsNotNone(cut_node.topo_delta) self.assertGreater(len(cut_node.topo_delta.modified), 0) session.stop() def test_context_manager(self): with GraphSession() as session: n1 = record_operation( "make_extrude_rsolid", {"direction": (0, 0, 1), "distance": 1.0} ) n2 = record_operation( "make_extrude_rsolid", {"direction": (0, 0, 1), "distance": 2.0} ) record_operation("make_union_rsolid", {}, inputs=[n1, n2]) self.assertEqual(session.graph.node_count, 3) def test_no_session_raises(self): with self.assertRaises(RuntimeError): record_operation("make_line_redge", {}) def test_graph_is_dag(self): with GraphSession() as session: n1 = record_operation( "make_line_redge", {"start": (0, 0, 0), "end": (1, 0, 0)} ) n2 = record_operation( "make_line_redge", {"start": (1, 0, 0), "end": (1, 1, 0)} ) n3 = record_operation( "make_wire_from_edges_rwire", {"edge_count": 2}, inputs=[n1, n2] ) self.assertTrue(session.graph.is_dag()) def test_graph_topological_order(self): with GraphSession() as session: n1 = record_operation( "make_line_redge", {"start": (0, 0, 0), "end": (1, 0, 0)} ) n2 = record_operation( "make_line_redge", {"start": (1, 0, 0), "end": (1, 1, 0)} ) n3 = record_operation( "make_wire_from_edges_rwire", {"edge_count": 2}, inputs=[n1, n2] ) n4 = record_operation("make_face_from_wire_rface", {}, inputs=[n3]) order = session.graph.topological_order() self.assertEqual(len(order), 4) idx = {node.node_id: i for i, node in enumerate(order)} self.assertLess(idx[n1.node_id], idx[n3.node_id]) self.assertLess(idx[n3.node_id], idx[n4.node_id]) if __name__ == "__main__": unittest.main()