This commit is contained in:
Andrej730
2025-06-30 18:59:26 +05:00
parent e743db27f4
commit 94d2d32d18
14 changed files with 105 additions and 56 deletions
+1 -1
View File
@@ -662,7 +662,7 @@ class IfcImporter:
settings: Optional[ifcopenshell.geom.main.settings] = None,
) -> set[ifcopenshell.entity_instance]:
checkpoint = time.time()
results = set()
results: set[ifcopenshell.entity_instance] = set()
if not products:
return results
+1 -1
View File
@@ -35,7 +35,7 @@ from bpy.props import (
from typing import Union, TYPE_CHECKING, Literal
def get_active_dictionary(self, context):
def get_active_dictionary(self: "BIMBSDDProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
if not BSDDData.is_loaded:
BSDDData.load()
return BSDDData.data["active_dictionary"]
@@ -16,6 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
# pyright: reportAttributeAccessIssue=false
import bpy
from . import ui, prop, operator
+10 -9
View File
@@ -36,24 +36,25 @@ class ExecuteIfcFM(bpy.types.Operator, ExportHelper):
@property
def filename_ext(self) -> str:
props = bpy.context.scene.BIMFMProperties
props = tool.Blender.get_fm_props()
return f".{props.format}"
def draw(self, context):
assert self.layout
layout = self.layout
props = context.scene.BIMFMProperties
props = tool.Blender.get_fm_props()
layout.prop(props, "format")
@classmethod
def poll(cls, context):
props = context.scene.BIMFMProperties
props = tool.Blender.get_fm_props()
if not props.should_load_from_memory and not props.ifc_files.single_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 execute(self, context):
props = context.scene.BIMFMProperties
props = tool.Blender.get_fm_props()
ifc_file = tool.Ifc.get()
filepaths = []
if ifc_file and props.should_load_from_memory:
@@ -95,7 +96,7 @@ class SelectFMSpreadsheetFiles(bpy.types.Operator, ImportHelper):
files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement)
def execute(self, context):
props = context.scene.BIMFMProperties
props = tool.Blender.get_fm_props()
props.spreadsheet_files.clear()
dirname = os.path.dirname(self.filepath)
for f in self.files:
@@ -112,24 +113,24 @@ class ExecuteIfcFMFederate(bpy.types.Operator, ExportHelper):
@property
def filename_ext(self) -> str:
props = bpy.context.scene.BIMFMProperties
props = tool.Blender.get_fm_props()
return f".{props.format}"
def draw(self, context):
layout = self.layout
props = context.scene.BIMFMProperties
props = tool.Blender.get_fm_props()
layout.prop(props, "format")
@classmethod
def poll(cls, context):
props = context.scene.BIMFMProperties
props = tool.Blender.get_fm_props()
if not props.spreadsheet_files:
cls.poll_message_set("No spreadsheet files selected.")
return False
return True
def execute(self, context):
props = context.scene.BIMFMProperties
props = tool.Blender.get_fm_props()
parser = ifcfm.Parser(preset=props.engine)
parser.federate([f.name for f in props.spreadsheet_files])
writer = ifcfm.Writer(parser)
+10 -1
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.module.fm.data import FMData
from bonsai.bim.prop import MultipleFileSelect, StrProperty
from bpy.types import PropertyGroup
@@ -30,9 +31,10 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING, Literal
def get_engine(self, context):
def get_engine(self: "BIMFMProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
if not FMData.is_loaded:
FMData.load()
return FMData.data["engine"]
@@ -57,3 +59,10 @@ class BIMFMProperties(PropertyGroup):
default="ods",
options=set(),
)
if TYPE_CHECKING:
ifc_files: MultipleFileSelect
spreadsheet_files: bpy.types.bpy_prop_collection_idprop[StrProperty]
should_load_from_memory: bool
engine: str
format: Literal["csv", "xlsx", "ods"]
+2 -2
View File
@@ -33,11 +33,11 @@ class BIM_PT_fm(Panel):
if not FMData.is_loaded:
FMData.load()
assert self.layout
layout = self.layout
layout.use_property_split = True
scene = context.scene
props = scene.BIMFMProperties
props = tool.Blender.get_fm_props()
if tool.Ifc.get():
row = layout.row()
@@ -16,6 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
# pyright: reportAttributeAccessIssue=false
import bpy
from . import ui, prop, operator
@@ -367,9 +367,10 @@ class WorkPlansData:
@classmethod
def active_work_plan_schedules(cls):
results = []
if not bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id:
props = tool.Sequence.get_work_plan_props()
if not props.active_work_plan_id:
return []
for rel in tool.Ifc.get().by_id(bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id).IsDecomposedBy:
for rel in tool.Ifc.get().by_id(props.active_work_plan_id).IsDecomposedBy:
for work_schedule in rel.RelatedObjects:
results.append({"id": work_schedule.id(), "name": work_schedule.Name or "Unnamed"})
return results
@@ -140,10 +140,11 @@ class EditWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Edit Work Plan"
def _execute(self, context):
props = tool.Sequence.get_work_plan_props()
core.edit_work_plan(
tool.Ifc,
tool.Sequence,
work_plan=tool.Ifc.get().by_id(context.scene.BIMWorkPlanProperties.active_work_plan_id),
work_plan=tool.Ifc.get().by_id(props.active_work_plan_id),
)
+12 -8
View File
@@ -88,7 +88,8 @@ class BIM_PT_work_plans(Panel):
def draw(self, context):
if not WorkPlansData.is_loaded:
WorkPlansData.load()
self.props = context.scene.BIMWorkPlanProperties
assert self.layout
self.props = tool.Sequence.get_work_plan_props()
row = self.layout.row()
if WorkPlansData.data["total_work_plans"]:
@@ -99,7 +100,7 @@ class BIM_PT_work_plans(Panel):
for work_plan in WorkPlansData.data["work_plans"]:
self.draw_work_plan_ui(work_plan)
def draw_work_plan_ui(self, work_plan):
def draw_work_plan_ui(self, work_plan: dict[str, Any]) -> None:
row = self.layout.row(align=True)
row.label(text=work_plan["name"], icon="TEXT")
if self.props.active_work_plan_id == work_plan["id"]:
@@ -121,10 +122,10 @@ class BIM_PT_work_plans(Panel):
elif self.props.editing_type == "SCHEDULES":
self.draw_work_schedule_ui()
def draw_editable_ui(self):
def draw_editable_ui(self) -> None:
draw_attributes(self.props.work_plan_attributes, self.layout)
def draw_work_schedule_ui(self):
def draw_work_schedule_ui(self) -> None:
if WorkPlansData.data["has_work_schedules"]:
row = self.layout.row(align=True)
row.prop(self.props, "work_schedules", text="")
@@ -177,7 +178,8 @@ class BIM_PT_work_schedules(Panel):
for work_schedule_id, work_schedule in SequenceData.data["work_schedules"].items():
self.draw_work_schedule_ui(work_schedule_id, work_schedule)
def draw_work_schedule_ui(self, work_schedule_id, work_schedule):
def draw_work_schedule_ui(self, work_schedule_id: int, work_schedule: dict[str, Any]) -> None:
assert self.layout
if work_schedule["PredefinedType"] == "BASELINE":
self.draw_readonly_work_schedule_ui(work_schedule_id)
else:
@@ -250,7 +252,7 @@ class BIM_PT_work_schedules(Panel):
self.draw_column_ui()
self.draw_editable_task_ui(work_schedule_id)
def draw_task_operators(self):
def draw_task_operators(self) -> None:
row = self.layout.row(align=True)
row.alignment = "RIGHT"
ifc_definition_id = None
@@ -285,9 +287,10 @@ class BIM_PT_work_schedules(Panel):
row.operator("bim.duplicate_task", text="Copy", icon="DUPLICATE").task = ifc_definition_id
row.operator("bim.remove_task", text="Delete", icon="X").task = ifc_definition_id
def draw_column_ui(self):
def draw_column_ui(self) -> None:
if not self.props.should_show_column_ui:
return
assert self.layout
row = self.layout.row()
row.operator("bim.setup_default_task_columns", text="Setup Default Columns", icon="ANCHOR_BOTTOM")
row.alignment = "RIGHT"
@@ -318,7 +321,8 @@ class BIM_PT_work_schedules(Panel):
def draw_editable_work_schedule_ui(self):
draw_attributes(self.props.work_schedule_attributes, self.layout)
def draw_editable_task_ui(self, work_schedule_id):
def draw_editable_task_ui(self, work_schedule_id: int) -> None:
assert self.layout
row = self.layout.row(align=True)
row.label(text="Task Tools")
row = self.layout.row(align=True)
+7 -5
View File
@@ -47,7 +47,7 @@ from collections.abc import Iterable
from natsort import natsorted
if TYPE_CHECKING:
from bonsai.bim.prop import MultipleFileSelect
from bonsai.bim.prop import MultipleFileSelect, Attribute
class SetTab(bpy.types.Operator):
@@ -1343,7 +1343,7 @@ class BIM_OT_attribute_search_values(bpy.types.Operator):
collection_values: bpy.props.CollectionProperty(type=StrProperty, options={"SKIP_SAVE"})
@staticmethod
def resolve_data_path(data_path: str) -> tuple[str, object]:
def resolve_data_path(data_path: str) -> tuple[str, "Attribute"]:
"""Resolve the data path of an object's attribute to get the attribute name and the object."""
path_parts = data_path.split(".")
obj_path = ".".join(path_parts[:-1])
@@ -1361,11 +1361,12 @@ class BIM_OT_attribute_search_values(bpy.types.Operator):
for value in string_values:
self.collection_values.add().name = value
assert context.window_manager
return context.window_manager.invoke_props_dialog(self)
def get_unique_attribute_values(self):
def get_unique_attribute_values(self) -> list[str]:
ifc_file = tool.Ifc.get()
unique_values = set()
unique_values: set[str] = set()
ifc_class = self.attribute_ifc_class
elements = ifc_file.by_type(ifc_class, include_subtypes=True)
@@ -1379,7 +1380,8 @@ class BIM_OT_attribute_search_values(bpy.types.Operator):
return list(unique_values)
def draw(self, context):
def draw(self, context) -> None:
assert self.layout
row = self.layout.row()
row.label(text=f"Select {self.attribute_name} value:")
row = self.layout.row()
+17 -11
View File
@@ -61,6 +61,7 @@ if TYPE_CHECKING:
from bonsai.bim.module.constraint.prop import BIMConstraintProperties, BIMObjectConstraintProperties
from bonsai.bim.module.csv.prop import CsvProperties
from bonsai.bim.module.diff.prop import DiffProperties
from bonsai.bim.module.fm.prop import BIMFMProperties
from bonsai.bim.module.group.prop import BIMGroupProperties
from bonsai.bim.module.light.prop import BIMSolarProperties, RadianceExporterProperties
@@ -1663,51 +1664,56 @@ class Blender(bonsai.core.tool.Blender):
@classmethod
def get_object_constraint_props(cls, obj: bpy.types.Object) -> BIMObjectConstraintProperties:
return obj.BIMObjectConstraintProperties
return obj.BIMObjectConstraintProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_constraint_props(cls) -> BIMConstraintProperties:
assert (scene := bpy.context.scene)
return scene.BIMConstraintProperties
return scene.BIMConstraintProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_csv_props(cls) -> CsvProperties:
assert (scene := bpy.context.scene)
return scene.CsvProperties
return scene.CsvProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_diff_props(cls) -> DiffProperties:
assert (scene := bpy.context.scene)
return scene.DiffProperties
return scene.DiffProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_group_props(cls) -> BIMGroupProperties:
assert (scene := bpy.context.scene)
return scene.BIMGroupProperties
return scene.BIMGroupProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_bim_props(cls, scene: Optional[bpy.types.Scene] = None) -> BIMProperties:
if scene is None:
scene = bpy.context.scene
return scene.BIMProperties
assert (scene := bpy.context.scene)
return scene.BIMProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_object_bim_props(cls, obj: bpy.types.Object) -> BIMObjectProperties:
return obj.BIMObjectProperties
return obj.BIMObjectProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_object_attribute_props(cls, obj: bpy.types.Object) -> BIMAttributeProperties:
return obj.BIMAttributeProperties
return obj.BIMAttributeProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_solar_props(cls) -> BIMSolarProperties:
assert (scene := bpy.context.scene)
return scene.BIMSolarProperties
return scene.BIMSolarProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_radiance_exporter_props(cls) -> RadianceExporterProperties:
assert (scene := bpy.context.scene)
return scene.BIMRadianceExporeterProperies
return scene.BIMRadianceExporeterProperies # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_fm_props(cls) -> BIMFMProperties:
assert (scene := bpy.context.scene)
return scene.BIMFMProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_ifc_definition_id(cls, obj: IFC_CONNECTED_TYPE) -> int:
+24 -12
View File
@@ -45,10 +45,11 @@ from mathutils import Color
if TYPE_CHECKING:
import bonsai.bim.prop
from bonsai.bim.module.sequence.prop import (
BIMTaskTreeProperties,
BIMWorkScheduleProperties,
BIMAnimationProperties,
BIMStatusProperties,
BIMTaskTreeProperties,
BIMWorkPlanProperties,
BIMWorkScheduleProperties,
)
@@ -58,21 +59,29 @@ class Sequence(bonsai.core.tool.Sequence):
@classmethod
def get_animation_props(cls) -> BIMAnimationProperties:
return bpy.context.scene.BIMAnimationProperties
assert (scene := bpy.context.scene)
return scene.BIMAnimationProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_task_tree_props(cls) -> BIMTaskTreeProperties:
return bpy.context.scene.BIMTaskTreeProperties
assert (scene := bpy.context.scene)
return scene.BIMTaskTreeProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_work_schedule_props(cls) -> BIMWorkScheduleProperties:
return bpy.context.scene.BIMWorkScheduleProperties
assert (scene := bpy.context.scene)
return scene.BIMWorkScheduleProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_status_props(cls) -> BIMStatusProperties:
assert (scene := bpy.context.scene)
return scene.BIMStatusProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_work_plan_props(cls) -> BIMWorkPlanProperties:
assert (scene := bpy.context.scene)
return scene.BIMWorkPlanProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_work_plan_attributes(cls) -> dict[str, Any]:
import bonsai.bim.module.sequence.helper as helper
@@ -91,7 +100,7 @@ class Sequence(bonsai.core.tool.Sequence):
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
props = bpy.context.scene.BIMWorkPlanProperties
props = cls.get_work_plan_props()
return bonsai.bim.helper.export_attributes(props.work_plan_attributes, callback)
@classmethod
@@ -101,25 +110,28 @@ class Sequence(bonsai.core.tool.Sequence):
prop.string_value = "" if prop.is_null else data[name]
return True
props = bpy.context.scene.BIMWorkPlanProperties
props = cls.get_work_plan_props()
props.work_plan_attributes.clear()
bonsai.bim.helper.import_attributes2(work_plan, props.work_plan_attributes, callback)
@classmethod
def enable_editing_work_plan(cls, work_plan: Union[ifcopenshell.entity_instance, None]) -> None:
if work_plan:
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id()
bpy.context.scene.BIMWorkPlanProperties.editing_type = "ATTRIBUTES"
props = cls.get_work_plan_props()
props.active_work_plan_id = work_plan.id()
props.editing_type = "ATTRIBUTES"
@classmethod
def disable_editing_work_plan(cls) -> None:
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = 0
props = cls.get_work_plan_props()
props.active_work_plan_id = 0
@classmethod
def enable_editing_work_plan_schedules(cls, work_plan: Union[ifcopenshell.entity_instance, None]) -> None:
if work_plan:
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id()
bpy.context.scene.BIMWorkPlanProperties.editing_type = "SCHEDULES"
props = cls.get_work_plan_props()
props.active_work_plan_id = work_plan.id()
props.editing_type = "SCHEDULES"
@classmethod
def get_work_schedule_attributes(cls) -> dict[str, Any]:
@@ -318,12 +318,21 @@ class Iterator:
def get_native(self): ...
def get_object(self, id): ...
def had_error_processing_elements(self): ...
def initialize(self): ...
def initialize(self) -> bool:
"""Return true if the iterator is initialized with any elements, false otherwise."""
...
def next(self): ...
def process_concurrently(self): ...
def process_finished_rep(self, rep): ...
def progress(self): ...
def set_cache(self, cache): ...
def progress(self) -> int:
"""Return current progress (0-100).
Updated after processing each representation.
"""
...
def set_cache(self, cache: GeometrySerializer): ...
def unit_magnitude(self): ...
def unit_name(self): ...