mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +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.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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user