Compare commits

...

7 Commits

Author SHA1 Message Date
Andrej730 48bce57a65 shape_builder.create_axis2_placement_3d_from_matrix - small optimization
create_axis2_placement_3d now is fine with numpy arrays
2025-03-03 13:50:51 +05:00
Andrej730 bcb67cd77b TessellateElements - small optimization
tolist() supports conversion of multidimensional arrays too
2025-03-03 13:50:51 +05:00
Andrej730 77444b5cfd docs fixes
1) material.add_profile_set doesn't exist
2) usecase.file is not defined, use `model` instead`
3) Specify material for material.assign_material
2025-03-03 13:50:50 +05:00
Andrej730 6785b64756 edit_profile_usage - add test 2025-03-03 13:50:50 +05:00
Andrej730 8a44c0d545 Quick to select profile from BIM Tool in Profile UI
Example - https://imgur.com/a/tJdQUGq
2025-03-03 13:50:50 +05:00
Andrej730 68f98d457e Simplify edit_profile_usage with util.shape 2025-03-03 13:50:50 +05:00
Andrej730 512a9207ef ifcopenshell.util.shape to return Python floats if return is just a scalar 2025-03-03 13:50:50 +05:00
11 changed files with 110 additions and 55 deletions
@@ -406,6 +406,9 @@ class EditItemUI:
if mesh_props.item_profile == "-":
op = row.operator("bim.name_profile", text="", icon="TAG")
op.extrusion_item_obj = obj.name
else:
op = row.operator("bim.profiles_ui_select", icon="ZOOM_SELECTED", text="")
op.profile_id = int(mesh_props.item_profile)
for item_attribute in mesh_props.item_attributes:
row = cls.layout.row()
@@ -342,7 +342,7 @@ class SelectProfileInProfilesUI(bpy.types.Operator):
props.active_profile_index = profile_index
self.report(
{"INFO"},
f"Profile '{profile.Name or 'Unnamed'}' is selected in Profiles UI.",
f"Profile '{profile.ProfileName or 'Unnamed'}' is selected in Profiles UI.",
)
return {"FINISHED"}
@@ -67,7 +67,7 @@ def add_profile(
# First, let's create a material set. This will later be assigned
# to our beam type element.
material_set = ifcopenshell.api.material.add_profile_set(model,
material_set = ifcopenshell.api.material.add_material_set(model,
name="B1", set_type="IfcMaterialProfileSet")
# Create a steel material.
@@ -48,7 +48,7 @@ def assign_profile(
# First, let's create a material set. This will later be assigned
# to our beam type element.
material_set = ifcopenshell.api.material.add_profile_set(model,
material_set = ifcopenshell.api.material.add_material_set(model,
name="B1", set_type="IfcMaterialProfileSet")
# Create a steel material.
@@ -56,7 +56,7 @@ def assign_profile(
# Create an I-beam profile curve. Notice how we name our profiles
# based on standardised steel profile names.
hea100 = usecase.file.create_entity(
hea100 = model.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
)
@@ -84,7 +84,7 @@ def assign_profile(
# Now let's change the profile to a HEA200 standard profile instead.
# This will automatically change the body representation that we
# just added as well to a HEA200 profile.
hea200 = usecase.file.create_entity(
hea200 = model.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA200", ProfileType="AREA",
OverallWidth=200, OverallDepth=190, WebThickness=6.5, FlangeThickness=10, FilletRadius=18,
)
@@ -46,7 +46,7 @@ def edit_profile(
.. code:: python
# Let's create a material set to store our profiles.
material_set = ifcopenshell.api.material.add_profile_set(model,
material_set = ifcopenshell.api.material.add_material_set(model,
name="B1", set_type="IfcMaterialProfileSet")
# Create a couple steel materials.
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.geom
import ifcopenshell.util.representation
import ifcopenshell.util.shape
from ifcopenshell.geom import ShapeType
from typing import Any
@@ -49,7 +50,7 @@ def edit_profile_usage(
# First, let's create a material set. This will later be assigned
# to our beam type element.
material_set = ifcopenshell.api.material.add_profile_set(model,
material_set = ifcopenshell.api.material.add_material_set(model,
name="B1", set_type="IfcMaterialProfileSet")
# Create a steel material.
@@ -57,7 +58,7 @@ def edit_profile_usage(
# Create an I-beam profile curve. Notice how we name our profiles
# based on standardised steel profile names.
hea100 = usecase.file.create_entity(
hea100 = model.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
)
@@ -73,7 +74,7 @@ def edit_profile_usage(
# Let's create an occurrence of this beam.
beam = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBeam", name="B1.01")
rel = ifcopenshell.api.material.assign_material(model,
rel = ifcopenshell.api.material.assign_material(model, material=material_set,
products=[beam], type="IfcMaterialProfileSetUsage")
# Let's give a 1000mm long beam body representation.
@@ -168,62 +169,42 @@ class Usecase:
return self.get_top_right(shape)
def get_bottom_left(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
y = [v[i + 1] for i in range(0, len(v), 3)]
width = max(x) - min(x)
height = max(y) - min(y)
width = ifcopenshell.util.shape.get_x(shape)
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, height / 2, 0.0)))
def get_bottom_centre(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts
y = [v[i + 1] for i in range(0, len(v), 3)]
height = max(y) - min(y)
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, height / 2, 0.0)))
def get_bottom_right(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
y = [v[i + 1] for i in range(0, len(v), 3)]
width = max(x) - min(x)
height = max(y) - min(y)
width = ifcopenshell.util.shape.get_x(shape)
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, height / 2, 0.0)))
def get_mid_depth_left(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
width = max(x) - min(x)
width = ifcopenshell.util.shape.get_x(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, 0.0, 0.0)))
def get_mid_depth_centre(self, shape: ShapeType) -> ifcopenshell.entity_instance:
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)))
def get_mid_depth_right(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
width = max(x) - min(x)
width = ifcopenshell.util.shape.get_x(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, 0.0, 0.0)))
def get_top_left(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
y = [v[i + 1] for i in range(0, len(v), 3)]
width = max(x) - min(x)
height = max(y) - min(y)
width = ifcopenshell.util.shape.get_x(shape)
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, -height / 2, 0.0)))
def get_top_centre(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts
y = [v[i + 1] for i in range(0, len(v), 3)]
height = max(y) - min(y)
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, -height / 2, 0.0)))
def get_top_right(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts
x = [v[i] for i in range(0, len(v), 3)]
y = [v[i + 1] for i in range(0, len(v), 3)]
width = max(x) - min(x)
height = max(y) - min(y)
width = ifcopenshell.util.shape.get_x(shape)
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, -height / 2, 0.0)))
def update_representation(self, element: ifcopenshell.entity_instance) -> None:
@@ -40,7 +40,7 @@ def remove_profile(
.. code:: python
# First, let's create a material set.
material_set = ifcopenshell.api.material.add_profile_set(model,
material_set = ifcopenshell.api.material.add_material_set(model,
name="B1", set_type="IfcMaterialProfileSet")
# Create a steel material.
@@ -38,6 +38,12 @@ MatrixType = npt.NDArray[np.float64]
# NOTE: See IfcGeomRepresentation.h for ShapeType buffer types.
# NOTE: For fubction that retunr a single scalar ensure to use .item() to
# return the Python float instead of numpy float
# as it's less intrusive (doesn't promote numpy arrays on interactions),
# doesn't fail saving to IFC
# and precise enough anyway (internally Python floats are doubles).
def is_x(value: float, x: float, tolerance: Optional[float] = None) -> bool:
"""Checks whether a value is equivalent to X given a tolerance
@@ -89,7 +95,7 @@ def get_x(geometry: ShapeType) -> float:
:return: The X dimension
"""
verts_flat = get_vertices(geometry).ravel()
return np.max(verts_flat[0::3]) - np.min(verts_flat[0::3])
return (np.max(verts_flat[0::3]) - np.min(verts_flat[0::3])).item()
def get_y(geometry: ShapeType) -> float:
@@ -99,7 +105,7 @@ def get_y(geometry: ShapeType) -> float:
:return: The Y dimension
"""
verts_flat = get_vertices(geometry).ravel()
return np.max(verts_flat[1::3]) - np.min(verts_flat[1::3])
return (np.max(verts_flat[1::3]) - np.min(verts_flat[1::3])).item()
def get_z(geometry: ShapeType) -> float:
@@ -109,7 +115,7 @@ def get_z(geometry: ShapeType) -> float:
:return: The Z dimension
"""
verts_flat = get_vertices(geometry).ravel()
return np.max(verts_flat[2::3]) - np.min(verts_flat[2::3])
return (np.max(verts_flat[2::3]) - np.min(verts_flat[2::3])).item()
def get_max_xy(geometry: ShapeType) -> float:
@@ -351,8 +357,8 @@ def get_bottom_elevation(geometry: ShapeType) -> float:
:param geometry: Geometry output calculated by IfcOpenShell
:return: The Z value
"""
z_values = [geometry.verts[i + 2] for i in range(0, len(geometry.verts), 3)]
return min(z_values)
verts_flat = get_vertices(geometry).ravel()
return np.min(verts_flat[2::3]).item()
def get_top_elevation(geometry: ShapeType) -> float:
@@ -362,7 +368,7 @@ def get_top_elevation(geometry: ShapeType) -> float:
:return: The Z value
"""
verts_flat = get_vertices(geometry).ravel()
return np.max(verts_flat[2::3])
return np.max(verts_flat[2::3]).item()
def get_shape_bottom_elevation(shape: ShapeType, geometry: ShapeType) -> float:
@@ -447,7 +453,7 @@ def get_area_vf(vertices: npt.NDArray[np.float64], faces: npt.NDArray[np.int32])
# Sum up the areas to get the total area of the mesh
mesh_area = np.sum(triangle_areas)
return mesh_area
return mesh_area.item()
def get_area(geometry: ShapeType) -> float:
@@ -674,7 +680,7 @@ def get_footprint_perimeter(geometry: ShapeType) -> float:
else:
all_edges.add(edge)
return sum([np.linalg.norm(vertices[e[0]] - vertices[e[1]]) for e in (all_edges - shared_edges)])
return np.sum([np.linalg.norm(vertices[e[0]] - vertices[e[1]]) for e in (all_edges - shared_edges)]).item()
def get_profiles(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
@@ -723,4 +729,4 @@ def get_total_edge_length(geometry: ShapeType) -> float:
"""
vertices = get_vertices(geometry)
vertices = vertices[get_edges(geometry)]
return np.linalg.norm(vertices[:, 1] - vertices[:, 0], axis=1).sum()
return np.linalg.norm(vertices[:, 1] - vertices[:, 0], axis=1).sum().item()
@@ -743,9 +743,7 @@ class ShapeBuilder:
"""
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()
)
return self.create_axis2_placement_3d(position=matrix[:3, 3], z_axis=matrix[:3, 2], x_axis=matrix[:3, 0])
def create_axis2_placement_2d(
self, position: VectorType = (0.0, 0.0), x_direction: Optional[VectorType] = None
@@ -0,0 +1,67 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import test.bootstrap
import ifcopenshell.api.context
import ifcopenshell.api.geometry
import ifcopenshell.api.material
import ifcopenshell.api.root
import ifcopenshell.util.placement
from ifcopenshell.util.shape_builder import ShapeBuilder
# IfcMaterialProfileSetUsage added in IFC4.
class TestEditProfileUsageIFC4(test.bootstrap.IFC4):
def test_update_cardinal_point(self):
model = self.file
builder = ShapeBuilder(model)
ifcopenshell.api.root.create_entity(model, ifc_class="IfcProject")
model_context = ifcopenshell.api.context.add_context(model, context_type="Model")
body = ifcopenshell.api.context.add_context(
model, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model_context
)
material_set = ifcopenshell.api.material.add_material_set(model, name="B1", set_type="IfcMaterialProfileSet")
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
rectangle = builder.rectangle((100, 100))
profile = builder.profile(rectangle)
ifcopenshell.api.material.add_profile(model, profile_set=material_set, material=steel, profile=profile)
beam = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBeam", name="B1.01")
rel = ifcopenshell.api.material.assign_material(
model, material=material_set, products=[beam], type="IfcMaterialProfileSetUsage"
)
assert isinstance(rel, ifcopenshell.entity_instance)
usage = rel.RelatingMaterial
assert usage.CardinalPoint is None
representation = ifcopenshell.api.geometry.add_profile_representation(
model,
context=body,
profile=profile,
depth=1000,
cardinal_point=5,
)
assert representation.Items[0].Position.Location.Coordinates == (0.0, 0.0, 0.0)
ifcopenshell.api.geometry.assign_representation(model, product=beam, representation=representation)
ifcopenshell.api.material.edit_profile_usage(model, usage=rel.RelatingMaterial, attributes={"CardinalPoint": 1})
assert representation.Items[0].Position.Location.Coordinates == (-50.0, 50.0, 0.0)
class TestEditProfileUsageIFC4X3(test.bootstrap.IFC4X3, TestEditProfileUsageIFC4):
pass
@@ -89,7 +89,7 @@ class Patcher:
shape: Union[ifcopenshell.geom.ShapeType, ifcopenshell.geom.ShapeElementType],
) -> None:
geometry = getattr(shape, "geometry", shape)
v = [[x.tolist() for x in ifcopenshell.util.shape.get_vertices(geometry)]]
v = [ifcopenshell.util.shape.get_vertices(geometry).tolist()]
f = [ifcopenshell.util.shape.get_faces(geometry).tolist()]
replacements[element] = (v, f)