mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-10-01 04:29:38 +00:00
Minimum viable protoype for Smart Clash Grouping completed
This commit is contained in:
committed by
Dion Moult
parent
51dca4329c
commit
5a06bd5a57
@@ -144,6 +144,7 @@ if bpy is not None:
|
|||||||
operator.ExecuteIfcClash,
|
operator.ExecuteIfcClash,
|
||||||
operator.SelectIfcClashResults,
|
operator.SelectIfcClashResults,
|
||||||
operator.SmartClashGroup,
|
operator.SmartClashGroup,
|
||||||
|
operator.IsolateSmartGroup,
|
||||||
operator.SwitchContext,
|
operator.SwitchContext,
|
||||||
operator.RemoveContext,
|
operator.RemoveContext,
|
||||||
operator.OpenUpstream,
|
operator.OpenUpstream,
|
||||||
@@ -245,6 +246,7 @@ if bpy is not None:
|
|||||||
prop.DocumentReference,
|
prop.DocumentReference,
|
||||||
prop.ClashSource,
|
prop.ClashSource,
|
||||||
prop.ClashSet,
|
prop.ClashSet,
|
||||||
|
prop.SmartClashGroup,
|
||||||
prop.Constraint,
|
prop.Constraint,
|
||||||
prop.Drawing,
|
prop.Drawing,
|
||||||
prop.Schedule,
|
prop.Schedule,
|
||||||
@@ -324,6 +326,7 @@ if bpy is not None:
|
|||||||
ui.BIM_PT_misc_utilities,
|
ui.BIM_PT_misc_utilities,
|
||||||
ui.BIM_UL_generic,
|
ui.BIM_UL_generic,
|
||||||
ui.BIM_UL_clash_sets,
|
ui.BIM_UL_clash_sets,
|
||||||
|
ui.BIM_UL_smart_groups,
|
||||||
ui.BIM_UL_constraints,
|
ui.BIM_UL_constraints,
|
||||||
ui.BIM_UL_document_information,
|
ui.BIM_UL_document_information,
|
||||||
ui.BIM_UL_document_references,
|
ui.BIM_UL_document_references,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import ifcopenshell.util.selector
|
|||||||
import ifcopenshell.util.geolocation
|
import ifcopenshell.util.geolocation
|
||||||
import ifcopenshell.util.pset
|
import ifcopenshell.util.pset
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import numpy as np
|
||||||
from . import export_ifc
|
from . import export_ifc
|
||||||
from . import import_ifc
|
from . import import_ifc
|
||||||
from . import qto
|
from . import qto
|
||||||
@@ -29,6 +30,8 @@ from mathutils import Vector, Matrix, Euler, geometry
|
|||||||
from math import radians, atan, tan, cos, sin
|
from math import radians, atan, tan, cos, sin
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from bpy.app.handlers import persistent
|
from bpy.app.handlers import persistent
|
||||||
|
from sklearn.cluster import OPTICS
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
colour_list = [
|
colour_list = [
|
||||||
(0.651, 0.81, 0.892, 1),
|
(0.651, 0.81, 0.892, 1),
|
||||||
@@ -1889,19 +1892,73 @@ class SmartClashGroup(bpy.types.Operator):
|
|||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
import ifcclash
|
||||||
|
|
||||||
|
settings = ifcclash.IfcClashSettings()
|
||||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
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:
|
with open(self.filepath) as f:
|
||||||
clash_sets = json.load(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:
|
for obj in bpy.context.visible_objects:
|
||||||
global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
|
global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
|
||||||
if global_id:
|
if global_id:
|
||||||
for smart_groups in clash_sets.values():
|
for id in selected_smart_group.global_ids:
|
||||||
for smart_group_list in smart_groups:
|
#print("Id: ", id)
|
||||||
for smart_group, clashes in smart_group_list.items():
|
#print("Global id: ", global_id.string_value)
|
||||||
for id in clashes:
|
if global_id.string_value in id.name:
|
||||||
if global_id.string_value in id:
|
#print("object match: ", global_id)
|
||||||
print("object match: ", global_id)
|
|
||||||
obj.select_set(True)
|
obj.select_set(True)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -803,6 +803,10 @@ class PresentationLayer(PropertyGroup):
|
|||||||
layer_frozen: BoolProperty(name="LayerFrozen", default=False)
|
layer_frozen: BoolProperty(name="LayerFrozen", default=False)
|
||||||
layer_blocked: BoolProperty(name="LayerBlocked", 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):
|
class Constraint(PropertyGroup):
|
||||||
name: StringProperty(name="Name")
|
name: StringProperty(name="Name")
|
||||||
@@ -1519,6 +1523,8 @@ class BIMProperties(PropertyGroup):
|
|||||||
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
|
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
|
||||||
clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet)
|
clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet)
|
||||||
active_clash_set_index: IntProperty(name="Active Clash Set Index")
|
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)
|
constraints: CollectionProperty(name="Constraints", type=Constraint)
|
||||||
active_constraint_index: IntProperty(name="Active Constraint Index")
|
active_constraint_index: IntProperty(name="Active Constraint Index")
|
||||||
eastings: StringProperty(name="Eastings")
|
eastings: StringProperty(name="Eastings")
|
||||||
|
|||||||
@@ -2102,6 +2102,14 @@ class BIM_UL_clash_sets(bpy.types.UIList):
|
|||||||
else:
|
else:
|
||||||
layout.label(text="", translate=False)
|
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):
|
class BIM_UL_constraints(bpy.types.UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||||
@@ -2371,6 +2379,12 @@ class BIM_PT_clash_manager(Panel):
|
|||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator("bim.smart_clash_group")
|
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):
|
class BIM_PT_misc_utilities(Panel):
|
||||||
bl_idname = "BIM_PT_misc_utilities"
|
bl_idname = "BIM_PT_misc_utilities"
|
||||||
bl_label = "Miscellaneous"
|
bl_label = "Miscellaneous"
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import json
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
from sklearn.cluster import OPTICS
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
class Mesh:
|
class Mesh:
|
||||||
faces: []
|
faces: []
|
||||||
@@ -267,6 +268,63 @@ class IfcClasher:
|
|||||||
if element.ObjectPlacement.RelativePlacement.RefDirection:
|
if element.ObjectPlacement.RelativePlacement.RefDirection:
|
||||||
element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1.0, 0.0, 0.0)
|
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:
|
class IfcClashSettings:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user