mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-20 04:04:00 +00:00
Merge v0.8.0 into light/newUI
Resolved conflict in light/operator.py by keeping the modular re-export architecture from light/newUI and incorporating RADIANCE_OT_select_camera that was missed during refactoring. Added it to render.py, registered in __init__.py, and restored the eyedropper button in ui.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -64,7 +64,7 @@ PYNUMBER:=3$(PYMINOR)
|
||||
PYPI_VERSION:=3.$(PYMINOR)
|
||||
endif # def PYVERSION
|
||||
|
||||
IFCMERGE_VERSION:=2026-04-02
|
||||
IFCMERGE_VERSION:=2026-04-07
|
||||
|
||||
ifdef PLATFORM
|
||||
SUPPORTED_PLATFORMS := linux macos macosm1 win
|
||||
|
||||
@@ -101,7 +101,7 @@ class AggregateDecorator:
|
||||
cls.is_installed = False
|
||||
|
||||
def dotted_line_shader(self):
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments]
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||
vert_out.smooth("FLOAT", "v_ArcLength")
|
||||
|
||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||
|
||||
@@ -73,6 +73,22 @@ def poll_related_object(self: "BIMObjectAggregateProperties", related_obj: bpy.t
|
||||
return True
|
||||
|
||||
|
||||
def update_relating_object(self, context):
|
||||
if self.relating_object:
|
||||
ifc_id = tool.Blender.get_object_bim_props(self.relating_object).ifc_definition_id
|
||||
if ifc_id:
|
||||
bpy.ops.bim.aggregate_assign_object(relating_object=ifc_id)
|
||||
bpy.ops.bim.disable_editing_aggregate()
|
||||
|
||||
|
||||
def update_related_object(self, context):
|
||||
if self.related_object:
|
||||
ifc_id = tool.Blender.get_object_bim_props(self.related_object).ifc_definition_id
|
||||
if ifc_id:
|
||||
bpy.ops.bim.aggregate_assign_object(related_object=ifc_id)
|
||||
bpy.ops.bim.disable_editing_aggregate()
|
||||
|
||||
|
||||
def update_aggregate_decorator(self, context):
|
||||
if self.aggregate_decorator:
|
||||
AggregateDecorator.install(bpy.context)
|
||||
@@ -89,12 +105,15 @@ def update_aggregate_mode_decorator(self, context):
|
||||
|
||||
class BIMObjectAggregateProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
relating_object: PointerProperty(name="Relating Whole", type=bpy.types.Object, poll=poll_relating_object)
|
||||
relating_object: PointerProperty(
|
||||
name="Relating Whole", type=bpy.types.Object, poll=poll_relating_object, update=update_relating_object
|
||||
)
|
||||
related_object: PointerProperty(
|
||||
name="Related Part",
|
||||
description="Related Part, will be used to derive the Relating Object",
|
||||
type=bpy.types.Object,
|
||||
poll=poll_related_object,
|
||||
update=update_related_object,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -295,13 +295,13 @@ class ExplorerShowUIPopup(bpy.types.Operator):
|
||||
bl_description = "Show Explorer UI to select element as attribute value or edit it."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
ifc_class: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
"""Element IFC class."""
|
||||
attribute_name: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
attribute_name: bpy.props.StringProperty()
|
||||
"""IFC class attribute name."""
|
||||
data_path: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
data_path: bpy.props.StringProperty()
|
||||
"""Full data path"""
|
||||
preselect_ifc_id: bpy.props.IntProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
|
||||
preselect_ifc_id: bpy.props.IntProperty(options={"SKIP_SAVE"})
|
||||
"""IFC id to preselect in the popup."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -41,7 +41,7 @@ class BIMAttributeProperties(PropertyGroup):
|
||||
|
||||
|
||||
class ExplorerEntity(PropertyGroup):
|
||||
ifc_definition_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ifc_definition_id: int
|
||||
@@ -60,7 +60,7 @@ class BIMExplorerProperties(PropertyGroup):
|
||||
self.property_unset("editing_entity_id")
|
||||
self.entity_attributes.clear()
|
||||
|
||||
is_loaded: BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
is_loaded: BoolProperty(
|
||||
name="Toggle Explorer UI",
|
||||
update=update_is_loaded,
|
||||
)
|
||||
@@ -76,15 +76,15 @@ class BIMExplorerProperties(PropertyGroup):
|
||||
def update_ifc_class(self, context: object) -> None:
|
||||
tool.Attribute.refresh_uilist_entities()
|
||||
|
||||
ifc_class: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
ifc_class: EnumProperty(
|
||||
name="IFC Class To Search",
|
||||
items=get_ifc_class,
|
||||
update=update_ifc_class,
|
||||
)
|
||||
entities: CollectionProperty(type=ExplorerEntity) # pyright: ignore[reportRedeclaration]
|
||||
active_entity_index: IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
editing_entity_id: IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
entity_attributes: CollectionProperty(type=Attribute) # pyright: ignore[reportRedeclaration]
|
||||
entities: CollectionProperty(type=ExplorerEntity)
|
||||
active_entity_index: IntProperty()
|
||||
editing_entity_id: IntProperty()
|
||||
entity_attributes: CollectionProperty(type=Attribute)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_loaded: bool
|
||||
|
||||
@@ -201,16 +201,10 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
"ALT+click to run a quick clash without selecting a file to save."
|
||||
)
|
||||
|
||||
filter_glob: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
default="*.bcf;*.json", options={"HIDDEN"}
|
||||
)
|
||||
format: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Format", items=[(i, i, "") for i in ("bcf", "json")]
|
||||
)
|
||||
filepath: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
subtype="FILE_PATH", options={"SKIP_SAVE"}
|
||||
)
|
||||
quick_clash: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.json", options={"HIDDEN"})
|
||||
format: bpy.props.EnumProperty(name="Format", items=[(i, i, "") for i in ("bcf", "json")])
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
|
||||
quick_clash: bpy.props.BoolProperty(
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
|
||||
@@ -37,12 +37,12 @@ from bonsai.bim.prop import BIMFilterGroup, StrProperty
|
||||
|
||||
|
||||
class ClashSource(PropertyGroup):
|
||||
name: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
name: StringProperty(
|
||||
name="File",
|
||||
description="Absolute filepath to existing .ifc file to use as a clash source.",
|
||||
)
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups") # pyright: ignore[reportRedeclaration]
|
||||
mode: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
|
||||
mode: EnumProperty(
|
||||
items=[
|
||||
("a", "All Elements", "All elements will be used for clashing"),
|
||||
("i", "Include", "Only the selected elements are included for clashing"),
|
||||
@@ -62,7 +62,7 @@ class Clash(PropertyGroup):
|
||||
b_global_id: StringProperty(name="B")
|
||||
a_name: StringProperty(name="A Name")
|
||||
b_name: StringProperty(name="B Name")
|
||||
clash_type: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
clash_type: EnumProperty(
|
||||
name="Clash Type",
|
||||
items=tuple((i, i, "") for i in CLASH_TYPE_ITEMS),
|
||||
)
|
||||
|
||||
@@ -87,7 +87,7 @@ class CopyCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Copy Cost Schedule"
|
||||
bl_description = "Create a duplicate of the provided cost schedule."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_schedule: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_schedule: int
|
||||
|
||||
@@ -260,14 +260,14 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
)
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
geometry_library: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
geometry_library: bpy.props.EnumProperty(
|
||||
name="Geometry Library",
|
||||
description="Geometry library to use for testing shape creation.",
|
||||
items=[(i, i, "") for i in get_args(ifcopenshell.geom.GEOMETRY_LIBRARY)],
|
||||
# By default use the same library as used for importing ifc project.
|
||||
default="hybrid-cgal-simple-opencascade",
|
||||
)
|
||||
custom_geometry_library: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
custom_geometry_library: bpy.props.StringProperty(
|
||||
name="Custom Geometry Library",
|
||||
description="Provide a custom geometry library name, will override the 'geometry library' property.",
|
||||
)
|
||||
@@ -781,7 +781,7 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Purge Unused Objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
object_type: bpy.props.EnumProperty(
|
||||
name="Object Type",
|
||||
items=((s, s.capitalize(), "") for s in get_args(tool.Debug.PurgeMergeObjectType)),
|
||||
)
|
||||
@@ -827,7 +827,7 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
object_type: bpy.props.EnumProperty(
|
||||
name="Object Type",
|
||||
items=((s, s.capitalize(), "") for s in get_args(tool.Debug.PurgeMergeObjectType)),
|
||||
)
|
||||
@@ -1073,7 +1073,7 @@ class ChangeLogLevel(bpy.types.Operator):
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Change general log level across all Python code in Blender"
|
||||
|
||||
log_level: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
log_level: bpy.props.EnumProperty(
|
||||
name="Log Level",
|
||||
items=[(i, i, "") for i in get_args(LogLevelType)],
|
||||
default="WARNING",
|
||||
|
||||
@@ -246,17 +246,17 @@ class CreateDrawing(bpy.types.Operator):
|
||||
+ "Add the CTRL modifier to optionally open drawings to view them as\n"
|
||||
+ "they are created"
|
||||
)
|
||||
print_all: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
print_all: bpy.props.BoolProperty(
|
||||
name="Print All",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
open_viewer: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
open_viewer: bpy.props.BoolProperty(
|
||||
name="Open in Viewer",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
sync: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
sync: bpy.props.BoolProperty(
|
||||
name="Sync Before Creating Drawing",
|
||||
description="Could save some time if you're sure IFC and current Blender session are already in sync",
|
||||
default=True,
|
||||
@@ -2322,14 +2322,14 @@ class ActivateDrawingBase(tool.Ifc.Operator):
|
||||
+ "SHIFT+CLICK to load a quick preview of the drawing view"
|
||||
)
|
||||
|
||||
drawing: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
should_view_from_camera: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
drawing: bpy.props.IntProperty()
|
||||
should_view_from_camera: bpy.props.BoolProperty(
|
||||
name="Should View From Camera",
|
||||
description="Move view to the activated drawing's camera position.",
|
||||
default=True,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
use_quick_preview: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
use_quick_preview: bpy.props.BoolProperty(
|
||||
name="Use Quick Preview",
|
||||
description="Just move the camera to the drawing view, without loading anything else.",
|
||||
default=False,
|
||||
@@ -3635,14 +3635,12 @@ class ToggleTargetView(bpy.types.Operator):
|
||||
bl_label = "Toggle Target View"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
target_view: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
toggle_all: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
target_view: bpy.props.StringProperty()
|
||||
toggle_all: bpy.props.BoolProperty(
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
items=[(i, i, "") for i in get_args(ToggleOption)]
|
||||
)
|
||||
option: bpy.props.EnumProperty(items=[(i, i, "") for i in get_args(ToggleOption)])
|
||||
|
||||
if TYPE_CHECKING:
|
||||
target_view: str
|
||||
|
||||
@@ -860,13 +860,13 @@ class BIMTextProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
literals: CollectionProperty(name="Literals", type=LiteralProps)
|
||||
newline_at: IntProperty(name="Newline At")
|
||||
symbol: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
symbol: EnumProperty(
|
||||
name="Symbol",
|
||||
description="Symbol from symbols.svg to use for this text.",
|
||||
items=[(s, s, "") for s in ["NO SYMBOL", "CUSTOM SYMBOL"] + tool.Drawing.DEFAULT_SYMBOLS],
|
||||
default="NO SYMBOL",
|
||||
)
|
||||
custom_symbol: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
custom_symbol: StringProperty(
|
||||
name="Custom Symbol",
|
||||
description="Non-default symbol to use for this text.",
|
||||
)
|
||||
|
||||
@@ -85,7 +85,7 @@ class EditObjectPlacement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.override_mesh_separate"
|
||||
bl_label = "IFC Mesh Separate"
|
||||
blender_op = bpy.ops.mesh.separate.get_rna_type()
|
||||
blender_op = bpy.ops.mesh.separate.get_rna_type() # ty: ignore[missing-argument]
|
||||
bl_description = blender_op.description + ".\nAlso makes sure changes are in sync with IFC."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
blender_type_prop = blender_op.properties["type"]
|
||||
@@ -246,7 +246,7 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class OverrideOriginSet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.override_origin_set"
|
||||
blender_op = bpy.ops.object.origin_set.get_rna_type()
|
||||
blender_op = bpy.ops.object.origin_set.get_rna_type() # ty: ignore[missing-argument]
|
||||
bl_label = "IFC Origin Set"
|
||||
bl_description = (
|
||||
blender_op.description + ".\nAlso makes sure changes are in sync with IFC (operator works only on IFC objects)"
|
||||
@@ -801,7 +801,7 @@ def calc_delete_is_batch(ifc_file: ifcopenshell.file, context: bpy.types.Context
|
||||
class OverrideDelete(bpy.types.Operator):
|
||||
bl_idname = "bim.override_object_delete"
|
||||
bl_label = "IFC Delete"
|
||||
blender_op = bpy.ops.object.delete.get_rna_type()
|
||||
blender_op = bpy.ops.object.delete.get_rna_type() # ty: ignore[missing-argument]
|
||||
bl_description = (
|
||||
blender_op.description
|
||||
+ ".\nAlso makes sure changes in sync with IFC."
|
||||
@@ -821,7 +821,7 @@ class OverrideDelete(bpy.types.Operator):
|
||||
def poll(cls, context):
|
||||
# Match `object.delete` poll for consistency.
|
||||
# `object.delete` poll just checks for OBJECT mode.
|
||||
poll = bpy.ops.object.delete.poll()
|
||||
poll = bpy.ops.object.delete.poll() # ty: ignore[missing-argument]
|
||||
if poll:
|
||||
return True
|
||||
cls.poll_message_set("Only available in OBJECT mode")
|
||||
@@ -1045,7 +1045,7 @@ class SelectedIdsData(NamedTuple):
|
||||
class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.override_outliner_delete"
|
||||
bl_label = "IFC Delete"
|
||||
blender_op = bpy.ops.outliner.delete.get_rna_type()
|
||||
blender_op = bpy.ops.outliner.delete.get_rna_type() # ty: ignore[missing-argument]
|
||||
bl_description = (
|
||||
blender_op.description
|
||||
+ ".\nAlso makes sure changes in sync with IFC."
|
||||
@@ -1060,7 +1060,7 @@ class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def poll(cls, context) -> bool:
|
||||
# Match `outliner.delete` poll for consistency.
|
||||
# `outliner.delete` just checks `area.type` == `OUTLINER`.
|
||||
poll = bpy.ops.outliner.delete.poll()
|
||||
poll = bpy.ops.outliner.delete.poll() # ty: ignore[missing-argument]
|
||||
if poll:
|
||||
return True
|
||||
cls.poll_message_set("Only available from Outliner.")
|
||||
@@ -1164,7 +1164,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
def poll(cls, context) -> bool:
|
||||
# Match `object.duplicate_move` poll for consistency.
|
||||
# `object.duplicate_move` poll checks for OBJECT mode.
|
||||
poll = bpy.ops.object.duplicate_move.poll()
|
||||
poll = bpy.ops.object.duplicate_move.poll() # ty: ignore[missing-argument]
|
||||
if poll:
|
||||
return True
|
||||
cls.poll_message_set("Only available in OBJECT mode")
|
||||
@@ -1908,7 +1908,7 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.override_object_join"
|
||||
bl_label = "IFC Join"
|
||||
blender_op = bpy.ops.mesh.separate.get_rna_type()
|
||||
blender_op = bpy.ops.mesh.separate.get_rna_type() # ty: ignore[missing-argument]
|
||||
bl_description = (
|
||||
blender_op.description
|
||||
+ ".\nAlso makes sure changes are in sync with IFC."
|
||||
@@ -1926,7 +1926,7 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not bpy.ops.object.join.poll():
|
||||
if not bpy.ops.object.join.poll(): # ty: ignore[missing-argument]
|
||||
cls.poll_message_set("Active object is not EDITable.")
|
||||
return False
|
||||
if not context.selected_editable_objects:
|
||||
|
||||
@@ -43,11 +43,11 @@ class ToggleGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Toggle Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
ifc_definition_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
group_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
group_type: bpy.props.EnumProperty(
|
||||
items=[(i, i, "") for i in get_args(tool.Group.GroupType)],
|
||||
)
|
||||
option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
option: bpy.props.EnumProperty(
|
||||
items=[(i, i, "") for i in get_args(tool.Group.ToggleOption)],
|
||||
)
|
||||
|
||||
|
||||
@@ -34,8 +34,10 @@ classes = (
|
||||
operator.Fetch,
|
||||
operator.Merge,
|
||||
operator.ObjectLog,
|
||||
operator.SelectConflictEntity,
|
||||
operator.Push,
|
||||
operator.RefreshGit,
|
||||
operator.RenameBranch,
|
||||
operator.SwitchRevision,
|
||||
operator.InstallGit,
|
||||
operator.RunGitDiff,
|
||||
|
||||
@@ -202,7 +202,7 @@ class DeleteTag(bpy.types.Operator):
|
||||
class RefreshGit(bpy.types.Operator):
|
||||
"""Refresh revision list"""
|
||||
|
||||
bl_label = ""
|
||||
bl_label = "Refresh"
|
||||
bl_idname = "ifcgit.refresh"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@@ -225,7 +225,7 @@ class RefreshGit(bpy.types.Operator):
|
||||
class DisplayRevision(bpy.types.Operator):
|
||||
"""Colourise objects by selected revision"""
|
||||
|
||||
bl_label = ""
|
||||
bl_label = "Colourise Revision"
|
||||
bl_idname = "ifcgit.display_revision"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@@ -260,7 +260,7 @@ class DisplayUncommitted(bpy.types.Operator):
|
||||
class SwitchRevision(bpy.types.Operator):
|
||||
"""Switches the repository to the selected revision and reloads the IFC file"""
|
||||
|
||||
bl_label = ""
|
||||
bl_label = "Switch Revision"
|
||||
bl_idname = "ifcgit.switch_revision"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@@ -278,7 +278,7 @@ class SwitchRevision(bpy.types.Operator):
|
||||
|
||||
|
||||
class Merge(bpy.types.Operator):
|
||||
"""Merges the selected branch into working branch"""
|
||||
"""Merges the selected branch into working branch.\nCtrl+click to preview without merging"""
|
||||
|
||||
bl_label = "Merge this branch"
|
||||
bl_idname = "ifcgit.merge"
|
||||
@@ -292,8 +292,14 @@ class Merge(bpy.types.Operator):
|
||||
return True
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
def invoke(self, context, event):
|
||||
if event.ctrl:
|
||||
core.dry_run_merge(tool.IfcGit, tool.Ifc, self)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
if core.merge_branch(tool.IfcGit, tool.Ifc, self) is not False:
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
@@ -301,6 +307,69 @@ class Merge(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class SelectConflictEntity(bpy.types.Operator):
|
||||
"""Select the conflicting entity in the viewport"""
|
||||
|
||||
bl_label = "Select Conflict Entity"
|
||||
bl_idname = "ifcgit.select_conflict_entity"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
step_id: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
step_id: int
|
||||
|
||||
def execute(self, context):
|
||||
model = tool.Ifc.get()
|
||||
if not model:
|
||||
return {"CANCELLED"}
|
||||
|
||||
try:
|
||||
entity = model.by_id(self.step_id)
|
||||
except Exception:
|
||||
self.report({"WARNING"}, f"Entity #{self.step_id} not found (may have been deleted locally)")
|
||||
return {"CANCELLED"}
|
||||
|
||||
obj = tool.Ifc.get_object(entity)
|
||||
if obj is None:
|
||||
# Walk inverse references up to 5 hops to find nearest entity with a Blender object
|
||||
visited = {entity.id()}
|
||||
queue = [entity]
|
||||
for _ in range(5):
|
||||
next_queue = []
|
||||
for ent in queue:
|
||||
for inv in model.get_inverse(ent):
|
||||
if inv.id() in visited:
|
||||
continue
|
||||
visited.add(inv.id())
|
||||
obj = tool.Ifc.get_object(inv)
|
||||
if obj is not None:
|
||||
break
|
||||
next_queue.append(inv)
|
||||
if obj is not None:
|
||||
break
|
||||
if obj is not None:
|
||||
break
|
||||
queue = next_queue
|
||||
|
||||
if obj is None:
|
||||
self.report({"INFO"}, f"No viewport representation found for #{self.step_id} ({entity.is_a()})")
|
||||
return {"CANCELLED"}
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
obj.select_set(True)
|
||||
context.view_layer.objects.active = obj
|
||||
for area in context.screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
region = next((r for r in area.regions if r.type == "WINDOW"), None)
|
||||
if region:
|
||||
with context.temp_override(area=area, region=region):
|
||||
bpy.ops.view3d.view_selected()
|
||||
break
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class Push(bpy.types.Operator):
|
||||
"""Pushes the working branch to selected remote"""
|
||||
|
||||
@@ -446,7 +515,7 @@ class RunGitDiff(bpy.types.Operator):
|
||||
)
|
||||
bl_options = set()
|
||||
|
||||
save_to_temp: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
|
||||
save_to_temp: bpy.props.BoolProperty(options={"SKIP_SAVE"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
save_to_temp: bool
|
||||
@@ -469,3 +538,37 @@ class RunGitDiff(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
core.run_git_diff(tool.IfcGit, self, self.save_to_temp)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RenameBranch(bpy.types.Operator):
|
||||
"""Rename the current branch"""
|
||||
|
||||
bl_label = "Rename Branch"
|
||||
bl_idname = "ifcgit.rename_branch"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
new_name: bpy.props.StringProperty(name="New name")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
new_name: str
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
if not IfcGitData.data["repo"]:
|
||||
return False
|
||||
if IfcGitData.data["is_detached"]:
|
||||
return False
|
||||
if IfcGitData.data["is_dirty"]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.new_name = IfcGitData.data["active_branch_name"]
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.rename_branch(tool.IfcGit, repo, self.new_name)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -139,6 +139,11 @@ class IfcGitProperties(PropertyGroup):
|
||||
],
|
||||
update=update_revlist,
|
||||
)
|
||||
merge_conflicts: StringProperty(
|
||||
name="Merge Conflicts",
|
||||
description="JSON report from last failed merge attempt",
|
||||
default="",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ifcgit_commits: bpy.types.bpy_prop_collection_idprop[IfcGitListItem]
|
||||
@@ -153,3 +158,4 @@ class IfcGitProperties(PropertyGroup):
|
||||
display_branch: str
|
||||
select_remote: str
|
||||
ifcgit_filter: Literal["all", "tagged", "relevant"]
|
||||
merge_conflicts: str
|
||||
|
||||
@@ -112,15 +112,13 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
row.label(text="Working branch: Detached HEAD")
|
||||
else:
|
||||
row.label(text="Working branch: " + IfcGitData.data["active_branch_name"])
|
||||
row.operator("ifcgit.rename_branch", icon="GREASEPENCIL", text="")
|
||||
|
||||
grouped = layout.row()
|
||||
column = grouped.column()
|
||||
row = column.row()
|
||||
row = layout.row()
|
||||
row.prop(props, "display_branch", text="Browse branch")
|
||||
row.prop(props, "ifcgit_filter", text="Filter revisions")
|
||||
|
||||
row = column.row()
|
||||
row.template_list(
|
||||
layout.template_list(
|
||||
"COMMIT_UL_List",
|
||||
"The_List",
|
||||
props,
|
||||
@@ -128,20 +126,64 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
props,
|
||||
"commit_index",
|
||||
)
|
||||
column = grouped.column()
|
||||
row = column.row()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("ifcgit.refresh", icon="FILE_REFRESH")
|
||||
|
||||
if not is_dirty:
|
||||
|
||||
row = column.row()
|
||||
row.operator("ifcgit.display_revision", icon="SELECT_DIFFERENCE")
|
||||
|
||||
row = column.row()
|
||||
row.operator("ifcgit.switch_revision", icon="CURRENT_FILE")
|
||||
row.operator("ifcgit.merge", icon="SYSTEM")
|
||||
|
||||
row = column.row()
|
||||
row.operator("ifcgit.merge", icon="EXPERIMENTAL", text="")
|
||||
conflicts = tool.IfcGit.get_merge_conflicts()
|
||||
if conflicts is not None:
|
||||
box = layout.box()
|
||||
box.alert = True
|
||||
row = box.row()
|
||||
row.label(
|
||||
text=f"Merge failed \u2014 {len(conflicts)} conflict(s)",
|
||||
icon="ERROR",
|
||||
)
|
||||
for conflict in conflicts:
|
||||
col = box.column(align=True)
|
||||
conflict_type = conflict.get("type", "")
|
||||
entity_id = conflict.get("entity_id", "?")
|
||||
local_id = conflict.get("original_local_id")
|
||||
|
||||
if conflict_type == "attribute_conflict":
|
||||
entity_class = conflict.get("entity_class", "Entity")
|
||||
attr_idx = conflict.get("attribute_index", "?")
|
||||
desc = f"#{entity_id} {entity_class}: attribute {attr_idx} conflict"
|
||||
elif conflict_type == "entity_deleted_and_modified":
|
||||
entity_class = conflict.get("entity_class", "Entity")
|
||||
desc = f"#{entity_id} {entity_class}: " + conflict.get("message", "deleted/modified conflict")
|
||||
elif conflict_type == "class_changed":
|
||||
desc = (
|
||||
f"#{entity_id}: class changed "
|
||||
+ conflict.get("base_class", "?")
|
||||
+ " \u2192 "
|
||||
+ conflict.get("modified_class", "?")
|
||||
)
|
||||
elif conflict_type == "required_entity_deleted":
|
||||
desc = f"#{entity_id}: " + conflict.get("message", "required entity deleted")
|
||||
else:
|
||||
desc = f"#{entity_id}: {conflict_type}"
|
||||
|
||||
row = col.row(align=True)
|
||||
row.label(text=desc)
|
||||
if local_id:
|
||||
op = row.operator(
|
||||
"ifcgit.select_conflict_entity",
|
||||
text="",
|
||||
icon="RESTRICT_SELECT_OFF",
|
||||
)
|
||||
op.step_id = local_id
|
||||
|
||||
if conflict_type == "attribute_conflict":
|
||||
sub = col.column(align=True)
|
||||
sub.scale_y = 0.75
|
||||
sub.label(text=f" Base: {conflict.get('base_value', '')}")
|
||||
sub.label(text=f" Local: {conflict.get('local_value', '')}")
|
||||
sub.label(text=f" Remote: {conflict.get('remote_value', '')}")
|
||||
|
||||
if not props.ifcgit_commits:
|
||||
return
|
||||
|
||||
@@ -37,6 +37,7 @@ classes = (
|
||||
solar.MoveSunPathTo3DCursor,
|
||||
render.RadianceRender,
|
||||
render.FalseColorRadiance,
|
||||
render.RADIANCE_OT_select_camera,
|
||||
solar.ViewFromSun,
|
||||
solar.LightPickCoordinates,
|
||||
solar.LightSetTimeToNow,
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
All operator classes are defined in their respective submodules:
|
||||
- export.py: ExportOBJ, CleanupRadianceFiles
|
||||
- prepare.py: PrepareRadianceScene
|
||||
- render.py: RadianceRender, FalseColorRadiance
|
||||
- render.py: RadianceRender, FalseColorRadiance, RADIANCE_OT_select_camera
|
||||
- solar.py: ImportTrueNorth, ImportLatLong, MoveSunPathTo3DCursor,
|
||||
ViewFromSun, LightPickCoordinates, LightSetTimeToNow
|
||||
- material.py: RefreshIFCMaterials, UnmapMaterial,
|
||||
@@ -46,6 +46,7 @@ from bonsai.bim.module.light.material import ( # noqa: F401
|
||||
from bonsai.bim.module.light.prepare import PrepareRadianceScene # noqa: F401
|
||||
from bonsai.bim.module.light.render import ( # noqa: F401
|
||||
FalseColorRadiance,
|
||||
RADIANCE_OT_select_camera,
|
||||
RadianceRender,
|
||||
)
|
||||
from bonsai.bim.module.light.solar import ( # noqa: F401
|
||||
|
||||
@@ -307,3 +307,22 @@ class FalseColorRadiance(bpy.types.Operator):
|
||||
|
||||
traceback.print_exc()
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class RADIANCE_OT_select_camera(bpy.types.Operator):
|
||||
bl_idname = "radiance.select_camera"
|
||||
bl_label = "Select Camera"
|
||||
bl_description = "Select a camera from the viewport"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object is not None and context.object.type == "CAMERA"
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Blender.get_radiance_exporter_props()
|
||||
props.selected_camera = context.object
|
||||
props.use_active_camera = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ class BIM_PT_radiance_scene_setup(bpy.types.Panel):
|
||||
if not props.use_active_camera:
|
||||
row = layout.row()
|
||||
row.prop(props, "selected_camera")
|
||||
row.operator("radiance.select_camera", text="", icon="EYEDROPPER")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Resolution")
|
||||
|
||||
@@ -136,7 +136,7 @@ class SplitAlongEdge(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"Will unassign element from a type if type has a representation."
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
mode: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
mode: bpy.props.EnumProperty(
|
||||
default="BOOLEAN",
|
||||
items=tuple((i, i, "") for i in get_args(SplitAlongEdgeMode)),
|
||||
)
|
||||
@@ -359,7 +359,7 @@ class ConfirmQuickFavoriteOperator(bpy.types.Operator):
|
||||
bl_idname = "bim.confirm_quick_favorite_operator"
|
||||
bl_label = "Confirm Operator"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
index: int
|
||||
@@ -452,10 +452,8 @@ class MoveQuickFavoritesItem(bpy.types.Operator):
|
||||
bl_idname = "bim.move_quick_favorites_item"
|
||||
bl_label = "Move Quick Favorites Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
direction: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
items=[("UP", "Up", ""), ("DOWN", "Down", "")]
|
||||
)
|
||||
index: bpy.props.IntProperty()
|
||||
direction: bpy.props.EnumProperty(items=[("UP", "Up", ""), ("DOWN", "Down", "")])
|
||||
|
||||
if TYPE_CHECKING:
|
||||
index: int
|
||||
@@ -474,7 +472,7 @@ class RemoveQuickFavoritesItem(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_quick_favorites_item"
|
||||
bl_label = "Remove Quick Favorites Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
index: int
|
||||
|
||||
@@ -36,9 +36,9 @@ QuickFavoriteValueType = Literal["float_value", "bool_value", "int_value", "stri
|
||||
|
||||
|
||||
class QuickFavoriteEnumItem(PropertyGroup):
|
||||
name: StringProperty(name="Name", default="") # pyright: ignore[reportRedeclaration]
|
||||
display_name: StringProperty(name="Display Name", default="") # pyright: ignore[reportRedeclaration]
|
||||
description: StringProperty(name="Description", default="") # pyright: ignore[reportRedeclaration]
|
||||
name: StringProperty(name="Name", default="")
|
||||
display_name: StringProperty(name="Display Name", default="")
|
||||
description: StringProperty(name="Description", default="")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
@@ -51,19 +51,19 @@ def get_enum_items(self: "QuickFavoriteProperty", context: bpy.types.Context | N
|
||||
|
||||
|
||||
class QuickFavoriteProperty(PropertyGroup):
|
||||
name: StringProperty(name="Name", default="") # pyright: ignore[reportRedeclaration]
|
||||
display_name: StringProperty(name="Display Name", default="") # pyright: ignore[reportRedeclaration]
|
||||
value_prop: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name: StringProperty(name="Name", default="")
|
||||
display_name: StringProperty(name="Display Name", default="")
|
||||
value_prop: EnumProperty(
|
||||
name="Value Prop",
|
||||
items=tuple((v, v, "") for v in get_args(QuickFavoriteValueType)),
|
||||
)
|
||||
string_value: StringProperty(name="String Value", default="") # pyright: ignore[reportRedeclaration]
|
||||
float_value: FloatProperty(name="Float Value", default=0.0) # pyright: ignore[reportRedeclaration]
|
||||
int_value: IntProperty(name="Int Value", default=0) # pyright: ignore[reportRedeclaration]
|
||||
bool_value: BoolProperty(name="Bool Value", default=False) # pyright: ignore[reportRedeclaration]
|
||||
enum_value: EnumProperty(name="Enum Value", items=get_enum_items) # pyright: ignore[reportRedeclaration]
|
||||
enum_items: CollectionProperty(type=QuickFavoriteEnumItem) # pyright: ignore[reportRedeclaration]
|
||||
is_active: BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
string_value: StringProperty(name="String Value", default="")
|
||||
float_value: FloatProperty(name="Float Value", default=0.0)
|
||||
int_value: IntProperty(name="Int Value", default=0)
|
||||
bool_value: BoolProperty(name="Bool Value", default=False)
|
||||
enum_value: EnumProperty(name="Enum Value", items=get_enum_items)
|
||||
enum_items: CollectionProperty(type=QuickFavoriteEnumItem)
|
||||
is_active: BoolProperty(
|
||||
name="Is Active",
|
||||
description="Only active properties will be added to the operator when invoked from Quick Favorites",
|
||||
default=False,
|
||||
@@ -100,20 +100,20 @@ def get_operator_suggestions(self: "QuickFavoritesItem", context: bpy.types.Cont
|
||||
|
||||
|
||||
class QuickFavoritesItem(PropertyGroup):
|
||||
is_expanded: BoolProperty(name="Is Expanded", default=False) # pyright: ignore[reportRedeclaration]
|
||||
search: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
is_expanded: BoolProperty(name="Is Expanded", default=False)
|
||||
search: StringProperty(
|
||||
name="Search",
|
||||
default="",
|
||||
search=get_operator_suggestions,
|
||||
# Resetting `search_options`, allowing users only to use suggestions.
|
||||
search_options=set(),
|
||||
)
|
||||
properties: CollectionProperty(type=QuickFavoriteProperty) # pyright: ignore[reportRedeclaration]
|
||||
operator_id: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
properties: CollectionProperty(type=QuickFavoriteProperty)
|
||||
operator_id: StringProperty(
|
||||
name="Operator ID",
|
||||
default="",
|
||||
)
|
||||
label: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
label: StringProperty(
|
||||
name="Label",
|
||||
description="Label that will be used in Quick Favorites for this operator",
|
||||
default="",
|
||||
@@ -139,15 +139,15 @@ class QuickFavoritesItem(PropertyGroup):
|
||||
|
||||
|
||||
class BIMMiscProperties(PropertyGroup):
|
||||
total_storeys: IntProperty( # pyright: ignore[reportRedeclaration]
|
||||
total_storeys: IntProperty(
|
||||
name="Total Storeys",
|
||||
description="Number of storeys above object's storey to take into account for resizing",
|
||||
default=1,
|
||||
)
|
||||
override_colour: FloatVectorProperty( # pyright: ignore[reportRedeclaration]
|
||||
override_colour: FloatVectorProperty(
|
||||
name="Override Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
quick_favorites: CollectionProperty(type=QuickFavoritesItem) # pyright: ignore[reportRedeclaration]
|
||||
quick_favorites: CollectionProperty(type=QuickFavoritesItem)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
total_storeys: int
|
||||
|
||||
@@ -545,7 +545,7 @@ class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.change_type_page"
|
||||
bl_label = "Change Type Page"
|
||||
bl_options = {"REGISTER"}
|
||||
page: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
page: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
page: int
|
||||
|
||||
@@ -271,7 +271,7 @@ class ExtendProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.extend_profile"
|
||||
bl_label = "Extend Profile"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
join_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
join_type: bpy.props.EnumProperty(
|
||||
items=[("-", "Unjoin", ""), ("L", "L", ""), ("V", "V", ""), ("T", "T", "")],
|
||||
default="-",
|
||||
)
|
||||
|
||||
@@ -1729,20 +1729,20 @@ def poll_sverchok_nodes(self: "BIMExternalParametricGeometryProperties", node_tr
|
||||
|
||||
|
||||
class BIMExternalParametricGeometryProperties(bpy.types.PropertyGroup):
|
||||
is_editing: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
is_editing: bpy.props.BoolProperty(
|
||||
name="Is Editing Paramteric Geometry",
|
||||
description="Toggle editing parametric geometry.",
|
||||
default=False,
|
||||
update=update_is_editing,
|
||||
)
|
||||
geometry_source: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
geometry_source: bpy.props.EnumProperty(
|
||||
name="Geometry Source",
|
||||
items=[
|
||||
("GEONODES", "Geometry Nodes", ""),
|
||||
("IFCSVERCHOK", "IFC Sverchok", ""),
|
||||
],
|
||||
)
|
||||
geo_nodes: bpy.props.PointerProperty( # pyright: ignore[reportRedeclaration]
|
||||
geo_nodes: bpy.props.PointerProperty(
|
||||
name="Geometry Nodes",
|
||||
description="Geometry nodes tree to use as a source for representation.",
|
||||
type=bpy.types.GeometryNodeTree,
|
||||
@@ -1750,7 +1750,7 @@ class BIMExternalParametricGeometryProperties(bpy.types.PropertyGroup):
|
||||
poll=lambda self, node_tree: not node_tree.name.startswith("BBIM_EPG"),
|
||||
)
|
||||
|
||||
sverchok_nodes: bpy.props.PointerProperty( # pyright: ignore[reportRedeclaration]
|
||||
sverchok_nodes: bpy.props.PointerProperty(
|
||||
name="Sverchok Nodes",
|
||||
description="Sverchok node tree to use as a source for representation.",
|
||||
type=bpy.types.NodeTree,
|
||||
|
||||
@@ -101,7 +101,7 @@ class NestDecorator:
|
||||
cls.is_installed = False
|
||||
|
||||
def dotted_line_shader(self):
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments]
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||
vert_out.smooth("FLOAT", "v_ArcLength")
|
||||
|
||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||
|
||||
@@ -33,7 +33,7 @@ class EnableEditingPerson(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_person"
|
||||
bl_label = "Enable Editing Person"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
person: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
person: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
person: int
|
||||
@@ -75,7 +75,7 @@ class RemovePerson(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_person"
|
||||
bl_label = "Remove Person"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
person: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
person: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
person: int
|
||||
@@ -88,7 +88,7 @@ class AddPersonAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.add_person_attribute"
|
||||
bl_label = "Add Person Attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name: bpy.props.EnumProperty(
|
||||
items=tuple((i, i, "") for i in get_args(tool.Owner.PersonAttributeType)),
|
||||
)
|
||||
|
||||
@@ -104,10 +104,10 @@ class RemovePersonAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_person_attribute"
|
||||
bl_label = "Remove Person Attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name: bpy.props.EnumProperty(
|
||||
items=tuple((i, i, "") for i in get_args(tool.Owner.PersonAttributeType)),
|
||||
)
|
||||
id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
id: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: tool.Owner.PersonAttributeType # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
@@ -122,7 +122,7 @@ class EnableEditingRole(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_role"
|
||||
bl_label = "Enable Editing Role"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
role: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
role: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
role: int
|
||||
@@ -146,7 +146,7 @@ class AddRole(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_role"
|
||||
bl_label = "Add Role"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
parent: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
parent: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
parent: int
|
||||
@@ -168,7 +168,7 @@ class RemoveRole(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_role"
|
||||
bl_label = "Remove Role"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
role: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
role: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
role: int
|
||||
@@ -181,8 +181,8 @@ class AddAddress(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_address"
|
||||
bl_label = "Add Address"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
parent: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
ifc_class: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
parent: bpy.props.IntProperty()
|
||||
ifc_class: bpy.props.EnumProperty(
|
||||
items=tuple((i, i, "") for i in get_args(ADDRESS_TYPE)),
|
||||
)
|
||||
|
||||
@@ -198,7 +198,7 @@ class AddAddressAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.add_address_attribute"
|
||||
bl_label = "Add Address Attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name: bpy.props.EnumProperty(
|
||||
items=tuple((i, i, "") for i in get_args(tool.Owner.AddressAttributeType)),
|
||||
)
|
||||
|
||||
@@ -214,10 +214,10 @@ class RemoveAddressAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_address_attribute"
|
||||
bl_label = "Remove Address Attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name: bpy.props.EnumProperty(
|
||||
items=tuple((i, i, "") for i in get_args(tool.Owner.AddressAttributeType)),
|
||||
)
|
||||
id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
id: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: tool.Owner.AddressAttributeType # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
@@ -232,7 +232,7 @@ class EnableEditingAddress(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_address"
|
||||
bl_label = "Enable Editing Address"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
address: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
address: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
address: int
|
||||
@@ -265,7 +265,7 @@ class RemoveAddress(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_address"
|
||||
bl_label = "Remove Address"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
address: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
address: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
address: int
|
||||
@@ -278,7 +278,7 @@ class EnableEditingOrganisation(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_organisation"
|
||||
bl_label = "Enable Editing Organisation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
organisation: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
organisation: int
|
||||
@@ -320,7 +320,7 @@ class RemoveOrganisation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_organisation"
|
||||
bl_label = "Remove Organisation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
organisation: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
organisation: int
|
||||
@@ -333,8 +333,8 @@ class AddPersonAndOrganisation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_person_and_organisation"
|
||||
bl_label = "Add Person And Organisation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
person: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
person: bpy.props.IntProperty()
|
||||
organisation: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
person: int
|
||||
@@ -350,7 +350,7 @@ class RemovePersonAndOrganisation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_person_and_organisation"
|
||||
bl_label = "Remove Person And Organisation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
person_and_organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
person_and_organisation: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
person_and_organisation: int
|
||||
@@ -365,7 +365,7 @@ class SetUser(bpy.types.Operator):
|
||||
bl_idname = "bim.set_user"
|
||||
bl_label = "Set User"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
user: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
user: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
user: int
|
||||
@@ -401,7 +401,7 @@ class EnableEditingActor(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_actor"
|
||||
bl_label = "Enable Editing Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
actor: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
actor: int
|
||||
@@ -434,7 +434,7 @@ class RemoveActor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_actor"
|
||||
bl_label = "Remove Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
actor: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
actor: int
|
||||
@@ -447,7 +447,7 @@ class AssignActor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_actor"
|
||||
bl_label = "Assign Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
actor: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
actor: int
|
||||
@@ -462,7 +462,7 @@ class UnassignActor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.unassign_actor"
|
||||
bl_label = "Unassign Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
actor: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
actor: int
|
||||
@@ -481,7 +481,7 @@ class RemoveApplication(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"Remove provided IfcApplication."
|
||||
"\n\nFor safety will only work on applications without inverses (they are typically marked as '(unused)'."
|
||||
)
|
||||
application_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
application_id: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
application_id: int
|
||||
@@ -525,7 +525,7 @@ class EnableEditingApplication(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_application"
|
||||
bl_label = "Enable Editing Application"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
application_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
application_id: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
application_id: int
|
||||
|
||||
@@ -86,9 +86,7 @@ class NewProject(bpy.types.Operator):
|
||||
bl_label = "New Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Start a new IFC project in a fresh session"
|
||||
preset: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
items=[(i, i, "") for i in get_args(PresetType)]
|
||||
)
|
||||
preset: bpy.props.EnumProperty(items=[(i, i, "") for i in get_args(PresetType)])
|
||||
|
||||
if TYPE_CHECKING:
|
||||
preset: PresetType
|
||||
@@ -178,13 +176,9 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
bl_description = (
|
||||
"Select an IFC file that can be used as a library.\n\nALT+click to reload the current loaded library file."
|
||||
)
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
append_all: bpy.props.BoolProperty(default=False) # pyright: ignore[reportRedeclaration]
|
||||
use_relative_path: bpy.props.BoolProperty(
|
||||
name="Use Relative Path", default=False
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
append_all: bpy.props.BoolProperty(default=False)
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
filter_glob: str
|
||||
@@ -568,7 +562,7 @@ class AppendLibraryElementByQuery(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.append_library_element_by_query"
|
||||
bl_label = "Append Library Element By Query"
|
||||
|
||||
query: bpy.props.StringProperty(name="Query") # pyright: ignore[reportRedeclaration]
|
||||
query: bpy.props.StringProperty(name="Query")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
query: str
|
||||
@@ -600,11 +594,9 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"Append element to the current project.\n\n"
|
||||
"ALT+CLICK to skip reusing materials, profiles, styles based on their name (may result in duplicates)"
|
||||
)
|
||||
definition: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
prop_index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
assume_unique_by_name: bpy.props.BoolProperty(
|
||||
name="Assume Unique By Name", default=True, options={"SKIP_SAVE"}
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
definition: bpy.props.IntProperty()
|
||||
prop_index: bpy.props.IntProperty()
|
||||
assume_unique_by_name: bpy.props.BoolProperty(name="Assume Unique By Name", default=True, options={"SKIP_SAVE"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
definition: int
|
||||
@@ -959,28 +951,24 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
bl_label = "Load Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load an existing IFC project"
|
||||
filepath: bpy.props.StringProperty(
|
||||
subtype="FILE_PATH", options={"SKIP_SAVE"}
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"}
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
is_advanced: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"})
|
||||
is_advanced: bpy.props.BoolProperty(
|
||||
name="Enable Advanced Mode",
|
||||
description="Load IFC file with advanced settings. Checking this option will skip loading IFC file and will open advanced load settings",
|
||||
default=False,
|
||||
)
|
||||
use_relative_path: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
use_relative_path: bpy.props.BoolProperty(
|
||||
name="Use Relative Path",
|
||||
description="Store the IFC project path relative to the .blend file. Requires .blend file to be saved",
|
||||
default=False,
|
||||
)
|
||||
should_start_fresh_session: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
should_start_fresh_session: bpy.props.BoolProperty(
|
||||
name="Should Start Fresh Session",
|
||||
description="Clear current Blender session before loading IFC. Not supported with 'Use Relative Path' option",
|
||||
default=True,
|
||||
)
|
||||
import_without_ifc_data: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
import_without_ifc_data: bpy.props.BoolProperty(
|
||||
name="Import Without IFC Data",
|
||||
description=(
|
||||
"Import IFC objects as Blender objects without any IFC metadata and authoring capabilities."
|
||||
@@ -988,9 +976,7 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
use_detailed_tooltip: bpy.props.BoolProperty(
|
||||
default=False, options={"HIDDEN"}
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
use_detailed_tooltip: bpy.props.BoolProperty(default=False, options={"HIDDEN"})
|
||||
filename_ext = ".ifc"
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -1300,7 +1286,7 @@ class ToggleFilterCategories(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_filter_categories"
|
||||
bl_label = "Toggle Filter Categories"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
should_select: bpy.props.BoolProperty(name="Should Select", default=True) # pyright: ignore[reportRedeclaration]
|
||||
should_select: bpy.props.BoolProperty(name="Should Select", default=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
should_select: bool
|
||||
@@ -1327,7 +1313,7 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
default=False,
|
||||
)
|
||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
|
||||
query: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
query: bpy.props.StringProperty(
|
||||
name="Query",
|
||||
description=(
|
||||
"Custom selector query to use to load element from a linked model. E.g. 'IfcElement'.\n\n"
|
||||
@@ -1404,7 +1390,7 @@ class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Remove the selected file from the link list"
|
||||
|
||||
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
@@ -1428,7 +1414,7 @@ class UnloadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Unload the selected linked file"
|
||||
|
||||
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
@@ -1454,9 +1440,9 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load the selected file"
|
||||
|
||||
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True) # pyright: ignore[reportRedeclaration]
|
||||
query: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
|
||||
query: bpy.props.StringProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
@@ -1631,7 +1617,7 @@ class ReloadLink(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Reload the selected file"
|
||||
|
||||
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
@@ -1647,7 +1633,7 @@ class ToggleLinkSelectability(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Toggle selectability"
|
||||
|
||||
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
@@ -1679,8 +1665,8 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Toggle visibility between SOLID and WIREFRAME"
|
||||
|
||||
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||
mode: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
mode: bpy.props.EnumProperty(
|
||||
name="Visibility Mode",
|
||||
items=((i, i, "") for i in ("WIREFRAME", "VISIBLE")),
|
||||
)
|
||||
@@ -1821,7 +1807,7 @@ class SelectLinkHandle(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select link empty object handle"
|
||||
|
||||
link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
@@ -1843,7 +1829,7 @@ class SelectLinkedModelElement(bpy.types.Operator):
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Select an element in the currently selected linked model by providing GlobalId."
|
||||
|
||||
guid: bpy.props.StringProperty(name="GlobalId") # pyright: ignore[reportRedeclaration]
|
||||
guid: bpy.props.StringProperty(name="GlobalId")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
guid: str
|
||||
@@ -1882,21 +1868,11 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".ifc"
|
||||
supported_filexts = (".ifc", ".ifczip", ".ifcjson")
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default=";".join(f"*{ext}" for ext in supported_filexts), options={"HIDDEN"}
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
json_version: bpy.props.EnumProperty(
|
||||
items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version"
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
json_compact: bpy.props.BoolProperty(
|
||||
name="Export Compact IFCJSON", default=False
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
should_save_as: bpy.props.BoolProperty(
|
||||
name="Should Save As", default=False, options={"HIDDEN"}
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
use_relative_path: bpy.props.BoolProperty(
|
||||
name="Use Relative Path", default=False
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
filter_glob: bpy.props.StringProperty(default=";".join(f"*{ext}" for ext in supported_filexts), options={"HIDDEN"})
|
||||
json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version")
|
||||
json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False)
|
||||
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
filter_glob: str
|
||||
@@ -2053,7 +2029,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
bl_description = "Operator is used to load a project .cache.blend to then link it to the IFC file."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
query: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
query: bpy.props.StringProperty()
|
||||
"""See ``bim.link_ifc``."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -2443,8 +2419,8 @@ class HideQueriedLinkedElement(bpy.types.Operator):
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
unhide_all: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
|
||||
hide_all_except: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
|
||||
unhide_all: bpy.props.BoolProperty(options={"SKIP_SAVE"})
|
||||
hide_all_except: bpy.props.BoolProperty(options={"SKIP_SAVE"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
unhide_all: bool
|
||||
@@ -2918,12 +2894,8 @@ class IFCFileHandlerOperator(bpy.types.Operator):
|
||||
bl_label = "Import .ifc file"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
directory: bpy.props.StringProperty(
|
||||
subtype="FILE_PATH", options={"SKIP_SAVE", "HIDDEN"}
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
files: bpy.props.CollectionProperty(
|
||||
type=bpy.types.OperatorFileListElement, options={"SKIP_SAVE", "HIDDEN"}
|
||||
) # pyright: ignore[reportRedeclaration]
|
||||
directory: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE", "HIDDEN"})
|
||||
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={"SKIP_SAVE", "HIDDEN"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
directory: str
|
||||
@@ -2978,7 +2950,7 @@ class MeasureTool(bpy.types.Operator, PolylineOperator):
|
||||
bl_label = "Measure Tool"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
measure_type: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
measure_type: bpy.props.StringProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
measure_type: str
|
||||
@@ -3077,7 +3049,7 @@ class MeasureFaceAreaTool(bpy.types.Operator, PolylineOperator):
|
||||
bl_label = "Measure Face Area Tool"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
measure_type: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
measure_type: bpy.props.StringProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
measure_type: str
|
||||
@@ -3379,7 +3351,7 @@ class LoadBlendMetadataAndIFC(bpy.types.Operator):
|
||||
bl_idname = "bim.load_blend_metadata_and_ifc"
|
||||
bl_label = "Load Blend Metadata and IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(name="IFC File Path", default="") # pyright: ignore[reportRedeclaration]
|
||||
filepath: bpy.props.StringProperty(name="IFC File Path", default="")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
filepath: str
|
||||
|
||||
@@ -345,7 +345,7 @@ class BIMProjectProperties(PropertyGroup):
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
should_cache: BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
should_cache: BoolProperty(
|
||||
name="Cache",
|
||||
description=(
|
||||
"Cache loaded geometry to .h5 file in your cache directory (see in preferences) "
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import bpy
|
||||
@@ -384,6 +385,18 @@ class BIM_PT_new_project_wizard(Panel):
|
||||
row = self.layout.row()
|
||||
row.operator("bim.create_project")
|
||||
|
||||
if shutil.which("git"):
|
||||
git_props = context.scene.IfcGitProperties
|
||||
box = self.layout.box()
|
||||
row = box.row()
|
||||
row.label(text="Clone a remote Git repository")
|
||||
row = box.row()
|
||||
row.prop(git_props, "remote_url")
|
||||
row = box.row()
|
||||
row.prop(git_props, "local_folder")
|
||||
row = box.row()
|
||||
row.operator("ifcgit.clone_repo", icon="IMPORT")
|
||||
|
||||
|
||||
class BIM_PT_project_library(Panel):
|
||||
bl_label = "Project Library"
|
||||
|
||||
@@ -240,7 +240,7 @@ class CopyPropertyToSelection(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Copy Property To Selection"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
name: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
@@ -280,10 +280,10 @@ class BIM_OT_add_property_to_edit(bpy.types.Operator):
|
||||
bl_label = "Add Property to Edit"
|
||||
bl_idname = "bim.add_property_to_edit"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
option: bpy.props.EnumProperty(
|
||||
items=[(t, t, "") for t in tool.Pset.BULK_OPERATION_TYPES],
|
||||
)
|
||||
index: bpy.props.IntProperty(default=-1) # pyright: ignore[reportRedeclaration]
|
||||
index: bpy.props.IntProperty(default=-1)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
option: tool.Pset.BulkOperationType
|
||||
@@ -307,9 +307,9 @@ class BIM_OT_remove_property_to_edit(bpy.types.Operator):
|
||||
bl_label = "Remove Property from Editing"
|
||||
bl_idname = "bim.remove_property_to_edit"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
index2: bpy.props.IntProperty(default=-1) # pyright: ignore[reportRedeclaration]
|
||||
option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
index: bpy.props.IntProperty()
|
||||
index2: bpy.props.IntProperty(default=-1)
|
||||
option: bpy.props.EnumProperty(
|
||||
items=[(t, t, "") for t in tool.Pset.BULK_OPERATION_TYPES],
|
||||
)
|
||||
|
||||
@@ -336,7 +336,7 @@ class BIM_OT_bulk_edit_clear_list(bpy.types.Operator):
|
||||
bl_label = "Clear List of Properties"
|
||||
bl_idname = "bim.pset_bulk_edit_clear_list"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
option: bpy.props.EnumProperty(
|
||||
items=[(t, t, "") for t in tool.Pset.BULK_OPERATION_TYPES],
|
||||
)
|
||||
|
||||
|
||||
@@ -368,9 +368,9 @@ class GlobalPsetProperties(PropertyGroup):
|
||||
qto_filter: StringProperty(name="Qto Filter", options={"TEXTEDIT_UPDATE"})
|
||||
|
||||
# Bulk operations.
|
||||
psets_to_delete: CollectionProperty(type=DeletePsetEntry) # pyright: ignore[reportRedeclaration]
|
||||
psets_to_rename: CollectionProperty(type=RenamePropertyEntry) # pyright: ignore[reportRedeclaration]
|
||||
psets_to_add_edit: CollectionProperty(type=AddEditPropertyEntry) # pyright: ignore[reportRedeclaration]
|
||||
psets_to_delete: CollectionProperty(type=DeletePsetEntry)
|
||||
psets_to_rename: CollectionProperty(type=RenamePropertyEntry)
|
||||
psets_to_add_edit: CollectionProperty(type=AddEditPropertyEntry)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pset_filter: str
|
||||
|
||||
@@ -799,7 +799,7 @@ class SelectQueryElements(Operator):
|
||||
bl_description = "Select elements matching an provided selector query"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
query: StringProperty(name="Query") # pyright: ignore[reportRedeclaration]
|
||||
query: StringProperty(name="Query")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
query: str
|
||||
@@ -829,12 +829,12 @@ class SaveSearch(Operator, tool.Ifc.Operator):
|
||||
# Extra item so it will be easy to select current text.
|
||||
return [text] + SaveSearch.name_search_items
|
||||
|
||||
name: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
name: StringProperty(
|
||||
name="Name",
|
||||
search=get_name_search_items,
|
||||
search_options={"SORT"},
|
||||
)
|
||||
module: StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
module: StringProperty()
|
||||
|
||||
def update_use_all_ifcgroups(self, context: object = None) -> None:
|
||||
ifc_file = tool.Ifc.get()
|
||||
@@ -845,7 +845,7 @@ class SaveSearch(Operator, tool.Ifc.Operator):
|
||||
}
|
||||
self.name_search_items[:] = natsorted(groups)
|
||||
|
||||
use_all_ifcgroups: BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
use_all_ifcgroups: BoolProperty(
|
||||
name="Use Any IfcGroup",
|
||||
description=(
|
||||
"By default we're targeting only IfcGroups with SEARCH ObjectType "
|
||||
|
||||
@@ -106,7 +106,7 @@ class ActivateStatusFilters(bpy.types.Operator):
|
||||
bl_description = "Filter and display objects based on currently selected IFC statuses"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
only_if_enabled: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
only_if_enabled: bpy.props.BoolProperty(
|
||||
name="Only If Filters are Enabled",
|
||||
description="Activate status filters only in case if they were enabled from the UI before.",
|
||||
default=False,
|
||||
@@ -137,7 +137,7 @@ class SelectStatusFilter(bpy.types.Operator):
|
||||
bl_description = "Select elements with currently selected status"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
status: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
status: bpy.props.StringProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
status: tool.Sequence.ElementStatusUI
|
||||
@@ -156,7 +156,7 @@ class AssignStatus(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Assign status to the selected elements.\n\nAlt+CLICK to unassign the status."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
should_override_previous_status: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
should_override_previous_status: bpy.props.BoolProperty(
|
||||
name="Override Previous Status",
|
||||
description=(
|
||||
"Whether assigning new status should override previous one.\n\n"
|
||||
@@ -165,8 +165,8 @@ class AssignStatus(bpy.types.Operator, tool.Ifc.Operator):
|
||||
),
|
||||
default=True,
|
||||
)
|
||||
status: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
should_unassign_status: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
status: bpy.props.StringProperty()
|
||||
should_unassign_status: bpy.props.BoolProperty(
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
@@ -415,7 +415,7 @@ class CopyWorkSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Copy Work Schedule"
|
||||
bl_description = "Create a duplicate of the provided work schedule."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
work_schedule: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
work_schedule: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
work_schedule: int
|
||||
|
||||
@@ -412,7 +412,7 @@ WorkPlanEditingType = Literal["-", "ATTRIBUTES", "SCHEDULES", "WORK_SCHEDULE", "
|
||||
|
||||
class BIMWorkPlanProperties(PropertyGroup):
|
||||
work_plan_attributes: CollectionProperty(name="Work Plan Attributes", type=Attribute)
|
||||
editing_type: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
editing_type: EnumProperty(
|
||||
items=[(i, i, "") for i in get_args(WorkPlanEditingType)],
|
||||
)
|
||||
work_plans: CollectionProperty(name="Work Plans", type=WorkPlan)
|
||||
@@ -430,8 +430,8 @@ class BIMWorkPlanProperties(PropertyGroup):
|
||||
|
||||
|
||||
class IFCStatus(PropertyGroup):
|
||||
name: StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
is_visible: BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
name: StringProperty()
|
||||
is_visible: BoolProperty(
|
||||
name="Is Visible", default=True, update=lambda x, y: (None, bpy.ops.bim.activate_status_filters())[0]
|
||||
)
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Copy to Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
container: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
container: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
container: int
|
||||
|
||||
@@ -167,7 +167,7 @@ class EnableEditingStructuralBoundaryCondition(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_structural_boundary_condition"
|
||||
bl_label = "Enable Editing Structural Boundary Condition"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
boundary_condition: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
boundary_condition: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
boundary_condition: int
|
||||
@@ -186,7 +186,7 @@ class EditStructuralBoundaryCondition(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_structural_boundary_condition"
|
||||
bl_label = "Edit Structural Boundary Condition"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
connection: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
connection: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
connection: int
|
||||
@@ -917,7 +917,7 @@ class EnableEditingBoundaryCondition(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_boundary_condition"
|
||||
bl_label = "Enable Editing Boundary Condition"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
boundary_condition: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
boundary_condition: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
boundary_condition: int
|
||||
|
||||
@@ -83,7 +83,7 @@ class DecorationShader:
|
||||
PARALLEL DISTRIBUTED FORCE,
|
||||
DISTRIBUTED MOMENT,
|
||||
"""
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments]
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||
vert_out.smooth("VEC3", "forces")
|
||||
vert_out.smooth("VEC3", "co")
|
||||
|
||||
@@ -203,7 +203,7 @@ class DecorationShader:
|
||||
"""param: pattern: type of pattern
|
||||
SINGLE FORCE,
|
||||
SINGLE MOMENT"""
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty: ignore[too-many-positional-arguments]
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||
vert_out.smooth("VEC3", "co")
|
||||
|
||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||
@@ -253,7 +253,7 @@ class DecorationShader:
|
||||
|
||||
def get_planar_shader(self) -> gpu.types.GPUShader:
|
||||
"""shader for planar loads"""
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty: ignore[too-many-positional-arguments]
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||
vert_out.smooth("VEC3", "co")
|
||||
|
||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||
|
||||
@@ -54,7 +54,7 @@ class AddSystem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Add System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
parent_system_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
parent_system_id: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
parent_system_id: int
|
||||
|
||||
@@ -26,16 +26,16 @@ from bpy.types import PropertyGroup
|
||||
|
||||
|
||||
class WebProperties(PropertyGroup):
|
||||
webserver_port: IntProperty( # pyright: ignore[reportRedeclaration]
|
||||
webserver_port: IntProperty(
|
||||
name="Webserver Port",
|
||||
min=0,
|
||||
max=65535,
|
||||
)
|
||||
is_running: BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
is_running: BoolProperty(
|
||||
name="Webserver Running Status",
|
||||
default=False,
|
||||
)
|
||||
is_connected: BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
is_connected: BoolProperty(
|
||||
name="Connection Status",
|
||||
default=False,
|
||||
)
|
||||
|
||||
@@ -159,9 +159,9 @@ class SelectURIAttribute(bpy.types.Operator, ImportHelper):
|
||||
bl_label = "Select URI Attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select a local file"
|
||||
attribute_data_path: bpy.props.StringProperty(name="Data Path") # pyright: ignore[reportRedeclaration]
|
||||
attribute_data_path: bpy.props.StringProperty(name="Data Path")
|
||||
"""Full data path to `Attribute`/string property."""
|
||||
use_relative_path: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
use_relative_path: bpy.props.BoolProperty(
|
||||
name="Use Relative Path",
|
||||
default=False,
|
||||
)
|
||||
@@ -601,7 +601,7 @@ class CreateMacBonsaiApp(bpy.types.Operator):
|
||||
"ALT+click to uninstall Bonsai app if it was installed previously."
|
||||
)
|
||||
|
||||
uninstall: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
|
||||
uninstall: bpy.props.BoolProperty(options={"SKIP_SAVE"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
uninstall: bool
|
||||
@@ -1667,7 +1667,7 @@ class BIM_OT_attribute_add_subitem(bpy.types.Operator):
|
||||
bl_description = "Add subitem to the current attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
data_path: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
data_path: bpy.props.StringProperty()
|
||||
"""Full data path."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -1691,9 +1691,9 @@ class BIM_OT_attribute_remove_subitem(bpy.types.Operator):
|
||||
bl_description = "Add subitem to the current attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
data_path: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
|
||||
data_path: bpy.props.StringProperty()
|
||||
"""Full data path."""
|
||||
index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
data_path: str
|
||||
|
||||
@@ -333,7 +333,7 @@ class Attribute(PropertyGroup):
|
||||
filter_glob: StringProperty()
|
||||
is_null: BoolProperty(name="Is Null", update=update_is_null)
|
||||
is_selected: BoolProperty(name="Is Selected", default=False)
|
||||
subitems_values: CollectionProperty(type=StrProperty) # pyright: ignore[reportRedeclaration]
|
||||
subitems_values: CollectionProperty(type=StrProperty)
|
||||
|
||||
# Attribute parameters.
|
||||
is_optional: BoolProperty(name="Is Optional")
|
||||
@@ -342,7 +342,7 @@ class Attribute(PropertyGroup):
|
||||
value_max: FloatProperty(description="This is used to validate int_value and float_value")
|
||||
value_max_constraint: BoolProperty(default=False, description="True if the numerical value has an upper bound")
|
||||
special_type: StringProperty(name="Special Value Type", default="")
|
||||
use_explorer_ui: BoolProperty() # pyright: ignore[reportRedeclaration]
|
||||
use_explorer_ui: BoolProperty()
|
||||
metadata: StringProperty(name="Metadata", description="For storing some additional information about the attribute")
|
||||
update: StringProperty(name="Update", description="Custom update function to be executed")
|
||||
|
||||
|
||||
@@ -665,7 +665,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
name="Disable Undo When Saving (Faster saves, no undo for you!)", default=False
|
||||
)
|
||||
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
|
||||
should_always_cache: BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
should_always_cache: BoolProperty(
|
||||
name="Always Cache Geometry",
|
||||
description="Whether to always cache geometry regardless of 'Cache' setting during Advanced Project Load.",
|
||||
)
|
||||
|
||||
@@ -87,6 +87,10 @@ def delete_remote(ifcgit: type[tool.IfcGit], repo: git.Repo, remote_name: str) -
|
||||
ifcgit.delete_remote(repo, remote_name)
|
||||
|
||||
|
||||
def rename_branch(ifcgit: type[tool.IfcGit], repo: git.Repo, new_name: str) -> None:
|
||||
ifcgit.rename_branch(repo, new_name)
|
||||
|
||||
|
||||
def push(ifcgit: type[tool.IfcGit], repo: git.Repo, remote_name: str, operator: bpy.types.Operator) -> None:
|
||||
error_message = ifcgit.push(repo, remote_name, ifcgit.get_active_branch_name())
|
||||
if error_message:
|
||||
@@ -94,6 +98,7 @@ def push(ifcgit: type[tool.IfcGit], repo: git.Repo, remote_name: str, operator:
|
||||
|
||||
|
||||
def refresh_revision_list(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc]) -> None:
|
||||
ifcgit.clear_merge_conflicts()
|
||||
if ifcgit.repo_has_commits():
|
||||
ifcgit.refresh_revision_list(ifc.get_path())
|
||||
|
||||
@@ -143,19 +148,63 @@ def merge_branch(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], operator: bpy.t
|
||||
operator.report({"ERROR"}, "Unknown IFC Merge failure")
|
||||
return False
|
||||
elif merge_result == "conflict":
|
||||
error = ifcgit.git_mergetool(mergetool)
|
||||
if error:
|
||||
conflicts = ifcgit.git_mergetool(mergetool, path_ifc)
|
||||
if conflicts is not None:
|
||||
ifcgit.git_merge_abort()
|
||||
operator.report({"ERROR"}, "IFC Merge failed:" + error)
|
||||
if conflicts:
|
||||
ifcgit.store_merge_conflicts(conflicts)
|
||||
operator.report({"WARNING"}, "Merge failed — see the conflict report in the panel below")
|
||||
else:
|
||||
operator.report({"ERROR"}, "Merge tool failed — check that ifcmerge is installed correctly")
|
||||
return False
|
||||
ifcgit.commit_merge(path_ifc)
|
||||
|
||||
ifcgit.clear_merge_conflicts()
|
||||
ifcgit.set_display_branch()
|
||||
ifcgit.git_checkout(path_ifc)
|
||||
ifcgit.load_project(path_ifc)
|
||||
ifcgit.refresh_revision_list(path_ifc)
|
||||
ifcgit.decolourise()
|
||||
|
||||
|
||||
def dry_run_merge(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], operator: bpy.types.Operator) -> None:
|
||||
path_ifc = ifc.get_path()
|
||||
ifcgit.config_ifcmerge()
|
||||
|
||||
branch_name = ifcgit.get_selected_branch()
|
||||
if branch_name is None:
|
||||
return
|
||||
|
||||
mergetool = ifcgit.get_merge_tool(branch_name)
|
||||
merge_result = ifcgit.git_merge_no_commit(branch_name)
|
||||
|
||||
if merge_result == "error":
|
||||
try:
|
||||
ifcgit.git_merge_abort()
|
||||
except Exception:
|
||||
pass
|
||||
operator.report({"ERROR"}, "Unknown IFC Merge failure")
|
||||
return
|
||||
|
||||
if merge_result == "conflict":
|
||||
conflicts = ifcgit.git_mergetool(mergetool, path_ifc)
|
||||
ifcgit.git_merge_abort()
|
||||
if conflicts is not None:
|
||||
ifcgit.store_merge_conflicts(conflicts)
|
||||
operator.report({"WARNING"}, "Merge preview: conflicts found — see the panel below")
|
||||
else:
|
||||
ifcgit.clear_merge_conflicts()
|
||||
operator.report({"INFO"}, "Merge preview: no conflicts")
|
||||
else:
|
||||
# Clean merge or already up to date — abort the pending merge state if any
|
||||
try:
|
||||
ifcgit.git_merge_abort()
|
||||
except Exception:
|
||||
pass
|
||||
ifcgit.clear_merge_conflicts()
|
||||
operator.report({"INFO"}, "Merge preview: no conflicts")
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@@ -559,7 +559,11 @@ class IfcGit:
|
||||
def get_selected_branch(cls): pass
|
||||
def git_merge(cls, branch_name): pass
|
||||
def git_merge_abort(cls): pass
|
||||
def git_mergetool(cls, mergetool): pass
|
||||
def git_merge_no_commit(cls, branch_name): pass
|
||||
def git_mergetool(cls, mergetool, path_ifc): pass
|
||||
def store_merge_conflicts(cls, conflicts): pass
|
||||
def clear_merge_conflicts(cls): pass
|
||||
def get_merge_conflicts(cls): pass
|
||||
def set_display_branch(cls): pass
|
||||
def get_active_branch_name(cls): pass
|
||||
def get_ifcgit_props(cls): pass
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@@ -155,6 +156,10 @@ class IfcGit:
|
||||
if tag_name in repo.tags:
|
||||
repo.delete_tag(tag_name)
|
||||
|
||||
@classmethod
|
||||
def rename_branch(cls, repo: git.Repo, new_name: str) -> None:
|
||||
repo.active_branch.rename(new_name)
|
||||
|
||||
@classmethod
|
||||
def add_remote(cls, repo: git.Repo, remote_name: str, remote_url: str) -> None:
|
||||
repo.create_remote(name=remote_name, url=remote_url)
|
||||
@@ -212,7 +217,7 @@ class IfcGit:
|
||||
rev=[props.display_branch],
|
||||
)
|
||||
)
|
||||
commits_relevant = list(
|
||||
commits_relevant = set(
|
||||
git.objects.commit.Commit.iter_items(
|
||||
repo=repo,
|
||||
rev=[props.display_branch],
|
||||
@@ -220,11 +225,17 @@ class IfcGit:
|
||||
)
|
||||
)
|
||||
|
||||
def is_relevant(commit):
|
||||
if commit in commits_relevant:
|
||||
return True
|
||||
# Merge commits are relevant too
|
||||
return len(commit.parents) > 1 and any(p in commits_relevant for p in commit.parents)
|
||||
|
||||
for commit in commits:
|
||||
|
||||
if props.ifcgit_filter == "tagged" and commit.hexsha not in lookup:
|
||||
continue
|
||||
elif props.ifcgit_filter == "relevant" and commit not in commits_relevant:
|
||||
elif props.ifcgit_filter == "relevant" and not is_relevant(commit):
|
||||
continue
|
||||
|
||||
props.ifcgit_commits.add()
|
||||
@@ -234,7 +245,7 @@ class IfcGit:
|
||||
list_item.author_name = commit.author.name
|
||||
list_item.author_email = commit.author.email
|
||||
list_item.committed_date = int(commit.committed_date)
|
||||
if commit in commits_relevant:
|
||||
if is_relevant(commit):
|
||||
list_item.relevant = True
|
||||
if commit.hexsha in lookup:
|
||||
for tag in lookup[commit.hexsha]:
|
||||
@@ -275,20 +286,25 @@ class IfcGit:
|
||||
if re.match("^Ifc", obj.name):
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
|
||||
bpy.data.orphans_purge(do_recursive=True) # ty:ignore[unknown-argument]
|
||||
bpy.data.orphans_purge(do_recursive=True)
|
||||
|
||||
import bonsai.bim.handler
|
||||
from bonsai.bim.module.model.data import AuthoringData
|
||||
from bonsai.bim.module.root.data import IfcClassData
|
||||
|
||||
AuthoringData.type_thumbnails = {}
|
||||
|
||||
IfcClassData.is_loaded = False
|
||||
|
||||
settings = import_ifc.IfcImportSettings.factory(bpy.context, path_ifc, logging.getLogger("ImportIFC"))
|
||||
settings.should_setup_viewport_camera = False
|
||||
ifc_importer = import_ifc.IfcImporter(settings)
|
||||
ifc_importer.execute()
|
||||
tool.Project.load_project_pset_templates()
|
||||
tool.Project.load_default_thumbnails()
|
||||
tool.Project.set_default_context()
|
||||
tool.Project.set_default_modeling_dimensions()
|
||||
tool.Root.reload_grid_decorator()
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
|
||||
@classmethod
|
||||
@@ -388,20 +404,43 @@ class IfcGit:
|
||||
model = tool.Ifc.get()
|
||||
modified_step_ids = {"modified": set()}
|
||||
|
||||
for step_id in step_ids["modified"] | step_ids["added"]:
|
||||
try:
|
||||
entity = model.by_id(step_id)
|
||||
except:
|
||||
continue
|
||||
if entity.is_a("IfcProductDefinitionShape"):
|
||||
def collect(entity, depth=0):
|
||||
if depth > 2:
|
||||
return
|
||||
if entity.is_a("IfcProduct"):
|
||||
modified_step_ids["modified"].add(entity.id())
|
||||
elif entity.is_a("IfcProductDefinitionShape"):
|
||||
for product in entity.ShapeOfProduct:
|
||||
modified_step_ids["modified"].add(product.id())
|
||||
elif entity.is_a("IfcObjectPlacement"):
|
||||
for product in entity.PlacesObject:
|
||||
modified_step_ids["modified"].add(product.id())
|
||||
elif entity.is_a("IfcTypeProduct") and entity.Types:
|
||||
for related_object in entity.Types[0].RelatedObjects:
|
||||
modified_step_ids["modified"].add(related_object.id())
|
||||
elif entity.is_a("IfcTypeProduct"):
|
||||
for rel in entity.Types:
|
||||
for obj in rel.RelatedObjects:
|
||||
modified_step_ids["modified"].add(obj.id())
|
||||
elif entity.is_a("IfcShapeRepresentation"):
|
||||
for prod_rep in entity.OfProductRepresentation:
|
||||
for product in prod_rep.ShapeOfProduct:
|
||||
modified_step_ids["modified"].add(product.id())
|
||||
elif entity.is_a("IfcRepresentationItem"):
|
||||
for referencing in model.get_inverse(entity):
|
||||
if referencing.is_a("IfcShapeRepresentation"):
|
||||
collect(referencing, depth + 1)
|
||||
elif entity.is_a("IfcPropertySet"):
|
||||
for rel in entity.DefinesOccurrence:
|
||||
for obj in rel.RelatedObjects:
|
||||
modified_step_ids["modified"].add(obj.id())
|
||||
elif entity.is_a("IfcProperty"):
|
||||
for pset in entity.PartOfPset:
|
||||
collect(pset, depth + 1)
|
||||
|
||||
for step_id in step_ids["modified"] | step_ids["added"]:
|
||||
try:
|
||||
entity = model.by_id(step_id)
|
||||
except:
|
||||
continue
|
||||
collect(entity)
|
||||
|
||||
return modified_step_ids
|
||||
|
||||
@@ -453,11 +492,28 @@ class IfcGit:
|
||||
if item.hexsha in lookup:
|
||||
for branch in lookup[item.hexsha]:
|
||||
if branch.name == props.display_branch:
|
||||
if isinstance(branch, git.RemoteReference):
|
||||
# Checking out a remote branch tip goes to detached HEAD.
|
||||
# Pre-fill the new branch name field with the local equivalent
|
||||
# so the user isn't blocked from committing without a hint.
|
||||
local_name = branch.remote_head
|
||||
props.new_branch_name = cls._unique_branch_name(repo, local_name)
|
||||
branch.checkout()
|
||||
return
|
||||
# NOTE this is calling the git binary in a subprocess
|
||||
repo.git.checkout(item.hexsha)
|
||||
|
||||
@classmethod
|
||||
def _unique_branch_name(cls, repo: git.Repo, name: str) -> str:
|
||||
"""Return name if unused, otherwise name-2, name-3, etc."""
|
||||
existing = {h.name for h in repo.heads}
|
||||
if name not in existing:
|
||||
return name
|
||||
i = 2
|
||||
while f"{name}-{i}" in existing:
|
||||
i += 1
|
||||
return f"{name}-{i}"
|
||||
|
||||
@classmethod
|
||||
def delete_collection(cls, blender_collection: bpy.types.Collection) -> None:
|
||||
for obj in blender_collection.objects:
|
||||
@@ -468,14 +524,24 @@ class IfcGit:
|
||||
def config_ifcmerge(cls) -> None:
|
||||
config_reader = IfcGitRepo.repo.config_reader()
|
||||
section = 'mergetool "ifcmerge"'
|
||||
new_cmd = "ifcmerge $BASE $LOCAL $REMOTE $MERGED > $MERGED.ifcmerge"
|
||||
if not config_reader.has_section(section):
|
||||
with IfcGitRepo.repo.config_writer() as config_writer:
|
||||
config_writer.set_value(section, "cmd", "ifcmerge $BASE $LOCAL $REMOTE $MERGED")
|
||||
config_writer.set_value(section, "cmd", new_cmd)
|
||||
config_writer.set_value(section, "trustExitCode", True)
|
||||
elif config_reader.get_value(section, "cmd") != new_cmd:
|
||||
with IfcGitRepo.repo.config_writer() as config_writer:
|
||||
config_writer.set_value(section, "cmd", new_cmd)
|
||||
config_writer.set_value(section, "trustExitCode", True)
|
||||
section = 'mergetool "ifcmerge-forward"'
|
||||
new_cmd = "ifcmerge --prioritise-local $BASE $LOCAL $REMOTE $MERGED > $MERGED.ifcmerge"
|
||||
if not config_reader.has_section(section):
|
||||
with IfcGitRepo.repo.config_writer() as config_writer:
|
||||
config_writer.set_value(section, "cmd", "ifcmerge $BASE $REMOTE $LOCAL $MERGED")
|
||||
config_writer.set_value(section, "cmd", new_cmd)
|
||||
config_writer.set_value(section, "trustExitCode", True)
|
||||
elif config_reader.get_value(section, "cmd") != new_cmd:
|
||||
with IfcGitRepo.repo.config_writer() as config_writer:
|
||||
config_writer.set_value(section, "cmd", new_cmd)
|
||||
config_writer.set_value(section, "trustExitCode", True)
|
||||
|
||||
@classmethod
|
||||
@@ -529,7 +595,7 @@ class IfcGit:
|
||||
"""Attempt a git merge. Returns None on clean merge, 'conflict' on expected
|
||||
GitCommandError, or 'error' on an unknown GitError."""
|
||||
repo = IfcGitRepo.repo
|
||||
branch = repo.branches[branch_name]
|
||||
branch = repo.refs[branch_name]
|
||||
try:
|
||||
repo.git.merge(branch)
|
||||
return None
|
||||
@@ -539,14 +605,66 @@ class IfcGit:
|
||||
return "error"
|
||||
|
||||
@classmethod
|
||||
def git_mergetool(cls, mergetool: str) -> Union[str, None]:
|
||||
"""Run ifcmerge tool. Returns None on success, error message string on failure."""
|
||||
def git_merge_no_commit(cls, branch_name: str) -> Union[str, None]:
|
||||
"""Attempt a git merge without committing (always leaves a merge state to abort).
|
||||
Returns None on clean merge, 'conflict' on conflict, or 'error' on unknown failure."""
|
||||
repo = IfcGitRepo.repo
|
||||
branch = repo.refs[branch_name]
|
||||
try:
|
||||
repo.git.merge(branch, no_commit=True, no_ff=True)
|
||||
return None
|
||||
except git.exc.GitCommandError:
|
||||
return "conflict"
|
||||
except git.exc.GitError:
|
||||
return "error"
|
||||
|
||||
@classmethod
|
||||
def git_mergetool(cls, mergetool: str, path_ifc: str) -> Union[list, None]:
|
||||
"""Run ifcmerge tool. Returns None on success, list of conflict dicts on failure."""
|
||||
repo = IfcGitRepo.repo
|
||||
report_path = path_ifc + ".ifcmerge"
|
||||
try:
|
||||
repo.git.mergetool(tool=mergetool)
|
||||
except git.exc.GitCommandError as e:
|
||||
print(f"ifcgit: mergetool failed: {e}")
|
||||
|
||||
conflicts = None
|
||||
if os.path.exists(report_path):
|
||||
try:
|
||||
with open(report_path) as f:
|
||||
content = f.read().strip()
|
||||
if content:
|
||||
data = json.loads(content)
|
||||
conflicts = data.get("conflicts", [])
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pass
|
||||
try:
|
||||
os.remove(report_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if conflicts is None and repo.index.unmerged_blobs():
|
||||
conflicts = []
|
||||
|
||||
return conflicts
|
||||
|
||||
@classmethod
|
||||
def store_merge_conflicts(cls, conflicts: list) -> None:
|
||||
cls.get_ifcgit_props().merge_conflicts = json.dumps(conflicts)
|
||||
|
||||
@classmethod
|
||||
def clear_merge_conflicts(cls) -> None:
|
||||
cls.get_ifcgit_props().merge_conflicts = ""
|
||||
|
||||
@classmethod
|
||||
def get_merge_conflicts(cls) -> Union[list, None]:
|
||||
raw = cls.get_ifcgit_props().merge_conflicts
|
||||
if not raw:
|
||||
return None
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
except git.exc.GitCommandError as exc:
|
||||
return re.sub("( stdout: '|')", "", exc.stdout)
|
||||
|
||||
@classmethod
|
||||
def git_merge_abort(cls) -> None:
|
||||
|
||||
@@ -25,13 +25,12 @@ import bmesh
|
||||
import bpy
|
||||
import mathutils
|
||||
import numpy as np
|
||||
from mathutils import Vector
|
||||
|
||||
from bpy_extras import view3d_utils
|
||||
from mathutils import Vector
|
||||
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
|
||||
class Raycast(bonsai.core.tool.Raycast):
|
||||
offset = 10
|
||||
@@ -434,11 +433,8 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
seg_len_sq = sx * sx + sy * sy
|
||||
|
||||
if seg_len_sq == 0.0:
|
||||
# degenerate segment: return distance to p0
|
||||
dx = px - p0x
|
||||
dy = py - p0y
|
||||
dist = math.hypot(dx, dy)
|
||||
return dist, (p0x, p0y), 0.0
|
||||
# degenerate segment: skip it
|
||||
continue
|
||||
|
||||
# project (p - p0) onto seg: t = dot(p-p0, seg) / |seg|^2
|
||||
apx = px - p0x
|
||||
@@ -892,8 +888,19 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
def create_snap_obj(cls, obj):
|
||||
if obj.data is None or not isinstance(obj.data, bpy.types.Mesh):
|
||||
return None
|
||||
for snap_obj in cls.snap_objs:
|
||||
for i, snap_obj in enumerate(cls.snap_objs):
|
||||
if obj.name == snap_obj.obj.name:
|
||||
# Handle objects modified while a modal operator is active.
|
||||
# Example: adding a door or window alters the wall geometry.
|
||||
if len(obj.data.vertices) != len(snap_obj.verts_3d):
|
||||
cls.snap_objs.pop(i)
|
||||
snap_obj = SnapObj(obj)
|
||||
cls.snap_objs.append(snap_obj)
|
||||
for v1, v2 in zip(obj.data.vertices, snap_obj.verts_3d):
|
||||
if (obj.matrix_world @ v1.co) != v2:
|
||||
cls.snap_objs.pop(i)
|
||||
snap_obj = SnapObj(obj)
|
||||
cls.snap_objs.append(snap_obj)
|
||||
return snap_obj
|
||||
snap_obj = SnapObj(obj)
|
||||
cls.snap_objs.append(snap_obj)
|
||||
|
||||
@@ -7,7 +7,7 @@ Python code formatters
|
||||
For Python code formatting, we use `Black code formatter <https://pypi.org/project/black/>`__,
|
||||
black settings are stored in the repository's pyproject.toml.
|
||||
|
||||
We have GitHub workflow `ci-black-formatting` to maintain black formatting across the repository.
|
||||
We have GitHub workflow `ci-lint` to maintain black formatting across the repository.
|
||||
|
||||
``black`` can be installed using ``pip install black`` and files can be formatted with the following example command:
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ When adding or removing a supported Python version, update the following:
|
||||
|
||||
* - File
|
||||
- What to update
|
||||
* - ``.github/workflows/ci-black-formatting.yaml``
|
||||
* - ``.github/workflows/ci-lint.yaml``
|
||||
- ``MIN_IOS_PY_VERSION``
|
||||
* - ``.github/workflows/ci-ifcopenshell-python-pypi.yml``
|
||||
- ``pyver`` matrix
|
||||
@@ -44,6 +44,8 @@ When a new Blender version is released and supported:
|
||||
|
||||
* - File
|
||||
- What to update
|
||||
* - ``.github/workflows/ci-bonsai.yml``
|
||||
- ``pyver`` matrix
|
||||
* - ``.github/workflows/ci-bonsai-daily.yml``
|
||||
- Blender download URL
|
||||
|
||||
@@ -57,9 +59,47 @@ When Blender ships with a new Python version:
|
||||
|
||||
* - File
|
||||
- What to update
|
||||
* - ``.github/workflows/ci-black-formatting.yaml``
|
||||
* - ``.github/workflows/ci-lint.yaml``
|
||||
- ``MIN_BLENDER_PY_VERSION``
|
||||
* - ``src/bonsai/Makefile``
|
||||
- ``SUPPORTED_PYVERSIONS``
|
||||
* - ``src/bonsai/scripts/dev_environment.py``
|
||||
- ``PYTHON_VERSION`` mapping (Blender version, bundled Python version)
|
||||
|
||||
Release
|
||||
-------
|
||||
|
||||
Notes:
|
||||
|
||||
- Typically all packages are released at once using the same version schema
|
||||
- The ``README.md`` badges can serve as a visual reference for what versions have been released
|
||||
|
||||
Things to update:
|
||||
|
||||
- ``.github/workflows/ci-bcf-pypi.yml`` - release `bcf-client <https://pypi.org/project/bcf-client/>`_ to PyPI
|
||||
- ``.github/workflows/ci-bonsai.yml`` - release bonsai in GitHub releases
|
||||
- ``.github/workflows/ci-bsdd-pypi.yaml`` - release `bsdd <https://pypi.org/project/bsdd/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifc4d-pypi.yaml`` - release `ifc4d <https://pypi.org/project/ifc4d/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifc5d-pypi.yaml`` - release `ifc5d <https://pypi.org/project/ifc5d/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcclash-pypi.yaml`` - release `ifcclash <https://pypi.org/project/ifcclash/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcconvert.yml`` - release ifcconvert binaries in GitHub releases
|
||||
- ``.github/workflows/ci-ifccsv-pypi.yaml`` - release `ifccsv <https://pypi.org/project/ifccsv/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcdiff-pypi.yaml`` - release `ifcdiff <https://pypi.org/project/ifcdiff/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcedit-pypi.yaml`` - release `ifcedit <https://pypi.org/project/ifcedit/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcfm-pypi.yaml`` - release `ifcfm <https://pypi.org/project/ifcfm/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifccityjson-pypi.yaml`` - release `ifccityjson <https://pypi.org/project/ifccityjson/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcmcp-pypi.yaml`` - release `ifcopenshell-mcp <https://pypi.org/project/ifcopenshell-mcp/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcopenshell-python.yml`` - release ifcopenshell-python binaries in GitHub releases
|
||||
- ``.github/workflows/ci-ifcopenshell-python-pypi.yml`` - release `ifcopenshell <https://pypi.org/project/ifcopenshell/>`_ wheels to PyPI
|
||||
- ``.github/workflows/ci-ifcpatch-pypi.yaml`` - release `ifcpatch <https://pypi.org/project/ifcpatch/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcquery-pypi.yaml`` - release `ifcquery <https://pypi.org/project/ifcquery/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcsverchok.yml`` - release ifcsverchok Blender add-on in GitHub releases
|
||||
- ``.github/workflows/ci-ifctester-pypi.yml`` - release `ifctester <https://pypi.org/project/ifctester/>`_ to PyPI
|
||||
- ``.github/workflows/ci-pyodide-wasm-release.yml`` - release pyodide wasm wheel to `wasm-wheels <https://github.com/IfcOpenShell/wasm-wheels>`_
|
||||
- Release Bonsai Blender extension - zip files from ci-bonsai.yml releases should be uploaded manually to `Blender extensions platform <https://extensions.blender.org/add-ons/bonsai/>`_
|
||||
- Publishing documentation and websites (see `website <https://github.com/IfcOpenShell/website>`_ repository):
|
||||
|
||||
- `ifcopenshell-docs.yml` - builds and publishes IfcOpenShell documentation to `docs.ifcopenshell.org <https://docs.ifcopenshell.org>`_ (`ifcopenshell_org_docs <https://github.com/IfcOpenShell/ifcopenshell_org_docs>`_ repo)
|
||||
- `bonsai-docs.yml` - builds and publishes Bonsai documentation to `docs.bonsaibim.org <https://docs.bonsaibim.org>`_ (`bonsaibim_org_docs <https://github.com/IfcOpenShell/bonsaibim_org_docs>`_ repo)
|
||||
- `main.yml` - publishes `bonsaibim.org <https://bonsaibim.org>`_ (`bonsaibim_org_static_html <https://github.com/IfcOpenShell/bonsaibim_org_static_html>`_ repo) and `ifcopenshell.org <https://ifcopenshell.org>`_ (`ifcopenshell_org_static_html <https://github.com/IfcOpenShell/ifcopenshell_org_static_html>`_ repo)
|
||||
- ``VERSION`` to the release version - **UPDATE THIS LAST** as all workflows above typically depend on it to set the version correctly
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import pytest
|
||||
|
||||
import bonsai.core.ifcgit as subject
|
||||
from test.core.bootstrap import ifcgit, ifc
|
||||
from test.core.bootstrap import ifc, ifcgit
|
||||
|
||||
|
||||
class MockOperator:
|
||||
@@ -132,12 +132,14 @@ class TestPush:
|
||||
|
||||
class TestRefreshRevisionList:
|
||||
def test_refreshes_when_repo_has_heads(self, ifcgit, ifc):
|
||||
ifcgit.clear_merge_conflicts().should_be_called()
|
||||
ifcgit.repo_has_commits().should_be_called().will_return(True)
|
||||
ifc.get_path().should_be_called().will_return("path/to/model.ifc")
|
||||
ifcgit.refresh_revision_list("path/to/model.ifc").should_be_called()
|
||||
subject.refresh_revision_list(ifcgit, ifc)
|
||||
|
||||
def test_skips_when_repo_has_no_heads(self, ifcgit, ifc):
|
||||
ifcgit.clear_merge_conflicts().should_be_called()
|
||||
ifcgit.repo_has_commits().should_be_called().will_return(False)
|
||||
subject.refresh_revision_list(ifcgit, ifc)
|
||||
# nothing else should be called — Prophecy will verify
|
||||
@@ -194,7 +196,9 @@ class TestMergeBranch:
|
||||
ifcgit.get_selected_branch().should_be_called().will_return("feature")
|
||||
ifcgit.get_merge_tool("feature").should_be_called().will_return("ifcmerge-forward")
|
||||
ifcgit.git_merge("feature").should_be_called().will_return(None)
|
||||
ifcgit.clear_merge_conflicts().should_be_called()
|
||||
ifcgit.set_display_branch().should_be_called()
|
||||
ifcgit.git_checkout("path/to/model.ifc").should_be_called()
|
||||
ifcgit.load_project("path/to/model.ifc").should_be_called()
|
||||
ifcgit.refresh_revision_list("path/to/model.ifc").should_be_called()
|
||||
ifcgit.decolourise().should_be_called()
|
||||
@@ -206,25 +210,29 @@ class TestMergeBranch:
|
||||
ifcgit.get_selected_branch().should_be_called().will_return("feature")
|
||||
ifcgit.get_merge_tool("feature").should_be_called().will_return("ifcmerge-forward")
|
||||
ifcgit.git_merge("feature").should_be_called().will_return("conflict")
|
||||
ifcgit.git_mergetool("ifcmerge-forward").should_be_called().will_return(None)
|
||||
ifcgit.git_mergetool("ifcmerge-forward", "path/to/model.ifc").should_be_called().will_return(None)
|
||||
ifcgit.commit_merge("path/to/model.ifc").should_be_called()
|
||||
ifcgit.clear_merge_conflicts().should_be_called()
|
||||
ifcgit.set_display_branch().should_be_called()
|
||||
ifcgit.git_checkout("path/to/model.ifc").should_be_called()
|
||||
ifcgit.load_project("path/to/model.ifc").should_be_called()
|
||||
ifcgit.refresh_revision_list("path/to/model.ifc").should_be_called()
|
||||
ifcgit.decolourise().should_be_called()
|
||||
subject.merge_branch(ifcgit, ifc, operator=None)
|
||||
|
||||
def test_conflict_mergetool_failure(self, ifcgit, ifc):
|
||||
conflicts = [{"type": "attribute_conflict", "entity_id": 42}]
|
||||
ifc.get_path().should_be_called().will_return("path/to/model.ifc")
|
||||
ifcgit.config_ifcmerge().should_be_called()
|
||||
ifcgit.get_selected_branch().should_be_called().will_return("feature")
|
||||
ifcgit.get_merge_tool("feature").should_be_called().will_return("ifcmerge-forward")
|
||||
ifcgit.git_merge("feature").should_be_called().will_return("conflict")
|
||||
ifcgit.git_mergetool("ifcmerge-forward").should_be_called().will_return("merge error")
|
||||
ifcgit.git_mergetool("ifcmerge-forward", "path/to/model.ifc").should_be_called().will_return(conflicts)
|
||||
ifcgit.git_merge_abort().should_be_called()
|
||||
ifcgit.store_merge_conflicts(conflicts).should_be_called()
|
||||
op = MockOperator()
|
||||
subject.merge_branch(ifcgit, ifc, op)
|
||||
assert op.reports == [({"ERROR"}, "IFC Merge failed:merge error")]
|
||||
assert op.reports == [({"WARNING"}, "Merge failed — see the conflict report in the panel below")]
|
||||
|
||||
def test_unknown_merge_error(self, ifcgit, ifc):
|
||||
ifc.get_path().should_be_called().will_return("path/to/model.ifc")
|
||||
@@ -237,6 +245,53 @@ class TestMergeBranch:
|
||||
assert op.reports == [({"ERROR"}, "Unknown IFC Merge failure")]
|
||||
|
||||
|
||||
class TestDryRunMerge:
|
||||
def test_no_branch_at_selected_commit(self, ifcgit, ifc):
|
||||
ifc.get_path().should_be_called().will_return("path/to/model.ifc")
|
||||
ifcgit.config_ifcmerge().should_be_called()
|
||||
ifcgit.get_selected_branch().should_be_called().will_return(None)
|
||||
subject.dry_run_merge(ifcgit, ifc, operator=None)
|
||||
|
||||
def test_clean_merge_preview(self, ifcgit, ifc):
|
||||
ifc.get_path().should_be_called().will_return("path/to/model.ifc")
|
||||
ifcgit.config_ifcmerge().should_be_called()
|
||||
ifcgit.get_selected_branch().should_be_called().will_return("feature")
|
||||
ifcgit.get_merge_tool("feature").should_be_called().will_return("ifcmerge-forward")
|
||||
ifcgit.git_merge_no_commit("feature").should_be_called().will_return(None)
|
||||
ifcgit.git_merge_abort().should_be_called()
|
||||
ifcgit.clear_merge_conflicts().should_be_called()
|
||||
op = MockOperator()
|
||||
subject.dry_run_merge(ifcgit, ifc, op)
|
||||
assert op.reports == [({"INFO"}, "Merge preview: no conflicts")]
|
||||
|
||||
def test_conflict_preview_shows_report(self, ifcgit, ifc):
|
||||
conflicts = [{"type": "attribute_conflict", "entity_id": 42}]
|
||||
ifc.get_path().should_be_called().will_return("path/to/model.ifc")
|
||||
ifcgit.config_ifcmerge().should_be_called()
|
||||
ifcgit.get_selected_branch().should_be_called().will_return("feature")
|
||||
ifcgit.get_merge_tool("feature").should_be_called().will_return("ifcmerge-forward")
|
||||
ifcgit.git_merge_no_commit("feature").should_be_called().will_return("conflict")
|
||||
ifcgit.git_mergetool("ifcmerge-forward", "path/to/model.ifc").should_be_called().will_return(conflicts)
|
||||
ifcgit.git_merge_abort().should_be_called()
|
||||
ifcgit.store_merge_conflicts(conflicts).should_be_called()
|
||||
op = MockOperator()
|
||||
subject.dry_run_merge(ifcgit, ifc, op)
|
||||
assert op.reports == [({"WARNING"}, "Merge preview: conflicts found — see the panel below")]
|
||||
|
||||
def test_conflict_preview_mergetool_succeeds(self, ifcgit, ifc):
|
||||
ifc.get_path().should_be_called().will_return("path/to/model.ifc")
|
||||
ifcgit.config_ifcmerge().should_be_called()
|
||||
ifcgit.get_selected_branch().should_be_called().will_return("feature")
|
||||
ifcgit.get_merge_tool("feature").should_be_called().will_return("ifcmerge-forward")
|
||||
ifcgit.git_merge_no_commit("feature").should_be_called().will_return("conflict")
|
||||
ifcgit.git_mergetool("ifcmerge-forward", "path/to/model.ifc").should_be_called().will_return(None)
|
||||
ifcgit.git_merge_abort().should_be_called()
|
||||
ifcgit.clear_merge_conflicts().should_be_called()
|
||||
op = MockOperator()
|
||||
subject.dry_run_merge(ifcgit, ifc, op)
|
||||
assert op.reports == [({"INFO"}, "Merge preview: no conflicts")]
|
||||
|
||||
|
||||
class TestEntityLog:
|
||||
def test_run(self, ifcgit, ifc):
|
||||
ifc.get_path().should_be_called().will_return("path/to/model.ifc")
|
||||
|
||||
@@ -454,3 +454,128 @@ class TestIfcDiffIds(NewFile):
|
||||
result = IfcGit.ifc_diff_ids(repo, sha_a, sha_b, ifc_path)
|
||||
assert 1 in result["modified"]
|
||||
assert 2 in result["modified"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Merge conflict report — store / clear / get
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestStoreClearGetMergeConflicts(NewFile):
|
||||
def test_round_trip(self):
|
||||
conflicts = [{"type": "attribute_conflict", "entity_id": 42}]
|
||||
IfcGit.store_merge_conflicts(conflicts)
|
||||
result = IfcGit.get_merge_conflicts()
|
||||
assert result == conflicts
|
||||
|
||||
def test_get_returns_none_when_empty(self):
|
||||
IfcGit.clear_merge_conflicts()
|
||||
assert IfcGit.get_merge_conflicts() is None
|
||||
|
||||
def test_clear_removes_stored_conflicts(self):
|
||||
IfcGit.store_merge_conflicts([{"type": "class_changed"}])
|
||||
IfcGit.clear_merge_conflicts()
|
||||
assert IfcGit.get_merge_conflicts() is None
|
||||
|
||||
def test_get_returns_none_on_corrupt_json(self):
|
||||
import bpy
|
||||
|
||||
bpy.context.scene.IfcGitProperties.merge_conflicts = "not valid json {"
|
||||
assert IfcGit.get_merge_conflicts() is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# git_mergetool — report file reading
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestGitMergetool:
|
||||
@requires_git
|
||||
def test_returns_none_when_report_file_absent(self):
|
||||
import unittest.mock as mock
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
ifc_path = os.path.join(tmpdir, "model.ifc")
|
||||
mock_repo = mock.MagicMock()
|
||||
IfcGitRepo.repo = mock_repo
|
||||
result = IfcGit.git_mergetool("ifcmerge", ifc_path)
|
||||
assert result is None
|
||||
IfcGitRepo.repo = None
|
||||
|
||||
@requires_git
|
||||
def test_returns_none_when_report_file_empty(self):
|
||||
import unittest.mock as mock
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
ifc_path = os.path.join(tmpdir, "model.ifc")
|
||||
report_path = ifc_path + ".ifcmerge"
|
||||
open(report_path, "w").close()
|
||||
mock_repo = mock.MagicMock()
|
||||
IfcGitRepo.repo = mock_repo
|
||||
result = IfcGit.git_mergetool("ifcmerge", ifc_path)
|
||||
assert result is None
|
||||
assert not os.path.exists(report_path)
|
||||
IfcGitRepo.repo = None
|
||||
|
||||
@requires_git
|
||||
def test_parses_conflict_report_and_deletes_file(self):
|
||||
import json
|
||||
import unittest.mock as mock
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
ifc_path = os.path.join(tmpdir, "model.ifc")
|
||||
report_path = ifc_path + ".ifcmerge"
|
||||
conflicts = [{"type": "attribute_conflict", "entity_id": 5}]
|
||||
with open(report_path, "w") as f:
|
||||
json.dump({"status": "failed", "conflicts": conflicts}, f)
|
||||
mock_repo = mock.MagicMock()
|
||||
mock_repo.git.mergetool.side_effect = git.exc.GitCommandError("mergetool", 1)
|
||||
IfcGitRepo.repo = mock_repo
|
||||
result = IfcGit.git_mergetool("ifcmerge", ifc_path)
|
||||
assert result == conflicts
|
||||
assert not os.path.exists(report_path)
|
||||
IfcGitRepo.repo = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# config_ifcmerge — cmd format and update
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestConfigIfcmerge:
|
||||
@requires_git
|
||||
def test_writes_redirect_cmd_on_first_call(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
repo = _make_repo(tmpdir)
|
||||
IfcGitRepo.repo = repo
|
||||
IfcGit.config_ifcmerge()
|
||||
reader = repo.config_reader()
|
||||
cmd = reader.get_value('mergetool "ifcmerge"', "cmd")
|
||||
assert "> $MERGED.ifcmerge" in cmd
|
||||
IfcGitRepo.repo = None
|
||||
|
||||
@requires_git
|
||||
def test_updates_cmd_missing_redirect(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
repo = _make_repo(tmpdir)
|
||||
IfcGitRepo.repo = repo
|
||||
with repo.config_writer() as w:
|
||||
w.set_value('mergetool "ifcmerge"', "cmd", "ifcmerge $BASE $LOCAL $REMOTE $MERGED")
|
||||
w.set_value('mergetool "ifcmerge"', "trustExitCode", True)
|
||||
IfcGit.config_ifcmerge()
|
||||
reader = repo.config_reader()
|
||||
cmd = reader.get_value('mergetool "ifcmerge"', "cmd")
|
||||
assert "> $MERGED.ifcmerge" in cmd
|
||||
IfcGitRepo.repo = None
|
||||
|
||||
@requires_git
|
||||
def test_forward_tool_writes_redirect_cmd(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
repo = _make_repo(tmpdir)
|
||||
IfcGitRepo.repo = repo
|
||||
IfcGit.config_ifcmerge()
|
||||
reader = repo.config_reader()
|
||||
cmd = reader.get_value('mergetool "ifcmerge-forward"', "cmd")
|
||||
assert "--prioritise-local" in cmd
|
||||
assert "> $MERGED.ifcmerge" in cmd
|
||||
IfcGitRepo.repo = None
|
||||
|
||||
Reference in New Issue
Block a user