mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Selector: add rotation_x/y/z value keys #6262
Expose the Euler rotation of an element's placement in degrees through get_element_value, alongside the existing x/y/z and easting/northing/ elevation keys. This makes element rotation exportable through ifccsv, e.g. for placing oriented symbols in GIS. Adopts the approach agreed in the review of the stale PR #6272 by @TZwielehner: reuse util.shape_builder.np_matrix_to_euler and do the degree conversion inside get_element_value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Dion Moult
parent
69a4be68e8
commit
d4805387ef
@@ -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."
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -491,6 +492,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.
|
||||
@@ -699,9 +707,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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user