diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst index f03f01d3d4..f4f7c76a3f 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst @@ -222,6 +222,9 @@ Valid keys are: "``easting``", "Gets the map easting of the element's placement" "``northing``", "Gets the map northing of the element's placement" "``elevation``", "Gets the map elevation of the element's placement" + "``rotation_x``", "Gets the X Euler rotation of the element's placement in degrees" + "``rotation_y``", "Gets the Y Euler rotation of the element's placement in degrees" + "``rotation_z``", "Gets the Z Euler rotation of the element's placement in degrees (e.g. plan rotation of a symbol)" "``count``", "If the previous key returns multiple things, count that list. Otherwise, return 1." "``{{number}}``", "If the previous key returns multiple things, fetch the ``{{number}}`` index (e.g. 0, 1, 2, 3, etc) item in that list." diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 438691e5b1..73d3df5867 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -36,6 +36,7 @@ import ifcopenshell.util.placement import ifcopenshell.util.pset import ifcopenshell.util.schema import ifcopenshell.util.shape +import ifcopenshell.util.shape_builder import ifcopenshell.util.system import ifcopenshell.util.unit @@ -497,6 +498,13 @@ def _get_element_value(element: ifcopenshell.entity_instance, keys: list[str]) - value = enh[("easting", "northing", "elevation").index(key)] else: value = None + elif key in ("rotation_x", "rotation_y", "rotation_z") and hasattr(value, "ObjectPlacement"): + if getattr(value, "ObjectPlacement", None): + matrix = ifcopenshell.util.placement.get_local_placement(value.ObjectPlacement) + euler = ifcopenshell.util.shape_builder.np_matrix_to_euler(matrix) + value = float(np.degrees(euler[("rotation_x", "rotation_y", "rotation_z").index(key)])) + else: + value = None elif isinstance(value, ifcopenshell.entity_instance): if key == "Name" and value.is_a("IfcMaterialLayerSet"): key = "LayerSetName" # This oddity in the IFC spec is annoying so we account for it. @@ -705,9 +713,9 @@ def set_element_value( return elif key == "classification": element = ifcopenshell.util.classification.get_references(element) - elif key in ("x", "y", "z", "easting", "northing", "elevation") and hasattr(element, "ObjectPlacement"): + elif key in ("x", "y", "z", "easting", "northing", "elevation", "rotation_x", "rotation_y", "rotation_z") and hasattr(element, "ObjectPlacement"): # TODO: add support - if key in ("easting", "northing", "elevation"): + if key in ("easting", "northing", "elevation", "rotation_x", "rotation_y", "rotation_z"): return placement = element.ObjectPlacement diff --git a/src/ifcopenshell-python/test/util/test_selector.py b/src/ifcopenshell-python/test/util/test_selector.py index 485a1b886a..5ee2267269 100644 --- a/src/ifcopenshell-python/test/util/test_selector.py +++ b/src/ifcopenshell-python/test/util/test_selector.py @@ -125,6 +125,22 @@ class TestGetElementValue(test.bootstrap.IFC4): element.Name = "Foobar" assert subject.get_element_value(element, "Name") == "Foobar" + def test_selecting_an_elements_rotation_using_a_query(self): + # Feature test for #6262: rotation_x/y/z value keys expose the + # placement's Euler angles in degrees, e.g. for GIS symbol placement. + ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") + ifcopenshell.api.unit.assign_unit(self.file) + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + theta = np.radians(30) + matrix = np.eye(4) + matrix[:2, :2] = [[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]] + ifcopenshell.api.geometry.edit_object_placement(self.file, product=element, matrix=matrix, is_si=False) + assert subject.get_element_value(element, "rotation_x") == pytest.approx(0.0) + assert subject.get_element_value(element, "rotation_y") == pytest.approx(0.0) + assert subject.get_element_value(element, "rotation_z") == pytest.approx(30.0) + element_without_placement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + assert subject.get_element_value(element_without_placement, "rotation_z") is None + def test_selecting_using_a_multiple_key_query(self): element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") material = ifcopenshell.api.material.add_material(self.file, name="CON01")