diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index 7ce6344b24..26b72dc96e 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -197,6 +197,9 @@ class DumbSlabGenerator: if not sum(thicknesses): return + self.body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") + self.footprint_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "FootPrint", "SKETCH_VIEW") + self.collection = bpy.context.view_layer.active_layer_collection.collection self.collection_obj = bpy.data.objects.get(self.collection.name) self.depth = sum(thicknesses) * unit_scale @@ -211,26 +214,12 @@ class DumbSlabGenerator: return self.create_slab(link_to_scene) def create_slab(self, link_to_scene): - verts = [ - Vector((0, 0, 0)), - Vector((0, self.width, 0)), - Vector((self.length, self.width, 0)), - Vector((self.length, 0, 0)), - ] - edges = [] - faces = [[0, 3, 2, 1]] - ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema) # Standard cases are deprecated, so let's cull them ifc_class = [c for c in ifc_classes if "StandardCase" not in c][0] - mesh = bpy.data.meshes.new(name="Dumb Slab") - mesh.from_pydata(verts, edges, faces) + mesh = bpy.data.meshes.new("Dummy") obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh) - modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY") - modifier.use_even_offset = True - modifier.offset = 1 - modifier.thickness = self.depth if link_to_scene: obj.location = self.location @@ -239,26 +228,38 @@ class DumbSlabGenerator: else: obj.location[2] -= self.depth self.collection.objects.link(obj) - bpy.ops.bim.assign_class( - obj=obj.name, + + element = blenderbim.core.root.assign_class( + tool.Ifc, + tool.Collector, + tool.Root, + obj=obj, ifc_class=ifc_class, - ifc_representation_class="IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids", + should_add_representation=False, + context=self.body_context, ) - blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=self.relating_type) - element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=self.relating_type) + + representation = ifcopenshell.api.run( + "geometry.add_slab_representation", tool.Ifc.get(), context=self.body_context, depth=self.depth + ) + ifcopenshell.api.run( + "geometry.assign_representation", tool.Ifc.get(), product=element, representation=representation + ) + blenderbim.core.geometry.switch_representation( + tool.Geometry, + obj=obj, + representation=representation, + should_reload=True, + enable_dynamic_voids=False, + is_global=True, + should_sync_changes_first=False, + ) + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric") ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer3"}) MaterialData.load(self.file) - try: - obj.select_set(True) - except RuntimeError: - - def msg(self, context): - txt = "The created object could not be assigned to a collection. " - txt += "Has any IfcSpatialElement been deleted?" - self.layout.label(text=txt) - - bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR") + obj.select_set(True) return obj @@ -288,6 +289,9 @@ class DumbSlabPlaner: self.change_thickness(element, thickness) def regenerate_from_type(self, usecase_path, ifc_file, settings): + obj = tool.Ifc.get_object(settings["related_object"]) + if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id: + return self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) new_material = ifcopenshell.util.element.get_material(settings["relating_type"]) if not new_material or not new_material.is_a("IfcMaterialLayerSet"): @@ -502,10 +506,14 @@ class EnableEditingExtrusionProfile(bpy.types.Operator): body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") extrusion = self.get_extrusion(body) - position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist()) - position[0][3] *= self.unit_scale - position[1][3] *= self.unit_scale - position[2][3] *= self.unit_scale + + if extrusion.Position: + position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist()) + position[0][3] *= self.unit_scale + position[1][3] *= self.unit_scale + position[2][3] *= self.unit_scale + else: + position = Matrix() z_values = [v[2] for v in obj.bound_box] origin = obj.matrix_world @ Vector((0, 0, max(z_values))) @@ -585,7 +593,7 @@ class EnableEditingExtrusionProfile(bpy.types.Operator): self.arcs[-1].append(len(self.vertices) - 1) is_arc = False else: - for local_point in curve.PointsCoordList: + for local_point in curve.Points.CoordList: global_point = position @ Vector(self.convert_unit_to_si(local_point)).to_3d() self.vertices.append(global_point) @@ -626,10 +634,13 @@ class EditExtrusionProfile(bpy.types.Operator): representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") extrusion = self.get_extrusion(representation) - position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist()) - position[0][3] *= self.unit_scale - position[1][3] *= self.unit_scale - position[2][3] *= self.unit_scale + if extrusion.Position: + position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist()) + position[0][3] *= self.unit_scale + position[1][3] *= self.unit_scale + position[2][3] *= self.unit_scale + else: + position = Matrix() helper = Helper(tool.Ifc.get()) indices = helper.auto_detect_arbitrary_profile_with_voids(obj, obj.data) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py new file mode 100644 index 0000000000..af6f0b062d --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py @@ -0,0 +1,89 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2022 Dion Moult +# +# 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 . + +import ifcopenshell.util.unit + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "context": None, # IfcGeometricRepresentationContext + "depth": 0.2, + # Planes are defined as a matrix. The XY plane is the clipping boundary and +Z is removed. + "clippings": [], # A list of planes that define clipping half space solids + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file) + return self.file.createIfcShapeRepresentation( + self.settings["context"], + self.settings["context"].ContextIdentifier, + "Clipping" if self.settings["clippings"] else "SweptSolid", + [self.create_item()], + ) + + def create_item(self): + size = self.convert_si_to_unit(1) + points = ( + (0.0, 0.0, 0.0), + (size, 0.0, 0.0), + (size, size, 0.0), + (0.0, size, 0.0), + (0.0, 0.0, 0.0), + ) + if self.file.schema == "IFC2X3": + curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points]) + else: + curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList3D(points)) + extrusion = self.file.createIfcExtrudedAreaSolid( + self.file.createIfcArbitraryClosedProfileDef("AREA", None, curve), + None, + self.file.createIfcDirection((0.0, 0.0, 1.0)), + self.convert_si_to_unit(self.settings["depth"]), + ) + if self.settings["clippings"]: + return self.apply_clippings(extrusion) + return extrusion + + def apply_clippings(self, first_operand): + while self.settings["clippings"]: + clipping = self.settings["clippings"].pop() + second_operand = self.file.createIfcHalfSpaceSolid( + self.file.createIfcPlane( + self.file.createIfcAxis2Placement3D( + self.file.createIfcCartesianPoint( + ( + self.convert_si_to_unit(clipping[0][3]), + self.convert_si_to_unit(clipping[1][3]), + self.convert_si_to_unit(clipping[2][3]), + ) + ), + self.file.createIfcDirection((clipping[0][2], clipping[1][2], clipping[2][2])), + self.file.createIfcDirection((clipping[0][0], clipping[1][0], clipping[2][0])), + ) + ), + False, + ) + first_operand = self.file.createIfcBooleanClippingResult("DIFFERENCE", first_operand, second_operand) + return first_operand + + def convert_si_to_unit(self, co): + return co / self.settings["unit_scale"] diff --git a/src/ifcopenshell-python/ifcopenshell/util/placement.py b/src/ifcopenshell-python/ifcopenshell/util/placement.py index 784aa4dd77..bcce72d3a1 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/placement.py +++ b/src/ifcopenshell-python/ifcopenshell/util/placement.py @@ -28,7 +28,7 @@ def a2p(o, z, x): def get_axis2placement(plc): - z = np.array(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1)) + z = np.array(plc.Axis.DirectionRatios if getattr(plc, "Axis", None) else (0, 0, 1)) x = np.array(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0)) o = plc.Location.Coordinates return a2p(o, z, x)