WIP refactor clash and smart clash. See #1222.

This commit is contained in:
Dion Moult
2021-01-26 09:51:33 +11:00
parent 20ab036016
commit 8618befa0f
10 changed files with 728 additions and 796 deletions
@@ -13,6 +13,7 @@ if bpy is not None:
"aggregate": None, "aggregate": None,
"attribute": None, "attribute": None,
"bcf": None, "bcf": None,
"clash": None,
"classification": None, "classification": None,
"cobie": None, "cobie": None,
"context": None, "context": None,
@@ -61,10 +62,6 @@ if bpy is not None:
operator.SelectSweptSolidInnerCurves, operator.SelectSweptSolidInnerCurves,
operator.AssignSweptSolidExtrusion, operator.AssignSweptSolidExtrusion,
operator.SelectSweptSolidExtrusion, operator.SelectSweptSolidExtrusion,
operator.AddMaterialPset,
operator.RemoveMaterialPset,
operator.AddMaterialAttribute,
operator.RemoveMaterialAttribute,
operator.FetchLibraryInformation, operator.FetchLibraryInformation,
operator.FetchExternalMaterial, operator.FetchExternalMaterial,
operator.FetchObjectPassport, operator.FetchObjectPassport,
@@ -76,20 +73,6 @@ if bpy is not None:
operator.OpenView, operator.OpenView,
operator.OpenViewCamera, operator.OpenViewCamera,
operator.ActivateView, operator.ActivateView,
operator.ExportClashSets,
operator.ImportClashSets,
operator.AddClashSet,
operator.RemoveClashSet,
operator.AddClashSource,
operator.RemoveClashSource,
operator.SelectClashSource,
operator.ExecuteIfcClash,
operator.SelectIfcClashResults,
operator.SelectClashResults,
operator.SelectSmartGroupedClashesPath,
operator.SmartClashGroup,
operator.SelectSmartGroup,
operator.LoadSmartGroupsForActiveClashSet,
operator.OpenUpstream, operator.OpenUpstream,
operator.AddSectionPlane, operator.AddSectionPlane,
operator.RemoveSectionPlane, operator.RemoveSectionPlane,
@@ -123,9 +106,6 @@ if bpy is not None:
operator.CopyPropertyToSelection, operator.CopyPropertyToSelection,
operator.CopyAttributeToSelection, operator.CopyAttributeToSelection,
operator.RefreshDrawingList, operator.RefreshDrawingList,
operator.SetBlenderClashSetA,
operator.SetBlenderClashSetB,
operator.ExecuteBlenderClash,
operator.CleanWireframes, operator.CleanWireframes,
operator.LinkIfc, operator.LinkIfc,
operator.SnapSpacesTogether, operator.SnapSpacesTogether,
@@ -134,9 +114,6 @@ if bpy is not None:
prop.StrProperty, prop.StrProperty,
prop.Attribute, prop.Attribute,
prop.Variable, prop.Variable,
prop.ClashSource,
prop.ClashSet,
prop.SmartClashGroup,
prop.Drawing, prop.Drawing,
prop.Schedule, prop.Schedule,
prop.DrawingStyle, prop.DrawingStyle,
@@ -160,18 +137,13 @@ if bpy is not None:
ui.BIM_PT_drawings, ui.BIM_PT_drawings,
ui.BIM_PT_schedules, ui.BIM_PT_schedules,
ui.BIM_PT_sheets, ui.BIM_PT_sheets,
ui.BIM_PT_ifcclash,
ui.BIM_PT_library, ui.BIM_PT_library,
ui.BIM_PT_object_structural,
ui.BIM_PT_camera, ui.BIM_PT_camera,
ui.BIM_PT_text, ui.BIM_PT_text,
ui.BIM_PT_annotation_utilities, ui.BIM_PT_annotation_utilities,
ui.BIM_PT_clash_manager,
ui.BIM_PT_misc_utilities, ui.BIM_PT_misc_utilities,
ui.BIM_UL_generic, ui.BIM_UL_generic,
ui.BIM_UL_drawinglist, ui.BIM_UL_drawinglist,
ui.BIM_UL_clash_sets,
ui.BIM_UL_smart_groups,
ui.BIM_UL_topics, ui.BIM_UL_topics,
ui.BIM_ADDON_preferences, ui.BIM_ADDON_preferences,
] ]
@@ -725,9 +725,15 @@ class ActivateBcfViewpoint(bpy.types.Operator):
obj.hide_set(True) obj.hide_set(True)
continue continue
if "IfcSpace" in obj.name: if "IfcSpace" in obj.name:
is_visible = viewpoint.components.view_setup_hints.spaces_visible if viewpoint.components.view_setup_hints:
is_visible = viewpoint.components.view_setup_hints.spaces_visible
else:
is_visible = False
elif "IfcOpeningElement" in obj.name: elif "IfcOpeningElement" in obj.name:
is_visible = viewpoint.components.view_setup_hints.openings_visible if viewpoint.components.view_setup_hints:
is_visible = viewpoint.components.view_setup_hints.openings_visible
else:
is_visible = False
obj.hide_set(not is_visible) obj.hide_set(not is_visible)
if not is_visible: if not is_visible:
continue continue
@@ -4,7 +4,7 @@ from . import bcfstore
from bpy.types import Panel from bpy.types import Panel
class BIM_PT_bcf(Panel): class BIM_PT_bcf(Panel):
bl_label = "BIM Collaboration Format (BCF)" bl_label = "BCF Project"
bl_idname = "BIM_PT_bcf" bl_idname = "BIM_PT_bcf"
bl_options = {"DEFAULT_CLOSED"} bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
@@ -0,0 +1,38 @@
import bpy
from . import ui, prop, operator
classes = (
operator.ExportClashSets,
operator.ImportClashSets,
operator.AddClashSet,
operator.RemoveClashSet,
operator.AddClashSource,
operator.RemoveClashSource,
operator.SelectClashSource,
operator.ExecuteIfcClash,
operator.SelectIfcClashResults,
operator.SelectClashResults,
operator.SelectSmartGroupedClashesPath,
operator.SmartClashGroup,
operator.SelectSmartGroup,
operator.LoadSmartGroupsForActiveClashSet,
operator.SetBlenderClashSetA,
operator.SetBlenderClashSetB,
operator.ExecuteBlenderClash,
prop.ClashSource,
prop.ClashSet,
prop.SmartClashGroup,
prop.BIMClashProperties,
ui.BIM_PT_ifcclash,
ui.BIM_PT_clash_manager,
ui.BIM_UL_clash_sets,
ui.BIM_UL_smart_groups,
)
def register():
bpy.types.Scene.BIMClashProperties = bpy.props.PointerProperty(type=prop.BIMClashProperties)
def unregister():
del bpy.types.Scene.BIMClashProperties
@@ -0,0 +1,456 @@
import os
import bpy
import json
import bmesh
import logging
import numpy as np
from mathutils import Matrix
from math import radians
class ExportClashSets(bpy.types.Operator):
bl_idname = "bim.export_clash_sets"
bl_label = "Export Clash Sets"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
clash_sets = []
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
self.a = []
self.b = []
for ab in ["a", "b"]:
for data in getattr(clash_set, ab):
clash_source = {"file": data.name}
if data.selector:
clash_source["selector"] = data.selector
clash_source["mode"] = data.mode
getattr(self, ab).append(clash_source)
clash_sets.append({"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b})
with open(self.filepath, "w") as destination:
destination.write(json.dumps(clash_sets, indent=4))
return {"FINISHED"}
class ImportClashSets(bpy.types.Operator):
bl_idname = "bim.import_clash_sets"
bl_label = "Import Clash Sets"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
with open(self.filepath) as f:
clash_sets = json.load(f)
for clash_set in clash_sets:
new = bpy.context.scene.BIMClashProperties.clash_sets.add()
new.name = clash_set["name"]
new.tolerance = clash_set["tolerance"]
for clash_source in clash_set["a"]:
new_source = new.a.add()
new_source.name = clash_source["file"]
if "selector" in clash_source:
new_source.selector = clash_source["selector"]
new_source.mode = clash_source["mode"]
if clash_set["b"]:
for clash_source in clash_set["b"]:
new_source = new.b.add()
new_source.name = clash_source["file"]
if "selector" in clash_source:
new_source.selector = clash_source["selector"]
new_source.mode = clash_source["mode"]
return {"FINISHED"}
class AddClashSet(bpy.types.Operator):
bl_idname = "bim.add_clash_set"
bl_label = "Add Clash Set"
def execute(self, context):
new = bpy.context.scene.BIMClashProperties.clash_sets.add()
new.name = "New Clash Set"
new.tolerance = 0.01
return {"FINISHED"}
class RemoveClashSet(bpy.types.Operator):
bl_idname = "bim.remove_clash_set"
bl_label = "Remove Clash Set"
index: bpy.props.IntProperty()
def execute(self, context):
bpy.context.scene.BIMClashProperties.clash_sets.remove(self.index)
return {"FINISHED"}
class AddClashSource(bpy.types.Operator):
bl_idname = "bim.add_clash_source"
bl_label = "Add Clash Source"
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
source = getattr(clash_set, self.group).add()
return {"FINISHED"}
class RemoveClashSource(bpy.types.Operator):
bl_idname = "bim.remove_clash_source"
bl_label = "Remove Clash Source"
index: bpy.props.IntProperty()
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
getattr(clash_set, self.group).remove(self.index)
return {"FINISHED"}
class SelectClashSource(bpy.types.Operator):
bl_idname = "bim.select_clash_source"
bl_label = "Select Clash Source"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
index: bpy.props.IntProperty()
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index]
getattr(clash_set, self.group)[self.index].name = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectClashResults(bpy.types.Operator):
bl_idname = "bim.select_clash_results"
bl_label = "Select Clash Results"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BIMClashProperties.clash_results_path = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectSmartGroupedClashesPath(bpy.types.Operator):
bl_idname = "bim.select_smart_grouped_clashes_path"
bl_label = "Select Smart-Grouped Clashes Path"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ExecuteIfcClash(bpy.types.Operator):
bl_idname = "bim.execute_ifc_clash"
bl_label = "Execute IFC Clash"
filename_ext = ".bcf"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
if ".json" not in bpy.data.filepath:
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
import ifcclash
settings = ifcclash.IfcClashSettings()
if ".json" not in self.filepath:
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
ifc_clasher = ifcclash.IfcClasher(settings)
if bpy.context.scene.BIMClashProperties.should_create_clash_snapshots:
def get_viewpoint_snapshot(self, viewpoint, mat):
camera = bpy.data.objects.get("IFC Clash Camera")
if not camera:
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
bpy.context.scene.collection.objects.link(camera)
camera.matrix_world = Matrix(mat)
bpy.context.scene.camera = camera
camera.data.angle = radians(60)
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
area.spaces[0].shading.show_xray = True
bpy.context.scene.render.resolution_x = 480
bpy.context.scene.render.resolution_y = 270
bpy.context.scene.render.image_settings.file_format = "PNG"
bpy.context.scene.render.filepath = os.path.join(
bpy.context.scene.BIMProperties.data_dir, "snapshot.png"
)
bpy.ops.render.opengl(write_still=True)
return bpy.context.scene.render.filepath
ifcclash.IfcClasher.get_viewpoint_snapshot = get_viewpoint_snapshot
ifc_clasher.clash_sets = []
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
self.a = []
self.b = []
for ab in ["a", "b"]:
for data in getattr(clash_set, ab):
clash_source = {"file": data.name}
if data.selector:
clash_source["selector"] = data.selector
clash_source["mode"] = data.mode
getattr(self, ab).append(clash_source)
ifc_clasher.clash_sets.append(
{"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b}
)
ifc_clasher.clash()
ifc_clasher.export()
return {"FINISHED"}
class SelectIfcClashResults(bpy.types.Operator):
bl_idname = "bim.select_ifc_clash_results"
bl_label = "Select IFC Clash Results"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
clash_sets = json.load(f)
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
bpy.context.scene.BIMClashProperties.active_clash_set_index
].name
global_ids = []
for clash_set in clash_sets:
if clash_set["name"] != clash_set_name:
continue
if not "clashes" in clash_set.keys():
self.report({"WARNING"}, "No clashes found for the selected Clash Set.")
return {"CANCELLED"}
for clash in clash_set["clashes"].values():
global_ids.extend([clash["a_global_id"], clash["b_global_id"]])
for obj in bpy.context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if element.GlobalId in global_ids:
obj.select_set(True)
return {"FINISHED"}
class SmartClashGroup(bpy.types.Operator):
bl_idname = "bim.smart_clash_group"
bl_label = "Smart Group Clashes"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
import ifcclash
settings = ifcclash.IfcClashSettings()
self.filepath = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.clash_results_path, ".json")
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
ifc_clasher = ifcclash.IfcClasher(settings)
with open(self.filepath) as f:
clash_sets = json.load(f)
# execute the smart grouping
save_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
smart_grouped_clashes = ifc_clasher.smart_group_clashes(
clash_sets, bpy.context.scene.BIMClashProperties.smart_clash_grouping_max_distance
)
# save smart_groups to json
with open(save_path, "w") as f:
f.write(json.dumps(smart_grouped_clashes))
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
bpy.context.scene.BIMClashProperties.active_clash_set_index
].name
# Reset the list of smart_clash_groups for the UI
bpy.context.scene.BIMClashProperties.smart_clash_groups.clear()
for clash_set, smart_groups in smart_grouped_clashes.items():
# Only select the clashes that correspond to the actively selected IFC Clash Set
if clash_set != clash_set_name:
continue
else:
for smart_group, global_id_pairs in smart_groups[0].items():
new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add()
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
for id in pair:
new_global_id = new_group.global_ids.add()
new_global_id.name = id
return {"FINISHED"}
class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
bl_idname = "bim.load_smart_groups_for_active_clash_set"
bl_label = "Load Smart Groups for Active Clash Set"
def execute(self, context):
smart_groups_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[
bpy.context.scene.BIMClashProperties.active_clash_set_index
].name
with open(smart_groups_path) as f:
smart_grouped_clashes = json.load(f)
# Reset the list of smart_clash_groups for the UI
bpy.context.scene.BIMClashProperties.smart_clash_groups.clear()
for clash_set, smart_groups in smart_grouped_clashes.items():
# Only select the clashes that correspond to the actively selected IFC Clash Set
if clash_set != clash_set_name:
continue
else:
for smart_group, global_id_pairs in smart_groups[0].items():
new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add()
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
for id in pair:
new_global_id = new_group.global_ids.add()
new_global_id.name = id
return {"FINISHED"}
class SelectSmartGroup(bpy.types.Operator):
bl_idname = "bim.select_smart_group"
bl_label = "Select Smart Group"
def execute(self, context):
# Select smart group in view
selected_smart_group = bpy.context.scene.BIMClashProperties.smart_clash_groups[
bpy.context.scene.BIMCLashProperties.active_smart_group_index
]
# print(selected_smart_group.number)
for obj in bpy.context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
for id in selected_smart_group.global_ids:
# print("Id: ", id)
# print("Global id: ", element.GlobalId)
if element.GlobalId in id.name:
# print("object match: ", global_id)
obj.select_set(True)
return {"FINISHED"}
class BlenderClasher:
def process_clash_set(self):
import collision
a_cm = collision.CollisionManager()
b_cm = collision.CollisionManager()
self.add_to_cm(a_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_a)
self.add_to_cm(b_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_b)
results = a_cm.in_collision_other(b_cm, return_data=True)
if not results[0]:
print("No clashes")
return
for contact in results[1]:
if contact.raw.penetration_depth < 0.01:
continue
print("-----")
print(contact.names)
print(contact.raw.normal)
print(contact.raw.pos)
def add_to_cm(self, cm, object_names):
import ifcclash
for object_name in object_names:
name = object_name.name
obj = bpy.data.objects[name]
triangulated_mesh = self.triangulate_mesh(obj)
mesh = ifcclash.Mesh()
mesh.vertices = np.array([tuple(obj.matrix_world @ v.co) for v in triangulated_mesh.vertices])
mesh.faces = np.array([tuple(p.vertices) for p in triangulated_mesh.polygons])
cm.add_object(name, mesh)
def triangulate_mesh(self, obj):
mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(mesh)
bm.free()
del bm
return mesh
class SetBlenderClashSetA(bpy.types.Operator):
bl_idname = "bim.set_blender_clash_set_a"
bl_label = "Set Blender Clash Set A"
def execute(self, context):
while len(bpy.context.scene.BIMClashProperties.blender_clash_set_a) > 0:
bpy.context.scene.BIMClashProperties.blender_clash_set_a.remove(0)
for obj in bpy.context.selected_objects:
new = bpy.context.scene.BIMClashProperties.blender_clash_set_a.add()
new.name = obj.name
return {"FINISHED"}
class SetBlenderClashSetB(bpy.types.Operator):
bl_idname = "bim.set_blender_clash_set_b"
bl_label = "Set Blender Clash Set B"
def execute(self, context):
while len(bpy.context.scene.BIMClashProperties.blender_clash_set_b) > 0:
bpy.context.scene.BIMClashProperties.blender_clash_set_b.remove(0)
for obj in bpy.context.selected_objects:
new = bpy.context.scene.BIMClashProperties.blender_clash_set_b.add()
new.name = obj.name
return {"FINISHED"}
class ExecuteBlenderClash(bpy.types.Operator):
bl_idname = "bim.execute_blender_clash"
bl_label = "Execute Blender Clash"
def execute(self, context):
blender_clasher = BlenderClasher()
blender_clasher.process_clash_set()
return {"FINISHED"}
@@ -0,0 +1,53 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.owner.data import Data
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class ClashSource(PropertyGroup):
name: StringProperty(name="File")
selector: StringProperty(name="Selector")
mode: EnumProperty(
items=[
("i", "Include", "Only the selected objects are included for clashing"),
("e", "Exclude", "All objects except the selected objects are included for clashing"),
],
name="Mode",
)
class ClashSet(PropertyGroup):
name: StringProperty(name="Name")
tolerance: FloatProperty(name="Tolerance")
a: CollectionProperty(name="Group A", type=ClashSource)
b: CollectionProperty(name="Group B", type=ClashSource)
class SmartClashGroup(PropertyGroup):
number: StringProperty(name="Number")
global_ids: CollectionProperty(name="GlobalIDs", type=StrProperty)
class BIMClashProperties(PropertyGroup):
blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty)
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet)
should_create_clash_snapshots: BoolProperty(name="Create Snapshots", default=True)
clash_results_path: StringProperty(name="Clash Results Path")
smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path")
active_clash_set_index: IntProperty(name="Active Clash Set Index")
smart_clash_groups: CollectionProperty(name="Smart Clash Groups", type=SmartClashGroup)
active_smart_group_index: IntProperty(name="Active Smart Group Index")
smart_clash_grouping_max_distance: IntProperty(
name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10
)
@@ -0,0 +1,148 @@
import bpy
from bpy.types import Panel
class BIM_PT_ifcclash(Panel):
bl_label = "IFC Clash Sets"
bl_idname = "BIM_PT_ifcclash"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = context.scene
props = scene.BIMClashProperties
layout.label(text="Blender Clash:")
row = layout.row(align=True)
row.operator("bim.set_blender_clash_set_a")
row.operator("bim.set_blender_clash_set_b")
row = layout.row(align=True)
row.operator("bim.execute_blender_clash")
layout.label(text="IFC Clash:")
row = layout.row(align=True)
row.operator("bim.add_clash_set")
row.operator("bim.import_clash_sets", text="", icon="IMPORT")
row.operator("bim.export_clash_sets", text="", icon="EXPORT")
if not props.clash_sets:
return
layout.template_list("BIM_UL_clash_sets", "", props, "clash_sets", props, "active_clash_set_index")
if props.active_clash_set_index < len(props.clash_sets):
clash_set = props.clash_sets[props.active_clash_set_index]
row = layout.row(align=True)
row.prop(clash_set, "name")
row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index
row = layout.row(align=True)
row.prop(clash_set, "tolerance")
layout.label(text="Group A:")
row = layout.row()
row.operator("bim.add_clash_source").group = "a"
for index, source in enumerate(clash_set.a):
row = layout.row(align=True)
row.prop(source, "name", text="")
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = "a"
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = "a"
row = layout.row(align=True)
row.prop(source, "mode", text="")
row.prop(source, "selector", text="")
layout.label(text="Group B:")
row = layout.row()
row.operator("bim.add_clash_source").group = "b"
for index, source in enumerate(clash_set.b):
row = layout.row(align=True)
row.prop(source, "name", text="")
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = "b"
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = "b"
row = layout.row(align=True)
row.prop(source, "mode", text="")
row.prop(source, "selector", text="")
row = layout.row()
row.prop(props, "should_create_clash_snapshots")
row = layout.row(align=True)
row.operator("bim.execute_ifc_clash")
row.operator("bim.select_ifc_clash_results")
class BIM_PT_clash_manager(Panel):
bl_idname = "BIM_PT_clash_manager"
bl_label = "Clash Manager"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BlenderBIM"
def draw(self, context):
layout = self.layout
props = context.scene.BIMClashProperties
row = layout.row()
layout.label(text="Select clash results to group:")
row = layout.row(align=True)
row.prop(props, "clash_results_path", text="")
op = row.operator("bim.select_clash_results", icon="FILE_FOLDER", text="")
row = layout.row()
layout.label(text="Select output path for smart-grouped clashes:")
row = layout.row(align=True)
row.prop(props, "smart_grouped_clashes_path", text="")
op = row.operator("bim.select_smart_grouped_clashes_path", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(props, "smart_clash_grouping_max_distance")
row = layout.row(align=True)
row.operator("bim.smart_clash_group")
row = layout.row(align=True)
row.operator("bim.load_smart_groups_for_active_clash_set")
layout.template_list("BIM_UL_smart_groups", "", props, "smart_clash_groups", props, "active_smart_group_index")
row = layout.row(align=True)
row.operator("bim.select_smart_group")
class BIM_UL_clash_sets(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
if item:
layout.prop(item, "name", text="", emboss=False)
else:
layout.label(text="", translate=False)
class BIM_UL_smart_groups(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
if item:
layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0)
else:
layout.label(text="", translate=False)
@@ -12,7 +12,6 @@ import ifcopenshell.util.selector
import ifcopenshell.util.geolocation import ifcopenshell.util.geolocation
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.schema import ifcopenshell.util.schema
import tempfile
import numpy as np import numpy as np
from . import export_ifc from . import export_ifc
from . import import_ifc from . import import_ifc
@@ -28,7 +27,6 @@ from bpy_extras.io_utils import ImportHelper
from mathutils import Vector, Matrix, Euler, geometry from mathutils import Vector, Matrix, Euler, geometry
import bmesh import bmesh
from math import radians, degrees, atan, tan, cos, sin from math import radians, degrees, atan, tan, cos, sin
from pathlib import Path
from bpy.app.handlers import persistent from bpy.app.handlers import persistent
@@ -179,96 +177,6 @@ class OpenUri(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class AddQto(bpy.types.Operator):
bl_idname = "bim.add_qto"
bl_label = "Add Qto"
def execute(self, context):
name = bpy.context.active_object.BIMObjectProperties.qto_name
qto_template = schema.ifc.psetqto.get_by_name(name)
if not qto_template:
return {"FINISHED"}
for obj in bpy.context.selected_objects:
if "/" not in obj.name or obj.BIMObjectProperties.qtos.find(name) != -1:
continue
applicable_qtos = schema.ifc.psetqto.get_applicable_names(obj.name.split("/")[0], qto_only=True)
if name not in applicable_qtos:
continue
qto = obj.BIMObjectProperties.qtos.add()
qto.name = name
for prop_name in (p.Name for p in qto_template.HasPropertyTemplates):
prop = qto.properties.add()
prop.name = prop_name
return {"FINISHED"}
class RemoveQto(bpy.types.Operator):
bl_idname = "bim.remove_qto"
bl_label = "Remove Qto"
index: bpy.props.IntProperty()
def execute(self, context):
name = bpy.context.active_object.BIMObjectProperties.qtos[self.index].name
for obj in bpy.context.selected_objects:
if "/" not in obj.name:
continue
index = obj.BIMObjectProperties.qtos.find(name)
if index != -1:
obj.BIMObjectProperties.qtos.remove(index)
return {"FINISHED"}
class AddMaterialPset(bpy.types.Operator):
bl_idname = "bim.add_material_pset"
bl_label = "Add Material Pset"
def execute(self, context):
material = bpy.context.active_object.active_material
name = material.BIMMaterialProperties.pset_name
pset_template = schema.ifc.psetqto.get_by_name(name)
if not pset_template:
return {"FINISHED"}
if material.BIMMaterialProperties.psets.find(name) != -1:
return {"FINISHED"}
pset = material.BIMMaterialProperties.psets.add()
pset.name = name
for prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
prop = pset.properties.add()
prop.name = prop_name
return {"FINISHED"}
class RemoveMaterialPset(bpy.types.Operator):
bl_idname = "bim.remove_material_pset"
bl_label = "Remove Pset"
pset_index: bpy.props.IntProperty()
def execute(self, context):
bpy.context.active_object.active_material.BIMMaterialProperties.psets.remove(self.pset_index)
return {"FINISHED"}
class AddMaterialAttribute(bpy.types.Operator):
bl_idname = "bim.add_material_attribute"
bl_label = "Add Material Attribute"
def execute(self, context):
if bpy.context.active_object.active_material.BIMMaterialProperties.applicable_attributes:
attribute = bpy.context.active_object.active_material.BIMMaterialProperties.attributes.add()
attribute.name = bpy.context.active_object.active_material.BIMMaterialProperties.applicable_attributes
return {"FINISHED"}
class RemoveMaterialAttribute(bpy.types.Operator):
bl_idname = "bim.remove_material_attribute"
bl_label = "Remove Material Attribute"
attribute_index: bpy.props.IntProperty()
def execute(self, context):
bpy.context.active_object.active_material.BIMMaterialProperties.attributes.remove(self.attribute_index)
return {"FINISHED"}
class AddSweptSolid(bpy.types.Operator): class AddSweptSolid(bpy.types.Operator):
bl_idname = "bim.add_swept_solid" bl_idname = "bim.add_swept_solid"
bl_label = "Add Swept Solid" bl_label = "Add Swept Solid"
@@ -413,374 +321,6 @@ class SelectExternalMaterialDir(bpy.types.Operator):
return {"RUNNING_MODAL"} return {"RUNNING_MODAL"}
class ExportClashSets(bpy.types.Operator):
bl_idname = "bim.export_clash_sets"
bl_label = "Export Clash Sets"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
clash_sets = []
for clash_set in bpy.context.scene.BIMProperties.clash_sets:
self.a = []
self.b = []
for ab in ["a", "b"]:
for data in getattr(clash_set, ab):
clash_source = {"file": data.name}
if data.selector:
clash_source["selector"] = data.selector
clash_source["mode"] = data.mode
getattr(self, ab).append(clash_source)
clash_sets.append({"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b})
with open(self.filepath, "w") as destination:
destination.write(json.dumps(clash_sets, indent=4))
return {"FINISHED"}
class ImportClashSets(bpy.types.Operator):
bl_idname = "bim.import_clash_sets"
bl_label = "Import Clash Sets"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
with open(self.filepath) as f:
clash_sets = json.load(f)
for clash_set in clash_sets:
new = bpy.context.scene.BIMProperties.clash_sets.add()
new.name = clash_set["name"]
new.tolerance = clash_set["tolerance"]
for clash_source in clash_set["a"]:
new_source = new.a.add()
new_source.name = clash_source["file"]
if "selector" in clash_source:
new_source.selector = clash_source["selector"]
new_source.mode = clash_source["mode"]
if clash_set["b"]:
for clash_source in clash_set["b"]:
new_source = new.b.add()
new_source.name = clash_source["file"]
if "selector" in clash_source:
new_source.selector = clash_source["selector"]
new_source.mode = clash_source["mode"]
return {"FINISHED"}
class AddClashSet(bpy.types.Operator):
bl_idname = "bim.add_clash_set"
bl_label = "Add Clash Set"
def execute(self, context):
new = bpy.context.scene.BIMProperties.clash_sets.add()
new.name = "New Clash Set"
new.tolerance = 0.01
return {"FINISHED"}
class RemoveClashSet(bpy.types.Operator):
bl_idname = "bim.remove_clash_set"
bl_label = "Remove Clash Set"
index: bpy.props.IntProperty()
def execute(self, context):
bpy.context.scene.BIMProperties.clash_sets.remove(self.index)
return {"FINISHED"}
class AddClashSource(bpy.types.Operator):
bl_idname = "bim.add_clash_source"
bl_label = "Add Clash Source"
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = bpy.context.scene.BIMProperties.clash_sets[bpy.context.scene.BIMProperties.active_clash_set_index]
source = getattr(clash_set, self.group).add()
return {"FINISHED"}
class RemoveClashSource(bpy.types.Operator):
bl_idname = "bim.remove_clash_source"
bl_label = "Remove Clash Source"
index: bpy.props.IntProperty()
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = bpy.context.scene.BIMProperties.clash_sets[bpy.context.scene.BIMProperties.active_clash_set_index]
getattr(clash_set, self.group).remove(self.index)
return {"FINISHED"}
class SelectClashSource(bpy.types.Operator):
bl_idname = "bim.select_clash_source"
bl_label = "Select Clash Source"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
index: bpy.props.IntProperty()
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = bpy.context.scene.BIMProperties.clash_sets[bpy.context.scene.BIMProperties.active_clash_set_index]
getattr(clash_set, self.group)[self.index].name = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectClashResults(bpy.types.Operator):
bl_idname = "bim.select_clash_results"
bl_label = "Select Clash Results"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BIMProperties.clash_results_path = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectSmartGroupedClashesPath(bpy.types.Operator):
bl_idname = "bim.select_smart_grouped_clashes_path"
bl_label = "Select Smart-Grouped Clashes Path"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.context.scene.BIMProperties.smart_grouped_clashes_path = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class ExecuteIfcClash(bpy.types.Operator):
bl_idname = "bim.execute_ifc_clash"
bl_label = "Execute IFC Clash"
filename_ext = ".bcf"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
if ".json" not in bpy.data.filepath:
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
import ifcclash
settings = ifcclash.IfcClashSettings()
if ".json" not in self.filepath:
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
ifc_clasher = ifcclash.IfcClasher(settings)
if bpy.context.scene.BIMProperties.should_create_clash_snapshots:
def get_viewpoint_snapshot(self, viewpoint, mat):
camera = bpy.data.objects.get("IFC Clash Camera")
if not camera:
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
bpy.context.scene.collection.objects.link(camera)
camera.matrix_world = Matrix(mat)
bpy.context.scene.camera = camera
camera.data.angle = radians(60)
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].region_3d.view_perspective = "CAMERA"
area.spaces[0].shading.show_xray = True
bpy.context.scene.render.resolution_x = 480
bpy.context.scene.render.resolution_y = 270
bpy.context.scene.render.image_settings.file_format = "PNG"
bpy.context.scene.render.filepath = os.path.join(
bpy.context.scene.BIMProperties.data_dir, "snapshot.png"
)
bpy.ops.render.opengl(write_still=True)
return bpy.context.scene.render.filepath
ifcclash.IfcClasher.get_viewpoint_snapshot = get_viewpoint_snapshot
ifc_clasher.clash_sets = []
for clash_set in bpy.context.scene.BIMProperties.clash_sets:
self.a = []
self.b = []
for ab in ["a", "b"]:
for data in getattr(clash_set, ab):
clash_source = {"file": data.name}
if data.selector:
clash_source["selector"] = data.selector
clash_source["mode"] = data.mode
getattr(self, ab).append(clash_source)
ifc_clasher.clash_sets.append(
{"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b}
)
ifc_clasher.clash()
ifc_clasher.export()
return {"FINISHED"}
class SelectIfcClashResults(bpy.types.Operator):
bl_idname = "bim.select_ifc_clash_results"
bl_label = "Select IFC Clash Results"
filename_ext = ".json"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
def execute(self, context):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
clash_sets = json.load(f)
clash_set_name = bpy.context.scene.BIMProperties.clash_sets[
bpy.context.scene.BIMProperties.active_clash_set_index
].name
global_ids = []
for clash_set in clash_sets:
if clash_set["name"] != clash_set_name:
continue
if not "clashes" in clash_set.keys():
self.report({"WARNING"}, "No clashes found for the selected Clash Set.")
return {"CANCELLED"}
for clash in clash_set["clashes"].values():
global_ids.extend([clash["a_global_id"], clash["b_global_id"]])
for obj in bpy.context.visible_objects:
global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
if global_id and global_id.string_value in global_ids:
obj.select_set(True)
return {"FINISHED"}
class SmartClashGroup(bpy.types.Operator):
bl_idname = "bim.smart_clash_group"
bl_label = "Smart Group Clashes"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
import ifcclash
settings = ifcclash.IfcClashSettings()
self.filepath = bpy.path.ensure_ext(bpy.context.scene.BIMProperties.clash_results_path, ".json")
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
ifc_clasher = ifcclash.IfcClasher(settings)
with open(self.filepath) as f:
clash_sets = json.load(f)
# execute the smart grouping
save_path = bpy.path.ensure_ext(bpy.context.scene.BIMProperties.smart_grouped_clashes_path, ".json")
smart_grouped_clashes = ifc_clasher.smart_group_clashes(
clash_sets, bpy.context.scene.BIMProperties.smart_clash_grouping_max_distance
)
# save smart_groups to json
with open(save_path, "w") as f:
f.write(json.dumps(smart_grouped_clashes))
clash_set_name = bpy.context.scene.BIMProperties.clash_sets[
bpy.context.scene.BIMProperties.active_clash_set_index
].name
# Reset the list of smart_clash_groups for the UI
bpy.context.scene.BIMProperties.smart_clash_groups.clear()
for clash_set, smart_groups in smart_grouped_clashes.items():
# Only select the clashes that correspond to the actively selected IFC Clash Set
if clash_set != clash_set_name:
continue
else:
for smart_group, global_id_pairs in smart_groups[0].items():
new_group = bpy.context.scene.BIMProperties.smart_clash_groups.add()
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
for id in pair:
new_global_id = new_group.global_ids.add()
new_global_id.name = id
return {"FINISHED"}
class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
bl_idname = "bim.load_smart_groups_for_active_clash_set"
bl_label = "Load Smart Groups for Active Clash Set"
def execute(self, context):
smart_groups_path = bpy.path.ensure_ext(bpy.context.scene.BIMProperties.smart_grouped_clashes_path, ".json")
clash_set_name = bpy.context.scene.BIMProperties.clash_sets[
bpy.context.scene.BIMProperties.active_clash_set_index
].name
with open(smart_groups_path) as f:
smart_grouped_clashes = json.load(f)
# Reset the list of smart_clash_groups for the UI
bpy.context.scene.BIMProperties.smart_clash_groups.clear()
for clash_set, smart_groups in smart_grouped_clashes.items():
# Only select the clashes that correspond to the actively selected IFC Clash Set
if clash_set != clash_set_name:
continue
else:
for smart_group, global_id_pairs in smart_groups[0].items():
new_group = bpy.context.scene.BIMProperties.smart_clash_groups.add()
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
for id in pair:
new_global_id = new_group.global_ids.add()
new_global_id.name = id
return {"FINISHED"}
class SelectSmartGroup(bpy.types.Operator):
bl_idname = "bim.select_smart_group"
bl_label = "Select Smart Group"
def execute(self, context):
# Select smart group in view
selected_smart_group = bpy.context.scene.BIMProperties.smart_clash_groups[
bpy.context.scene.BIMProperties.active_smart_group_index
]
# print(selected_smart_group.number)
for obj in bpy.context.visible_objects:
global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
if global_id:
for id in selected_smart_group.global_ids:
# print("Id: ", id)
# print("Global id: ", global_id.string_value)
if global_id.string_value in id.name:
# print("object match: ", global_id)
obj.select_set(True)
return {"FINISHED"}
class SelectIfcFile(bpy.types.Operator): class SelectIfcFile(bpy.types.Operator):
bl_idname = "bim.select_ifc_file" bl_idname = "bim.select_ifc_file"
bl_label = "Select IFC File" bl_label = "Select IFC File"
@@ -2082,88 +1622,6 @@ class RefreshDrawingList(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class BlenderClasher:
def process_clash_set(self):
import collision
a_cm = collision.CollisionManager()
b_cm = collision.CollisionManager()
self.add_to_cm(a_cm, bpy.context.scene.BIMProperties.blender_clash_set_a)
self.add_to_cm(b_cm, bpy.context.scene.BIMProperties.blender_clash_set_b)
results = a_cm.in_collision_other(b_cm, return_data=True)
if not results[0]:
print("No clashes")
return
for contact in results[1]:
if contact.raw.penetration_depth < 0.01:
continue
print("-----")
print(contact.names)
print(contact.raw.normal)
print(contact.raw.pos)
def add_to_cm(self, cm, object_names):
import numpy as np
import ifcclash
for object_name in object_names:
name = object_name.name
obj = bpy.data.objects[name]
triangulated_mesh = self.triangulate_mesh(obj)
mesh = ifcclash.Mesh()
mesh.vertices = np.array([tuple(obj.matrix_world @ v.co) for v in triangulated_mesh.vertices])
mesh.faces = np.array([tuple(p.vertices) for p in triangulated_mesh.polygons])
cm.add_object(name, mesh)
def triangulate_mesh(self, obj):
import bmesh
mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(mesh)
bm.free()
del bm
return mesh
class SetBlenderClashSetA(bpy.types.Operator):
bl_idname = "bim.set_blender_clash_set_a"
bl_label = "Set Blender Clash Set A"
def execute(self, context):
while len(bpy.context.scene.BIMProperties.blender_clash_set_a) > 0:
bpy.context.scene.BIMProperties.blender_clash_set_a.remove(0)
for obj in bpy.context.selected_objects:
new = bpy.context.scene.BIMProperties.blender_clash_set_a.add()
new.name = obj.name
return {"FINISHED"}
class SetBlenderClashSetB(bpy.types.Operator):
bl_idname = "bim.set_blender_clash_set_b"
bl_label = "Set Blender Clash Set B"
def execute(self, context):
while len(bpy.context.scene.BIMProperties.blender_clash_set_b) > 0:
bpy.context.scene.BIMProperties.blender_clash_set_b.remove(0)
for obj in bpy.context.selected_objects:
new = bpy.context.scene.BIMProperties.blender_clash_set_b.add()
new.name = obj.name
return {"FINISHED"}
class ExecuteBlenderClash(bpy.types.Operator):
bl_idname = "bim.execute_blender_clash"
bl_label = "Execute Blender Clash"
def execute(self, context):
blender_clasher = BlenderClasher()
blender_clasher.process_clash_set()
return {"FINISHED"}
class CleanWireframes(bpy.types.Operator): class CleanWireframes(bpy.types.Operator):
bl_idname = "bim.clean_wireframes" bl_idname = "bim.clean_wireframes"
bl_label = "Clean Wireframes" bl_label = "Clean Wireframes"
+23 -44
View File
@@ -26,12 +26,8 @@ from bpy.props import (
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
diagram_scales_enum = [] diagram_scales_enum = []
profiledef_enum = []
availablematerialpsets_enum = []
titleblocks_enum = [] titleblocks_enum = []
materialpsetnames_enum = [] materialpsetnames_enum = []
attributes_enum = []
materialattributes_enum = []
contexts_enum = [] contexts_enum = []
subcontexts_enum = [] subcontexts_enum = []
target_views_enum = [] target_views_enum = []
@@ -255,14 +251,22 @@ def getMaterialPsetNames(self, context):
def getContexts(self, context): def getContexts(self, context):
from blenderbim.bim.module.context.data import Data from blenderbim.bim.module.context.data import Data
if not Data.is_loaded: if not Data.is_loaded:
Data.load() Data.load()
results = [] results = []
for ifc_id, context in Data.contexts.items(): for ifc_id, context in Data.contexts.items():
results.append((str(ifc_id), context["ContextType"], "")) results.append((str(ifc_id), context["ContextType"], ""))
for ifc_id2, subcontext in context["HasSubContexts"].items(): for ifc_id2, subcontext in context["HasSubContexts"].items():
results.append((str(ifc_id2), "{}/{}/{}".format( results.append(
subcontext["ContextType"], subcontext["ContextIdentifier"], subcontext["TargetView"]), "")) (
str(ifc_id2),
"{}/{}/{}".format(
subcontext["ContextType"], subcontext["ContextIdentifier"], subcontext["TargetView"]
),
"",
)
)
return results return results
@@ -372,7 +376,11 @@ class DrawingStyle(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
raster_style: StringProperty(name="Raster Style") raster_style: StringProperty(name="Raster Style")
render_type: EnumProperty( render_type: EnumProperty(
items=[("NONE", "None", ""), ("DEFAULT", "Default", ""), ("VIEWPORT", "Viewport", ""),], items=[
("NONE", "None", ""),
("DEFAULT", "Default", ""),
("VIEWPORT", "Viewport", ""),
],
name="Render Type", name="Render Type",
default="VIEWPORT", default="VIEWPORT",
) )
@@ -397,8 +405,9 @@ class DocProperties(PropertyGroup):
ifc_files: CollectionProperty(name="IFCs", type=StrProperty) ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle) drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations) should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
decorations_colour: FloatVectorProperty(name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), decorations_colour: FloatVectorProperty(
min=0.0, max=1.0, size=4) name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
)
class BIMCameraProperties(PropertyGroup): class BIMCameraProperties(PropertyGroup):
@@ -448,7 +457,11 @@ class BIMTextProperties(PropertyGroup):
name="Font Size", name="Font Size",
) )
symbol: EnumProperty( symbol: EnumProperty(
items=[("None", "None", ""), ("rectangle-tag", "Rectangle Tag", ""), ("door-tag", "Door Tag", ""),], items=[
("None", "None", ""),
("rectangle-tag", "Rectangle Tag", ""),
("door-tag", "Door Tag", ""),
],
update=refreshFontSize, update=refreshFontSize,
name="Symbol", name="Symbol",
) )
@@ -456,30 +469,6 @@ class BIMTextProperties(PropertyGroup):
variables: CollectionProperty(name="Variables", type=Variable) variables: CollectionProperty(name="Variables", type=Variable)
class ClashSource(PropertyGroup):
name: StringProperty(name="File")
selector: StringProperty(name="Selector")
mode: EnumProperty(
items=[
("i", "Include", "Only the selected objects are included for clashing"),
("e", "Exclude", "All objects except the selected objects are included for clashing"),
],
name="Mode",
)
class ClashSet(PropertyGroup):
name: StringProperty(name="Name")
tolerance: FloatProperty(name="Tolerance")
a: CollectionProperty(name="Group A", type=ClashSource)
b: CollectionProperty(name="Group B", type=ClashSource)
class SmartClashGroup(PropertyGroup):
number: StringProperty(name="Number")
global_ids: CollectionProperty(name="GlobalIDs", type=StrProperty)
class BIMProperties(PropertyGroup): class BIMProperties(PropertyGroup):
schema_dir: StringProperty(default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory") schema_dir: StringProperty(default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory")
data_dir: StringProperty(default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory") data_dir: StringProperty(default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory")
@@ -494,16 +483,6 @@ class BIMProperties(PropertyGroup):
section_plane_colour: FloatVectorProperty( section_plane_colour: FloatVectorProperty(
name="Temporary Section Cutaway Colour", subtype="COLOR", default=(1, 0, 0), min=0.0, max=1.0 name="Temporary Section Cutaway Colour", subtype="COLOR", default=(1, 0, 0), min=0.0, max=1.0
) )
blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty)
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet)
should_create_clash_snapshots: BoolProperty(name="Create Snapshots", default=True)
clash_results_path: StringProperty(name="Clash Results Path")
smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path")
active_clash_set_index: IntProperty(name="Active Clash Set Index")
smart_clash_groups: CollectionProperty(name="Smart Clash Groups", type=SmartClashGroup)
active_smart_group_index: IntProperty(name="Active Smart Group Index")
smart_clash_grouping_max_distance: IntProperty(name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10)
area_unit: EnumProperty( area_unit: EnumProperty(
default="SQUARE_METRE", default="SQUARE_METRE",
items=[ items=[
-178
View File
@@ -5,38 +5,6 @@ from bpy.types import Panel
from bpy.props import StringProperty from bpy.props import StringProperty
class BIM_PT_object_structural(Panel):
bl_label = "IFC Structural Relationships"
bl_idname = "BIM_PT_object_structural"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
@classmethod
def poll(cls, context):
return context.active_object is not None and hasattr(context.active_object, "BIMObjectProperties")
def draw(self, context):
if context.active_object is None:
return
layout = self.layout
props = context.active_object.BIMObjectProperties
row = layout.row()
row.prop(props, "has_boundary_condition")
if bpy.context.active_object.BIMObjectProperties.has_boundary_condition:
row = layout.row()
row.prop(props.boundary_condition, "name")
for index, attribute in enumerate(props.boundary_condition.attributes):
row = layout.row(align=True)
row.prop(attribute, "name", text="")
row.prop(attribute, "string_value", text="")
row = layout.row()
row.prop(props, "structural_member_connection")
class BIM_PT_drawings(Panel): class BIM_PT_drawings(Panel):
bl_label = "SVG Drawings" bl_label = "SVG Drawings"
bl_idname = "BIM_PT_drawings" bl_idname = "BIM_PT_drawings"
@@ -341,24 +309,6 @@ class BIM_UL_topics(bpy.types.UIList):
layout.label(text="", translate=False) layout.label(text="", translate=False)
class BIM_UL_clash_sets(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
if item:
layout.prop(item, "name", text="", emboss=False)
else:
layout.label(text="", translate=False)
class BIM_UL_smart_groups(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
if item:
layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0)
else:
layout.label(text="", translate=False)
class BIM_ADDON_preferences(bpy.types.AddonPreferences): class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bl_idname = "blenderbim" bl_idname = "blenderbim"
svg2pdf_command: StringProperty(name="SVG to PDF Command", description="E.g. [['inkscape', svg, '-o', pdf]]") svg2pdf_command: StringProperty(name="SVG to PDF Command", description="E.g. [['inkscape', svg, '-o', pdf]]")
@@ -387,94 +337,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(self, "pdf_command") row.prop(self, "pdf_command")
class BIM_PT_ifcclash(Panel):
bl_label = "IFC Clash Sets"
bl_idname = "BIM_PT_ifcclash"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = context.scene
props = scene.BIMProperties
layout.label(text="Blender Clash:")
row = layout.row(align=True)
row.operator("bim.set_blender_clash_set_a")
row.operator("bim.set_blender_clash_set_b")
row = layout.row(align=True)
row.operator("bim.execute_blender_clash")
layout.label(text="IFC Clash:")
row = layout.row(align=True)
row.operator("bim.add_clash_set")
row.operator("bim.import_clash_sets", text="", icon="IMPORT")
row.operator("bim.export_clash_sets", text="", icon="EXPORT")
if not props.clash_sets:
return
layout.template_list("BIM_UL_clash_sets", "", props, "clash_sets", props, "active_clash_set_index")
if props.active_clash_set_index < len(props.clash_sets):
clash_set = props.clash_sets[props.active_clash_set_index]
row = layout.row(align=True)
row.prop(clash_set, "name")
row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index
row = layout.row(align=True)
row.prop(clash_set, "tolerance")
layout.label(text="Group A:")
row = layout.row()
row.operator("bim.add_clash_source").group = "a"
for index, source in enumerate(clash_set.a):
row = layout.row(align=True)
row.prop(source, "name", text="")
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = "a"
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = "a"
row = layout.row(align=True)
row.prop(source, "mode", text="")
row.prop(source, "selector", text="")
layout.label(text="Group B:")
row = layout.row()
row.operator("bim.add_clash_source").group = "b"
for index, source in enumerate(clash_set.b):
row = layout.row(align=True)
row.prop(source, "name", text="")
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
op.index = index
op.group = "b"
op = row.operator("bim.remove_clash_source", icon="X", text="")
op.index = index
op.group = "b"
row = layout.row(align=True)
row.prop(source, "mode", text="")
row.prop(source, "selector", text="")
row = layout.row()
row.prop(props, "should_create_clash_snapshots")
row = layout.row(align=True)
row.operator("bim.execute_ifc_clash")
row.operator("bim.select_ifc_clash_results")
class BIM_PT_annotation_utilities(Panel): class BIM_PT_annotation_utilities(Panel):
bl_idname = "BIM_PT_annotation_utilities" bl_idname = "BIM_PT_annotation_utilities"
bl_label = "Annotation" bl_label = "Annotation"
@@ -550,46 +412,6 @@ class BIM_PT_annotation_utilities(Panel):
layout.prop(props, "decorations_colour") layout.prop(props, "decorations_colour")
class BIM_PT_clash_manager(Panel):
bl_idname = "BIM_PT_clash_manager"
bl_label = "Clash Manager"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BlenderBIM"
def draw(self, context):
layout = self.layout
props = context.scene.BIMProperties
row = layout.row()
layout.label(text="Select clash results to group:")
row = layout.row(align=True)
row.prop(props, "clash_results_path", text="")
op = row.operator("bim.select_clash_results", icon="FILE_FOLDER", text="")
row = layout.row()
layout.label(text="Select output path for smart-grouped clashes:")
row = layout.row(align=True)
row.prop(props, "smart_grouped_clashes_path", text="")
op = row.operator("bim.select_smart_grouped_clashes_path", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(props, "smart_clash_grouping_max_distance")
row = layout.row(align=True)
row.operator("bim.smart_clash_group")
row = layout.row(align=True)
row.operator("bim.load_smart_groups_for_active_clash_set")
layout.template_list("BIM_UL_smart_groups", "", props, "smart_clash_groups", props, "active_smart_group_index")
row = layout.row(align=True)
row.operator("bim.select_smart_group")
class BIM_PT_misc_utilities(Panel): class BIM_PT_misc_utilities(Panel):
bl_idname = "BIM_PT_misc_utilities" bl_idname = "BIM_PT_misc_utilities"
bl_label = "Miscellaneous" bl_label = "Miscellaneous"