import unittest import simplecadapi as scad from simplecadapi import ql as Q class TestQLTagPredicates(unittest.TestCase): def test_tag_exact_and_wildcard(self): box = scad.make_box_rsolid(1.0, 1.0, 1.0) scad.apply_tag(box, "role.mounting_surface") self.assertTrue(Q.tag("role.mounting_surface")(box)) self.assertTrue(Q.tag("role.*")(box)) self.assertFalse(Q.tag("role.other")(box)) def test_tag_face_prefix(self): box = scad.make_box_rsolid(1.0, 1.0, 1.0) box.auto_tag_faces("box") top_faces = [face for face in box.get_faces() if "face.top" in scad.list_tags(face)] self.assertTrue(top_faces) top_face = top_faces[0] self.assertTrue(Q.tag("face.top")(top_face)) self.assertTrue(Q.tag("face.*")(top_face)) class TestQLMetadataPredicates(unittest.TestCase): def test_meta_eq_and_compare(self): box = scad.make_box_rsolid(2.0, 3.0, 4.0) self.assertTrue(Q.meta("geo.type", "==", "box")(box)) self.assertTrue(Q.meta("geo.size.x", ">", 1.0)(box)) def test_select_where_order_first(self): c1 = scad.make_cylinder_rsolid(1.0, 1.0) c2 = scad.make_cylinder_rsolid(1.0, 3.0) c3 = scad.make_cylinder_rsolid(1.0, 2.0) result = ( Q.select([c1, c2, c3]).order_by(Q.value("geo.height"), desc=True).first() ) self.assertIsNotNone(result) assert result is not None self.assertEqual(result.get_metadata("geo")["height"], 3.0) def test_where_and_not(self): box = scad.make_box_rsolid(1.0, 1.0, 1.0) cyl = scad.make_cylinder_rsolid(1.0, 1.0) scad.apply_tag(box, "role.mounting_surface") scad.apply_tag(box, "state.debug") predicate = Q.and_( Q.meta("geo.type", "==", "box"), Q.tag("role.mounting_surface"), Q.not_(Q.tag("state.*")), ) result = Q.select([box, cyl]).where(predicate).all() self.assertEqual(result, []) predicate = Q.and_( Q.meta("geo.type", "==", "box"), Q.tag("role.mounting_surface"), ) result = Q.select([box, cyl]).where(predicate).all() self.assertEqual(result, [box]) def test_first_empty(self): self.assertIsNone(Q.select([]).first()) class TestSerializableGeometrySelectors(unittest.TestCase): def test_generic_property_predicate_matches_geometry_type(self): edge = scad.make_circle_redge((0, 0, 0), 1.0) pred = Q.prop("geom.type", "==", "CIRCLE") self.assertTrue(pred(edge)) def test_generic_property_key_reads_center_axis(self): face = scad.make_rectangle_rface(2.0, 2.0, center=(0, 0, 3.0)) key = Q.key("geom.center.z") self.assertAlmostEqual(key(face), 3.0, places=6) def test_custom_property_resolver_extends_ql_without_core_changes(self): obj = object() def resolver(target, path): if target is obj and path == "custom.answer": return 42 return Q.MISSING Q.register_property_resolver("custom.", resolver) try: self.assertTrue(Q.prop("custom.answer", "==", 42)(obj)) self.assertEqual(Q.key("custom.answer")(obj), 42) finally: Q.unregister_property_resolver("custom.") def test_edge_selector_serializes_and_roundtrips(self): selector = ( Q.edges() .where(Q.prop("geom.type", "==", "CIRCLE")) .order_by(Q.key("geom.center.z")) .take(1) .exactly(1) ) payload = selector.to_dict() restored = Q.selector_from_dict(payload) self.assertEqual(payload["target_kind"], "edge") self.assertEqual(payload["limit"], 1) self.assertEqual(payload["cardinality"]["exactly"], 1) self.assertEqual(restored.to_dict(), payload) def test_selector_supports_graph_source_multi_key_and_range_cardinality(self): selector = ( Q.faces() .from_source("node_a", 0) .order_by(Q.key("geom.center.z"), desc=True) .order_by(Q.key("geom.center.x")) .at_least(1) .at_most(6) ) payload = selector.to_dict() restored = Q.selector_from_dict(payload) self.assertEqual(payload["source_node_id"], "node_a") self.assertEqual(payload["source_output_slot"], 0) self.assertEqual(len(payload["order_keys"]), 2) self.assertEqual(payload["cardinality"]["at_least"], 1) self.assertEqual(payload["cardinality"]["at_most"], 6) self.assertEqual(restored.to_dict(), payload) def test_wire_selector_reads_loop_role_property(self): face = scad.make_rectangle_rface(2.0, 2.0) wires = ( Q.wires() .where(Q.prop("topo.loop_role", "==", "outer")) .take(1) .exactly(1) .resolve(face) ) self.assertEqual(len(wires), 1) self.assertTrue(Q.prop("topo.loop_role", "==", "outer")(wires[0])) def test_face_selector_resolves_geometry_predicates(self): box = scad.make_box_rsolid(2.0, 3.0, 4.0) selector = ( Q.faces() .where(Q.prop("geom.type", "==", "PLANE")) .order_by(Q.key("geom.center.z"), desc=True) .take(1) .exactly(1) ) faces = selector.resolve(box) self.assertEqual(len(faces), 1) top_face = faces[0] self.assertGreater(top_face.get_normal_at().z, 0.9) def test_edge_selector_resolves_circular_bottom_edge(self): with scad.GraphSession(): rod = scad.make_cylinder_rsolid(1.0, 5.0, bottom_face_center=(0, 0, 0)) selector = ( Q.edges() .where(Q.prop("geom.type", "==", "CIRCLE")) .order_by(Q.key("geom.center.z")) .take(1) .exactly(1) ) edges = selector.resolve(rod) self.assertEqual(len(edges), 1) meta = edges[0].get_metadata("topo_ref") self.assertIsNotNone(meta) self.assertEqual(meta["kind"], "EDGE") def test_boundary_traversal_selects_top_face_outer_edges(self): box = scad.make_box_rsolid(2.0, 3.0, 4.0) selector = ( Q.faces() .where(Q.prop("geom.normal.z", ">", 0.9)) .order_by(Q.key("geom.center.z"), desc=True) .take(1) .exactly(1) .boundary("wire") .where(Q.prop("topo.loop_role", "==", "outer")) .take(1) .exactly(1) .boundary("edge") .exactly(4) ) edges = selector.resolve(box) self.assertEqual(len(edges), 4) payload = selector.to_dict() restored = Q.selector_from_dict(payload) self.assertEqual(len(restored.resolve(box)), 4) self.assertEqual(payload["traversal"]["relation"], "boundary") self.assertEqual(payload["source"]["traversal"]["relation"], "boundary") def test_boundary_traversal_selects_cut_top_edges(self): base = scad.make_cylinder_rsolid(1.5, 4.0, bottom_face_center=(0.0, 0.0, -2.0)) tool = scad.make_box_rsolid( 4.0, 4.0, 4.0, bottom_face_center=(-2.0, -2.0, 0.25) ) result = scad.cut_rsolid(base, tool) selector = ( Q.faces() .where(Q.prop("geom.type", "==", "PLANE")) .where(Q.prop("geom.normal.z", ">", 0.9)) .order_by(Q.key("geom.center.z"), desc=True) .take(1) .exactly(1) .boundary("wire") .where(Q.prop("topo.loop_role", "==", "outer")) .take(1) .exactly(1) .boundary("edge") ) edges = selector.resolve(result) self.assertGreater(len(edges), 0) def test_selector_exactly_enforces_cardinality(self): box = scad.make_box_rsolid(1.0, 1.0, 1.0) selector = ( Q.faces().where(Q.prop("geom.type", "==", "PLANE")).take(2).exactly(1) ) with self.assertRaises(ValueError): selector.resolve(box) def test_selector_range_cardinality_enforces_bounds(self): box = scad.make_box_rsolid(1.0, 1.0, 1.0) with self.assertRaises(ValueError): Q.faces().take(0).at_least(1).resolve(box) with self.assertRaises(ValueError): Q.faces().at_most(1).resolve(box) if __name__ == "__main__": unittest.main()