shape_builder.create_axis2_placement_3d utility method and docs

This commit is contained in:
Andrej730
2024-03-06 12:47:57 +05:00
parent 26e7742472
commit ad87d31e6a
2 changed files with 78 additions and 22 deletions
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import numpy as np import numpy as np
import ifcopenshell
def a2p(o, z, x): def a2p(o, z, x):
@@ -44,7 +45,7 @@ def a2p(o, z, x):
return r.T return r.T
def get_axis2placement(placement): def get_axis2placement(placement: ifcopenshell.entity_instance) -> np.array:
"""Parses an IfcAxis2Placement (2D or 3D) to a 4x4 transformation matrix """Parses an IfcAxis2Placement (2D or 3D) to a 4x4 transformation matrix
Note that this function only parses a single placement axis. If you want to Note that this function only parses a single placement axis. If you want to
@@ -202,6 +203,7 @@ def rotation(angle, axis, is_degrees=True):
theta = np.radians(angle) if is_degrees else angle theta = np.radians(angle) if is_degrees else angle
cos, sin = np.cos(theta), np.sin(theta) cos, sin = np.cos(theta), np.sin(theta)
# fmt: off
if axis == "X": if axis == "X":
return np.array([ return np.array([
[1, 0, 0, 0], [1, 0, 0, 0],
@@ -223,3 +225,4 @@ def rotation(angle, axis, is_degrees=True):
[0, 0, 1, 0], [0, 0, 1, 0],
[0, 0, 0, 1] [0, 0, 0, 1]
]) ])
# fmt: on
@@ -21,7 +21,7 @@ import collections
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
from math import cos, sin, pi, tan, radians, degrees, atan, sqrt, ceil from math import cos, sin, pi, tan, radians, degrees, atan, sqrt, ceil
from typing import List from typing import List, Tuple, Type, Union
from itertools import chain from itertools import chain
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
@@ -29,6 +29,9 @@ V = lambda *x: Vector([float(i) for i in x])
sign = lambda x: x and (1, -1)[x < 0] sign = lambda x: x and (1, -1)[x < 0]
PRECISION = 1.0e-5 PRECISION = 1.0e-5
VectorTuple = Type[Tuple[float, float, float]]
"tuple of 3 `float` values"
def is_x(value, x, si_conversion=None): def is_x(value, x, si_conversion=None):
if si_conversion: if si_conversion:
@@ -217,15 +220,24 @@ class ShapeBuilder:
def plane( def plane(
self, location: Vector = Vector((0.0, 0.0, 0.0)).freeze(), normal: Vector = Vector((0.0, 0.0, 1.0)).freeze() self, location: Vector = Vector((0.0, 0.0, 0.0)).freeze(), normal: Vector = Vector((0.0, 0.0, 1.0)).freeze()
): ) -> ifcopenshell.entity_instance:
location = self.file.createIfcCartesianPoint(location) """
direction = self.file.createIfcDirection(normal) Create IfcPlane.
:param location: plane position, defaults to `(0.0, 0.0, 0.0)`
:type location: Vector, optional
:param normal: plane normal direction, defaults to `(0.0, 0.0, 1.0)`
:type normal: Vector, optional
:return: IfcPlane
:rtype: ifcopenshell.entity_instance
"""
if normal.to_tuple(2) == Vector((0.0, 0.0, 1.0)): if normal.to_tuple(2) == Vector((0.0, 0.0, 1.0)):
arbitrary_vector = Vector((0.0, 1.0, 0.0)) arbitrary_vector = Vector((0.0, 1.0, 0.0))
else: else:
arbitrary_vector = Vector((0.0, 0.0, 1.0)) arbitrary_vector = Vector((0.0, 0.0, 1.0))
x_axis = self.file.createIfcDirection(normal.cross(arbitrary_vector).normalized()) x_axis = normal.cross(arbitrary_vector).normalized()
axis_placement = self.file.createIfcAxis2Placement3D(location, direction, x_axis) axis_placement = self.create_axis2_placement_3d(location, normal, x_axis)
return self.file.createIfcPlane(axis_placement) return self.file.createIfcPlane(axis_placement)
# TODO: explain points order for the curve_between_two_points # TODO: explain points order for the curve_between_two_points
@@ -465,8 +477,15 @@ class ShapeBuilder:
point_2d = relative_point + mirror_point point_2d = relative_point + mirror_point
return point_2d return point_2d
def get_axis2_placement_3d_matrix(self, axis2_placement_3d): def get_axis2_placement_3d_matrix(self, axis2_placement_3d: ifcopenshell.entity_instance) -> Matrix:
# > IfcAxis2Placement3D """
Generate a Matrix from IfcAxis2Placement3D.
:param axis2_placement_3d: IfcAxis2Placement3D
:type axis2_placement_3d: ifcopenshell.entity_instance
:return: generated matrix
:rtype: Matrix
"""
p = axis2_placement_3d p = axis2_placement_3d
M = Matrix.Identity(3) M = Matrix.Identity(3)
@@ -484,6 +503,48 @@ class ShapeBuilder:
return rotation_matrix return rotation_matrix
def create_axis2_placement_3d(
self,
position: VectorTuple = (0.0, 0.0, 0.0),
z_axis: VectorTuple = (0.0, 0.0, 1.0),
x_axis: VectorTuple = (1.0, 0.0, 0.0),
) -> ifcopenshell.entity_instance:
"""
Create IfcAxis2Placement3D.
:param position: placement position (Axis), defaults to `(0.0, 0.0, 0.0)`
:type position: VectorTuple, optional
:param z_axis: local Z axis direction, defaults to `(0.0, 0.0, 1.0)`
:type z_axis: VectorTuple, optional
:param x_axis: local X axis direction (RefDirection), defaults to `(1.0, 0.0, 0.0)`
:type x_axis: VectorTuple, optional
:return: IfcAxis2Placement3D
:rtype: ifcopenshell.entity_instance
"""
return self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint(position),
Axis=self.file.createIfcDirection(z_axis),
RefDirection=self.file.createIfcDirection(x_axis),
)
def create_axis2_placement_3d_from_matrix(
self,
matrix: Union[np.ndarray, None] = None,
) -> ifcopenshell.entity_instance:
"""
Create IfcAxis2Placement3D from numpy matrix.
:param matrix: 4x4 transformation matrix, defaults to `np.eye(4)`
:type matrix: np.array[np.array[float]], optional
:return: IfcAxis2Placement3D
:rtype: ifcopenshell.entity_instance
"""
if matrix is None:
matrix = np.eye(4, dtype=float)
return self.create_axis2_placement_3d(
position=matrix[:, 3][:3].tolist(), z_axis=matrix[:, 2][:3].tolist(), x_axis=matrix[:, 0][:3].tolist()
)
def mirror( def mirror(
self, self,
curve_or_item, curve_or_item,
@@ -599,22 +660,18 @@ class ShapeBuilder:
return processed_objects if (multiple_objects or multiple_transformations) else processed_objects[0] return processed_objects if (multiple_objects or multiple_transformations) else processed_objects[0]
def sphere(self, radius: float = 1.0, center: Vector = Vector((0, 0, 0)).freeze()) -> ifcopenshell.entity_instance: def sphere(self, radius: float = 1.0, center: VectorTuple = (0.0, 0.0, 0.0)) -> ifcopenshell.entity_instance:
""" """
:param radius: radius of the sphere, defaults to 1.0 :param radius: radius of the sphere, defaults to 1.0
:type radius: float, optional :type radius: float, optional
:param center: sphere position, defaults to zero-vector :param center: sphere position, defaults to `(0.0, 0.0, 0.0)`
:type center: Vector, optional :type center: VectorTuple, optional
:return: IfcSphere :return: IfcSphere
:rtype: ifcopenshell.entity_instance :rtype: ifcopenshell.entity_instance
""" """
ifc_position = self.file.createIfcAxis2Placement3D( ifc_position = self.create_axis2_placement_3d(position=center)
self.file.createIfcCartesianPoint(center), # position
self.file.createIfcDirection((0.0, 0.0, 1.0)), # Z-axis / Axis
self.file.createIfcDirection((1.0, 0.0, 0.0)), # X-axis / RefDirection
)
return self.file.createIfcSphere(Radius=radius, Position=ifc_position) return self.file.createIfcSphere(Radius=radius, Position=ifc_position)
def extrude( def extrude(
@@ -655,11 +712,7 @@ class ShapeBuilder:
if position_y_axis: if position_y_axis:
position_z_axis = position_x_axis.cross(position_y_axis) position_z_axis = position_x_axis.cross(position_y_axis)
ifc_position = self.file.createIfcAxis2Placement3D( ifc_position = self.create_axis2_placement_3d(position, position_z_axis, position_x_axis)
self.file.createIfcCartesianPoint(position), # position
self.file.createIfcDirection(position_z_axis), # Z-axis / Axis
self.file.createIfcDirection(position_x_axis), # X-axis / RefDirection
)
ifc_direction = self.file.createIfcDirection(extrusion_vector) ifc_direction = self.file.createIfcDirection(extrusion_vector)
extruded_area = self.file.createIfcExtrudedAreaSolid( extruded_area = self.file.createIfcExtrudedAreaSolid(
SweptArea=profile_or_curve, Position=ifc_position, ExtrudedDirection=ifc_direction, Depth=magnitude SweptArea=profile_or_curve, Position=ifc_position, ExtrudedDirection=ifc_direction, Depth=magnitude