Files
IfcOpenShell/src/bonsai/bonsai/bim/module/model/slab.py
T
Andrej730 6dfb25862c black .
2024-12-23 18:59:55 +05:00

902 lines
35 KiB
Python

# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import json
import bmesh
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import ifcopenshell.util.type
import bonsai.core.type
import bonsai.core.geometry
import bonsai.core.root
import bonsai.tool as tool
from mathutils import Vector, Matrix
from bonsai.bim.module.geometry.helper import Helper
from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator
from bonsai.bim.module.model.polyline import PolylineOperator
from bonsai.bim.module.model.wall import DumbWallRecalculator
from typing import Optional
class DumbSlabGenerator:
def __init__(self, relating_type: ifcopenshell.entity_instance):
self.relating_type = relating_type
def generate(self, insertion_type="CURSOR"):
self.file = tool.Ifc.get()
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
thicknesses = []
for rel in self.relating_type.HasAssociations:
if rel.is_a("IfcRelAssociatesMaterial"):
material = rel.RelatingMaterial
if material.is_a("IfcMaterialLayerSet"):
thicknesses = [l.LayerThickness for l in material.MaterialLayers]
break
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"
)
props = bpy.context.scene.BIMModelProperties
self.polyline = None
self.container = None
self.container_obj = None
if container := tool.Root.get_default_container():
self.container = container
self.container_obj = tool.Ifc.get_object(container)
self.depth = sum(thicknesses) * self.unit_scale
self.width = 3
self.length = 3
self.rotation = 0
self.location = Vector((0, 0, 0))
self.x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
if insertion_type == "POLYLINE":
return self.derive_from_polyline()
elif insertion_type == "WALLS":
return self.derive_from_walls()
elif insertion_type == "CURSOR":
return self.derive_from_cursor()
def derive_from_polyline(self):
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
self.location = Vector((polyline_points[0].x, polyline_points[0].y, self.container_obj.location.z))
self.polyline = [tuple(Vector((p.x, p.y, 0.0)) - self.location) for p in polyline_points]
if len(self.polyline) <= 2:
return
# Always assume a closed polyline
if self.polyline[0] != self.polyline[-1]:
self.polyline.append(self.polyline[0])
return self.create_slab()
def derive_from_cursor(self):
self.location = bpy.context.scene.cursor.location
return self.create_slab()
def derive_from_walls(self):
walls, is_closed_loop = tool.Model.get_connected_walls(bpy.context.selected_objects)
polyline_points = []
poly = tool.Model.get_polygons_from_wall_axis(walls)
polyline_points = [tuple([v for v in c]) for c in poly.exterior.coords]
self.location = Vector((polyline_points[0][0], polyline_points[0][1], self.container_obj.location.z))
self.polyline = [tuple(Vector((p[0], p[1], 0.0)) - self.location) for p in polyline_points]
if len(self.polyline) <= 2:
return
# Always assume a closed polyline
if self.polyline[0] != self.polyline[-1]:
self.polyline.append(self.polyline[0])
return self.create_slab()
def create_slab(self):
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("Dummy")
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
matrix_world = Matrix()
matrix_world.translation = self.location
if self.container_obj:
matrix_world.translation.z = self.container_obj.location.z - self.depth
else:
matrix_world.translation.z -= self.depth
obj.matrix_world = Matrix.Rotation(self.x_angle, 4, "X") @ matrix_world
bpy.context.view_layer.update()
element = bonsai.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
ifc_class=ifc_class,
should_add_representation=False,
)
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
representation = ifcopenshell.api.run(
"geometry.add_slab_representation",
tool.Ifc.get(),
context=self.body_context,
depth=self.depth,
x_angle=self.x_angle,
polyline=self.polyline,
)
ifcopenshell.api.run(
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=representation
)
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
if self.footprint_context:
extrusion = tool.Model.get_extrusion(representation)
if extrusion.SweptArea.is_a("IfcArbitraryClosedProfileDef"):
curves = [extrusion.SweptArea.OuterCurve]
if extrusion.SweptArea.is_a("IfcArbitraryProfileDefWithVoids"):
curves.extend(extrusion.SweptArea.InnerCurves)
representation = ifcopenshell.api.run(
"geometry.add_footprint_representation",
tool.Ifc.get(),
context=self.footprint_context,
curves=curves,
)
ifcopenshell.api.run(
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=representation
)
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": "Bonsai.DumbLayer3"})
obj.select_set(True)
return obj
class DumbSlabPlaner:
def regenerate_from_layer(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:
return
for layer_set in layer.ToMaterialLayerSet:
total_thickness = sum([l.LayerThickness for l in layer_set.MaterialLayers])
if not total_thickness:
continue
for inverse in ifc_file.get_inverse(layer_set):
if not inverse.is_a("IfcMaterialLayerSetUsage") or inverse.LayerSetDirection != "AXIS3":
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.change_thickness(element, total_thickness)
else:
for rel in inverse.AssociatedTo:
for element in rel.RelatedObjects:
self.change_thickness(element, total_thickness)
def regenerate_from_type(self, usecase_path, ifc_file, settings):
relating_type = settings["relating_type"]
new_material = ifcopenshell.util.element.get_material(relating_type)
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
return
parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
layer_set_direction = None
if parametric:
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
for related_object in settings["related_objects"]:
self._regenerate_from_type(related_object, layer_set_direction, new_thickness)
def _regenerate_from_type(
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str], new_thickness: float
) -> None:
obj = tool.Ifc.get_object(related_object)
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
return
material = ifcopenshell.util.element.get_material(related_object)
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
return
if layer_set_direction:
material.LayerSetDirection = layer_set_direction
if material.LayerSetDirection == "AXIS3":
self.change_thickness(related_object, new_thickness)
def change_thickness(self, element: ifcopenshell.entity_instance, thickness: float) -> None:
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
obj = tool.Ifc.get_object(element)
if not obj:
return
delta_thickness = (thickness * self.unit_scale) - obj.dimensions.z
if round(delta_thickness, 2) == 0:
return
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if representation:
extrusion = tool.Model.get_extrusion(representation)
if extrusion:
extrusion.Depth = thickness
else:
props = bpy.context.scene.BIMModelProperties
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
new_rep = ifcopenshell.api.run(
"geometry.add_slab_representation",
tool.Ifc.get(),
context=body_context,
depth=thickness * self.unit_scale,
x_angle=x_angle,
)
for inverse in tool.Ifc.get().get_inverse(representation):
ifcopenshell.util.element.replace_attribute(inverse, representation, new_rep)
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=new_rep,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
bonsai.core.geometry.remove_representation(
tool.Ifc, tool.Geometry, obj=obj, representation=representation
)
return
else:
props = bpy.context.scene.BIMModelProperties
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
representation = ifcopenshell.api.run(
"geometry.add_slab_representation",
tool.Ifc.get(),
context=body_context,
depth=thickness * self.unit_scale,
x_angle=x_angle,
)
ifcopenshell.api.run(
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=representation
)
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
obj.location[2] -= delta_thickness
class EnableEditingSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_sketch_extrusion_profile"
bl_label = "Enable Editing Sketch Extrusion Profile"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects
def _execute(self, context):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
obj = context.active_object
element = tool.Ifc.get_entity(obj)
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric", None)
if pset and pset["Engine"] == "CADSketcher" and pset["Entities"]:
context.scene["sketcher"].update(json.loads(pset["Entities"]))
bpy.context.view_layer.update()
bpy.ops.wm.tool_set_by_id(name="sketcher.slvs_select")
bpy.ops.view3d.slvs_set_all_constraints_visibility(visibility="SHOW")
return {"FINISHED"}
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
extrusion = tool.Model.get_extrusion(body)
profile = extrusion.SweptArea
if extrusion.Position:
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
position.matrix_world.translation *= self.unit_scale
else:
position = Matrix()
z_values = [v[2] for v in obj.bound_box]
element = tool.Ifc.get_entity(obj)
entities = context.scene.sketcher.entities
entities.ensure_origin_elements(context)
origin = entities.add_point_3d(obj.matrix_world @ Vector((0, 0, max(z_values))))
origin.fixed = True
origin.origin = True
normal = entities.add_normal_3d(obj.matrix_world.to_quaternion())
normal.fixed = True
normal.origin = True
wp = entities.add_workplane(origin, normal)
sketch = entities.add_sketch(wp)
self.vertices = []
self.edges = []
self.arcs = []
self.circles = []
profile = extrusion.SweptArea
if profile.is_a("IfcArbitraryClosedProfileDef"):
self.process_curve(obj, position, profile.OuterCurve)
if profile.is_a("IfcArbitraryProfileDefWithVoids"):
for inner_curve in profile.InnerCurves:
self.process_curve(obj, position, inner_curve)
points = []
for vertex in self.vertices:
points.append(context.scene.sketcher.entities.add_point_2d(vertex.to_2d(), sketch))
for edge in self.edges:
context.scene.sketcher.entities.add_line_2d(points[edge[0]], points[edge[1]], sketch)
bpy.ops.view3d.slvs_set_active_sketch(index=sketch.slvs_index)
return {"FINISHED"}
def process_curve(self, obj, position, curve):
offset = len(self.vertices)
if curve.is_a("IfcPolyline"):
total_points = len(curve.Points)
last_index = len(curve.Points) - 1
for i, point in enumerate(curve.Points):
if i == last_index:
continue
global_point = position @ Vector(self.convert_unit_to_si(point.Coordinates)).to_3d()
self.vertices.append(global_point)
self.edges.extend([(i, i + 1) for i in range(offset, len(self.vertices))])
self.edges[-1] = (len(self.vertices) - 1, offset) # Close the loop
elif curve.is_a("IfcIndexedPolyCurve"):
is_arc = False
if curve.Segments:
for segment in curve.Segments:
if len(segment[0]) == 3: # IfcArcIndex
is_arc = True
local_point = self.convert_unit_to_si(curve.Points.CoordList[segment[0][0] - 1])
global_point = position @ Vector(local_point).to_3d()
self.vertices.append(global_point)
local_point = self.convert_unit_to_si(curve.Points.CoordList[segment[0][1] - 1])
global_point = position @ Vector(local_point).to_3d()
self.vertices.append(global_point)
self.arcs.append([len(self.vertices) - 2, len(self.vertices) - 1])
else:
local_point = self.convert_unit_to_si(curve.Points.CoordList[segment[0][0] - 1])
global_point = position @ Vector(local_point).to_3d()
self.vertices.append(global_point)
if is_arc:
self.arcs[-1].append(len(self.vertices) - 1)
is_arc = False
else:
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)
# Curves without segments are self closing
del self.vertices[-1]
self.edges.extend([(i, i + 1) for i in range(offset, len(self.vertices))])
self.edges[-1] = (len(self.vertices) - 1, offset) # Close the loop
elif curve.is_a("IfcCircle"):
center = self.convert_unit_to_si(
Matrix(ifcopenshell.util.placement.get_axis2placement(curve.Position).tolist()).translation
)
radius = self.convert_unit_to_si(curve.Radius)
self.vertices.extend(
[
position @ Vector((center[0], center[1] - curve.Radius, 0.0)),
position @ Vector((center[0], center[1] + curve.Radius, 0.0)),
]
)
self.circles.append([offset, offset + 1])
self.edges.append((offset, offset + 1))
def convert_unit_to_si(self, value):
if isinstance(value, (tuple, list)):
return [v * self.unit_scale for v in value]
return value * self.unit_scale
class EditSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_sketch_extrusion_profile"
bl_label = "Edit Sketch Extrusion Profile"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
extrusion = tool.Model.get_extrusion(representation)
if extrusion.Position:
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
else:
position = Matrix()
position_i = position.inverted()
cad_sketcher = __import__("CAD_Sketcher-main")
sketch = context.scene.sketcher.active_sketch
converter = cad_sketcher.convertors.BezierConverter(context.scene, sketch)
converter.run()
profile = tool.Ifc.get().createIfcArbitraryClosedProfileDef("AREA")
for path in converter.paths:
points = []
lines = path[0]
last_index = len(lines) - 1
for i, line in enumerate(lines):
local_point = (position_i @ Vector(line.p1.co).to_3d()).to_2d()
points.append(tool.Ifc.get().createIfcCartesianPoint(local_point))
points.append(points[0])
curve = tool.Ifc.get().createIfcPolyline(points)
profile.OuterCurve = curve
old_profile = extrusion.SweptArea
extrusion.SweptArea = profile
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile)
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric", None)
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Parametric")
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Engine": "CADSketcher", "Entities": json.dumps(context.scene["sketcher"].to_dict())},
)
bpy.ops.view3d.slvs_set_active_sketch(index=-1)
workplane = sketch.wp
p1 = workplane.p1
p1.origin = False
nm = workplane.nm
nm.origin = False
bpy.ops.view3d.slvs_delete_entity(index=sketch["slvs_index"])
bpy.ops.view3d.slvs_delete_entity(index=workplane["slvs_index"])
bpy.ops.view3d.slvs_delete_entity(index=p1["slvs_index"])
bpy.ops.view3d.slvs_delete_entity(index=nm["slvs_index"])
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
return {"FINISHED"}
class DisableEditingSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_sketch_extrusion_profile"
bl_label = "Disable Editing Sketch Extrusion Profile"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects
def _execute(self, context):
sketch = context.scene.sketcher.active_sketch
bpy.ops.view3d.slvs_set_active_sketch(index=-1)
workplane = sketch.wp
p1 = workplane.p1
p1.origin = False
nm = workplane.nm
nm.origin = False
bpy.ops.view3d.slvs_delete_entity(index=sketch["slvs_index"])
bpy.ops.view3d.slvs_delete_entity(index=workplane["slvs_index"])
bpy.ops.view3d.slvs_delete_entity(index=p1["slvs_index"])
bpy.ops.view3d.slvs_delete_entity(index=nm["slvs_index"])
return {"FINISHED"}
def disable_editing_extrusion_profile(context):
ProfileDecorator.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
element = tool.Ifc.get_entity(obj)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
profile_mesh = obj.data
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=body,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
return {"FINISHED"}
class DisableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_extrusion_profile"
bl_label = "Disable Editing Extrusion Profile"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects
def _execute(self, context):
return disable_editing_extrusion_profile(context)
class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_extrusion_profile"
bl_label = "Enable Editing Extrusion Profile"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects
def _execute(self, context):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
obj = context.active_object
element = tool.Ifc.get_entity(obj)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
body = ifcopenshell.util.representation.resolve_representation(body)
extrusion = tool.Model.get_extrusion(body)
if extrusion.Position:
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
position.translation *= self.unit_scale
else:
position = Matrix()
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position)
bpy.ops.object.mode_set(mode="EDIT")
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_extrusion_profile(context))
if not bpy.app.background:
tool.Blender.set_viewport_tool("bim.cad_tool")
return {"FINISHED"}
class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_extrusion_profile"
bl_label = "Edit Extrusion Profile"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
ProfileDecorator.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
element = tool.Ifc.get_entity(obj)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
body = ifcopenshell.util.representation.resolve_representation(body)
extrusion = tool.Model.get_extrusion(body)
if extrusion.Position:
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
position.translation *= self.unit_scale
else:
position = Matrix()
profile = tool.Model.export_profile(obj, position=position)
if not profile:
def msg(self, context):
self.layout.label(text="INVALID PROFILE")
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
ProfileDecorator.install(
context, exit_edit_mode_callback=lambda: disable_editing_extrusion_profile(context)
)
bpy.ops.object.mode_set(mode="EDIT")
return
old_profile = extrusion.SweptArea
for inverse in tool.Ifc.get().get_inverse(old_profile):
ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile)
profile_mesh = obj.data
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=body,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
# Only certain classes should have a footprint
if element.is_a() not in ("IfcSlab", "IfcRamp"):
return
footprint_context = ifcopenshell.util.representation.get_context(
tool.Ifc.get(), "Plan", "FootPrint", "SKETCH_VIEW"
)
if not footprint_context:
return
curves = [profile.OuterCurve]
if profile.is_a("IfcArbitraryProfileDefWithVoids"):
curves.extend(profile.InnerCurves)
new_footprint = ifcopenshell.api.run(
"geometry.add_footprint_representation", tool.Ifc.get(), context=footprint_context, curves=curves
)
old_footprint = ifcopenshell.util.representation.get_representation(element, "Plan", "FootPrint", "SKETCH_VIEW")
if old_footprint:
for inverse in tool.Ifc.get().get_inverse(old_footprint):
ifcopenshell.util.element.replace_attribute(inverse, old_footprint, new_footprint)
bonsai.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_footprint)
else:
ifcopenshell.api.run(
"geometry.assign_representation", tool.Ifc.get(), product=element, representation=new_footprint
)
class ResetVertex(bpy.types.Operator):
bl_idname = "bim.reset_vertex"
bl_label = "Reset Vertex"
bl_description = "Reset selected vertices group assignments (e.g. remove curve/circle)."
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def cancel_message(self, msg):
self.report({"WARNING"}, msg)
return {"CANCELLED"}
def execute(self, context):
obj = context.active_object
bpy.ops.object.mode_set(mode="OBJECT")
selected_vertices = [v.index for v in obj.data.vertices if v.select]
for group in obj.vertex_groups:
group.remove(selected_vertices)
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
class SetArcIndex(bpy.types.Operator):
bl_idname = "bim.set_arc_index"
bl_label = "Set Arc Index"
bl_description = (
"Add an IfcArcIndex based 3 point arc for the selected vertices, add a vertex group to mark the created arc."
)
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def cancel_message(self, msg):
self.report({"ERROR"}, msg)
return {"CANCELLED"}
def execute(self, context):
# NOTE: undo won't remove new verex group
# because of jumping between modes
obj = context.active_object
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
selected_vertices = [v.index for v in bm.verts if v.select]
if len(selected_vertices) != 3:
return self.cancel_message("Select 3 vertices.")
bpy.ops.object.mode_set(mode="OBJECT")
group = obj.vertex_groups.new(name="IFCARCINDEX")
group.add(selected_vertices, 1, "REPLACE")
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
class AddSlabFromWall(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.draw_slab_from_wall"
bl_label = "Draw Slab From Wall"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.space_data.type == "VIEW_3D"
def __init__(self):
self.relating_type = None
props = bpy.context.scene.BIMModelProperties
relating_type_id = props.relating_type_id
if relating_type_id:
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
def _execute(self, context):
if not self.relating_type:
return {"FINISHED"}
walls, is_closed_loop = tool.Model.get_connected_walls(bpy.context.selected_objects)
if not is_closed_loop:
self.report(
{"WARNING"},
"Please select a closed loop of walls, or deselect the walls to add a slab using the polyline tool.",
)
return {"FINISHED"}
DumbSlabGenerator(self.relating_type).generate("WALLS")
return {"FINISHED"}
class DrawPolylineSlab(bpy.types.Operator, PolylineOperator):
bl_idname = "bim.draw_polyline_slab"
bl_label = "Draw Polyline Slab"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.space_data.type == "VIEW_3D"
def __init__(self):
super().__init__()
self.relating_type = None
props = bpy.context.scene.BIMModelProperties
relating_type_id = props.relating_type_id
if relating_type_id:
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
def create_slab_from_polyline(self, context):
if not self.relating_type:
return {"FINISHED"}
DumbSlabGenerator(self.relating_type).generate("POLYLINE")
def modal(self, context, event):
if not self.relating_type:
self.report({"WARNING"}, "You need to select a slab type.")
PolylineDecorator.uninstall()
tool.Blender.update_viewport()
return {"FINISHED"}
PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0])
tool.Blender.update_viewport()
self.handle_lock_axis(context, event)
if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}:
self.handle_mouse_move(context, event)
return {"PASS_THROUGH"}
# TODO Slab settings
# if event.value == "RELEASE" and event.type == "F":
# direction_sense = context.scene.BIMModelProperties.direction_sense
# context.scene.BIMModelProperties.direction_sense = (
# "NEGATIVE" if direction_sense == "POSITIVE" else "POSITIVE"
# )
# if event.value == "RELEASE" and event.type == "O":
# offset_type = context.scene.BIMModelProperties.offset_type
# items = ["EXTERIOR", "CENTER", "INTERIOR"]
# index = items.index(offset_type)
# size = len(items)
# context.scene.BIMModelProperties.offset_type = items[((index + 1) % size)]
# props = bpy.context.scene.BIMModelProperties
# slab_config = f"""Direction: {props.direction_sense}
# Offset Type: {props.offset_type}
# Offset Value: {props.offset}
# """
self.handle_instructions(context)
self.handle_mouse_move(context, event, should_round=True)
self.choose_axis(event)
self.handle_snap_selection(context, event)
if (
not self.tool_state.is_input_on
and event.value == "RELEASE"
and event.type in {"RET", "NUMPAD_ENTER", "RIGHTMOUSE"}
):
self.create_slab_from_polyline(context)
context.workspace.status_text_set(text=None)
ProductDecorator.uninstall()
PolylineDecorator.uninstall()
tool.Polyline.clear_polyline()
tool.Blender.update_viewport()
return {"FINISHED"}
self.handle_keyboard_input(context, event)
self.handle_inserting_polyline(context, event)
cancel = self.handle_cancelation(context, event)
if cancel is not None:
ProductDecorator.uninstall()
return cancel
return {"RUNNING_MODAL"}
def invoke(self, context, event):
super().invoke(context, event)
ProductDecorator.install(context)
self.tool_state.use_default_container = True
self.tool_state.plane_method = "XY"
return {"RUNNING_MODAL"}
class RecalculateSlab(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.recalculate_slab"
bl_label = "Recalculate Slab"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects
def _execute(self, context):
walls = []
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if element.is_a("IfcSlab"):
for rel in element.ConnectedTo:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement.is_a("IfcWall"):
walls.append(tool.Ifc.get_object(rel.RelatedElement))
DumbWallRecalculator().recalculate(walls)
return {"FINISHED"}