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 bpy
import blenderbim.tool as tool
import blenderbim.bim.module.type.prop as type_prop
from bpy.types import WorkSpaceTool
from blenderbim.bim.module.model.data import AuthoringData
@@ -51,9 +52,14 @@ class CadTool(WorkSpaceTool):
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q")
row.operator("bim.edit_extrusion_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
if obj.BIMObjectProperties.ifc_definition_id:
row.operator("bim.edit_extrusion_profile", text="Save Profile")
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.label(text="", icon="EVENT_SHIFT")
@@ -180,7 +186,10 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_offset(distance=self.props.distance)
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):
if self.is_profile():
@@ -173,10 +173,9 @@ class DumbSlabGenerator:
matrix_world[2][3] = self.collection_obj.location[2] - self.depth
else:
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()
self.collection.objects.link(obj)
obj.matrix_world = Matrix.Rotation(self.x_angle, 4, 'X')
element = blenderbim.core.root.assign_class(
tool.Ifc,
@@ -618,122 +617,13 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
else:
position = Matrix()
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)
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")
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position)
bpy.ops.object.mode_set(mode="EDIT")
DecorationsHandler.install(context)
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
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):
bl_idname = "bim.edit_extrusion_profile"
@@ -758,40 +648,16 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
else:
position = Matrix()
helper = Helper(tool.Ifc.get())
indices = helper.auto_detect_arbitrary_profile_with_voids(obj, obj.data)
if isinstance(indices, tuple) and indices[0] is False: # Ugly
profile = tool.Model.export_profile(obj, position=position)
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 {"FINISHED"}
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()
return
old_profile = extrusion.SweptArea
extrusion.SweptArea = profile
@@ -831,45 +697,6 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
)
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):
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")
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")
elif self.active_class in ("IfcSlab", "IfcSlabStandardCase", "IfcRamp", "IfcRoof"):
if not bpy.context.active_object:
@@ -21,9 +21,12 @@ from . import ui, prop, operator
classes = (
operator.AddProfileDef,
operator.DisableEditingArbitraryProfile,
operator.DisableEditingProfile,
operator.DisableProfileEditingUI,
operator.EditArbitraryProfile,
operator.EditProfile,
operator.EnableEditingArbitraryProfile,
operator.EnableEditingProfile,
operator.LoadProfiles,
operator.RemoveProfileDef,
@@ -31,7 +31,12 @@ class ProfileData:
@classmethod
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
@classmethod
@@ -52,3 +57,16 @@ class ProfileData:
(c, c, ifcopenshell.util.doc.get_entity_doc(schema_identifier, c)["description"] or "")
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 blenderbim.bim.helper
import blenderbim.tool as tool
from blenderbim.bim.module.model.slab import DecorationsHandler
class LoadProfiles(bpy.types.Operator):
@@ -35,6 +36,7 @@ class LoadProfiles(bpy.types.Operator):
new = props.profiles.add()
new.ifc_definition_id = profile.id()
new.name = profile.ProfileName or "Unnamed"
new.ifc_class = profile.is_a()
props.is_editing = True
bpy.ops.bim.disable_editing_profile()
@@ -60,21 +62,19 @@ class RemoveProfileDef(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
ifcopenshell.api.run("profile.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile))
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_label = "Enable Editing Profile"
bl_options = {"REGISTER", "UNDO"}
profile: bpy.props.IntProperty()
def execute(self, context):
def _execute(self, context):
props = context.scene.BIMProfileProperties
props.profile_attributes.clear()
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.profile), props.profile_attributes)
props.active_profile_id = self.profile
return {"FINISHED"}
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)
ifcopenshell.api.run("profile.edit_profile", tool.Ifc.get(), profile=profile, attributes=attributes)
bpy.ops.bim.load_profiles()
return {"FINISHED"}
class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
@@ -110,9 +109,69 @@ class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
props = context.scene.BIMProfileProperties
profile_class = props.profile_classes
if profile_class == "IfcArbitraryClosedProfileDef":
points = [(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1)]
ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points)
points = [(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]
profile = ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points)
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()
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):
name: StringProperty(name="Name")
ifc_class: StringProperty(name="IFC Class")
ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -67,6 +67,14 @@ class BIM_PT_profiles(Panel):
self.draw_editable_ui(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)
@@ -76,6 +84,7 @@ class BIM_UL_profiles(UIList):
if item:
row = layout.row(align=True)
row.label(text=item.name or "Unnamed")
row.label(text=item.ifc_class)
if props.active_profile_id == item.ifc_definition_id:
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.
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 ``PYVERSION`` of ``py39``, ``py37``, or ``py310``.
choose between a ``PLATFORM`` of ``linux``, ``macos``, ``macosm1``, and ``win``.
You can choose between a ``PYVERSION`` of ``py39``, ``py37``, or ``py310``.
::
$ cd src/blenderbim