Dumb walls now auto update thicknesses if the wall layer thickness changes

This commit is contained in:
Dion Moult
2021-05-26 11:36:06 +10:00
parent 96465bed8f
commit f5f314a58f
2 changed files with 103 additions and 10 deletions
@@ -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"}