mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 18:21:59 +00:00
Dumb walls now auto update thicknesses if the wall layer thickness changes
This commit is contained in:
@@ -133,11 +133,18 @@ class DumbWallAligner:
|
|||||||
self.reference_wall = reference_wall
|
self.reference_wall = reference_wall
|
||||||
|
|
||||||
def align_centerline(self):
|
def align_centerline(self):
|
||||||
self.align(self.reference_wall.matrix_world.translation, self.reference_wall.matrix_world @ Vector((1, 0, 0)))
|
width = (Vector(self.wall.bound_box[3]) - Vector(self.wall.bound_box[0])).y
|
||||||
|
reference_width = (Vector(self.reference_wall.bound_box[3]) - Vector(self.reference_wall.bound_box[0])).y
|
||||||
|
offset = self.wall.matrix_world.to_quaternion() @ Vector((0, (reference_width / 2) - (width / 2), 0))
|
||||||
|
self.align(
|
||||||
|
self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[0]),
|
||||||
|
self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[4]),
|
||||||
|
offset,
|
||||||
|
)
|
||||||
|
|
||||||
def align_exterior(self):
|
def align_exterior(self):
|
||||||
width = (Vector(self.wall.bound_box[3]) - Vector(self.wall.bound_box[0])).y
|
wall_width = (Vector(self.wall.bound_box[3]) - Vector(self.wall.bound_box[0])).y
|
||||||
offset = self.wall.matrix_world.to_quaternion() @ Vector((0, -width / 2, 0))
|
offset = self.wall.matrix_world.to_quaternion() @ Vector((0, -wall_width, 0))
|
||||||
self.align(
|
self.align(
|
||||||
self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[3]),
|
self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[3]),
|
||||||
self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[7]),
|
self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[7]),
|
||||||
@@ -145,13 +152,7 @@ class DumbWallAligner:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def align_interior(self):
|
def align_interior(self):
|
||||||
width = (Vector(self.wall.bound_box[3]) - Vector(self.wall.bound_box[0])).y
|
self.align(self.reference_wall.matrix_world.translation, self.reference_wall.matrix_world @ Vector((1, 0, 0)))
|
||||||
offset = self.wall.matrix_world.to_quaternion() @ Vector((0, width / 2, 0))
|
|
||||||
self.align(
|
|
||||||
self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[0]),
|
|
||||||
self.reference_wall.matrix_world @ Vector(self.reference_wall.bound_box[4]),
|
|
||||||
offset,
|
|
||||||
)
|
|
||||||
|
|
||||||
def align(self, start, end, offset=None):
|
def align(self, start, end, offset=None):
|
||||||
if offset is None:
|
if offset is None:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import ifcopenshell.util.element
|
|||||||
import ifcopenshell.util.representation
|
import ifcopenshell.util.representation
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from ifcopenshell.api.pset.data import Data as PsetData
|
from ifcopenshell.api.pset.data import Data as PsetData
|
||||||
|
from math import pi
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
|
|
||||||
|
|
||||||
@@ -104,6 +105,94 @@ def calculate_dumb_quantities(usecase_path, ifc_file, **settings):
|
|||||||
PsetData.load(ifc_file, obj.BIMObjectProperties.ifc_definition_id)
|
PsetData.load(ifc_file, obj.BIMObjectProperties.ifc_definition_id)
|
||||||
|
|
||||||
|
|
||||||
|
class DumbWallPlaner:
|
||||||
|
def regenerate_dumb_wall_thicknesses(self, usecase_path, ifc_file, **settings):
|
||||||
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||||
|
layer = settings["layer"]
|
||||||
|
thickness = settings["attributes"].get("LayerThickness")
|
||||||
|
if thickness is None or layer.LayerThickness == thickness:
|
||||||
|
return
|
||||||
|
delta_thickness = thickness - layer.LayerThickness
|
||||||
|
for layer_set in layer.ToMaterialLayerSet:
|
||||||
|
total_thickness = sum([l.LayerThickness for l in layer_set.MaterialLayers]) * self.unit_scale
|
||||||
|
if not total_thickness:
|
||||||
|
continue
|
||||||
|
for inverse in ifc_file.get_inverse(layer_set):
|
||||||
|
if not inverse.is_a("IfcMaterialLayerSetUsage"):
|
||||||
|
continue
|
||||||
|
if ifc_file.schema == "IFC2X3":
|
||||||
|
for rel in ifc_file.get_inverse(inverse):
|
||||||
|
if not rel.is_a("IfcRelAssociatesMaterial"):
|
||||||
|
continue
|
||||||
|
for element in rel.RelatedObjects:
|
||||||
|
self.regenerate_wall_thickness(element, delta_thickness)
|
||||||
|
else:
|
||||||
|
for rel in inverse.AssociatedTo:
|
||||||
|
for element in rel.RelatedObjects:
|
||||||
|
self.regenerate_wall_thickness(element, delta_thickness)
|
||||||
|
|
||||||
|
def regenerate_wall_thickness(self, element, delta_thickness):
|
||||||
|
parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric")
|
||||||
|
if not parametric or parametric["Engine"] != "BlenderBIM.DumbWall":
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
obj = IfcStore.id_map[element.id()]
|
||||||
|
obj.name # In case the object has been deleted
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||||
|
|
||||||
|
min_face, max_face = self.get_wall_end_faces(obj, bm)
|
||||||
|
|
||||||
|
self.thicken_face(min_face, delta_thickness)
|
||||||
|
self.thicken_face(max_face, delta_thickness)
|
||||||
|
|
||||||
|
bm.to_mesh(obj.data)
|
||||||
|
obj.data.update()
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
def thicken_face(self, face, delta_thickness):
|
||||||
|
slide_magnitude = abs(delta_thickness) / 2 * self.unit_scale
|
||||||
|
for vert in face.verts:
|
||||||
|
slide_vector = None
|
||||||
|
for edge in vert.link_edges:
|
||||||
|
other_vert = edge.verts[1] if edge.verts[0] == vert else edge.verts[0]
|
||||||
|
if delta_thickness > 0:
|
||||||
|
potential_slide_vector = vert.co - other_vert.co
|
||||||
|
else:
|
||||||
|
potential_slide_vector = other_vert.co - vert.co
|
||||||
|
if abs(potential_slide_vector.x) > 0.9 or abs(potential_slide_vector.z) > 0.9:
|
||||||
|
continue
|
||||||
|
slide_vector = potential_slide_vector
|
||||||
|
break
|
||||||
|
if not slide_vector:
|
||||||
|
continue
|
||||||
|
slide_vector *= (slide_magnitude / abs(slide_vector.y))
|
||||||
|
vert.co += slide_vector
|
||||||
|
|
||||||
|
# An end face is a quad that is on one end of the wall or the other. It must
|
||||||
|
# have at least one vertex on either extreme X-axis, and a non-insignificant
|
||||||
|
# X component of its face normal
|
||||||
|
def get_wall_end_faces(self, wall, bm):
|
||||||
|
min_face = None
|
||||||
|
max_face = None
|
||||||
|
min_x = min([v[0] for v in wall.bound_box])
|
||||||
|
max_x = max([v[0] for v in wall.bound_box])
|
||||||
|
bm.faces.ensure_lookup_table()
|
||||||
|
for f in bm.faces:
|
||||||
|
for v in f.verts:
|
||||||
|
if v.co.x == min_x and abs(f.normal.x) > 0.1:
|
||||||
|
min_face = f
|
||||||
|
elif v.co.x == max_x and abs(f.normal.x) > 0.1:
|
||||||
|
max_face = f
|
||||||
|
if min_face and max_face:
|
||||||
|
break
|
||||||
|
return min_face, max_face
|
||||||
|
|
||||||
|
|
||||||
class ActivateParametricEngine(bpy.types.Operator):
|
class ActivateParametricEngine(bpy.types.Operator):
|
||||||
bl_idname = "bim.activate_parametric_engine"
|
bl_idname = "bim.activate_parametric_engine"
|
||||||
bl_label = "Activate Parametric Engine"
|
bl_label = "Activate Parametric Engine"
|
||||||
@@ -116,4 +205,7 @@ class ActivateParametricEngine(bpy.types.Operator):
|
|||||||
ifcopenshell.api.add_post_listener(
|
ifcopenshell.api.add_post_listener(
|
||||||
"geometry.add_representation", IfcStore.get_file(), calculate_dumb_quantities
|
"geometry.add_representation", IfcStore.get_file(), calculate_dumb_quantities
|
||||||
)
|
)
|
||||||
|
ifcopenshell.api.add_pre_listener(
|
||||||
|
"material.edit_layer", IfcStore.get_file(), DumbWallPlaner().regenerate_dumb_wall_thicknesses
|
||||||
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
Reference in New Issue
Block a user