This commit is contained in:
Andrej730
2025-03-24 12:30:58 +05:00
parent 595ee3fa47
commit 7a55e837f7
18 changed files with 239 additions and 100 deletions
+1 -1
View File
@@ -1170,7 +1170,7 @@ class IfcImportSettings:
@staticmethod
def factory(context=None, input_file=None, logger=None):
scene_diff = bpy.context.scene.DiffProperties
scene_diff = tool.Blender.get_diff_props()
props = tool.Project.get_project_props()
settings = IfcImportSettings()
settings.input_file = input_file
@@ -31,6 +31,8 @@ import isodate
import bonsai.core.sequence as core
import bonsai.tool as tool
import bonsai.bim.module.sequence.helper as helper
import ifcopenshell.api.spatial
import ifcopenshell.geom
import ifcopenshell.util.sequence
import ifcopenshell.util.selector
from datetime import datetime
@@ -59,8 +61,6 @@ class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
return True
def _execute(self, context):
import ifcopenshell.api.alignment
self.file = tool.Ifc.get()
start = time.time()
alignment = ifcopenshell.api.alignment.create_alignment_from_csv(self.file, self.filepath)
@@ -60,8 +60,9 @@ class ClashDecorator:
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
text = context.scene.BIMClashProperties.active_clash_text
p = context.scene.BIMClashProperties.p1.lerp(context.scene.BIMClashProperties.p2, 0.5)
props = tool.Clash.get_clash_props()
text = props.active_clash_text
p = props.p1.lerp(props.p2, 0.5)
font_id = 0
blf.size(font_id, 12)
@@ -92,7 +93,8 @@ class ClashDecorator:
# general shader
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
selected_vertices = [context.scene.BIMClashProperties.p1, context.scene.BIMClashProperties.p2]
props = tool.Clash.get_clash_props()
selected_vertices = [props.p1, props.p2]
selected_edges = []
if selected_vertices[0] != selected_vertices[1]:
selected_edges = [[0, 1]]
+46 -29
View File
@@ -69,9 +69,10 @@ class ImportClashSets(bpy.types.Operator):
def execute(self, context):
tool.Clash.load_clash_sets(self.filepath)
context.scene.BIMClashProperties.clash_sets.clear()
props = tool.Clash.get_clash_props()
props.clash_sets.clear()
for clash_set in tool.Clash.get_clash_sets():
new = context.scene.BIMClashProperties.clash_sets.add()
new = props.clash_sets.add()
new.name = clash_set["name"]
new.mode = clash_set["mode"]
if new.mode == "intersection":
@@ -106,7 +107,8 @@ class AddClashSet(bpy.types.Operator):
bl_description = "Add a clash set"
def execute(self, context):
new = context.scene.BIMClashProperties.clash_sets.add()
props = tool.Clash.get_clash_props()
new = props.clash_sets.add()
new.name = "New Clash Set"
return {"FINISHED"}
@@ -119,7 +121,8 @@ class RemoveClashSet(bpy.types.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
context.scene.BIMClashProperties.clash_sets.remove(self.index)
props = tool.Clash.get_clash_props()
props.clash_sets.remove(self.index)
return {"FINISHED"}
@@ -131,7 +134,8 @@ class AddClashSource(bpy.types.Operator):
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = context.scene.BIMClashProperties.active_clash_set
props = tool.Clash.get_clash_props()
clash_set = props.active_clash_set
source = getattr(clash_set, self.group).add()
return {"FINISHED"}
@@ -145,7 +149,8 @@ class RemoveClashSource(bpy.types.Operator):
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = context.scene.BIMClashProperties.active_clash_set
props = tool.Clash.get_clash_props()
clash_set = props.active_clash_set
getattr(clash_set, self.group).remove(self.index)
return {"FINISHED"}
@@ -161,7 +166,8 @@ class SelectClashSource(bpy.types.Operator):
group: bpy.props.StringProperty()
def execute(self, context):
clash_set = context.scene.BIMClashProperties.active_clash_set
props = tool.Clash.get_clash_props()
clash_set = props.active_clash_set
getattr(clash_set, self.group)[self.index].name = self.filepath
return {"FINISHED"}
@@ -178,7 +184,8 @@ class SelectClashResults(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
context.scene.BIMClashProperties.clash_results_path = self.filepath
props = tool.Clash.get_clash_props()
props.clash_results_path = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -194,7 +201,8 @@ class SelectSmartGroupedClashesPath(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath
props = tool.Clash.get_clash_props()
props.smart_grouped_clashes_path = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -218,7 +226,7 @@ class ExecuteIfcClash(bpy.types.Operator):
def execute(self, context):
from ifcclash import ifcclash
self.props = context.scene.BIMClashProperties
self.props = tool.Clash.get_clash_props()
_, extension = os.path.splitext(self.filepath)
if extension != ".bcf":
@@ -306,7 +314,9 @@ class SelectIfcClashResults(bpy.types.Operator):
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
with open(self.filepath) as f:
clash_sets = json.load(f)
clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
clash_props = tool.Clash.get_clash_props()
assert clash_props.active_clash_set
clash_set_name = clash_props.active_clash_set.name
global_ids = []
for clash_set in clash_sets:
if clash_set["name"] != clash_set_name:
@@ -358,7 +368,7 @@ class SelectClash(bpy.types.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMClashProperties
self.props = tool.Clash.get_clash_props()
clash_set = tool.Clash.get_clash_set(self.props.active_clash_set.name)
active_clash = self.props.active_clash
clash = tool.Clash.get_clash(clash_set, active_clash.a_global_id, active_clash.b_global_id)
@@ -392,13 +402,15 @@ class SmartClashGroup(bpy.types.Operator):
@classmethod
def poll(cls, context):
return context.scene.BIMClashProperties.clash_results_path
props = tool.Clash.get_clash_props()
return bool(props.clash_results_path)
def execute(self, context):
from ifcclash import ifcclash
settings = ifcclash.ClashSettings()
self.filepath = bpy.path.ensure_ext(context.scene.BIMClashProperties.clash_results_path, ".json")
props = tool.Clash.get_clash_props()
self.filepath = bpy.path.ensure_ext(props.clash_results_path, ".json")
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
settings.logger.setLevel(logging.DEBUG)
@@ -408,19 +420,18 @@ class SmartClashGroup(bpy.types.Operator):
clash_sets = json.load(f)
# execute the smart grouping
save_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
smart_grouped_clashes = ifc_clasher.smart_group_clashes(
clash_sets, context.scene.BIMClashProperties.smart_clash_grouping_max_distance
)
save_path = bpy.path.ensure_ext(props.smart_grouped_clashes_path, ".json")
smart_grouped_clashes = ifc_clasher.smart_group_clashes(clash_sets, props.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 = context.scene.BIMClashProperties.active_clash_set.name
assert props.active_clash_set
clash_set_name = props.active_clash_set.name
# Reset the list of smart_clash_groups for the UI
context.scene.BIMClashProperties.smart_clash_groups.clear()
props.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
@@ -428,7 +439,7 @@ class SmartClashGroup(bpy.types.Operator):
continue
else:
for smart_group, global_id_pairs in smart_groups[0].items():
new_group = context.scene.BIMClashProperties.smart_clash_groups.add()
new_group = props.smart_clash_groups.add()
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
@@ -446,18 +457,21 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
@classmethod
def poll(cls, context):
return context.scene.BIMClashProperties.active_clash_set
props = tool.Clash.get_clash_props()
return bool(props.active_clash_set)
def execute(self, context):
smart_groups_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
props = tool.Clash.get_clash_props()
smart_groups_path = bpy.path.ensure_ext(props.smart_grouped_clashes_path, ".json")
clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
assert props.active_clash_set
clash_set_name = props.active_clash_set.name
with open(smart_groups_path) as f:
smart_grouped_clashes = json.load(f)
# Reset the list of smart_clash_groups for the UI
context.scene.BIMClashProperties.smart_clash_groups.clear()
props.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
@@ -465,7 +479,7 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
continue
else:
for smart_group, global_id_pairs in smart_groups[0].items():
new_group = context.scene.BIMClashProperties.smart_clash_groups.add()
new_group = props.smart_clash_groups.add()
new_group.number = f"{smart_group}"
for pair in global_id_pairs:
for guid in pair:
@@ -482,11 +496,14 @@ class SelectSmartGroup(bpy.types.Operator):
@classmethod
def poll(cls, context):
return tool.Ifc.get() and context.visible_objects and context.scene.BIMClashProperties.active_smart_group
props = tool.Clash.get_clash_props()
return tool.Ifc.get() and context.visible_objects and props.active_smart_group
def execute(self, context):
selected_smart_group = context.scene.BIMClashProperties.active_smart_group
products = []
props = tool.Clash.get_clash_props()
selected_smart_group = props.active_smart_group
assert selected_smart_group
products: list[ifcopenshell.entity_instance] = []
for global_id in selected_smart_group.global_ids:
try:
products.append(tool.Ifc.get().by_guid(global_id.guid))
+54 -11
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
from bonsai.bim.prop import StrProperty, Attribute, BIMFilterGroup
from bpy.types import PropertyGroup
from bpy.props import (
@@ -29,6 +30,8 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from mathutils import Vector
from typing import TYPE_CHECKING, Literal, Union
class ClashSource(PropertyGroup):
@@ -43,6 +46,10 @@ class ClashSource(PropertyGroup):
name="Mode",
)
if TYPE_CHECKING:
filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]
mode: Literal["a", "i", "e"]
class Clash(PropertyGroup):
a_global_id: StringProperty(name="A")
@@ -51,9 +58,15 @@ class Clash(PropertyGroup):
b_name: StringProperty(name="B Name")
status: BoolProperty(name="Status", default=False)
if TYPE_CHECKING:
a_global_id: str
b_global_id: str
a_name: str
b_name: str
status: bool
class ClashSet(PropertyGroup):
name: StringProperty(name="Name")
mode: EnumProperty(
items=[
(
@@ -76,11 +89,25 @@ class ClashSet(PropertyGroup):
b: CollectionProperty(name="Group B", type=ClashSource)
clashes: CollectionProperty(name="Clashes", type=Clash)
if TYPE_CHECKING:
mode: Literal["intersection", "collision", "clearance"]
tolerance: float
clearance: float
allow_touching: bool
check_all: bool
a: bpy.types.bpy_prop_collection_idprop[ClashSource]
b: bpy.types.bpy_prop_collection_idprop[ClashSource]
clashes: bpy.types.bpy_prop_collection_idprop[Clash]
class SmartClashGroup(PropertyGroup):
number: StringProperty(name="Number")
global_ids: CollectionProperty(name="GlobalIDs", type=StrProperty)
if TYPE_CHECKING:
number: str
global_ids: bpy.types.bpy_prop_collection_idprop[StrProperty]
class BIMClashProperties(PropertyGroup):
blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty)
@@ -107,17 +134,33 @@ class BIMClashProperties(PropertyGroup):
subtype="FILE_PATH",
)
@property
def active_clash_set(self):
if self.active_clash_set_index < len(self.clash_sets):
return self.clash_sets[self.active_clash_set_index]
if TYPE_CHECKING:
blender_clash_set_a: bpy.types.bpy_prop_collection_idprop[StrProperty]
blender_clash_set_b: bpy.types.bpy_prop_collection_idprop[StrProperty]
clash_sets: bpy.types.bpy_prop_collection_idprop[ClashSet]
should_create_clash_snapshots: bool
clash_results_path: str
smart_grouped_clashes_path: str
active_clash_set_index: int
active_clash_index: int
smart_clash_groups: bpy.types.bpy_prop_collection_idprop[SmartClashGroup]
active_smart_group_index: int
smart_clash_grouping_max_distance: int
p1: Vector
p2: Vector
active_clash_text: str
export_path: str
@property
def active_smart_group(self):
if self.active_smart_group_index < len(self.smart_clash_groups):
return self.smart_clash_groups[self.active_smart_group_index]
def active_clash_set(self) -> Union[ClashSet, None]:
return tool.Blender.get_active_uilist_element(self.clash_sets, self.active_clash_set_index)
@property
def active_clash(self):
if self.active_clash_index < len(self.active_clash_set.clashes):
return self.active_clash_set.clashes[self.active_clash_index]
def active_smart_group(self) -> Union[SmartClashGroup, None]:
return tool.Blender.get_active_uilist_element(self.smart_clash_groups, self.active_smart_group_index)
@property
def active_clash(self) -> Union[Clash, None]:
if not (clash_set := self.active_clash_set):
return None
return tool.Blender.get_active_uilist_element(clash_set.clashes, self.active_clash_index)
+3 -4
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
import bonsai.bim.helper
from bpy.types import Panel
from bonsai.bim.module.clash.data import ClashData
@@ -36,9 +37,7 @@ class BIM_PT_ifcclash(Panel):
ClashData.load()
layout = self.layout
scene = context.scene
props = scene.BIMClashProperties
props = tool.Clash.get_clash_props()
row = layout.row(align=True)
row.operator("bim.add_clash_set")
@@ -157,7 +156,7 @@ class BIM_PT_smart_clash_manager(Panel):
def draw(self, context):
layout = self.layout
props = context.scene.BIMClashProperties
props = tool.Clash.get_clash_props()
row = layout.row()
layout.label(text="Select clash results to group:")
+18 -13
View File
@@ -42,7 +42,8 @@ class AddCsvAttribute(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
attribute = context.scene.CsvProperties.csv_attributes.add()
props = tool.Blender.get_csv_props()
props.csv_attributes.add()
return {"FINISHED"}
@@ -53,7 +54,8 @@ class RemoveCsvAttribute(bpy.types.Operator):
index: bpy.props.IntProperty()
def execute(self, context):
context.scene.CsvProperties.csv_attributes.remove(self.index)
props = tool.Blender.get_csv_props()
props.csv_attributes.remove(self.index)
return {"FINISHED"}
@@ -64,7 +66,8 @@ class RemoveAllCsvAttributes(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.CsvProperties.csv_attributes.clear()
props = tool.Blender.get_csv_props()
props.csv_attributes.clear()
return {"FINISHED"}
@@ -76,8 +79,9 @@ class ReorderCsvAttribute(bpy.types.Operator):
new_index: bpy.props.IntProperty()
def execute(self, context):
old = context.scene.CsvProperties.csv_attributes[self.old_index]
new = context.scene.CsvProperties.csv_attributes[self.new_index]
props = tool.Blender.get_csv_props()
old = props.csv_attributes[self.old_index]
new = props.csv_attributes[self.new_index]
props = ["name", "header", "sort", "group", "varies_value", "summary", "formatting"]
for prop in props:
value = getattr(new, prop)
@@ -95,7 +99,7 @@ class ImportCsvAttributes(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
props = context.scene.CsvProperties
props = tool.Blender.get_csv_props()
data = json.load(open(self.filepath))
tool.Search.import_filter_query(data["query"], props.filter_groups)
@@ -136,7 +140,7 @@ class ExportCsvAttributes(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
props = context.scene.CsvProperties
props = tool.Blender.get_csv_props()
settings = {}
for prop in [
@@ -191,14 +195,14 @@ class ExportIfcCsv(bpy.types.Operator):
@classmethod
def poll(cls, context):
props = context.scene.CsvProperties
props = tool.Blender.get_csv_props()
if not props.should_load_from_memory and not props.csv_ifc_file:
cls.poll_message_set("Select an IFC file or use 'load from memory' if it's loaded in Bonsai.")
return False
return True
def invoke(self, context, event):
props = context.scene.CsvProperties
props = tool.Blender.get_csv_props()
if props.format == "web":
return self.execute(context)
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, f".{props.format}")
@@ -209,7 +213,7 @@ class ExportIfcCsv(bpy.types.Operator):
def execute(self, context):
import ifccsv
props = context.scene.CsvProperties
props = tool.Blender.get_csv_props()
self.filepath = bpy.path.ensure_ext(self.filepath, f".{props.format}")
if props.should_load_from_memory:
ifc_file = tool.Ifc.get()
@@ -298,7 +302,7 @@ class ImportIfcCsv(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
props = context.scene.CsvProperties
props = tool.Blender.get_csv_props()
if not props.should_load_from_memory and not props.csv_ifc_file:
cls.poll_message_set("Select an IFC file or use 'load from memory' if it's loaded in Bonsai.")
return False
@@ -313,7 +317,7 @@ class ImportIfcCsv(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
import ifccsv
props = context.scene.CsvProperties
props = tool.Blender.get_csv_props()
if props.should_load_from_memory:
ifc_file = tool.Ifc.get()
else:
@@ -349,7 +353,8 @@ class SelectCsvIfcFile(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
context.scene.CsvProperties.csv_ifc_file = self.filepath
props = tool.Blender.get_csv_props()
props.csv_ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
+32
View File
@@ -29,6 +29,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING, Literal
class CsvAttribute(PropertyGroup):
@@ -59,6 +60,14 @@ class CsvAttribute(PropertyGroup):
)
formatting: StringProperty(default="{{value}}", name="Formatting")
if TYPE_CHECKING:
header: str
sort: Literal["NONE", "ASC", "DESC"]
group: Literal["NONE", "GROUP", "CONCAT", "VARIES", "SUM", "AVERAGE", "MIN", "MAX"]
varies_value: str
summary: Literal["NONE", "SUM", "AVERAGE", "MIN", "MAX"]
formatting: str
class CsvProperties(PropertyGroup):
csv_ifc_file: StringProperty(default="", name="IFC File")
@@ -104,3 +113,26 @@ class CsvProperties(PropertyGroup):
name="Load from Memory",
description="Use IFC file currently loaded in Bonsai",
)
if TYPE_CHECKING:
csv_ifc_file: str
ifc_selector: str
filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]
csv_attributes: bpy.types.bpy_prop_collection_idprop[CsvAttribute]
should_generate_svg: bool
should_preserve_existing: bool
include_global_id: bool
null_value: str
empty_value: str
true_value: str
false_value: str
concat_value: str
csv_delimiter: Literal["NONE", "ASC", "DESC"]
format: Literal["csv", "xlsx", "ods", "web"]
csv_custom_delimiter: str
should_show_settings: bool
should_show_sort: bool
should_show_group: bool
should_show_summary: bool
should_show_formatting: bool
should_load_from_memory: bool
+1 -3
View File
@@ -33,9 +33,7 @@ class BIM_PT_ifccsv(Panel):
def draw(self, context):
layout = self.layout
scene = context.scene
props = scene.CsvProperties
props = tool.Blender.get_csv_props()
if tool.Ifc.get():
row = layout.row(align=True)
+1 -1
View File
@@ -43,7 +43,7 @@ class DiffData:
@classmethod
def diff_json(cls):
props = bpy.context.scene.DiffProperties
props = tool.Blender.get_diff_props()
if not props.diff_json_file:
cls.diff = None
return
+11 -6
View File
@@ -36,7 +36,8 @@ class SelectDiffJsonFile(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
def execute(self, context):
context.scene.DiffProperties.diff_json_file = self.filepath
props = tool.Blender.get_diff_props()
props.diff_json_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -51,7 +52,8 @@ class VisualiseDiff(bpy.types.Operator):
def execute(self, context):
ifc_file = tool.Ifc.get()
with open(context.scene.DiffProperties.diff_json_file, "r") as file:
props = tool.Blender.get_diff_props()
with open(props.diff_json_file, "r") as file:
diff = json.load(file)
for obj in context.visible_objects:
obj.color = (1.0, 1.0, 1.0, 1.0)
@@ -111,7 +113,8 @@ class SelectDiffOldFile(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
def execute(self, context):
context.scene.DiffProperties.old_file = self.filepath
props = tool.Blender.get_diff_props()
props.old_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -128,7 +131,8 @@ class SelectDiffNewFile(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
def execute(self, context):
context.scene.DiffProperties.new_file = self.filepath
props = tool.Blender.get_diff_props()
props.new_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -153,7 +157,7 @@ class ExecuteIfcDiff(bpy.types.Operator):
def execute(self, context):
import ifcdiff
self.props = context.scene.DiffProperties
self.props = tool.Blender.get_diff_props()
if tool.Ifc.get():
if self.props.active_file == "NONE":
@@ -242,7 +246,8 @@ class SelectDiffObjects(bpy.types.Operator):
def execute(self, context):
ifc_file = tool.Ifc.get()
with open(context.scene.DiffProperties.diff_json_file, "r") as file:
props = tool.Blender.get_diff_props()
with open(props.diff_json_file, "r") as file:
diff = json.load(file)
for obj in context.visible_objects:
obj.select_set(False)
+18 -2
View File
@@ -30,18 +30,25 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING, Literal, get_args
def update_diff_json_file(self, context):
def update_diff_json_file(self: "DiffProperties", context: bpy.types.Context) -> None:
DiffData.data["diff_json"] = DiffData.diff_json()
RelationshipType = Literal["type", "property", "container", "aggregate", "classification"]
class Relationships(PropertyGroup):
relationship: EnumProperty(
name="Relationship",
items=[(r, r.capitalize(), r) for r in ["type", "property", "container", "aggregate", "classification"]],
items=[(r, r.capitalize(), r) for r in get_args(RelationshipType)],
)
if TYPE_CHECKING:
relationship: RelationshipType
class DiffProperties(PropertyGroup):
diff_json_file: StringProperty(default="", name="JSON Output", update=update_diff_json_file)
@@ -59,3 +66,12 @@ class DiffProperties(PropertyGroup):
name="Active File",
default="NEW",
)
if TYPE_CHECKING:
diff_json_file: str
old_file: str
new_file: str
diff_relationships: bpy.types.bpy_prop_collection_idprop[Relationships]
filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]
should_load_changed_elements: bool
active_file: Literal["NONE", "OLD", "NEW"]
+1 -3
View File
@@ -37,9 +37,7 @@ class BIM_PT_diff(Panel):
layout = self.layout
layout.use_property_split = True
scene = context.scene
props = scene.DiffProperties
props = tool.Blender.get_diff_props()
layout.label(text="IFC Diff Setup:")
@@ -155,7 +155,7 @@ class Search(Operator):
def execute(self, context):
if self.property_group == "CsvProperties":
props = context.scene.CsvProperties
props = tool.Blender.get_csv_props()
elif self.property_group == "BIMSearchProperties":
props = tool.Search.get_search_props()
else:
+10
View File
@@ -43,6 +43,8 @@ from typing_extensions import assert_never
if TYPE_CHECKING:
from bonsai.bim.prop import BIMProperties, BIMObjectProperties
from bonsai.bim.module.csv.prop import CsvProperties
from bonsai.bim.module.diff.prop import DiffProperties
T = TypeVar("T")
@@ -1600,6 +1602,14 @@ class Blender(bonsai.core.tool.Blender):
dct = {cls.bl_idname: cls.ifc_element_type for cls in (BimTool.__subclasses__())}
return types.MappingProxyType(dct)
@classmethod
def get_csv_props(cls) -> CsvProperties:
return bpy.context.scene.CsvProperties
@classmethod
def get_diff_props(cls) -> DiffProperties:
return bpy.context.scene.DiffProperties
@classmethod
def get_bim_props(cls, scene: Optional[bpy.types.Scene] = None) -> BIMProperties:
if scene is None:
+29 -15
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import os
import bpy
import json
@@ -25,14 +26,23 @@ import bonsai.tool as tool
from contextlib import contextmanager
from mathutils import Vector
from ifcclash import ifcclash
from typing import TYPE_CHECKING, Union
if TYPE_CHECKING:
from bonsai.bim.module.clash.prop import BIMClashProperties
class Clash(bonsai.core.tool.Clash):
@classmethod
def get_clash_props(cls) -> BIMClashProperties:
return bpy.context.scene.BIMClashProperties
@classmethod
def export_clash_sets(cls) -> list[ifcclash.ClashSet]:
clash_sets = []
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
clash_sets: list[ifcclash.ClashSet] = []
props = cls.get_clash_props()
for clash_set in props.clash_sets:
a = []
b = []
for ab in ["a", "b"]:
@@ -46,7 +56,7 @@ class Clash(bonsai.core.tool.Clash):
a.append(clash_source)
elif ab == "b":
b.append(clash_source)
clash_set_data = {"name": clash_set.name, "mode": clash_set.mode, "a": a, "b": b}
clash_set_data = ifcclash.ClashSet(name=clash_set.name, mode=clash_set.mode, a=a, b=b)
if clash_set.mode == "intersection":
clash_set_data["tolerance"] = clash_set.tolerance
clash_set_data["check_all"] = clash_set.check_all
@@ -55,33 +65,37 @@ class Clash(bonsai.core.tool.Clash):
elif clash_set.mode == "clearance":
clash_set_data["clearance"] = clash_set.clearance
clash_set_data["check_all"] = clash_set.check_all
clash_sets.append(clash_set_data)
clash_sets.append(ifcclash.ClashSet(**clash_set_data))
return clash_sets
@classmethod
def get_clash(cls, clash_set, a_global_id, b_global_id):
def get_clash(
cls, clash_set: ifcclash.ClashSet, a_global_id: str, b_global_id: str
) -> Union[ifcclash.ClashResult, None]:
clashes = clash_set.get("clashes", None)
if not clashes:
return
return clashes.get(f"{a_global_id}-{b_global_id}", None)
@classmethod
def get_clash_set(cls, name):
def get_clash_set(cls, name: str) -> Union[ifcclash.ClashSet, None]:
for clash_set in ClashStore.clash_sets:
if clash_set["name"] == name:
return clash_set
@classmethod
def get_clash_sets(cls):
def get_clash_sets(cls) -> list[ifcclash.ClashSet]:
return ClashStore.clash_sets
@classmethod
def import_active_clashes(cls):
clash_set = bpy.context.scene.BIMClashProperties.active_clash_set
def import_active_clashes(cls) -> None:
props = cls.get_clash_props()
clash_set = props.active_clash_set
if not clash_set:
return
clash_set.clashes.clear()
result = tool.Clash.get_clash_set(clash_set.name)
assert result is not None
for clash in sorted(result.get("clashes", {}).values(), key=lambda x: x["distance"]):
blender_clash = clash_set.clashes.add()
blender_clash.a_global_id = clash["a_global_id"]
@@ -91,17 +105,19 @@ class Clash(bonsai.core.tool.Clash):
blender_clash.status = False if not "status" in clash.keys() else clash["status"]
@classmethod
def load_clash_sets(cls, fn):
def load_clash_sets(cls, fn: str) -> None:
with open(fn) as f:
ClashStore.clash_sets = json.load(f)
@classmethod
def look_at(cls, target, location):
def look_at(cls, target: Vector, location: Vector) -> None:
camera_location = location
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
region = next(region for region in area.regions if region.type == "WINDOW")
space = next(space for space in area.spaces if space.type == "VIEW_3D")
override = {"area": area, "region": region, "space_data": space}
assert isinstance(space, bpy.types.SpaceView3D)
assert space.region_3d
space.region_3d.view_location = target
space.region_3d.view_rotation = Vector((camera_location - target)).to_track_quat("Z", "Y")
space.region_3d.view_distance = (camera_location - target).length
@@ -109,10 +125,8 @@ class Clash(bonsai.core.tool.Clash):
class ClashStore:
clash_sets = None
path = None
clash_sets: list[ifcclash.ClashSet] = []
@staticmethod
def purge():
ClashStore.clash_sets = None
ClashStore.path = None
ClashStore.clash_sets = []
+5 -6
View File
@@ -43,22 +43,21 @@ class Search(bonsai.core.tool.Search):
return json.loads(group.Description)["query"]
@classmethod
def get_filter_groups(cls, module: str) -> bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]:
def get_filter_groups(cls, module: FilterModule) -> bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]:
if module == "search":
return cls.get_search_props().filter_groups
elif module == "csv":
return bpy.context.scene.CsvProperties.filter_groups
return tool.Blender.get_csv_props().filter_groups
elif module == "diff":
return bpy.context.scene.DiffProperties.filter_groups
return tool.Blender.get_diff_props().filter_groups
elif module == "drawing_include":
return bpy.context.scene.camera.data.BIMCameraProperties.include_filter_groups
elif module == "drawing_exclude":
return bpy.context.scene.camera.data.BIMCameraProperties.exclude_filter_groups
elif module.startswith("clash"):
_, clash_set_index, ab, clash_source_index = module.split("_")
return getattr(bpy.context.scene.BIMClashProperties.clash_sets[int(clash_set_index)], ab)[
int(clash_source_index)
].filter_groups
props = tool.Clash.get_clash_props()
return getattr(props.clash_sets[int(clash_set_index)], ab)[int(clash_source_index)].filter_groups
assert False, f"Unsupported module: {module}"
@classmethod
@@ -18,6 +18,7 @@
import ifcopenshell
import ifcopenshell.api.alignment
import ifcopenshell.api.geometry
from ifcopenshell import entity_instance
import math