Minimum viable protoype for Smart Clash Grouping completed

This commit is contained in:
Vincent Cadoret
2020-11-10 21:01:51 -05:00
committed by Dion Moult
parent 51dca4329c
commit 5a06bd5a57
5 changed files with 146 additions and 8 deletions
@@ -144,6 +144,7 @@ if bpy is not None:
operator.ExecuteIfcClash,
operator.SelectIfcClashResults,
operator.SmartClashGroup,
operator.IsolateSmartGroup,
operator.SwitchContext,
operator.RemoveContext,
operator.OpenUpstream,
@@ -245,6 +246,7 @@ if bpy is not None:
prop.DocumentReference,
prop.ClashSource,
prop.ClashSet,
prop.SmartClashGroup,
prop.Constraint,
prop.Drawing,
prop.Schedule,
@@ -324,6 +326,7 @@ if bpy is not None:
ui.BIM_PT_misc_utilities,
ui.BIM_UL_generic,
ui.BIM_UL_clash_sets,
ui.BIM_UL_smart_groups,
ui.BIM_UL_constraints,
ui.BIM_UL_document_information,
ui.BIM_UL_document_references,
@@ -11,6 +11,7 @@ import ifcopenshell.util.selector
import ifcopenshell.util.geolocation
import ifcopenshell.util.pset
import tempfile
import numpy as np
from . import export_ifc
from . import import_ifc
from . import qto
@@ -29,6 +30,8 @@ from mathutils import Vector, Matrix, Euler, geometry
from math import radians, atan, tan, cos, sin
from pathlib import Path
from bpy.app.handlers import persistent
from sklearn.cluster import OPTICS
from collections import defaultdict
colour_list = [
(0.651, 0.81, 0.892, 1),
@@ -1889,20 +1892,74 @@ class SmartClashGroup(bpy.types.Operator):
return {"RUNNING_MODAL"}
def execute(self, context):
import ifcclash
settings = ifcclash.IfcClashSettings()
self.filepath = bpy.path.ensure_ext(self.filepath, ".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 here (but put the heavy lifting code somewhere else in a class)
save_path = r"C:\Users\vince\Desktop\SmartGrouping Demo\smart-groups.json"
smart_grouped_clashes = ifc_clasher.smart_group_clashes(clash_sets)
# save smart_groups to json
with open(save_path, 'w') as f:
f.write(json.dumps(smart_grouped_clashes))
# TODO: load into BIM Properties for easy access
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():
#print(smart_group)
#print(type(smart_group))
print("Global Ids: ", global_id_pairs)
print(type(global_id_pairs))
new_group = bpy.context.scene.BIMProperties.smart_clash_groups.add()
new_group.number = smart_group
for pair in global_id_pairs:
print("id: ", pair)
for id in pair:
new_global_id = new_group.global_ids.add()
new_global_id.name = id
return {"FINISHED"}
class IsolateSmartGroup(bpy.types.Operator):
bl_idname = "bim.isolate_smart_group"
bl_label = "Isolate 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 smart_groups in clash_sets.values():
for smart_group_list in smart_groups:
for smart_group, clashes in smart_group_list.items():
for id in clashes:
if global_id.string_value in id:
print("object match: ", global_id)
obj.select_set(True)
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"}
@@ -803,6 +803,10 @@ class PresentationLayer(PropertyGroup):
layer_frozen: BoolProperty(name="LayerFrozen", default=False)
layer_blocked: BoolProperty(name="LayerBlocked", default=False)
class SmartClashGroup(PropertyGroup):
number: IntProperty(name="Number")
global_ids: CollectionProperty(name="GlobalIDs", type=StrProperty)
class Constraint(PropertyGroup):
name: StringProperty(name="Name")
@@ -1519,6 +1523,8 @@ class BIMProperties(PropertyGroup):
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet)
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")
constraints: CollectionProperty(name="Constraints", type=Constraint)
active_constraint_index: IntProperty(name="Active Constraint Index")
eastings: StringProperty(name="Eastings")
+14
View File
@@ -2102,6 +2102,14 @@ class BIM_UL_clash_sets(bpy.types.UIList):
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)
#layout.prop(item, "number", text="", emboss=False, icon_value=icon)
else:
layout.label(text="", translate=False)
class BIM_UL_constraints(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
@@ -2370,6 +2378,12 @@ class BIM_PT_clash_manager(Panel):
row = layout.row(align=True)
row.operator("bim.smart_clash_group")
layout.template_list('BIM_UL_smart_groups', '', props, 'smart_clash_groups', props, 'active_smart_group_index')
# TODO: add operator to isolate smart groups one by one in the viewport
row = layout.row(align=True)
row.operator("bim.isolate_smart_group")
class BIM_PT_misc_utilities(Panel):
bl_idname = "BIM_PT_misc_utilities"
+59 -1
View File
@@ -10,7 +10,8 @@ import json
import sys
import argparse
import logging
from sklearn.cluster import OPTICS
from collections import defaultdict
class Mesh:
faces: []
@@ -267,6 +268,63 @@ class IfcClasher:
if element.ObjectPlacement.RelativePlacement.RefDirection:
element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1.0, 0.0, 0.0)
def smart_group_clashes(self, clash_sets):
count_of_input_clashes = 0
count_of_clash_sets = 0
count_of_smart_groups = 0
count_of_final_clash_sets = 0
count_of_clash_sets = len(clash_sets)
for clash_set in clash_sets:
if not "clashes" in clash_set.keys():
print(f"Skipping clash set [{clash_set['name']}] since it contains no clash results.")
continue
clashes = clash_set["clashes"]
count_of_input_clashes += len(clashes)
positions = []
for clash in clashes.values():
positions.append(clash["position"])
data = np.array(positions)
# INPUTS
# set the desired maximum distance between the grouped points
max_distance_between_grouped_points = 3
model = OPTICS(min_samples=2, max_eps=max_distance_between_grouped_points)
model.fit_predict(data)
pred = model.fit_predict(data)
# Insert the smart groups into the clashes
if len(pred) == len(clashes.values()):
i = 0
for clash in clashes.values():
clash["smart_group"] = int(pred[i])
i += 1
# Create JSON with smart_groups that contain GlobalIDs
output_clash_sets = defaultdict(list)
for clash_set in clash_sets:
if not "clashes" in clash_set.keys():
continue
smart_groups = defaultdict(list)
for clash_id, content in clash_set['clashes'].items():
if "smart_group" in content:
object_id_list = list()
# Clash has been grouped, let's extract it.
object_id_list.append(content['a_global_id'])
object_id_list.append(content['b_global_id'])
smart_groups[content['smart_group']].append(object_id_list)
count_of_smart_groups += len(smart_groups)
output_clash_sets[clash_set["name"]].append(smart_groups)
count_of_final_clash_sets = len(output_clash_sets)
print(f"Took {count_of_input_clashes} clashes in {count_of_clash_sets} clash sets and turned",
f"them into {count_of_smart_groups} smart groups in {count_of_final_clash_sets} clash sets")
return output_clash_sets
class IfcClashSettings:
def __init__(self):