mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
typing
This commit is contained in:
@@ -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]]
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user