See #1227. Implement updating of object position.

This commit is contained in:
Dion Moult
2025-03-09 08:52:05 +11:00
parent af2dd1d7c6
commit e5027e0be8
3 changed files with 63 additions and 17 deletions
@@ -34,6 +34,27 @@ def edit_object_placement(
is_si: bool = True,
should_transform_children: bool = False,
) -> ifcopenshell.entity_instance:
"""Changes the object placement matrix of an element
The placement matrix is a 4x4 matrix describing the location and
orientation of an element in 3D. See
https://docs.ifcopenshell.org/ifcopenshell-python/geometry_creation.html#object-placements
for more details.
This only supports local placements. Grid and linear placements are not
supported.
:param matrix: A 4x4 matrix in numpy. If left blank, it is the identity
matrix (equivalent to ``np.eye(4)``).
:param is_si: If True, the matrix is given in SI units. If false, in
project units.
:param should_transform_children: A child element is a nested element,
opening, filling, etc. If true, child elements will move along with the
parent. If false, child elements will stay where they are. Because most
placements in IFC are relative, this means that if a child moves, we
actually don't change their placement.
:return: The new or updated IfcLocalPlacement entity
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {
@@ -19,9 +19,10 @@
import numpy as np
import ifcopenshell
import ifcopenshell.api.geometry
import ifcopenshell.util.shape_builder
import ifcopenshell.util.element
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.shape_builder
from collections import namedtuple
from math import sin, cos
from typing import Optional
@@ -63,6 +64,14 @@ def regenerate_wall_representation(
additional extrusions are generated for each connection that boolean
difference the base extrusion.
This will also update the axis line representation (e.g. trim the axis line
to any connections).
The wall's object placement will also be updated such that the placement is
equivalent to the axis line's start point (which therefore becomes (0.0,
0.0)). This is a logical, consistent, and useful placement coordinate
(especially for apps that can pivot using this point).
:param wall: The IfcWall for the representation,
only Model/Body/MODEL_VIEW type of representations are currently supported.
:param length: If the wall doesn't have an axis length, this is the default
@@ -165,7 +174,7 @@ class Regenerator:
end_points.reverse()
points.extend(end_points)
item = builder.extrude(
builder.polyline(points, closed=True),
builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1),
magnitude=self.wall_vectors["d"],
extrusion_vector=self.wall_vectors["z"],
)
@@ -186,7 +195,9 @@ class Regenerator:
magnitude = np.linalg.norm(self.start_vector * (self.wall_vectors["h"] / self.start_vector[2]))
operands.append(
builder.extrude(
builder.polyline(points, closed=True), magnitude=magnitude, extrusion_vector=self.start_vector
builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1),
magnitude=magnitude,
extrusion_vector=self.start_vector,
)
)
@@ -206,7 +217,9 @@ class Regenerator:
magnitude = np.linalg.norm(self.end_vector * (self.wall_vectors["h"] / self.end_vector[2]))
operands.append(
builder.extrude(
builder.polyline(points, closed=True), magnitude=magnitude, extrusion_vector=self.end_vector
builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1),
magnitude=magnitude,
extrusion_vector=self.end_vector,
)
)
@@ -216,7 +229,9 @@ class Regenerator:
magnitude = np.linalg.norm(atpath_vector * (self.wall_vectors["h"] / atpath_vector[2]))
operands.append(
builder.extrude(
builder.polyline(points, closed=True), magnitude=magnitude, extrusion_vector=atpath_vector
builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1),
magnitude=magnitude,
extrusion_vector=atpath_vector,
)
)
@@ -265,10 +280,14 @@ class Regenerator:
remaining_path_points.append(minpath_points)
self.minpath_points = remaining_path_points
profiles.append(builder.profile(builder.polyline(points, closed=True)))
profiles.append(
builder.profile(builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1))
)
for points in self.maxpath_points + self.minpath_points:
profiles.append(builder.profile(builder.polyline(points, closed=True)))
profiles.append(
builder.profile(builder.polyline(points, closed=True, position_offset=self.reference_p1 * -1))
)
if len(profiles) > 1:
profile = self.file.createIfcCompositeProfileDef("AREA", Profiles=profiles)
@@ -283,13 +302,21 @@ class Regenerator:
else:
ifcopenshell.api.geometry.assign_representation(self.file, product=wall, representation=body_rep)
item = builder.polyline([self.reference_p1, self.reference_p2])
item = builder.polyline([self.reference_p1, self.reference_p2], position_offset=self.reference_p1 * -1)
axis_rep = builder.get_representation(self.axis, items=[item])
if old_rep := ifcopenshell.util.representation.get_representation(wall, self.axis):
ifcopenshell.util.element.replace_element(old_rep, axis_rep)
ifcopenshell.util.element.remove_deep2(self.file, old_rep)
else:
ifcopenshell.api.geometry.assign_representation(self.file, product=wall, representation=axis_rep)
if not np.allclose(self.reference_p1, np.array((0.0, 0.0))):
matrix = ifcopenshell.util.placement.get_local_placement(wall.ObjectPlacement)
matrix[:, 3] = matrix @ np.concatenate((self.reference_p1, (0, 1)))
ifcopenshell.api.geometry.edit_object_placement(
self.file, product=wall, matrix=matrix, is_si=False, should_transform_children=False
)
return body_rep
def join(self, wall1, wall2, layers1, layers2, connection1, connection2):