diff --git a/src/bonsai/bonsai/bim/module/group/operator.py b/src/bonsai/bonsai/bim/module/group/operator.py
index 6932ec6f78..c82eb0d055 100644
--- a/src/bonsai/bonsai/bim/module/group/operator.py
+++ b/src/bonsai/bonsai/bim/module/group/operator.py
@@ -31,7 +31,7 @@ class LoadGroups(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
- self.props = context.scene.BIMGroupProperties
+ self.props = tool.Blender.get_group_props()
self.expanded_groups = json.loads(context.scene.ExpandedGroups.json_string)
self.props.groups.clear()
@@ -95,8 +95,9 @@ class DisableGroupEditingUI(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
- context.scene.BIMGroupProperties.is_editing = False
- context.scene.BIMGroupProperties.active_group_id = 0
+ props = tool.Blender.get_group_props()
+ props.is_editing = False
+ props.active_group_id = 0
return {"FINISHED"}
@@ -122,7 +123,7 @@ class EditGroup(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
- props = context.scene.BIMGroupProperties
+ props = tool.Blender.get_group_props()
attributes = bonsai.bim.helper.export_attributes(props.group_attributes)
ifc_file = tool.Ifc.get()
ifcopenshell.api.group.edit_group(ifc_file, group=ifc_file.by_id(props.active_group_id), attributes=attributes)
@@ -150,7 +151,7 @@ class EnableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
group: bpy.props.IntProperty()
def _execute(self, context):
- props = context.scene.BIMGroupProperties
+ props = tool.Blender.get_group_props()
props.group_attributes.clear()
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.group), props.group_attributes)
props.active_group_id = self.group
@@ -163,7 +164,8 @@ class DisableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
- context.scene.BIMGroupProperties.active_group_id = 0
+ props = tool.Blender.get_group_props()
+ props.active_group_id = 0
return {"FINISHED"}
@@ -183,9 +185,9 @@ class AssignGroup(bpy.types.Operator, tool.Ifc.Operator):
if not self.is_assigning:
return bpy.ops.bim.unassign_group(group=self.group)
products = [
- tool.Ifc.get_entity(o)
+ element
for o in tool.Blender.get_selected_objects(include_active=False)
- if tool.Ifc.get_entity(o)
+ if (element := tool.Ifc.get_entity(o))
]
ifcopenshell.api.group.assign_group(tool.Ifc.get(), products=products, group=tool.Ifc.get().by_id(self.group))
diff --git a/src/bonsai/bonsai/bim/module/group/prop.py b/src/bonsai/bonsai/bim/module/group/prop.py
index 2a091e21ae..af6b60ddd8 100644
--- a/src/bonsai/bonsai/bim/module/group/prop.py
+++ b/src/bonsai/bonsai/bim/module/group/prop.py
@@ -17,10 +17,10 @@
# along with Bonsai. If not, see .
import bpy
+import bonsai.tool as tool
from bonsai.bim.prop import StrProperty, Attribute
from bonsai.bim.module.pset.data import refresh as refresh_pset
from bpy.types import PropertyGroup
-import json
from bpy.props import (
PointerProperty,
StringProperty,
@@ -31,6 +31,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
+from typing import TYPE_CHECKING, Union
def update_active_group_index(self, context):
@@ -40,6 +41,9 @@ def update_active_group_index(self, context):
class ExpandedGroups(StrProperty):
json_string: StringProperty(name="JSON String", default="[]")
+ if TYPE_CHECKING:
+ json_string: str
+
class Group(PropertyGroup):
name: StringProperty(name="Name")
@@ -48,6 +52,13 @@ class Group(PropertyGroup):
has_children: BoolProperty(name="Has Children", default=False)
tree_depth: IntProperty(name="Tree Depth")
+ if TYPE_CHECKING:
+ name: str
+ ifc_definition_id: int
+ is_expanded: bool
+ has_children: bool
+ tree_depth: int
+
class BIMGroupProperties(PropertyGroup):
group_attributes: CollectionProperty(name="Group Attributes", type=Attribute)
@@ -56,7 +67,13 @@ class BIMGroupProperties(PropertyGroup):
active_group_index: IntProperty(name="Active Group Index", update=update_active_group_index)
active_group_id: IntProperty(name="Active Group Id")
+ if TYPE_CHECKING:
+ group_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
+ is_editing: bool
+ groups: bpy.types.bpy_prop_collection_idprop[Group]
+ active_group_index: int
+ active_group_id: int
+
@property
- def active_group(self):
- if self.active_group_index < len(self.groups):
- return self.groups[self.active_group_index]
+ def active_group(self) -> Union[Group, None]:
+ return tool.Blender.get_active_uilist_element(self.groups, self.active_group_index)
diff --git a/src/bonsai/bonsai/bim/module/group/ui.py b/src/bonsai/bonsai/bim/module/group/ui.py
index 309c49d5d0..e433549f34 100644
--- a/src/bonsai/bonsai/bim/module/group/ui.py
+++ b/src/bonsai/bonsai/bim/module/group/ui.py
@@ -16,11 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see .
+from __future__ import annotations
import bpy
import bonsai.tool as tool
from bpy.types import Panel, UIList
from bonsai.bim.helper import draw_attributes
from bonsai.bim.module.group.data import GroupsData, ObjectGroupsData
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from bonsai.bim.module.group.prop import BIMGroupProperties, Group
class BIM_PT_groups(Panel):
@@ -39,7 +44,7 @@ class BIM_PT_groups(Panel):
def draw(self, context):
if not GroupsData.is_loaded:
GroupsData.load()
- self.props = context.scene.BIMGroupProperties
+ self.props = tool.Blender.get_group_props()
row = self.layout.row(align=True)
row.label(text=f"{GroupsData.data['total_groups']} Groups Found", icon="OUTLINER")
@@ -99,7 +104,8 @@ class BIM_PT_object_groups(Panel):
def draw(self, context):
if not ObjectGroupsData.is_loaded:
ObjectGroupsData.load()
- self.props = context.scene.BIMGroupProperties
+ assert self.layout
+ self.props = tool.Blender.get_group_props()
for group in ObjectGroupsData.data["groups"]:
row = self.layout.row(align=True)
@@ -113,7 +119,17 @@ class BIM_PT_object_groups(Panel):
class BIM_UL_groups(UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+ def draw_item(
+ self,
+ context: bpy.types.Context,
+ layout: bpy.types.UILayout,
+ data: BIMGroupProperties,
+ item: Group,
+ icon,
+ active_data,
+ active_propname,
+ index: int,
+ ):
if item:
row = layout.row(align=True)
for i in range(0, item.tree_depth):
diff --git a/src/bonsai/bonsai/bim/module/ifcgit/data.py b/src/bonsai/bonsai/bim/module/ifcgit/data.py
index 2df65540ab..18ffe0e55c 100644
--- a/src/bonsai/bonsai/bim/module/ifcgit/data.py
+++ b/src/bonsai/bonsai/bim/module/ifcgit/data.py
@@ -153,7 +153,7 @@ class IfcGitData:
@classmethod
def commit(cls):
- props = bpy.context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
if cls.repo() and len(props.ifcgit_commits) > 0:
item = props.ifcgit_commits[props.commit_index]
try:
@@ -163,7 +163,7 @@ class IfcGitData:
@classmethod
def current_revision(cls):
- props = bpy.context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
if cls.repo() and cls.repo().head.is_valid() and len(props.ifcgit_commits) > 0:
return tool.IfcGitRepo.repo.commit()
diff --git a/src/bonsai/bonsai/bim/module/ifcgit/operator.py b/src/bonsai/bonsai/bim/module/ifcgit/operator.py
index 4b71a33fa1..69cc239261 100644
--- a/src/bonsai/bonsai/bim/module/ifcgit/operator.py
+++ b/src/bonsai/bonsai/bim/module/ifcgit/operator.py
@@ -68,7 +68,7 @@ class CloneRepo(bpy.types.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
if (
props.remote_url
and props.local_folder
@@ -80,7 +80,7 @@ class CloneRepo(bpy.types.Operator):
def execute(self, context):
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
core.clone_repo(tool.IfcGit, props.remote_url, props.local_folder, self)
props.remote_url = ""
refresh()
@@ -112,7 +112,7 @@ class CommitChanges(bpy.types.Operator):
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
repo = IfcGitData.data["repo"]
if props.commit_message == "":
return False
@@ -148,7 +148,7 @@ class AddTag(bpy.types.Operator):
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
if props.new_tag_name == "":
return False
repo = IfcGitData.data["repo"]
@@ -218,7 +218,7 @@ class DisplayRevision(bpy.types.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
if props.ifcgit_commits:
return True
@@ -253,7 +253,7 @@ class SwitchRevision(bpy.types.Operator):
@classmethod
def poll(cls, context):
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
if props.ifcgit_commits:
return True
@@ -274,7 +274,7 @@ class Merge(bpy.types.Operator):
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
if IfcGitData.data["ifcmerge_exe"] and props.ifcgit_commits and not IfcGitData.data["is_detached"]:
return True
return False
@@ -296,8 +296,7 @@ class Push(bpy.types.Operator):
bl_options = {"REGISTER"}
def execute(self, context):
-
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
repo = IfcGitData.data["repo"]
core.push(tool.IfcGit, repo, props.select_remote, self)
return {"FINISHED"}
@@ -311,8 +310,7 @@ class Fetch(bpy.types.Operator):
bl_options = {"REGISTER"}
def execute(self, context):
-
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
repo = IfcGitData.data["repo"]
remote = repo.remotes[props.select_remote]
remote.fetch()
@@ -329,7 +327,7 @@ class AddRemote(bpy.types.Operator):
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
repo = IfcGitData.data["repo"]
if (
not repo
diff --git a/src/bonsai/bonsai/bim/module/ifcgit/prop.py b/src/bonsai/bonsai/bim/module/ifcgit/prop.py
index 128256957d..128cfcd44b 100644
--- a/src/bonsai/bonsai/bim/module/ifcgit/prop.py
+++ b/src/bonsai/bonsai/bim/module/ifcgit/prop.py
@@ -1,4 +1,5 @@
import bpy
+import bonsai.tool as tool
from bpy.types import PropertyGroup
from bpy.props import (
StringProperty,
@@ -8,11 +9,10 @@ from bpy.props import (
EnumProperty,
)
from bonsai.bim.module.ifcgit.data import IfcGitData
+from typing import TYPE_CHECKING, Literal
-def git_branches(self, context):
- """branches enum"""
-
+def git_branches(self: "IfcGitProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
# NOTE "Python must keep a reference to the strings returned by
# the callback or Blender will misbehave or even crash"
IfcGitData.data["branch_names"] = sorted([branch.name for branch in IfcGitData.data["repo"].heads])
@@ -29,9 +29,7 @@ def git_branches(self, context):
return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]]
-def git_remotes(self, context):
- """remotes enum"""
-
+def git_remotes(self: "IfcGitProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
IfcGitData.data["remote_names"] = sorted([remote.name for remote in IfcGitData.data["remotes"]])
if "origin" in IfcGitData.data["remote_names"]:
@@ -41,12 +39,11 @@ def git_remotes(self, context):
return [(myname, myname, myname) for myname in IfcGitData.data["remote_names"]]
-def update_revlist(self, context):
+def update_revlist(self: "IfcGitProperties", context: bpy.types.Context) -> None:
"""wrapper to trigger update of the revision list"""
bpy.ops.ifcgit.refresh()
- props = context.scene.IfcGitProperties
- props.commit_index = 0
+ self.commit_index = 0
class IfcGitTag(PropertyGroup):
@@ -61,6 +58,10 @@ class IfcGitTag(PropertyGroup):
default="",
)
+ if TYPE_CHECKING:
+ name: str
+ message: str
+
class IfcGitListItem(PropertyGroup):
"""Group of properties representing an item in the list."""
@@ -89,6 +90,14 @@ class IfcGitListItem(PropertyGroup):
)
tags: CollectionProperty(type=IfcGitTag, name="List of revision tags")
+ if TYPE_CHECKING:
+ hexsha: str
+ relevant: bool
+ author_name: str
+ author_email: str
+ message: str
+ tags: bpy.types.bpy_prop_collection_idprop[IfcGitTag]
+
class IfcGitProperties(PropertyGroup):
@@ -140,3 +149,17 @@ class IfcGitProperties(PropertyGroup):
],
update=update_revlist,
)
+
+ if TYPE_CHECKING:
+ ifcgit_commits: bpy.types.bpy_prop_collection_idprop[IfcGitListItem]
+ commit_index: int
+ commit_message: str
+ new_branch_name: str
+ new_tag_name: str
+ new_tag_message: str
+ remote_name: str
+ remote_url: str
+ local_folder: str
+ display_branch: str
+ select_remote: str
+ ifcgit_filter: Literal["all", "tagged", "relevant"]
diff --git a/src/bonsai/bonsai/bim/module/ifcgit/ui.py b/src/bonsai/bonsai/bim/module/ifcgit/ui.py
index 760d719a60..631f33c7e1 100644
--- a/src/bonsai/bonsai/bim/module/ifcgit/ui.py
+++ b/src/bonsai/bonsai/bim/module/ifcgit/ui.py
@@ -1,10 +1,16 @@
+from __future__ import annotations
import bpy
import time
import os
import platform
+import bonsai.tool as tool
+from typing import TYPE_CHECKING
from bonsai.bim.module.ifcgit.data import IfcGitData
+if TYPE_CHECKING:
+ from bonsai.bim.module.ifcgit.prop import IfcGitProperties, IfcGitListItem
+
class IFCGIT_PT_panel(bpy.types.Panel):
"""Scene Properties panel to interact with IFC repository data"""
@@ -35,7 +41,7 @@ class IFCGIT_PT_panel(bpy.types.Panel):
)
return
- props = context.scene.IfcGitProperties
+ props = tool.IfcGit.get_ifcgit_props()
# TODO if file isn't saved, offer to save to disk
@@ -195,9 +201,17 @@ class IFCGIT_PT_panel(bpy.types.Panel):
class COMMIT_UL_List(bpy.types.UIList):
"""List of Git commits"""
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
-
- props = context.scene.IfcGitProperties
+ def draw_item(
+ self,
+ context: bpy.types.UILayout,
+ layout: bpy.types.UILayout,
+ data: IfcGitProperties,
+ item: IfcGitListItem,
+ icon,
+ active_data,
+ active_propname,
+ index: int,
+ ):
current_revision = IfcGitData.data["current_revision"]
@@ -212,7 +226,7 @@ class COMMIT_UL_List(bpy.types.UIList):
refs = ""
if item.hexsha in lookup:
for branch in lookup[item.hexsha]:
- if branch.name == props.display_branch:
+ if branch.name == data.display_branch:
refs = "[" + branch.name + "] "
lookup = IfcGitData.data["tags_by_hexsha"]
@@ -272,6 +286,7 @@ class IFCGIT_PT_revision_inspector(bpy.types.Panel):
IfcGitData.load()
layout = self.layout
+ assert layout
if not IfcGitData.data["git_exe"]:
row = layout.row()
diff --git a/src/bonsai/bonsai/bim/module/light/operator.py b/src/bonsai/bonsai/bim/module/light/operator.py
index 2275f7bc55..bf709b0222 100644
--- a/src/bonsai/bonsai/bim/module/light/operator.py
+++ b/src/bonsai/bonsai/bim/module/light/operator.py
@@ -566,6 +566,7 @@ class RefreshIFCMaterials(bpy.types.Operator):
def execute(self, context):
props = context.scene.radiance_exporter_properties
+ ifc_file: ifcopenshell.file
ifc_file = tool.Ifc.get() if props.should_load_from_memory else ifcopenshell.open(props.ifc_file)
props.materials.clear()
diff --git a/src/bonsai/bonsai/bim/module/pset/data.py b/src/bonsai/bonsai/bim/module/pset/data.py
index 09d63b41a5..f139de5f9e 100644
--- a/src/bonsai/bonsai/bim/module/pset/data.py
+++ b/src/bonsai/bonsai/bim/module/pset/data.py
@@ -266,8 +266,9 @@ class GroupQtosData(Data):
@classmethod
def load(cls):
- props = bpy.context.scene.BIMGroupProperties
- ifc_definition_id = props.groups[props.active_group_index].ifc_definition_id
+ props = tool.Blender.get_group_props()
+ assert (active_group := props.active_group)
+ ifc_definition_id = active_group.ifc_definition_id
cls.data = {"qtos": cls.psetqtos(tool.Ifc.get_entity_by_id(ifc_definition_id), qtos_only=True)}
cls.is_loaded = True
@@ -278,8 +279,9 @@ class GroupPsetData(Data):
@classmethod
def load(cls):
- props = bpy.context.scene.BIMGroupProperties
- ifc_definition_id = props.groups[props.active_group_index].ifc_definition_id
+ props = tool.Blender.get_group_props()
+ assert (active_group := props.active_group)
+ ifc_definition_id = active_group.ifc_definition_id
cls.data = {"psets": cls.psetqtos(tool.Ifc.get_entity_by_id(ifc_definition_id), psets_only=True)}
cls.is_loaded = True
diff --git a/src/bonsai/bonsai/bim/module/pset/ui.py b/src/bonsai/bonsai/bim/module/pset/ui.py
index 071d330b94..53e0ea45e9 100644
--- a/src/bonsai/bonsai/bim/module/pset/ui.py
+++ b/src/bonsai/bonsai/bim/module/pset/ui.py
@@ -662,7 +662,7 @@ class BIM_PT_group_qtos(Panel):
@classmethod
def poll(cls, context):
- props = context.scene.BIMGroupProperties
+ props = tool.Blender.get_group_props()
total_resources = len(props.groups)
if total_resources > 0 and props.active_group_index < total_resources:
return True
@@ -696,7 +696,7 @@ class BIM_PT_group_psets(Panel):
@classmethod
def poll(cls, context):
- props = context.scene.BIMGroupProperties
+ props = tool.Blender.get_group_props()
total_resources = len(props.groups)
if total_resources > 0 and props.active_group_index < total_resources:
return True
diff --git a/src/bonsai/bonsai/core/aggregate.py b/src/bonsai/bonsai/core/aggregate.py
index b50f91fa6b..a1f2723338 100644
--- a/src/bonsai/bonsai/core/aggregate.py
+++ b/src/bonsai/bonsai/core/aggregate.py
@@ -25,18 +25,18 @@ if TYPE_CHECKING:
import bonsai.tool as tool
-def enable_editing_aggregate(aggregator: tool.Aggregate, obj: bpy.types.Object) -> None:
+def enable_editing_aggregate(aggregator: type[tool.Aggregate], obj: bpy.types.Object) -> None:
aggregator.enable_editing(obj)
-def disable_editing_aggregate(aggregator: tool.Aggregate, obj: bpy.types.Object) -> None:
+def disable_editing_aggregate(aggregator: type[tool.Aggregate], obj: bpy.types.Object) -> None:
aggregator.disable_editing(obj)
def assign_object(
- ifc: tool.Ifc,
- aggregator: tool.Aggregate,
- collector: tool.Collector,
+ ifc: type[tool.Ifc],
+ aggregator: type[tool.Aggregate],
+ collector: type[tool.Collector],
relating_obj: Optional[bpy.types.Object] = None,
related_obj: Optional[bpy.types.Object] = None,
) -> Union[ifcopenshell.entity_instance, None]:
@@ -53,9 +53,9 @@ def assign_object(
def unassign_object(
- ifc: tool.Ifc,
- aggregate: tool.Aggregate,
- collector: tool.Collector,
+ ifc: type[tool.Ifc],
+ aggregate: type[tool.Aggregate],
+ collector: type[tool.Collector],
relating_obj: Optional[bpy.types.Object] = None,
related_obj: Optional[bpy.types.Object] = None,
) -> None:
@@ -74,10 +74,10 @@ def unassign_object(
def add_part_to_object(
- ifc: tool.Ifc,
- aggregator: tool.Aggregate,
- collector: tool.Collector,
- blender: tool.Blender,
+ ifc: type[tool.Ifc],
+ aggregator: type[tool.Aggregate],
+ collector: type[tool.Collector],
+ blender: type[tool.Blender],
obj: bpy.types.Object,
part_class: str,
part_name: Optional[str] = None,
@@ -88,7 +88,7 @@ def add_part_to_object(
def enter_aggregate_mode(
- aggregator: tool.Aggregate,
+ aggregator: type[tool.Aggregate],
obj: bpy.types.Object,
):
aggregator.update_previous_aggregate_mode_state()
@@ -98,7 +98,7 @@ def enter_aggregate_mode(
aggregator.enable_aggregate_mode(obj)
-def exit_aggregate_mode(aggregator: tool.Aggregate):
+def exit_aggregate_mode(aggregator: type[tool.Aggregate]):
aggregator.update_previous_aggregate_mode_state()
if new_obj := aggregator.get_higher_aggregate():
aggregator.disable_aggregate_mode()
diff --git a/src/bonsai/bonsai/core/ifcgit.py b/src/bonsai/bonsai/core/ifcgit.py
index 6d37a5c5c9..bbb7efa99f 100644
--- a/src/bonsai/bonsai/core/ifcgit.py
+++ b/src/bonsai/bonsai/core/ifcgit.py
@@ -27,19 +27,19 @@ if TYPE_CHECKING:
import git
-def create_repo(ifcgit: tool.IfcGit, ifc: tool.Ifc) -> None:
+def create_repo(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc]) -> None:
path_ifc = ifc.get_path()
path_dir = ifcgit.get_path_dir(path_ifc)
ifcgit.init_repo(path_dir)
-def add_file(ifcgit: tool.IfcGit, ifc: tool.Ifc) -> None:
+def add_file(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc]) -> None:
path_ifc = ifc.get_path()
repo = ifcgit.repo_from_path(path_ifc)
ifcgit.add_file_to_repo(repo, path_ifc)
-def clone_repo(ifcgit: tool.IfcGit, remote_url: str, local_folder: str, operator: bpy.types.Operator) -> None:
+def clone_repo(ifcgit: type[tool.IfcGit], remote_url: str, local_folder: str, operator: bpy.types.Operator) -> None:
repo = ifcgit.clone_repo(remote_url, local_folder)
if not repo:
operator.report({"ERROR"}, "Clone failed")
@@ -48,14 +48,14 @@ def clone_repo(ifcgit: tool.IfcGit, remote_url: str, local_folder: str, operator
ifcgit.load_anyifc(repo)
-def discard_uncommitted(ifcgit: tool.IfcGit, ifc: tool.Ifc) -> None:
+def discard_uncommitted(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc]) -> None:
path_ifc = ifc.get_path()
# NOTE this is calling the git binary in a subprocess
ifcgit.git_checkout(path_ifc)
ifcgit.load_project(path_ifc)
-def commit_changes(ifcgit: tool.IfcGit, ifc: tool.Ifc, repo: git.Repo) -> None:
+def commit_changes(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], repo: git.Repo) -> None:
"""Commit and create new branches as required"""
path_ifc = ifc.get_path()
@@ -67,34 +67,34 @@ def commit_changes(ifcgit: tool.IfcGit, ifc: tool.Ifc, repo: git.Repo) -> None:
ifcgit.git_commit(path_ifc)
-def add_tag(ifcgit: tool.IfcGit, repo: git.Repo) -> None:
+def add_tag(ifcgit: type[tool.IfcGit], repo: git.Repo) -> None:
ifcgit.add_tag(repo)
-def delete_tag(ifcgit: tool.IfcGit, repo: git.Repo, tag_name: git.TagReference) -> None:
+def delete_tag(ifcgit: type[tool.IfcGit], repo: git.Repo, tag_name: git.TagReference) -> None:
ifcgit.delete_tag(repo, tag_name)
-def add_remote(ifcgit: tool.IfcGit, repo: git.Repo) -> None:
+def add_remote(ifcgit: type[tool.IfcGit], repo: git.Repo) -> None:
ifcgit.add_remote(repo)
-def delete_remote(ifcgit: tool.IfcGit, repo: git.Repo) -> None:
+def delete_remote(ifcgit: type[tool.IfcGit], repo: git.Repo) -> None:
ifcgit.delete_remote(repo)
-def push(ifcgit: tool.IfcGit, repo: git.Repo, remote_name: str, operator: bpy.types.Operator) -> None:
+def push(ifcgit: type[tool.IfcGit], repo: git.Repo, remote_name: str, operator: bpy.types.Operator) -> None:
error_message = ifcgit.push(repo, remote_name, repo.active_branch.name)
if error_message:
operator.report({"ERROR"}, error_message)
-def refresh_revision_list(ifcgit: tool.IfcGit, repo: git.Repo, ifc: tool.Ifc) -> None:
+def refresh_revision_list(ifcgit: type[tool.IfcGit], repo: git.Repo, ifc: type[tool.Ifc]) -> None:
if repo.heads:
ifcgit.refresh_revision_list(ifc.get_path())
-def colourise_revision(ifcgit: tool.IfcGit) -> None:
+def colourise_revision(ifcgit: type[tool.IfcGit]) -> None:
step_ids = ifcgit.get_revisions_step_ids()
if not step_ids:
@@ -104,7 +104,7 @@ def colourise_revision(ifcgit: tool.IfcGit) -> None:
ifcgit.colourise(final_step_ids)
-def colourise_uncommitted(ifcgit: tool.IfcGit, ifc: tool.Ifc, repo: git.Repo) -> None:
+def colourise_uncommitted(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], repo: git.Repo) -> None:
path_ifc = ifc.get_path()
step_ids = ifcgit.ifc_diff_ids(repo, None, "HEAD", path_ifc)
if not step_ids:
@@ -114,7 +114,7 @@ def colourise_uncommitted(ifcgit: tool.IfcGit, ifc: tool.Ifc, repo: git.Repo) ->
ifcgit.colourise(final_step_ids)
-def switch_revision(ifcgit: tool.IfcGit, ifc: tool.Ifc) -> None:
+def switch_revision(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc]) -> None:
# FIXME bad things happen when switching to a revision that predates current project
path_ifc = ifc.get_path()
@@ -124,25 +124,25 @@ def switch_revision(ifcgit: tool.IfcGit, ifc: tool.Ifc) -> None:
ifcgit.decolourise()
-def merge_branch(ifcgit: tool.IfcGit, ifc: tool.Ifc, operator: bpy.types.Operator) -> None:
+def merge_branch(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], operator: bpy.types.Operator) -> None:
path_ifc = ifc.get_path()
ifcgit.config_ifcmerge()
ifcgit.execute_merge(path_ifc, operator)
-def entity_log(ifcgit: tool.IfcGit, ifc: tool.Ifc, step_id: int, operator: bpy.types.Operator) -> None:
+def entity_log(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], step_id: int, operator: bpy.types.Operator) -> None:
path_ifc = ifc.get_path()
log_text = ifcgit.entity_log(path_ifc, step_id)
# ERROR is only way to display a multi-line message
operator.report({"ERROR"}, log_text)
-def install_git(ifcgit: tool.IfcGit, operator: bpy.types.Operator) -> None:
+def install_git(ifcgit: type[tool.IfcGit], operator: bpy.types.Operator) -> None:
if platform.system() == "Windows":
ifcgit.install_git_windows(operator=operator)
else:
print("install_git() not implemented")
-def run_git_diff(ifcgit: tool.IfcGit, operator: bpy.types.Operator) -> None:
+def run_git_diff(ifcgit: type[tool.IfcGit], operator: bpy.types.Operator) -> None:
ifcgit.run_git_diff(operator)
diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py
index f9785c06a5..022ef95b96 100644
--- a/src/bonsai/bonsai/tool/blender.py
+++ b/src/bonsai/bonsai/tool/blender.py
@@ -52,6 +52,7 @@ if TYPE_CHECKING:
from bonsai.bim.module.csv.prop import CsvProperties
from bonsai.bim.module.constraint.prop import BIMConstraintProperties, BIMObjectConstraintProperties
from bonsai.bim.module.diff.prop import DiffProperties
+ from bonsai.bim.module.group.prop import BIMGroupProperties
T = TypeVar("T")
@@ -244,8 +245,9 @@ class Blender(bonsai.core.tool.Blender):
wsprops = tool.Sequence.get_work_schedule_props()
return wsprops.active_work_schedule_id
elif obj_type == "Group":
- prop = context.scene.BIMGroupProperties
- return prop.groups[prop.active_group_index].ifc_definition_id
+ props = tool.Blender.get_group_props()
+ assert (active_group := props.active_group)
+ return active_group.ifc_definition_id
assert_never(obj_type)
@classmethod
@@ -1664,6 +1666,11 @@ class Blender(bonsai.core.tool.Blender):
assert (scene := bpy.context.scene)
return scene.DiffProperties
+ @classmethod
+ def get_group_props(cls) -> BIMGroupProperties:
+ assert (scene := bpy.context.scene)
+ return scene.BIMGroupProperties
+
@classmethod
def get_bim_props(cls, scene: Optional[bpy.types.Scene] = None) -> BIMProperties:
if scene is None:
diff --git a/src/bonsai/bonsai/tool/ifcgit.py b/src/bonsai/bonsai/tool/ifcgit.py
index 6cf4760c4d..30e2712a7a 100644
--- a/src/bonsai/bonsai/tool/ifcgit.py
+++ b/src/bonsai/bonsai/tool/ifcgit.py
@@ -25,7 +25,7 @@ import logging
from bonsai.bim import import_ifc
from bonsai.bim.ifc import IfcStore
import bonsai.tool as tool
-from typing import TYPE_CHECKING, Union
+from typing import TYPE_CHECKING, Union, Literal, Any
# allows git import even if git executable isn't found
os.environ["GIT_PYTHON_REFRESH"] = "quiet"
@@ -37,10 +37,17 @@ except ImportError:
if TYPE_CHECKING:
import git
+ from bonsai.bim.module.ifcgit.prop import IfcGitProperties
+
class IfcGit:
STEP_IDS = dict[str, set[int]]
+ @classmethod
+ def get_ifcgit_props(cls) -> IfcGitProperties:
+ assert (scene := bpy.context.scene)
+ return scene.IfcGitProperties
+
@classmethod
def init_repo(cls, path_dir: str) -> None:
IfcGitRepo.repo = git.Repo.init(path_dir)
@@ -118,7 +125,7 @@ class IfcGit:
@classmethod
def checkout_new_branch(cls, path_file: str) -> None:
"""Create a branch and move uncommitted changes to this branch"""
- props = bpy.context.scene.IfcGitProperties
+ props = cls.get_ifcgit_props()
if props.new_branch_name:
IfcGitRepo.repo.git.checkout(b=props.new_branch_name)
props.display_branch = props.new_branch_name
@@ -127,7 +134,7 @@ class IfcGit:
@classmethod
def git_commit(cls, path_file: str) -> None:
- props = bpy.context.scene.IfcGitProperties
+ props = cls.get_ifcgit_props()
repo = IfcGitRepo.repo
if os.name == "nt":
cls.dos2unix(path_file)
@@ -137,7 +144,7 @@ class IfcGit:
@classmethod
def add_tag(cls, repo: git.Repo) -> None:
- props = bpy.context.scene.IfcGitProperties
+ props = cls.get_ifcgit_props()
item = props.ifcgit_commits[props.commit_index]
repo.create_tag(props.new_tag_name, ref=item.hexsha, message=props.new_tag_message)
props.new_tag_name = ""
@@ -150,14 +157,14 @@ class IfcGit:
@classmethod
def add_remote(cls, repo: git.Repo) -> None:
- props = bpy.context.scene.IfcGitProperties
+ props = cls.get_ifcgit_props()
repo.create_remote(name=props.remote_name, url=props.remote_url)
props.remote_name = ""
props.remote_url = ""
@classmethod
def delete_remote(cls, repo: git.Repo) -> None:
- props = bpy.context.scene.IfcGitProperties
+ props = cls.get_ifcgit_props()
remote_name = props.select_remote
if remote_name in repo.remotes:
repo.delete_remote(remote_name)
@@ -176,7 +183,7 @@ class IfcGit:
@classmethod
def create_new_branch(cls) -> None:
"""Convert a detached HEAD into a branch"""
- props = bpy.context.scene.IfcGitProperties
+ props = cls.get_ifcgit_props()
repo = IfcGitRepo.repo
new_branch = repo.create_head(props.new_branch_name)
new_branch.checkout()
@@ -187,7 +194,7 @@ class IfcGit:
@classmethod
def clear_commits_list(cls) -> None:
- props = bpy.context.scene.IfcGitProperties
+ props = cls.get_ifcgit_props()
# ifcgit_commits is registered list widget
props.ifcgit_commits.clear()
@@ -195,7 +202,7 @@ class IfcGit:
@classmethod
def get_commits_list(cls, path_ifc: str, lookup: dict[str, Any]) -> None:
- props = bpy.context.scene.IfcGitProperties
+ props = props = cls.get_ifcgit_props()
repo = cls.repo_from_path(path_ifc)
commits = list(
git.objects.commit.Commit.iter_items(
@@ -342,7 +349,7 @@ class IfcGit:
def get_revisions_step_ids(cls) -> Union[STEP_IDS, None]:
props = tool.Blender.get_bim_props()
path_ifc = tool.Blender.get_bim_props().ifc_file
- props = bpy.context.scene.IfcGitProperties
+ props = cls.get_ifcgit_props()
repo = IfcGitRepo.repo
item = props.ifcgit_commits[props.commit_index]
@@ -431,7 +438,7 @@ class IfcGit:
@classmethod
def switch_to_revision_item(cls) -> None:
- props = bpy.context.scene.IfcGitProperties
+ props = cls.get_ifcgit_props()
repo = IfcGitRepo.repo
item = props.ifcgit_commits[props.commit_index]
@@ -500,8 +507,8 @@ class IfcGit:
output.write(line + b"\n")
@classmethod
- def execute_merge(cls, path_ifc: str, operator: bpy.types.Operator) -> Union[None, False]:
- props = bpy.context.scene.IfcGitProperties
+ def execute_merge(cls, path_ifc: str, operator: bpy.types.Operator) -> Union[None, Literal[False]]:
+ props = cls.get_ifcgit_props()
repo = IfcGitRepo.repo
item = props.ifcgit_commits[props.commit_index]
lookup = cls.branches_by_hexsha(repo)
diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py
index 5ace7af9d4..5970c08d6d 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py
@@ -52,15 +52,11 @@ def assign_cost_item_quantity(
count towards the cost item (they only provide value).
:param cost_item: The IfcCostItem to assign parametric quantities to
- :type cost_item: ifcopenshell.entity_instance
:param products: The IfcObjects to assign parametric quantities to
- :type products: list[ifcopenshell.entity_instance]
:param prop_name: The name of the quantity. If this is not specified,
then it is assumed that there is no calculated quantity, and the
number of objects are counted instead.
- :type prop_name: str, optional
:return: None
- :rtype: None
Example: