You can now edit arbitrary profiles in the profile manager

This commit is contained in:
Dion Moult
2022-10-31 19:04:57 +11:00
parent cc056d5d53
commit 427411e758
9 changed files with 123 additions and 195 deletions
@@ -18,6 +18,7 @@
import os import os
import bpy import bpy
import blenderbim.tool as tool
import blenderbim.bim.module.type.prop as type_prop import blenderbim.bim.module.type.prop as type_prop
from bpy.types import WorkSpaceTool from bpy.types import WorkSpaceTool
from blenderbim.bim.module.model.data import AuthoringData from blenderbim.bim.module.model.data import AuthoringData
@@ -51,9 +52,14 @@ class CadTool(WorkSpaceTool):
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q") row.label(text="", icon="EVENT_Q")
row.operator("bim.edit_extrusion_profile", text="Save Profile") if obj.BIMObjectProperties.ifc_definition_id:
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT") row.operator("bim.edit_extrusion_profile", text="Save Profile")
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL") row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
else:
row.operator("bim.edit_arbitrary_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
@@ -180,7 +186,10 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_offset(distance=self.props.distance) bpy.ops.bim.cad_offset(distance=self.props.distance)
def hotkey_S_Q(self): def hotkey_S_Q(self):
bpy.ops.bim.edit_extrusion_profile() if tool.Ifc.get_entity(bpy.context.active_object):
bpy.ops.bim.edit_extrusion_profile()
else:
bpy.ops.bim.edit_arbitrary_profile()
def hotkey_S_R(self): def hotkey_S_R(self):
if self.is_profile(): if self.is_profile():
@@ -173,10 +173,9 @@ class DumbSlabGenerator:
matrix_world[2][3] = self.collection_obj.location[2] - self.depth matrix_world[2][3] = self.collection_obj.location[2] - self.depth
else: else:
matrix_world[2][3] -= self.depth matrix_world[2][3] -= self.depth
obj.matrix_world = matrix_world obj.matrix_world = Matrix.Rotation(self.x_angle, 4, 'X') @ matrix_world
bpy.context.view_layer.update() bpy.context.view_layer.update()
self.collection.objects.link(obj) self.collection.objects.link(obj)
obj.matrix_world = Matrix.Rotation(self.x_angle, 4, 'X')
element = blenderbim.core.root.assign_class( element = blenderbim.core.root.assign_class(
tool.Ifc, tool.Ifc,
@@ -618,122 +617,13 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
else: else:
position = Matrix() position = Matrix()
self.vertices = [] tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position)
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)
elif profile.is_a() == "IfcRectangleProfileDef":
self.process_rectangle(obj, position, profile)
mesh = bpy.data.meshes.new("Profile")
mesh.from_pydata(self.vertices, self.edges, [])
mesh.BIMMeshProperties.is_profile = True
obj.data = mesh
for arc in self.arcs:
group = obj.vertex_groups.new(name="IFCARCINDEX")
group.add(arc, 1, "REPLACE")
for circle in self.circles:
group = obj.vertex_groups.new(name="IFCCIRCLE")
group.add(circle, 1, "REPLACE")
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
DecorationsHandler.install(context) DecorationsHandler.install(context)
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool") bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
return {"FINISHED"} 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()).col[3].to_3d()
)
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 process_rectangle(self, obj, position, profile):
if profile.Position:
p_position = Matrix(ifcopenshell.util.placement.get_axis2placement(profile.Position).tolist())
p_position[0][3] *= self.unit_scale
p_position[1][3] *= self.unit_scale
p_position[2][3] *= self.unit_scale
else:
p_position = Matrix()
x = self.convert_unit_to_si(profile.XDim)
y = self.convert_unit_to_si(profile.YDim)
self.vertices.extend(
[
position @ p_position @ Vector((-x / 2, -y / 2, 0.0)),
position @ p_position @ Vector((x / 2, -y / 2, 0.0)),
position @ p_position @ Vector((x / 2, y / 2, 0.0)),
position @ p_position @ Vector((-x / 2, y / 2, 0.0)),
]
)
self.edges.extend([(i, i + 1) for i in range(0, len(self.vertices))])
self.edges[-1] = (len(self.vertices) - 1, 0) # Close the loop
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 EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_extrusion_profile" bl_idname = "bim.edit_extrusion_profile"
@@ -758,40 +648,16 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
else: else:
position = Matrix() position = Matrix()
helper = Helper(tool.Ifc.get()) profile = tool.Model.export_profile(obj, position=position)
indices = helper.auto_detect_arbitrary_profile_with_voids(obj, obj.data)
if isinstance(indices, tuple) and indices[0] is False: # Ugly
if not profile:
def msg(self, context): def msg(self, context):
self.layout.label(text="INVALID PROFILE: " + indices[1]) self.layout.label(text="INVALID PROFILE: " + indices[1])
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR") bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
DecorationsHandler.install(context) DecorationsHandler.install(context)
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"} return
self.bm = bmesh.new()
self.bm.from_mesh(obj.data)
self.bm.verts.ensure_lookup_table()
self.bm.edges.ensure_lookup_table()
if indices["inner_curves"]:
profile = tool.Ifc.get().createIfcArbitraryProfileDefWithVoids("AREA")
else:
profile = tool.Ifc.get().createIfcArbitraryClosedProfileDef("AREA")
if tool.Ifc.get().schema != "IFC2X3":
self.points = self.create_points(position, indices["points"])
profile.OuterCurve = self.create_curve(position, indices["profile"])
if indices["inner_curves"]:
results = []
for inner_curve in indices["inner_curves"]:
results.append(self.create_curve(position, inner_curve))
profile.InnerCurves = results
self.bm.free()
old_profile = extrusion.SweptArea old_profile = extrusion.SweptArea
extrusion.SweptArea = profile extrusion.SweptArea = profile
@@ -831,45 +697,6 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
) )
return {"FINISHED"} return {"FINISHED"}
def create_points(self, position, indices):
position_i = position.inverted()
points = []
for point in indices:
local_point = (position_i @ point).to_2d()
points.append(self.convert_si_to_unit(list(local_point)))
return tool.Ifc.get().createIfcCartesianPointList2D(points)
def create_curve(self, position, edge_indices, points=None):
position_i = position.inverted()
if len(edge_indices) == 2:
diameter = edge_indices[0]
p1 = self.bm.verts[diameter[0]].co
p2 = self.bm.verts[diameter[1]].co
center = self.convert_si_to_unit(list(position_i @ p1.lerp(p2, 0.5)))
radius = self.convert_si_to_unit((p1 - p2).length / 2)
return tool.Ifc.get().createIfcCircle(
tool.Ifc.get().createIfcAxis2Placement2D(tool.Ifc.get().createIfcCartesianPoint(center[0:2])), radius
)
if tool.Ifc.get().schema == "IFC2X3":
points = []
for edge in edge_indices:
local_point = (position_i @ Vector(self.bm.verts[edge[0]].co)).to_2d()
points.append(tool.Ifc.get().createIfcCartesianPoint(self.convert_si_to_unit(local_point)))
points.append(points[0])
return tool.Ifc.get().createIfcPolyline(points)
segments = []
for segment in edge_indices:
if len(segment) == 2:
segments.append(tool.Ifc.get().createIfcLineIndex([i + 1 for i in segment]))
elif len(segment) == 3:
segments.append(tool.Ifc.get().createIfcArcIndex([i + 1 for i in segment]))
return tool.Ifc.get().createIfcIndexedPolyCurve(self.points, segments, False)
def convert_si_to_unit(self, value):
if isinstance(value, (tuple, list)):
return [v / self.unit_scale for v in value]
return value / self.unit_scale
class ResetVertex(bpy.types.Operator): class ResetVertex(bpy.types.Operator):
bl_idname = "bim.reset_vertex" bl_idname = "bim.reset_vertex"
@@ -413,7 +413,9 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.bim.align_product(align_type="CENTERLINE") bpy.ops.bim.align_product(align_type="CENTERLINE")
def hotkey_S_E(self): def hotkey_S_E(self):
if self.active_class in ("IfcWall", "IfcWallStandardCase"): if not bpy.context.selected_objects:
return
elif self.active_class in ("IfcWall", "IfcWallStandardCase"):
bpy.ops.bim.join_wall(join_type="T") bpy.ops.bim.join_wall(join_type="T")
elif self.active_class in ("IfcSlab", "IfcSlabStandardCase", "IfcRamp", "IfcRoof"): elif self.active_class in ("IfcSlab", "IfcSlabStandardCase", "IfcRamp", "IfcRoof"):
if not bpy.context.active_object: if not bpy.context.active_object:
@@ -21,9 +21,12 @@ from . import ui, prop, operator
classes = ( classes = (
operator.AddProfileDef, operator.AddProfileDef,
operator.DisableEditingArbitraryProfile,
operator.DisableEditingProfile, operator.DisableEditingProfile,
operator.DisableProfileEditingUI, operator.DisableProfileEditingUI,
operator.EditArbitraryProfile,
operator.EditProfile, operator.EditProfile,
operator.EnableEditingArbitraryProfile,
operator.EnableEditingProfile, operator.EnableEditingProfile,
operator.LoadProfiles, operator.LoadProfiles,
operator.RemoveProfileDef, operator.RemoveProfileDef,
@@ -31,7 +31,12 @@ class ProfileData:
@classmethod @classmethod
def load(cls): def load(cls):
cls.data = {"total_profiles": cls.total_profiles(), "profile_classes": cls.profile_classes()} cls.data = {
"total_profiles": cls.total_profiles(),
"profile_classes": cls.profile_classes(),
"is_arbitrary_profile": cls.is_arbitrary_profile(),
"is_editing_arbitrary_profile": cls.is_editing_arbitrary_profile(),
}
cls.is_loaded = True cls.is_loaded = True
@classmethod @classmethod
@@ -52,3 +57,16 @@ class ProfileData:
(c, c, ifcopenshell.util.doc.get_entity_doc(schema_identifier, c)["description"] or "") (c, c, ifcopenshell.util.doc.get_entity_doc(schema_identifier, c)["description"] or "")
for c in sorted(classes) for c in sorted(classes)
] ]
@classmethod
def is_arbitrary_profile(cls):
props = bpy.context.scene.BIMProfileProperties
if props.active_profile_id:
profile = tool.Ifc.get().by_id(props.active_profile_id)
if profile.is_a("IfcArbitraryClosedProfileDef"):
return True
@classmethod
def is_editing_arbitrary_profile(cls):
obj = bpy.context.active_object
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile
@@ -20,6 +20,7 @@ import bpy
import ifcopenshell.api import ifcopenshell.api
import blenderbim.bim.helper import blenderbim.bim.helper
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.module.model.slab import DecorationsHandler
class LoadProfiles(bpy.types.Operator): class LoadProfiles(bpy.types.Operator):
@@ -35,6 +36,7 @@ class LoadProfiles(bpy.types.Operator):
new = props.profiles.add() new = props.profiles.add()
new.ifc_definition_id = profile.id() new.ifc_definition_id = profile.id()
new.name = profile.ProfileName or "Unnamed" new.name = profile.ProfileName or "Unnamed"
new.ifc_class = profile.is_a()
props.is_editing = True props.is_editing = True
bpy.ops.bim.disable_editing_profile() bpy.ops.bim.disable_editing_profile()
@@ -60,21 +62,19 @@ class RemoveProfileDef(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
ifcopenshell.api.run("profile.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile)) ifcopenshell.api.run("profile.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile))
bpy.ops.bim.load_profiles() bpy.ops.bim.load_profiles()
return {"FINISHED"}
class EnableEditingProfile(bpy.types.Operator): class EnableEditingProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_profile" bl_idname = "bim.enable_editing_profile"
bl_label = "Enable Editing Profile" bl_label = "Enable Editing Profile"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
profile: bpy.props.IntProperty() profile: bpy.props.IntProperty()
def execute(self, context): def _execute(self, context):
props = context.scene.BIMProfileProperties props = context.scene.BIMProfileProperties
props.profile_attributes.clear() props.profile_attributes.clear()
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.profile), props.profile_attributes) blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.profile), props.profile_attributes)
props.active_profile_id = self.profile props.active_profile_id = self.profile
return {"FINISHED"}
class DisableEditingProfile(bpy.types.Operator): class DisableEditingProfile(bpy.types.Operator):
@@ -98,7 +98,6 @@ class EditProfile(bpy.types.Operator, tool.Ifc.Operator):
profile = tool.Ifc.get().by_id(props.active_profile_id) profile = tool.Ifc.get().by_id(props.active_profile_id)
ifcopenshell.api.run("profile.edit_profile", tool.Ifc.get(), profile=profile, attributes=attributes) ifcopenshell.api.run("profile.edit_profile", tool.Ifc.get(), profile=profile, attributes=attributes)
bpy.ops.bim.load_profiles() bpy.ops.bim.load_profiles()
return {"FINISHED"}
class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator): class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
@@ -110,9 +109,69 @@ class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
props = context.scene.BIMProfileProperties props = context.scene.BIMProfileProperties
profile_class = props.profile_classes profile_class = props.profile_classes
if profile_class == "IfcArbitraryClosedProfileDef": if profile_class == "IfcArbitraryClosedProfileDef":
points = [(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1)] points = [(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]
ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points) profile = ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points)
else: else:
ifcopenshell.api.run("profile.add_parameterized_profile", tool.Ifc.get(), ifc_class=profile_class) profile = ifcopenshell.api.run("profile.add_parameterized_profile", tool.Ifc.get(), ifc_class=profile_class)
profile.ProfileName = "New Profile"
bpy.ops.bim.load_profiles() bpy.ops.bim.load_profiles()
return {"FINISHED"}
class EnableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_arbitrary_profile"
bl_label = "Enable Editing Arbitrary Profile"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMProfileProperties
profile = tool.Ifc.get().by_id(props.active_profile_id)
obj = tool.Model.import_profile(profile)
bpy.context.scene.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="EDIT")
DecorationsHandler.install(context)
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
class DisableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_arbitrary_profile"
bl_label = "Disable Editing Arbitrary Profile"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
obj = context.active_object
DecorationsHandler.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
bpy.data.objects.remove(obj)
class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_arbitrary_profile"
bl_label = "Edit Arbitrary Profile"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMProfileProperties
old_profile = tool.Ifc.get().by_id(props.active_profile_id)
obj = context.active_object
DecorationsHandler.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
profile = tool.Model.export_profile(obj)
if not profile:
def msg(self, context):
self.layout.label(text="INVALID PROFILE: " + indices[1])
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
DecorationsHandler.install(context)
bpy.ops.object.mode_set(mode="EDIT")
return
bpy.data.objects.remove(obj)
profile.ProfileType = old_profile.ProfileType
profile.ProfileName = old_profile.ProfileName
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile)
bpy.ops.bim.load_profiles()
props.active_profile_id = profile.id()
@@ -44,6 +44,7 @@ def get_profile_classes(self, context):
class Profile(PropertyGroup): class Profile(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
ifc_class: StringProperty(name="IFC Class")
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -67,6 +67,14 @@ class BIM_PT_profiles(Panel):
self.draw_editable_ui(context) self.draw_editable_ui(context)
def draw_editable_ui(self, context): def draw_editable_ui(self, context):
if ProfileData.data["is_arbitrary_profile"]:
if ProfileData.data["is_editing_arbitrary_profile"]:
row = self.layout.row(align=True)
row.operator("bim.edit_arbitrary_profile", text="Save Profile", icon="CHECKMARK")
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
else:
row = self.layout.row()
row.operator("bim.enable_editing_arbitrary_profile", text="Edit Profile", icon="GREASEPENCIL")
blenderbim.bim.helper.draw_attributes(self.props.profile_attributes, self.layout) blenderbim.bim.helper.draw_attributes(self.props.profile_attributes, self.layout)
@@ -76,6 +84,7 @@ class BIM_UL_profiles(UIList):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
row.label(text=item.name or "Unnamed") row.label(text=item.name or "Unnamed")
row.label(text=item.ifc_class)
if props.active_profile_id == item.ifc_definition_id: if props.active_profile_id == item.ifc_definition_id:
row.operator("bim.edit_profile", text="", icon="CHECKMARK") row.operator("bim.edit_profile", text="", icon="CHECKMARK")
+2 -2
View File
@@ -42,8 +42,8 @@ IfcOpenShell (using an IfcOpenBot build) for convenience. Instructions on how to
compile IfcOpenShell is out of scope of this document. compile IfcOpenShell is out of scope of this document.
You can create your own package by using the Makefile as shown below. You can You can create your own package by using the Makefile as shown below. You can
choose between a ``PLATFORM`` of ``linux``, ``macos``, and ``win``. You can choose between a ``PLATFORM`` of ``linux``, ``macos``, ``macosm1``, and ``win``.
choose between a ``PYVERSION`` of ``py39``, ``py37``, or ``py310``. You can choose between a ``PYVERSION`` of ``py39``, ``py37``, or ``py310``.
:: ::
$ cd src/blenderbim $ cd src/blenderbim