From f5f314a58fce78a0ac81a832543e57093574e0bb Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 26 May 2021 11:36:06 +1000 Subject: [PATCH] Dumb walls now auto update thicknesses if the wall layer thickness changes --- .../blenderbim/bim/module/model/operator.py | 21 +++-- .../bim/module/parametric/operator.py | 92 +++++++++++++++++++ 2 files changed, 103 insertions(+), 10 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/operator.py b/src/blenderbim/blenderbim/bim/module/model/operator.py index ac7e97a1d9..1f64aa0457 100644 --- a/src/blenderbim/blenderbim/bim/module/model/operator.py +++ b/src/blenderbim/blenderbim/bim/module/model/operator.py @@ -133,11 +133,18 @@ class DumbWallAligner: self.reference_wall = reference_wall 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): - 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)) + wall_width = (Vector(self.wall.bound_box[3]) - Vector(self.wall.bound_box[0])).y + offset = self.wall.matrix_world.to_quaternion() @ Vector((0, -wall_width, 0)) 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[7]), @@ -145,13 +152,7 @@ class DumbWallAligner: ) def align_interior(self): - 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)) - 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, - ) + self.align(self.reference_wall.matrix_world.translation, self.reference_wall.matrix_world @ Vector((1, 0, 0))) def align(self, start, end, offset=None): if offset is None: diff --git a/src/blenderbim/blenderbim/bim/module/parametric/operator.py b/src/blenderbim/blenderbim/bim/module/parametric/operator.py index e55c1628be..43cf4bcb0a 100644 --- a/src/blenderbim/blenderbim/bim/module/parametric/operator.py +++ b/src/blenderbim/blenderbim/bim/module/parametric/operator.py @@ -7,6 +7,7 @@ import ifcopenshell.util.element import ifcopenshell.util.representation from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.pset.data import Data as PsetData +from math import pi 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) +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): bl_idname = "bim.activate_parametric_engine" bl_label = "Activate Parametric Engine" @@ -116,4 +205,7 @@ class ActivateParametricEngine(bpy.types.Operator): ifcopenshell.api.add_post_listener( "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"}