This commit is contained in:
Thomas Krijnen
2024-11-14 13:28:39 +01:00
24 changed files with 689 additions and 264 deletions
+6
View File
@@ -317,6 +317,12 @@ endif
exit 1; \ exit 1; \
fi fi
# Temporary workaround for Blender not handling non-3.11 wheels #5743.
# Use '-n' as on macos x86-64 doesn't have a binary wheel and it autoincludes 'cp311'.
prev_whl_name=$$(find build/bonsai/wheels/tzfpy-*.whl); \
whl_name=$$(echo $$prev_whl_name | sed "s/-cp39-/-cp$(PYNUMBER)-/"); \
mv -n "$$prev_whl_name" "$$whl_name";
ifneq ($(PLATFORM), linux) ifneq ($(PLATFORM), linux)
# Safeguard: in case one of `pip download` will break, # Safeguard: in case one of `pip download` will break,
# it will produce a linux wheel for non-linux build (our github action machine is using linux). # it will produce a linux wheel for non-linux build (our github action machine is using linux).
+49 -12
View File
@@ -21,6 +21,7 @@ import bpy
import time import time
import json import json
import logging import logging
import traceback
import mathutils import mathutils
import numpy as np import numpy as np
import multiprocessing import multiprocessing
@@ -843,14 +844,46 @@ class IfcImporter:
curve = bpy.data.curves.new(mesh_name, type="CURVE") curve = bpy.data.curves.new(mesh_name, type="CURVE")
curve.dimensions = "3D" curve.dimensions = "3D"
curve.resolution_u = 2 curve.resolution_u = 2
polyline = curve.splines.new("POLY")
for item_data in ifcopenshell.util.representation.resolve_items(native_data["representation"]): rep_items = ifcopenshell.util.representation.resolve_items(native_data["representation"])
# Find item styles and add them to the curve.
material_style = None
material = ifcopenshell.util.element.get_material(element)
if material:
material_style = tool.Material.get_style(material)
item_styles: list[Union[bpy.types.Material, None]] = []
for item_data in rep_items:
item = item_data["item"] item = item_data["item"]
item_style = tool.Style.get_representation_item_style(item) or material_style
if item_style is not None:
item_style = tool.Ifc.get_object(item_style)
assert isinstance(item_style, bpy.types.Material)
item_styles.append(item_style)
item_styles_unique = list(set(item_styles))
for item_style in item_styles_unique:
curve.materials.append(item_style)
use_same_material_index = len(item_styles_unique) < 2
def new_polyline(item_style: Union[bpy.types.Material, None]) -> bpy.types.Spline:
if use_same_material_index:
material_index = 0
else:
material_index = item_styles_unique.index(item_style)
polyline = curve.splines.new("POLY")
polyline.material_index = material_index
return polyline
for item_data, item_style in zip(rep_items, item_styles):
item = item_data["item"]
polyline = new_polyline(item_style)
matrix = item_data["matrix"] matrix = item_data["matrix"]
matrix[0][3] *= self.unit_scale matrix[0][3] *= self.unit_scale
matrix[1][3] *= self.unit_scale matrix[1][3] *= self.unit_scale
matrix[2][3] *= self.unit_scale matrix[2][3] *= self.unit_scale
# TODO: support inner radius, start param, and end param # TODO: support inner radius, start param, and end param
geometry = tool.Loader.create_generic_shape(item.Directrix) geometry = tool.Loader.create_generic_shape(item.Directrix)
if not geometry: if not geometry:
@@ -863,7 +896,7 @@ class IfcImporter:
for edge in edges: for edge in edges:
v1 = vertices[edge[0]] v1 = vertices[edge[0]]
if v1 != v2: if v1 != v2:
polyline = curve.splines.new("POLY") polyline = new_polyline(item_style)
polyline.points[-1].co = native_data["matrix"] @ mathutils.Vector(v1) polyline.points[-1].co = native_data["matrix"] @ mathutils.Vector(v1)
v2 = vertices[edge[1]] v2 = vertices[edge[1]]
polyline.points.add(1) polyline.points.add(1)
@@ -1060,11 +1093,7 @@ class IfcImporter:
v2 = vertices[edge[1]] v2 = vertices[edge[1]]
polyline.points.add(1) polyline.points.add(1)
polyline.points[-1].co = mathutils.Vector(v2) polyline.points[-1].co = mathutils.Vector(v2)
# TODO: remove error handling after we update build in Bonsai. edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(geometry).tolist()
try:
edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(geometry).tolist()
except AttributeError:
edges_item_ids = []
curve["ios_edges_item_ids"] = edges_item_ids curve["ios_edges_item_ids"] = edges_item_ids
return curve return curve
@@ -1226,12 +1255,20 @@ class IfcImportSettings:
settings.false_origin_mode = props.false_origin_mode settings.false_origin_mode = props.false_origin_mode
try: try:
settings.false_origin = [float(o) for o in props.false_origin.split(",")[:3]] settings.false_origin = [float(o) for o in props.false_origin.split(",")[:3]]
except: except Exception as e:
settings.false_origin = [0, 0, 0] print(traceback.format_exc())
raise Exception(
f"Failed to set false origin from string '{props.false_origin}'.\n"
f"Error: {str(e)}.\nSee above for the details."
)
try: try:
settings.project_north = float(props.project_north) settings.project_north = float(props.project_north)
except: except Exception as e:
settings.project_north = 0 print(traceback.format_exc())
raise Exception(
f"Failed to set project north from string '{props.project_north}'.\n"
f"Error: {str(e)}.\nSee above for the details."
)
settings.element_limit_mode = props.element_limit_mode settings.element_limit_mode = props.element_limit_mode
settings.element_offset = props.element_offset settings.element_offset = props.element_offset
settings.element_limit = props.element_limit settings.element_limit = props.element_limit
@@ -21,6 +21,7 @@ from . import ui, prop, operator, handler, gizmos, workspace
classes = ( classes = (
operator.ActivateDrawing, operator.ActivateDrawing,
operator.ActivateDrawingFromSheet,
operator.ActivateDrawingStyle, operator.ActivateDrawingStyle,
operator.ActivateModel, operator.ActivateModel,
operator.AddAnnotation, operator.AddAnnotation,
@@ -52,6 +53,7 @@ classes = (
operator.DisableEditingSheets, operator.DisableEditingSheets,
operator.DisableEditingText, operator.DisableEditingText,
operator.DuplicateDrawing, operator.DuplicateDrawing,
operator.DuplicateSheet,
operator.EditAssignedProduct, operator.EditAssignedProduct,
operator.EditElementFilter, operator.EditElementFilter,
operator.EditSheet, operator.EditSheet,
@@ -68,6 +70,7 @@ classes = (
operator.LoadSchedules, operator.LoadSchedules,
operator.LoadSheets, operator.LoadSheets,
operator.OpenDrawing, operator.OpenDrawing,
operator.OpenLayout,
operator.OpenReference, operator.OpenReference,
operator.OpenSchedule, operator.OpenSchedule,
operator.OpenSheet, operator.OpenSheet,
@@ -86,6 +89,7 @@ classes = (
operator.SaveDrawingStyle, operator.SaveDrawingStyle,
operator.SaveDrawingStylesData, operator.SaveDrawingStylesData,
operator.SelectAllDrawings, operator.SelectAllDrawings,
operator.SelectAllSheets,
operator.SelectAssignedProduct, operator.SelectAssignedProduct,
operator.SelectDocIfcFile, operator.SelectDocIfcFile,
operator.OpenDocumentationWebUi, operator.OpenDocumentationWebUi,
+413 -116
View File
@@ -154,10 +154,19 @@ class AddDrawing(bpy.types.Operator, tool.Ifc.Operator):
class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator): class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.duplicate_drawing" bl_idname = "bim.duplicate_drawing"
bl_label = "Duplicate Drawing" bl_label = "Duplicate Drawing"
bl_description = "Make a copy of currently selected drawing"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
drawing: bpy.props.IntProperty() drawing: bpy.props.IntProperty()
should_duplicate_annotations: bpy.props.BoolProperty(name="Should Duplicate Annotations", default=False) should_duplicate_annotations: bpy.props.BoolProperty(name="Should Duplicate Annotations", default=False)
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
return True
def invoke(self, context, event): def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self) return context.window_manager.invoke_props_dialog(self)
@@ -190,10 +199,16 @@ class CreateDrawing(bpy.types.Operator):
bl_idname = "bim.create_drawing" bl_idname = "bim.create_drawing"
bl_label = "Create Drawing" bl_label = "Create Drawing"
bl_description = ( bl_description = (
"Creates/refreshes a .svg drawing based on currently active camera.\n\n" "Creates/refreshes a .svg drawing based on currently active camera\n"
+ "SHIFT+CLICK to print all selected drawings" + 'and open with default system viewer or using "svg_command" or\n'
+ '"pdf_command" from the Bonsai preferences (if provided).\n\n'
+ "SHIFT+CLICK to create/refresh all shown checked drawings, but doesn't\n"
+ "open them for viewing.\n\n"
+ "Add the CTRL modifier to optionally open drawings to view them as\n"
+ "they are created"
) )
print_all: bpy.props.BoolProperty(name="Print All", default=False, options={"SKIP_SAVE"}) print_all: bpy.props.BoolProperty(name="Print All", default=False, options={"SKIP_SAVE"})
open_viewer: bpy.props.BoolProperty(name="Open in Viewer", default=False, options={"SKIP_SAVE"})
sync: bpy.props.BoolProperty( sync: bpy.props.BoolProperty(
name="Sync Before Creating Drawing", name="Sync Before Creating Drawing",
description="Could save some time if you're sure IFC and current Blender session are already in sync", description="Could save some time if you're sure IFC and current Blender session are already in sync",
@@ -223,6 +238,8 @@ class CreateDrawing(bpy.types.Operator):
# make sure to use SKIP_SAVE on property, otherwise it might get stuck # make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift: if event.type == "LEFTMOUSE" and event.shift:
self.print_all = True self.print_all = True
if event.type == "LEFTMOUSE" and event.ctrl:
self.open_viewer = True
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
@@ -291,8 +308,11 @@ class CreateDrawing(bpy.types.Operator):
with profile("Combine SVG layers"): with profile("Combine SVG layers"):
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg) svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, svg_path) if self.open_viewer:
drawing_uri = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_document(self.drawing))
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, drawing_uri)
if not self.open_viewer:
self.report({"INFO"}, f"{len(drawings_to_print)} drawings created...")
if self.print_all: if self.print_all:
bpy.ops.bim.activate_drawing(drawing=original_drawing_id, should_view_from_camera=False) bpy.ops.bim.activate_drawing(drawing=original_drawing_id, should_view_from_camera=False)
return {"FINISHED"} return {"FINISHED"}
@@ -1436,22 +1456,163 @@ class AddSheet(bpy.types.Operator, tool.Ifc.Operator):
core.add_sheet(tool.Ifc, tool.Drawing, titleblock=context.scene.DocProperties.titleblock) core.add_sheet(tool.Ifc, tool.Drawing, titleblock=context.scene.DocProperties.titleblock)
class OpenSheet(bpy.types.Operator, tool.Ifc.Operator): class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.open_sheet" bl_idname = "bim.duplicate_sheet"
bl_label = "Open Sheet Layout" bl_label = "Duplicate Sheet"
bl_description = "Make a copy of currently selected sheet"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
drawing: bpy.props.IntProperty()
@classmethod
def poll(cls, context):
# Unconditionally disable until implemented
cls.poll_message_set("Not implemented yet.")
return False
props = context.scene.DocProperties
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
return True
def _execute(self, context):
pass
"""
self.props = context.scene.DocProperties
core.duplicate_sheet(
tool.Ifc,
tool.Drawing,
sheet=tool.Ifc.get().by_id(self.sheet),
)
try:
sheet = tool.Ifc.get().by_id(self.props.active_sheet_id)
core.sync_references(tool.Ifc, tool.Collector, tool.Sheet, drawing=sheet)
except:
pass
"""
class OpenLayout(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.open_layout"
bl_label = "Open Sheet Layout"
bl_description = (
"Opens selected .svg layout with default system viewer\n"
+ 'or using "layout_svg_command" from the Bonsai preferences\n'
+ "(if provided)"
)
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
active_sheet = props.sheets[props.active_sheet_index]
if not active_sheet.is_sheet:
cls.poll_message_set("No sheet selected.")
return False
return True
def _execute(self, context): def _execute(self, context):
self.props = context.scene.DocProperties self.props = context.scene.DocProperties
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id) sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
sheet_builder = sheeter.SheetBuilder() sheet_builder = sheeter.SheetBuilder()
sheet_builder.update_sheet_drawing_sizes(sheet) sheet_builder.update_sheet_drawing_sizes(sheet)
core.open_sheet(tool.Drawing, sheet=sheet) core.open_layout(tool.Drawing, sheet=sheet)
class SelectAllSheets(bpy.types.Operator):
bl_idname = "bim.select_all_sheets"
bl_label = "Select All Sheetss"
view: bpy.props.StringProperty()
bl_description = "Select all sheets in the sheet list.\n\n" + "SHIFT+CLICK to deselect all sheets"
select_all: bpy.props.BoolProperty(name="Open All", default=True, options={"SKIP_SAVE"})
def invoke(self, context, event):
# deselect all sheets on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift:
self.select_all = False
return self.execute(context)
def execute(self, context):
for sheet in context.scene.DocProperties.sheets:
if sheet.is_selected != self.select_all:
sheet.is_selected = self.select_all
return {"FINISHED"}
class OpenSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.open_sheet"
bl_label = "Open Sheet"
bl_description = (
"Opens selected sheet with default system viewer\n"
+ 'or using "svg_command" or "pdf_command" from\n'
+ "the Bonsai preferences (if provided).\n\n"
+ "SHIFT+CLICK to open all shown checked sheets"
)
bl_options = {"REGISTER", "UNDO"}
open_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
active_sheet = props.sheets[props.active_sheet_index]
if not active_sheet.is_sheet:
cls.poll_message_set("No sheet selected.")
return False
return True
def invoke(self, context, event):
# opening all sheets on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift:
self.open_all = True
return self.execute(context)
def execute(self, context):
self.props = context.scene.DocProperties
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
if self.open_all:
sheets = [
tool.Ifc.get().by_id(s.ifc_definition_id) for s in self.props.sheets if s.is_sheet and s.is_selected
]
else:
sheets = [tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)]
sheet_uris = []
sheets_not_found = []
for sheet in sheets:
if not sheet.is_a("IfcDocumentInformation"):
continue
sheet_builder = sheeter.SheetBuilder()
references = sheet_builder.build(sheet)
sheet_uri = references["SHEET"]
if svg2pdf_command:
sheet_uri = os.path.splitext(sheet_uri)[0] + ".pdf"
sheet_uris.append(sheet_uri)
if not os.path.exists(sheet_uri):
sheets_not_found.append(sheet.Name)
if sheets_not_found:
msg = "Some sheets .svg/.pdf files were not found, need to create them first: \n{}.".format(
"\n".join(sheets_not_found)
)
self.report({"ERROR"}, msg)
return {"CANCELLED"}
for sheet_uri in sheet_uris:
if svg2pdf_command:
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().pdf_command, sheet_uri)
else:
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, sheet_uri)
return {"FINISHED"}
class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator): class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_drawing_to_sheet" bl_idname = "bim.add_drawing_to_sheet"
bl_label = "Add Drawing To Sheet" bl_label = "Add Selected Drawing To Sheet"
bl_description = "Add the drawing selected in the\nDrawings list below to the sheet"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@classmethod @classmethod
@@ -1496,7 +1657,14 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
attributes = tool.Drawing.generate_reference_attributes( attributes = tool.Drawing.generate_reference_attributes(
reference, reference,
Identification=str( Identification=str(
len([r for r in references if tool.Drawing.get_reference_description(r) in ("DRAWING", "SCHEDULE")]) + 1 len(
[
r
for r in references
if tool.Drawing.get_reference_description(r) in ("DRAWING", "SCHEDULE", "REFERENCE")
]
)
+ 1
), ),
Location=drawing_reference.Location, Location=drawing_reference.Location,
Description="DRAWING", Description="DRAWING",
@@ -1512,9 +1680,20 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
class RemoveDrawingFromSheet(bpy.types.Operator, tool.Ifc.Operator): class RemoveDrawingFromSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_drawing_from_sheet" bl_idname = "bim.remove_drawing_from_sheet"
bl_label = "Remove Drawing From Sheet" bl_label = "Remove Drawing From Sheet"
bl_description = "Remove currently selected drawing from sheet"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty() reference: bpy.props.IntProperty()
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
active_sheet = props.sheets[props.active_sheet_index]
if active_sheet.reference_type == "TITLEBLOCK":
cls.poll_message_set("No effect deleting this.")
return False
return True
def _execute(self, context): def _execute(self, context):
reference = tool.Ifc.get().by_id(self.reference) reference = tool.Ifc.get().by_id(self.reference)
sheet = tool.Drawing.get_reference_document(reference) sheet = tool.Drawing.get_reference_document(reference)
@@ -1531,98 +1710,129 @@ class RemoveDrawingFromSheet(bpy.types.Operator, tool.Ifc.Operator):
class CreateSheets(bpy.types.Operator, tool.Ifc.Operator): class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.create_sheets" bl_idname = "bim.create_sheets"
bl_label = "Create Sheets" bl_label = "Create Sheets"
bl_description = "Build a sheet from the sheet layout" bl_description = (
"Build and open selected sheet from the sheet layout and\n"
+ "optionally create .pdf and .dxf from commands in\n"
+ "the Bonsai preferences (if provided).\n\n"
+ "SHIFT+CLICK to create all shown checked sheets, but doesn't\n"
+ "open them for viewing\n\n"
+ "Add the CTRL modifier to optionally open sheets to view them as\n"
+ "they are created"
)
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
create_all: bpy.props.BoolProperty(name="Create All", default=False, options={"SKIP_SAVE"})
open_viewer: bpy.props.BoolProperty(name="Open in Viewer", default=False, options={"SKIP_SAVE"})
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return context.scene.DocProperties.sheets and context.scene.BIMProperties.data_dir props = context.scene.DocProperties
active_sheet = props.sheets[props.active_sheet_index]
if not active_sheet.is_sheet:
cls.poll_message_set("No sheet selected.")
return False
return props.sheets and context.scene.BIMProperties.data_dir
def invoke(self, context, event):
# opening all sheets on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift:
self.create_all = True
if event.type == "LEFTMOUSE" and event.ctrl:
self.open_viewer = True
return self.execute(context)
def _execute(self, context): def _execute(self, context):
scene = context.scene scene = context.scene
props = scene.DocProperties props = scene.DocProperties
active_sheet = props.sheets[props.active_sheet_index]
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
# Update any drawing boundary changes
sheet_builder = sheeter.SheetBuilder()
sheet_builder.update_sheet_drawing_sizes(sheet)
if not sheet.is_a("IfcDocumentInformation"):
return
name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = scene.BIMProperties.data_dir
references = sheet_builder.build(sheet)
raster_references = [tool.Ifc.get_relative_uri(r) for r in references["RASTER"]]
# These variables will be made available to the evaluated commands
svg = references["SHEET"]
pdf = os.path.splitext(svg)[0] + ".pdf"
replacements = {
"svg": svg,
"basename": os.path.basename(svg),
"path": os.path.dirname(svg),
"pdf": pdf,
"eps": os.path.splitext(svg)[0] + ".eps",
"dxf": os.path.splitext(svg)[0] + ".dxf",
}
has_sheet_reference = False
for reference in tool.Drawing.get_document_references(sheet):
reference_description = tool.Drawing.get_reference_description(reference)
if reference_description == "SHEET":
has_sheet_reference = True
elif reference_description == "RASTER":
if reference.Location in raster_references:
raster_references.remove(reference.Location)
else:
tool.Ifc.run("document.remove_reference", reference=reference)
if not has_sheet_reference:
reference = tool.Ifc.run("document.add_reference", information=sheet)
tool.Ifc.run(
"document.edit_reference",
reference=reference,
attributes=tool.Drawing.generate_reference_attributes(
reference, Location=tool.Ifc.get_relative_uri(svg), Description="SHEET"
),
)
for raster_reference in raster_references:
reference = tool.Ifc.run("document.add_reference", information=sheet)
tool.Ifc.run(
"document.edit_reference",
reference=reference,
attributes=tool.Drawing.generate_reference_attributes(
reference, Location=tool.Ifc.get_relative_uri(raster_reference), Description="RASTER"
),
)
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
svg2dxf_command = tool.Blender.get_addon_preferences().svg2dxf_command svg2dxf_command = tool.Blender.get_addon_preferences().svg2dxf_command
if svg2pdf_command: if self.create_all:
# With great power comes great responsibility. Example: sheets = [tool.Ifc.get().by_id(s.ifc_definition_id) for s in props.sheets if s.is_sheet and s.is_selected]
# [["inkscape", "svg", "-o", "pdf"]]
commands = json.loads(svg2pdf_command)
for command in commands:
subprocess.run([replacements.get(c, c) for c in command])
if svg2dxf_command:
# With great power comes great responsibility. Example:
# [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]
commands = json.loads(svg2dxf_command)
for command in commands:
command[0] = shutil.which(command[0]) or command[0]
subprocess.run([replacements.get(c, c) for c in command])
if svg2pdf_command:
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().pdf_command, pdf)
else: else:
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, svg) sheets = [tool.Ifc.get().by_id(props.sheets[props.active_sheet_index].ifc_definition_id)]
for sheet in sheets:
# Update any drawing boundary changes
sheet_builder = sheeter.SheetBuilder()
sheet_builder.update_sheet_drawing_sizes(sheet)
if not sheet.is_a("IfcDocumentInformation"):
return
name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = scene.BIMProperties.data_dir
references = sheet_builder.build(sheet)
raster_references = [tool.Ifc.get_relative_uri(r) for r in references["RASTER"]]
# These variables will be made available to the evaluated commands
svg = references["SHEET"]
pdf = os.path.splitext(svg)[0] + ".pdf"
replacements = {
"svg": svg,
"basename": os.path.basename(svg),
"path": os.path.dirname(svg),
"pdf": pdf,
"eps": os.path.splitext(svg)[0] + ".eps",
"dxf": os.path.splitext(svg)[0] + ".dxf",
}
has_sheet_reference = False
for reference in tool.Drawing.get_document_references(sheet):
reference_description = tool.Drawing.get_reference_description(reference)
if reference_description == "SHEET":
has_sheet_reference = True
elif reference_description == "RASTER":
if reference.Location in raster_references:
raster_references.remove(reference.Location)
else:
tool.Ifc.run("document.remove_reference", reference=reference)
if not has_sheet_reference:
reference = tool.Ifc.run("document.add_reference", information=sheet)
tool.Ifc.run(
"document.edit_reference",
reference=reference,
attributes=tool.Drawing.generate_reference_attributes(
reference, Location=tool.Ifc.get_relative_uri(svg), Description="SHEET"
),
)
for raster_reference in raster_references:
reference = tool.Ifc.run("document.add_reference", information=sheet)
tool.Ifc.run(
"document.edit_reference",
reference=reference,
attributes=tool.Drawing.generate_reference_attributes(
reference, Location=tool.Ifc.get_relative_uri(raster_reference), Description="RASTER"
),
)
if svg2pdf_command:
# With great power comes great responsibility. Example:
# [["inkscape", "svg", "-o", "pdf"]]
commands = json.loads(svg2pdf_command)
for command in commands:
subprocess.run([replacements.get(c, c) for c in command])
if svg2dxf_command:
# With great power comes great responsibility. Example:
# [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]
commands = json.loads(svg2dxf_command)
for command in commands:
command[0] = shutil.which(command[0]) or command[0]
subprocess.run([replacements.get(c, c) for c in command])
if self.open_viewer:
if svg2pdf_command:
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().pdf_command, pdf)
else:
tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().svg_command, svg)
if not self.open_viewer:
self.report({"INFO"}, f"{len(sheets)} sheets created...")
class SelectAllDrawings(bpy.types.Operator): class SelectAllDrawings(bpy.types.Operator):
@@ -1651,12 +1861,20 @@ class OpenDrawing(bpy.types.Operator):
bl_label = "Open Drawing" bl_label = "Open Drawing"
view: bpy.props.StringProperty() view: bpy.props.StringProperty()
bl_description = ( bl_description = (
"Opens a .svg drawing based on currently active camera with default system viewer\n" "Opens selected .svg drawing with default system viewer\n"
+ 'or using "svg_command" from the Bonsai preferences (if provided).\n\n' + 'or using "svg_command" from the Bonsai preferences (if provided).\n\n'
+ "SHIFT+CLICK to open all selected drawings" + "SHIFT+CLICK to open all shown checked drawings"
) )
open_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"}) open_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
return True
def invoke(self, context, event): def invoke(self, context, event):
# opening all drawings on shift+click # opening all drawings on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck # make sure to use SKIP_SAVE on property, otherwise it might get stuck
@@ -1665,12 +1883,13 @@ class OpenDrawing(bpy.types.Operator):
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
self.props = context.scene.DocProperties
if self.open_all: if self.open_all:
drawings = [ drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected tool.Ifc.get().by_id(d.ifc_definition_id) for d in self.props.drawings if d.is_drawing and d.is_selected
] ]
else: else:
drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)] drawings = [tool.Ifc.get().by_id(self.props.drawings.get(self.view).ifc_definition_id)]
drawing_uris = [] drawing_uris = []
drawings_not_found = [] drawings_not_found = []
@@ -1682,7 +1901,7 @@ class OpenDrawing(bpy.types.Operator):
drawings_not_found.append(drawing.Name) drawings_not_found.append(drawing.Name)
if drawings_not_found: if drawings_not_found:
msg = "Some drawings .svg files were not found, need to print them first: \n{}.".format( msg = "Some drawings .svg files were not found, need to create them first: \n{}.".format(
"\n".join(drawings_not_found) "\n".join(drawings_not_found)
) )
self.report({"ERROR"}, msg) self.report({"ERROR"}, msg)
@@ -1755,20 +1974,7 @@ class ActivateModel(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class ActivateDrawing(bpy.types.Operator): class ActivateDrawingBase:
bl_idname = "bim.activate_drawing"
bl_label = "Activate Drawing"
bl_options = {"REGISTER", "UNDO"}
bl_description = (
"Activates the selected drawing view.\n\n"
+ "ALT+CLICK to keep the viewport position.\n\n"
+ "SHIFT+CLICK to load a quick preview of the drawing view."
)
drawing: bpy.props.IntProperty()
should_view_from_camera: bpy.props.BoolProperty(name="Should View From Camera", default=True, options={"SKIP_SAVE"})
use_quick_preview: bpy.props.BoolProperty(name="Use Quick Preview", default=False, options={"SKIP_SAVE"})
def invoke(self, context, event): def invoke(self, context, event):
if event.type == "LEFTMOUSE" and event.alt: if event.type == "LEFTMOUSE" and event.alt:
self.should_view_from_camera = False self.should_view_from_camera = False
@@ -1817,6 +2023,54 @@ class ActivateDrawing(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class ActivateDrawing(bpy.types.Operator, ActivateDrawingBase):
bl_idname = "bim.activate_drawing"
bl_label = "Activate Drawing"
bl_options = {"REGISTER", "UNDO"}
bl_description = (
"Activates the selected drawing view.\n\n"
+ "ALT+CLICK to keep the viewport position.\n\n"
+ "SHIFT+CLICK to load a quick preview of the drawing view"
)
drawing: bpy.props.IntProperty()
should_view_from_camera: bpy.props.BoolProperty(name="Should View From Camera", default=True, options={"SKIP_SAVE"})
use_quick_preview: bpy.props.BoolProperty(name="Use Quick Preview", default=False, options={"SKIP_SAVE"})
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
return True
class ActivateDrawingFromSheet(bpy.types.Operator, ActivateDrawingBase):
bl_idname = "bim.activate_drawing_from_sheet"
bl_label = "Activate Drawing"
bl_options = {"REGISTER", "UNDO"}
bl_description = (
"Activates the selected drawing view.\n\n"
+ "ALT+CLICK to keep the viewport position.\n\n"
+ "SHIFT+CLICK to load a quick preview of the drawing view"
)
drawing: bpy.props.IntProperty()
should_view_from_camera: bpy.props.BoolProperty(name="Should View From Camera", default=True, options={"SKIP_SAVE"})
use_quick_preview: bpy.props.BoolProperty(name="Use Quick Preview", default=False, options={"SKIP_SAVE"})
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
active_sheet = props.sheets[props.active_sheet_index]
is_drawing_selected = active_sheet.reference_type == "DRAWING"
if not is_drawing_selected:
cls.poll_message_set("No drawing selected.")
return False
return True
class SelectDocIfcFile(bpy.types.Operator): class SelectDocIfcFile(bpy.types.Operator):
bl_idname = "bim.select_doc_ifc_file" bl_idname = "bim.select_doc_ifc_file"
bl_label = "Select Documentation IFC File" bl_label = "Select Documentation IFC File"
@@ -1856,6 +2110,14 @@ class RemoveDrawing(bpy.types.Operator, tool.Ifc.Operator):
drawing: bpy.props.IntProperty() drawing: bpy.props.IntProperty()
remove_all: bpy.props.BoolProperty(name="Remove All", default=False, options={"SKIP_SAVE"}) remove_all: bpy.props.BoolProperty(name="Remove All", default=False, options={"SKIP_SAVE"})
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
return True
def invoke(self, context, event): def invoke(self, context, event):
# removing all selected drawings on shift+click # removing all selected drawings on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck # make sure to use SKIP_SAVE on property, otherwise it might get stuck
@@ -1866,7 +2128,9 @@ class RemoveDrawing(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
if self.remove_all: if self.remove_all:
drawings = [ drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected tool.Ifc.get().by_id(d.ifc_definition_id)
for d in context.scene.DocProperties.drawings
if d.is_drawing and d.is_selected
] ]
else: else:
if not self.drawing: if not self.drawing:
@@ -2192,6 +2456,7 @@ class RemoveSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_sheet" bl_idname = "bim.remove_sheet"
bl_label = "Remove Sheet" bl_label = "Remove Sheet"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Remove currently selected sheet"
sheet: bpy.props.IntProperty() sheet: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
@@ -2260,11 +2525,15 @@ class BuildSchedule(bpy.types.Operator, tool.Ifc.Operator):
class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator): class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_schedule_to_sheet" bl_idname = "bim.add_schedule_to_sheet"
bl_label = "Add Schedule To Sheet" bl_label = "Add Schedule To Sheet"
bl_description = "Add the schedule selected in the\nSchedules list below to the sheet"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = context.scene.DocProperties
if not props.schedules:
cls.poll_message_set("No schedule selected.")
return False
return props.schedules and props.sheets and context.scene.BIMProperties.data_dir return props.schedules and props.sheets and context.scene.BIMProperties.data_dir
def _execute(self, context): def _execute(self, context):
@@ -2299,7 +2568,14 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
attributes = tool.Drawing.generate_reference_attributes( attributes = tool.Drawing.generate_reference_attributes(
reference, reference,
Identification=str( Identification=str(
len([r for r in references if tool.Drawing.get_reference_description(r) in ("DRAWING", "SCHEDULE")]) + 1 len(
[
r
for r in references
if tool.Drawing.get_reference_description(r) in ("DRAWING", "SCHEDULE", "REFERENCE")
]
)
+ 1
), ),
Location=schedule_location, Location=schedule_location,
Description="SCHEDULE", Description="SCHEDULE",
@@ -2316,11 +2592,15 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator): class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_reference_to_sheet" bl_idname = "bim.add_reference_to_sheet"
bl_label = "Add Reference To Sheet" bl_label = "Add Reference To Sheet"
bl_description = "Add the reference selected in the\nReferences list below to the sheet"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.DocProperties props = context.scene.DocProperties
if not props.references:
cls.poll_message_set("No reference selected.")
return False
return props.references and props.sheets and context.scene.BIMProperties.data_dir return props.references and props.sheets and context.scene.BIMProperties.data_dir
def _execute(self, context): def _execute(self, context):
@@ -2355,7 +2635,13 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
attributes = tool.Drawing.generate_reference_attributes( attributes = tool.Drawing.generate_reference_attributes(
reference, reference,
Identification=str( Identification=str(
len([r for r in references if tool.Drawing.get_reference_description(r) in ("DRAWING", "REFERENCE")]) len(
[
r
for r in references
if tool.Drawing.get_reference_description(r) in ("DRAWING", "SCHEDULE", "REFERENCE")
]
)
+ 1 + 1
), ),
Location=extref_location, Location=extref_location,
@@ -2720,7 +3006,8 @@ class LoadSheets(bpy.types.Operator, tool.Ifc.Operator):
class EditSheet(bpy.types.Operator, tool.Ifc.Operator): class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_sheet" bl_idname = "bim.edit_sheet"
bl_label = "Edit Sheet" bl_label = "Edit Sheet / Drawing"
bl_description = "Edit details of sheet or drawing"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
identification: bpy.props.StringProperty() identification: bpy.props.StringProperty()
name: bpy.props.StringProperty() name: bpy.props.StringProperty()
@@ -3069,20 +3356,30 @@ class ConvertSVGToDXF(bpy.types.Operator):
bl_label = "Convert SVG to DXF" bl_label = "Convert SVG to DXF"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
view: bpy.props.StringProperty() view: bpy.props.StringProperty()
bl_description = "Convert current drawing's .svg to .dxf.\n\nSHIFT+CLICK to convert all selected drawings" bl_description = "Convert selected drawing's .svg to .dxf.\n\nSHIFT+CLICK to convert all shown checked drawings"
convert_all: bpy.props.BoolProperty(name="Convert All", default=False, options={"SKIP_SAVE"}) convert_all: bpy.props.BoolProperty(name="Convert All", default=False, options={"SKIP_SAVE"})
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
if not tool.Drawing.get_active_drawing_item():
cls.poll_message_set("No drawing selected.")
return False
return True
def invoke(self, context, event): def invoke(self, context, event):
# convert all drawings on shift+click # convert all drawings on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck # make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift: if event.type == "LEFTMOUSE" and event.shift:
self.open_all = True self.convert_all = True
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
if self.convert_all: if self.convert_all:
drawings = [ drawings = [
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected tool.Ifc.get().by_id(d.ifc_definition_id)
for d in context.scene.DocProperties.drawings
if d.is_drawing and d.is_selected
] ]
else: else:
drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)] drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)]
@@ -290,6 +290,7 @@ class Sheet(PropertyGroup):
identification: StringProperty(name="Identification") identification: StringProperty(name="Identification")
name: StringProperty(name="Name") name: StringProperty(name="Name")
is_sheet: BoolProperty(name="Is Sheet", default=False) is_sheet: BoolProperty(name="Is Sheet", default=False)
is_selected: BoolProperty(name="Is Selected", default=True)
reference_type: StringProperty(name="Reference Type") reference_type: StringProperty(name="Reference Type")
is_expanded: BoolProperty(name="Is Expanded", default=False) is_expanded: BoolProperty(name="Is Expanded", default=False)
+58 -53
View File
@@ -92,11 +92,6 @@ class BIM_PT_camera(Panel):
row = self.layout.row() row = self.layout.row()
row.prop(props, "dpi") row.prop(props, "dpi")
row = self.layout.row(align=True)
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
op = row.operator("bim.open_drawing", icon="URL", text="")
op.view = context.scene.camera.name.split("/")[1]
class BIM_PT_element_filters(Panel): class BIM_PT_element_filters(Panel):
bl_label = "Element Filters" bl_label = "Element Filters"
@@ -252,43 +247,29 @@ class BIM_PT_drawings(Panel):
if self.props.active_drawing_index < len(self.props.drawings): if self.props.active_drawing_index < len(self.props.drawings):
active_drawing = self.props.drawings[self.props.active_drawing_index] active_drawing = self.props.drawings[self.props.active_drawing_index]
row = self.layout.row(align=True) row = self.layout.row(align=True)
col = row.column() row2 = row.row(align=True)
col.alignment = "LEFT"
row2 = col.row(align=True)
row2.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id row2.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
row2.operator("bim.duplicate_drawing", icon="COPYDOWN", text="").drawing = ( row2.separator(factor=0.5, type="SPACE")
row2.operator("bim.duplicate_drawing", icon="DUPLICATE", text="").drawing = (
active_drawing.ifc_definition_id active_drawing.ifc_definition_id
) )
col = row.column() row3 = row.row(align=True)
col.alignment = "RIGHT" row3.alignment = "RIGHT"
convert_to_dxf = row.row(align=True) row3.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="").drawing = (
op = convert_to_dxf.operator("bim.convert_svg_to_dxf", text="", icon="IMAGE_DATA") active_drawing.ifc_definition_id
op.view = active_drawing.name )
convert_to_dxf.enabled = active_drawing.ifc_definition_id > 0 row3.operator("bim.activate_model", icon="VIEW3D", text="")
op = row.operator("bim.select_all_drawings", icon="SELECT_SUBTRACT", text="") row3.separator(factor=0.5, type="SPACE")
open_drawing_button = row.row(align=True) row3.operator("bim.select_all_drawings", icon="CHECKBOX_HLT", text="")
op = open_drawing_button.operator("bim.open_drawing", icon="URL", text="") row3.operator("bim.create_drawing", text="", icon="OUTPUT")
op.view = active_drawing.name row3.operator("bim.convert_svg_to_dxf", text="", icon="SEQ_PREVIEW").view = active_drawing.name
open_drawing_button.enabled = active_drawing.ifc_definition_id > 0 row3.operator("bim.open_drawing", icon="HIDE_OFF", text="").view = active_drawing.name
row.operator("bim.activate_model", icon="VIEW3D", text="")
drawing_button = row.row(align=True)
op = drawing_button.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = active_drawing.ifc_definition_id
drawing_button.enabled = active_drawing.ifc_definition_id > 0
create_drawing_button = row.row(align=True)
create_drawing_button.operator("bim.create_drawing", text="", icon="OUTPUT")
create_drawing_button.enabled = active_drawing.ifc_definition_id > 0
# might need a different icon since the URL icon is already used by the open_drawing
row.operator("bim.open_documentation_web_ui", icon="URL", text="")
self.layout.template_list( self.layout.template_list(
"BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index" "BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index"
) )
@@ -325,7 +306,7 @@ class BIM_PT_schedules(Panel):
if not self.props.is_editing_schedules: if not self.props.is_editing_schedules:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text=f"{DocumentsData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY") row.label(text=f"{DocumentsData.data['total_schedules']} Schedules Found", icon="PRESET")
row.operator("bim.load_schedules", text="", icon="IMPORT") row.operator("bim.load_schedules", text="", icon="IMPORT")
return return
@@ -375,7 +356,7 @@ class BIM_PT_references(Panel):
if not self.props.is_editing_references: if not self.props.is_editing_references:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="OBJECT_HIDDEN") row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="IMAGE_REFERENCE")
row.operator("bim.load_references", text="", icon="IMPORT") row.operator("bim.load_references", text="", icon="IMPORT")
return return
@@ -429,7 +410,23 @@ class BIM_PT_sheets(Panel):
if self.props.sheets and self.props.active_sheet_index < len(self.props.sheets): if self.props.sheets and self.props.active_sheet_index < len(self.props.sheets):
active_sheet = self.props.sheets[self.props.active_sheet_index] active_sheet = self.props.sheets[self.props.active_sheet_index]
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.alignment = "RIGHT" row2 = row.row(align=True)
if active_sheet.is_sheet:
row2.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id
else:
row2.operator("bim.remove_drawing_from_sheet", icon="X", text="").reference = (
active_sheet.ifc_definition_id
)
row2.separator(factor=0.5, type="SPACE")
row2.operator("bim.duplicate_sheet", icon="DUPLICATE", text="").drawing = active_sheet.ifc_definition_id
row2.operator("bim.open_documentation_web_ui", icon="URL", text="")
row3 = row.row(align=True)
row3.alignment = "RIGHT"
op = row3.operator("bim.activate_drawing_from_sheet", icon="OUTLINER_OB_CAMERA", text="")
if active_sheet.reference_type == "DRAWING": if active_sheet.reference_type == "DRAWING":
drawingnamesvg = active_sheet.name drawingnamesvg = active_sheet.name
@@ -445,23 +442,21 @@ class BIM_PT_sheets(Panel):
break break
if drawingid is not None: if drawingid is not None:
drawing_button = row.row(align=True)
op = drawing_button.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = drawingid op.drawing = drawingid
else:
print(f"No matching drawing ID found for {drawingname}")
row.operator("bim.edit_sheet", icon="GREASEPENCIL", text="") row3.separator(factor=0.5, type="SPACE")
row.operator("bim.open_sheet", icon="URL", text="")
row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="") row3.operator("bim.edit_sheet", icon="GREASEPENCIL", text="")
row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="") row3.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="")
row.operator("bim.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="") row3.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="")
row.operator("bim.create_sheets", icon="FILE_REFRESH", text="") row3.operator("bim.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="")
if active_sheet.is_sheet: row3.operator("bim.open_layout", icon="CURRENT_FILE", text="")
row.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id
else: row3.separator(factor=0.5, type="SPACE")
op = row.operator("bim.remove_drawing_from_sheet", icon="X", text="")
op.reference = active_sheet.ifc_definition_id row3.operator("bim.select_all_sheets", icon="CHECKBOX_HLT", text="")
row3.operator("bim.create_sheets", icon="OUTPUT", text="")
row3.operator("bim.open_sheet", icon="HIDE_OFF", text="")
self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index") self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index")
@@ -600,6 +595,13 @@ class BIM_UL_drawinglist(bpy.types.UIList):
selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT" selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT"
row.prop(item, "is_selected", text="", icon=selected_icon, emboss=False) row.prop(item, "is_selected", text="", icon=selected_icon, emboss=False)
row.prop(item, "name", text="", emboss=False) row.prop(item, "name", text="", emboss=False)
self.props = context.scene.DocProperties
if (
self.props.drawings
and self.props.active_drawing_id
and item.ifc_definition_id == self.props.active_drawing_id
):
row.label(text="", icon="OUTLINER_OB_CAMERA")
else: else:
if item.target_view == "PLAN_VIEW": if item.target_view == "PLAN_VIEW":
icon = "UV_FACESEL" icon = "UV_FACESEL"
@@ -641,13 +643,16 @@ class BIM_UL_sheets(bpy.types.UIList):
item.ifc_definition_id item.ifc_definition_id
) )
selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT"
row.prop(item, "is_selected", text="", icon=selected_icon, emboss=False)
row.label(text=f"{item.identification} - {item.name}") row.label(text=f"{item.identification} - {item.name}")
else: else:
row.label(text="", icon="BLANK1") row.label(text="", icon="BLANK1")
if item.reference_type == "DRAWING": if item.reference_type == "DRAWING":
row.label(text="", icon="IMAGE_DATA") row.label(text="", icon="IMAGE_DATA")
elif item.reference_type == "SCHEDULE": elif item.reference_type == "SCHEDULE":
row.label(text="", icon="LONGDISPLAY") row.label(text="", icon="PRESET")
elif item.reference_type == "TITLEBLOCK": elif item.reference_type == "TITLEBLOCK":
row.label(text="", icon="MENU_PANEL") row.label(text="", icon="MENU_PANEL")
elif item.reference_type == "REVISION": elif item.reference_type == "REVISION":
@@ -745,7 +745,6 @@ class ExpandMaterialCategory(bpy.types.Operator):
category.is_expanded = True category.is_expanded = True
if category.name == self.category: if category.name == self.category:
props.active_material_index = index props.active_material_index = index
break
core.load_materials(tool.Material, props.material_type) core.load_materials(tool.Material, props.material_type)
return {"FINISHED"} return {"FINISHED"}
@@ -775,7 +774,6 @@ class ContractMaterialCategory(bpy.types.Operator):
category.is_expanded = False category.is_expanded = False
if category.name == self.category: if category.name == self.category:
props.active_material_index = index props.active_material_index = index
break
core.load_materials(tool.Material, props.material_type) core.load_materials(tool.Material, props.material_type)
return {"FINISHED"} return {"FINISHED"}
@@ -109,10 +109,16 @@ class BimTool(WorkSpaceTool):
def draw_settings(cls, context, layout, ws_tool): def draw_settings(cls, context, layout, ws_tool):
if context.scene.BIMGeometryProperties.mode == "ITEM": if context.scene.BIMGeometryProperties.mode == "ITEM":
EditItemUI.draw(context, layout) EditItemUI.draw(context, layout)
elif context.active_object and context.selected_objects and tool.Ifc.get_entity(context.active_object): elif (
active_ifc_object := (context.active_object and tool.Ifc.get_entity(context.active_object))
) and context.selected_objects:
EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) EditObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
else: else:
CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) CreateObjectUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
# Show some UI for spatial elements that are unselectable by default.
if active_ifc_object:
EditObjectUI.layout = layout # Prevent .draw_modes from using old layout and crash.
EditObjectUI.draw_modes(context)
class WallTool(BimTool): class WallTool(BimTool):
@@ -26,6 +26,7 @@ import bonsai.core.profile as core
from bonsai.bim.module.model.decorator import ProfileDecorator from bonsai.bim.module.model.decorator import ProfileDecorator
from bonsai.bim.module.profile.prop import generate_thumbnail_for_active_profile from bonsai.bim.module.profile.prop import generate_thumbnail_for_active_profile
from bonsai.bim.module.profile.data import refresh from bonsai.bim.module.profile.data import refresh
from bonsai.bim.module.geometry.helper import Helper
class LoadProfiles(bpy.types.Operator): class LoadProfiles(bpy.types.Operator):
@@ -151,27 +152,36 @@ class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
obj_points = []
obj = context.scene.BIMProfileProperties.object_to_profile
if obj:
if len(obj.data.polygons) != 1:
self.report({"WARNING"}, "This mesh is invalid to create a profile. Select a flat mesh with no more then one face.")
return
for v in obj.data.polygons[0].vertices:
vert = obj.data.vertices[v]
if vert.co.z > 0:
self.report({"WARNING"}, "This mesh is invalid to create a profile. Select a flat mesh with all its vertices at z=0")
return
obj_points.append((vert.co.x, vert.co.y))
if obj_points:
obj_points.append(obj_points[0])
obj_points = [(v[0] - obj_points[0][0], v[1] - obj_points[0][1]) for v in obj_points] # Make sure the first point is (0, 0)
props = context.scene.BIMProfileProperties props = context.scene.BIMProfileProperties
profile_class = props.profile_classes profile_class = props.profile_classes
if profile_class == "IfcArbitraryClosedProfileDef": if profile_class == "IfcArbitraryClosedProfileDef":
points = [(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)] if not obj_points else obj_points obj = props.object_to_profile
profile = ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points) if obj:
if len(obj.data.polygons) == 0:
self.report({"WARNING"}, "This mesh is invalid to create a profile. Select a flat mesh with at least one face.")
props.object_to_profile = None
return
helper = Helper(tool.Ifc.get())
indices = helper.auto_detect_arbitrary_profile_with_voids_extruded_area_solid(obj.data)
if not indices["inner_curves"]:
indices = helper.auto_detect_arbitrary_closed_profile_extruded_area_solid(obj.data)
props.object_to_profile = None
if not indices:
points = [(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]
profile = ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points)
else:
if "inner_curves" not in indices:
points = [(obj.data.vertices[i].co.x,obj.data.vertices[i].co.y) for i in indices["profile"]]
points.append(points[0])
profile = ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points)
else:
outer_points = [(obj.data.vertices[i].co.x,obj.data.vertices[i].co.y) for i in indices["profile"]]
outer_points.append(outer_points[0])
inner_points = [[(obj.data.vertices[i].co.x,obj.data.vertices[i].co.y) for i in curve] for curve in indices["inner_curves"]]
for curve in inner_points:
curve.append(curve[0])
profile = ifcopenshell.api.run("profile.add_arbitrary_profile_with_voids", tool.Ifc.get(), outer_profile=outer_points, inner_profiles=inner_points)
else: else:
profile = ifcopenshell.api.run("profile.add_parameterized_profile", tool.Ifc.get(), ifc_class=profile_class) profile = ifcopenshell.api.run("profile.add_parameterized_profile", tool.Ifc.get(), ifc_class=profile_class)
tool.Profile.set_default_profile_attrs(profile) tool.Profile.set_default_profile_attrs(profile)
+3 -1
View File
@@ -72,7 +72,9 @@ class BIMProfileProperties(PropertyGroup):
description="Check to only show IfcProfileDefs attached to IfcMaterialProfiles", description="Check to only show IfcProfileDefs attached to IfcMaterialProfiles",
update=lambda self, context: bpy.ops.bim.load_profiles(), update=lambda self, context: bpy.ops.bim.load_profiles(),
) )
object_to_profile: PointerProperty(name="Object to profile", type=bpy.types.Object, description="Object to copy the mesh to a profile") object_to_profile: PointerProperty(
name="Object to profile", type=bpy.types.Object, description="Object to copy the mesh to a profile"
)
def generate_thumbnail_for_active_profile(): def generate_thumbnail_for_active_profile():
@@ -2503,7 +2503,6 @@ class ClearMeasurement(bpy.types.Operator):
def poll(cls, context): def poll(cls, context):
return len(context.scene.BIMPolylineProperties.measurement_polyline) > 0 return len(context.scene.BIMPolylineProperties.measurement_polyline) > 0
def execute(self, context): def execute(self, context):
context.scene.BIMPolylineProperties.measurement_polyline.clear() context.scene.BIMPolylineProperties.measurement_polyline.clear()
MeasureDecorator.uninstall() MeasureDecorator.uninstall()
@@ -25,6 +25,7 @@ classes = (
operator.AddProductivityData, operator.AddProductivityData,
operator.AssignResource, operator.AssignResource,
operator.CalculateResourceWork, operator.CalculateResourceWork,
operator.CalculateResourceQuantity,
operator.ConstrainResourceWork, operator.ConstrainResourceWork,
operator.ContractResource, operator.ContractResource,
operator.DisableEditingResource, operator.DisableEditingResource,
@@ -189,9 +189,13 @@ class CalculateResourceWork(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
active_resource = tool.Resource.get_highlighted_resource() active_resource = tool.Resource.get_highlighted_resource()
if active_resource: if not active_resource:
if tool.Resource.get_productivity(active_resource, should_inherit=True): cls.poll_message_set("No resource is active.")
return True return False
if not tool.Resource.get_productivity(active_resource, should_inherit=True):
cls.poll_message_set("No productivity data for active resource.")
return False
return True
def _execute(self, context): def _execute(self, context):
core.calculate_resource_work(tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(self.resource)) core.calculate_resource_work(tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(self.resource))
@@ -345,7 +349,6 @@ class ImportResources(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
class AddProductivityData(bpy.types.Operator, tool.Ifc.Operator): class AddProductivityData(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_productivity_data" bl_idname = "bim.add_productivity_data"
bl_description = "Apply"
bl_label = "Add Productivity" bl_label = "Add Productivity"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -355,7 +358,6 @@ class AddProductivityData(bpy.types.Operator, tool.Ifc.Operator):
class EditProductivityData(bpy.types.Operator, tool.Ifc.Operator): class EditProductivityData(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_productivity_data" bl_idname = "bim.edit_productivity_data"
bl_description = "Apply"
bl_label = "Edit Productivity" bl_label = "Edit Productivity"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -415,13 +417,27 @@ class CalculateResourceUsage(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
active_resource = tool.Resource.get_highlighted_resource() active_resource = tool.Resource.get_highlighted_resource()
if active_resource: if not active_resource:
if active_resource.Usage and active_resource.Usage.ScheduleWork: cls.poll_message_set("No resource is active.")
task = tool.Resource.get_task_assignments(active_resource) return False
if task and tool.Sequence.has_duration(task): if active_resource.Usage and active_resource.Usage.ScheduleWork:
return True task = tool.Resource.get_task_assignments(active_resource)
if task and tool.Sequence.has_duration(task):
return True
cls.poll_message_set("No usage data for active resource.")
return False return False
def _execute(self, context): def _execute(self, context):
core.calculate_resource_usage(tool.Ifc, tool.Resource, resource=tool.Resource.get_highlighted_resource()) core.calculate_resource_usage(tool.Ifc, tool.Resource, resource=tool.Resource.get_highlighted_resource())
class CalculateResourceQuantity(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.calculate_resource_quantity"
bl_label = "Calculate Resource Quantity"
bl_description = "Calcule resource quantity based on the same name quantities from the output products"
bl_options = {"REGISTER", "UNDO"}
resource: bpy.props.IntProperty(name="Resource ID")
def _execute(self, context):
core.calculate_resource_quantity(tool.Resource, resource=tool.Ifc.get().by_id(self.resource))
return {"FINISHED"}
+6 -2
View File
@@ -121,10 +121,11 @@ class BIM_PT_resources(Panel):
col3.ui_units_x = 2 col3.ui_units_x = 2
row1_col1 = col1.row() row1_col1 = col1.row()
row1_col1.label(text="Schedule Work")
row1col2 = col2.row()
schedule_work = resource.get("ScheduleWork", None) schedule_work = resource.get("ScheduleWork", None)
derived_schedule_work = resource.get("DerivedScheduleWork", None) derived_schedule_work = resource.get("DerivedScheduleWork", None)
derived_str = "" if schedule_work else " (Derived)"
row1_col1.label(text=f"Schedule Work {derived_str}")
row1col2 = col2.row()
row1col2.label( row1col2.label(
text="{}".format(schedule_work) if schedule_work else "{} h*".format(derived_schedule_work), text="{}".format(schedule_work) if schedule_work else "{} h*".format(derived_schedule_work),
icon="TIME", icon="TIME",
@@ -258,6 +259,9 @@ class BIM_PT_resources(Panel):
else: else:
op = row.operator("bim.enable_editing_resource_quantity", text="", icon="GREASEPENCIL") op = row.operator("bim.enable_editing_resource_quantity", text="", icon="GREASEPENCIL")
op.resource = self.props.active_resource_id op.resource = self.props.active_resource_id
if resource["type"] == "IfcConstructionMaterialResource":
op = row.operator("bim.calculate_resource_quantity", text="", icon="FILE_REFRESH")
op.resource = self.props.active_resource_id
op = row.operator("bim.remove_resource_quantity", text="", icon="X") op = row.operator("bim.remove_resource_quantity", text="", icon="X")
op.resource = self.props.active_resource_id op.resource = self.props.active_resource_id
@@ -510,6 +510,10 @@ class AssignProcess(bpy.types.Operator, tool.Ifc.Operator):
related_object_type: bpy.props.StringProperty() related_object_type: bpy.props.StringProperty()
related_object: bpy.props.IntProperty() related_object: bpy.props.IntProperty()
@classmethod
def description(cls, context, properties):
return f"Assign selected {properties.related_object_type} to the selected task"
def _execute(self, context): def _execute(self, context):
if self.related_object_type == "RESOURCE": if self.related_object_type == "RESOURCE":
core.assign_resource(tool.Ifc, tool.Sequence, tool.Resource, task=tool.Ifc.get().by_id(self.task)) core.assign_resource(tool.Ifc, tool.Sequence, tool.Resource, task=tool.Ifc.get().by_id(self.task))
@@ -537,6 +541,10 @@ class UnassignProcess(bpy.types.Operator, tool.Ifc.Operator):
related_object: bpy.props.IntProperty() related_object: bpy.props.IntProperty()
resource: bpy.props.IntProperty() resource: bpy.props.IntProperty()
@classmethod
def description(cls, context, properties):
return f"Unassign selected {properties.related_object_type} from the selected task"
def _execute(self, context): def _execute(self, context):
if self.related_object_type == "RESOURCE": if self.related_object_type == "RESOURCE":
core.unassign_resource( core.unassign_resource(
@@ -90,22 +90,24 @@ class AssignContainer(bpy.types.Operator, tool.Ifc.Operator):
core.assign_container(tool.Ifc, tool.Collector, tool.Spatial, container=container, element_obj=element_obj) core.assign_container(tool.Ifc, tool.Collector, tool.Spatial, container=container, element_obj=element_obj)
class EnableEditingContainer(bpy.types.Operator, tool.Ifc.Operator): class EnableEditingContainer(bpy.types.Operator):
bl_idname = "bim.enable_editing_container" bl_idname = "bim.enable_editing_container"
bl_label = "Enable Editing Container" bl_label = "Enable Editing Container"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def execute(self, context):
core.enable_editing_container(tool.Spatial, obj=context.active_object) core.enable_editing_container(tool.Spatial, obj=context.active_object)
return {"FINISHED"}
class DisableEditingContainer(bpy.types.Operator, tool.Ifc.Operator): class DisableEditingContainer(bpy.types.Operator):
bl_idname = "bim.disable_editing_container" bl_idname = "bim.disable_editing_container"
bl_label = "Disable Editing Container" bl_label = "Disable Editing Container"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def execute(self, context):
core.disable_editing_container(tool.Spatial, obj=context.active_object) core.disable_editing_container(tool.Spatial, obj=context.active_object)
return {"FINISHED"}
class RemoveContainer(bpy.types.Operator, tool.Ifc.Operator): class RemoveContainer(bpy.types.Operator, tool.Ifc.Operator):
@@ -155,7 +157,7 @@ class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
bonsai.bim.handler.refresh_ui_data() bonsai.bim.handler.refresh_ui_data()
class SelectContainer(bpy.types.Operator, tool.Ifc.Operator): class SelectContainer(bpy.types.Operator):
bl_idname = "bim.select_container" bl_idname = "bim.select_container"
bl_label = "Select Container" bl_label = "Select Container"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -172,13 +174,13 @@ class SelectContainer(bpy.types.Operator, tool.Ifc.Operator):
self.selection_mode = "SINGLE" self.selection_mode = "SINGLE"
return self.execute(context) return self.execute(context)
def _execute(self, context): def execute(self, context):
if self.container: if self.container:
container = tool.Ifc.get().by_id(self.container) container = tool.Ifc.get().by_id(self.container)
elif element := tool.Ifc.get_entity(context.active_object): elif element := tool.Ifc.get_entity(context.active_object):
container = ifcopenshell.util.element.get_container(element) container = ifcopenshell.util.element.get_container(element)
else: else:
return return {"CANCELLED"}
if container: if container:
core.select_container( core.select_container(
tool.Ifc, tool.Ifc,
@@ -186,15 +188,17 @@ class SelectContainer(bpy.types.Operator, tool.Ifc.Operator):
container=container, container=container,
selection_mode=self.selection_mode, selection_mode=self.selection_mode,
) )
return {"FINISHED"}
class SelectSimilarContainer(bpy.types.Operator, tool.Ifc.Operator): class SelectSimilarContainer(bpy.types.Operator):
bl_idname = "bim.select_similar_container" bl_idname = "bim.select_similar_container"
bl_label = "Select Similar Container" bl_label = "Select Similar Container"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def execute(self, context):
core.select_similar_container(tool.Ifc, tool.Spatial, obj=context.active_object) core.select_similar_container(tool.Ifc, tool.Spatial, obj=context.active_object)
return {"FINISHED"}
class SelectProduct(bpy.types.Operator): class SelectProduct(bpy.types.Operator):
@@ -279,18 +283,19 @@ class ToggleContainerElement(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class SelectDecomposedElement(bpy.types.Operator, tool.Ifc.Operator): class SelectDecomposedElement(bpy.types.Operator):
bl_idname = "bim.select_decomposed_element" bl_idname = "bim.select_decomposed_element"
bl_label = "Select Decomposed Element" bl_label = "Select Decomposed Element"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
element: bpy.props.IntProperty() element: bpy.props.IntProperty()
def _execute(self, context): def execute(self, context):
if self.element: if self.element:
core.select_decomposed_element(tool.Ifc, tool.Spatial, element=tool.Ifc.get().by_id(self.element)) core.select_decomposed_element(tool.Ifc, tool.Spatial, element=tool.Ifc.get().by_id(self.element))
return {"FINISHED"}
class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator): class SelectDecomposedElements(bpy.types.Operator):
bl_idname = "bim.select_decomposed_elements" bl_idname = "bim.select_decomposed_elements"
bl_label = "Select Children" bl_label = "Select Children"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -306,7 +311,7 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
self.should_filter = False self.should_filter = False
return self.execute(context) return self.execute(context)
def _execute(self, context): def execute(self, context):
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
container = ifc_file.by_id(self.container) container = ifc_file.by_id(self.container)
props = context.scene.BIMSpatialDecompositionProperties props = context.scene.BIMSpatialDecompositionProperties
@@ -315,13 +320,13 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
if not self.should_filter and not element_filter: if not self.should_filter and not element_filter:
tool.Spatial.select_products(tool.Spatial.get_decomposed_elements(container)) tool.Spatial.select_products(tool.Spatial.get_decomposed_elements(container))
return return {"CANCELLED"}
if props.element_mode == "TYPE": if props.element_mode == "TYPE":
if active_element.type == "OCCURRENCE": if active_element.type == "OCCURRENCE":
if obj := tool.Ifc.get_object(ifc_file.by_id(active_element.ifc_definition_id)): if obj := tool.Ifc.get_object(ifc_file.by_id(active_element.ifc_definition_id)):
tool.Blender.set_active_object(obj) tool.Blender.set_active_object(obj)
return return {"CANCELLED"}
ifc_class = relating_type = None ifc_class = relating_type = None
is_untyped = False is_untyped = False
@@ -346,7 +351,7 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
if active_element.type == "OCCURRENCE": if active_element.type == "OCCURRENCE":
if obj := tool.Ifc.get_object(ifc_file.by_id(active_element.ifc_definition_id)): if obj := tool.Ifc.get_object(ifc_file.by_id(active_element.ifc_definition_id)):
tool.Blender.set_active_object(obj) tool.Blender.set_active_object(obj)
return return {"CANCELLED"}
if active_element.type == "CLASSIFICATION": if active_element.type == "CLASSIFICATION":
identification = active_element.identification identification = active_element.identification
@@ -362,20 +367,22 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
return False return False
tool.Spatial.select_products(filter(filter_element, elements)) tool.Spatial.select_products(filter(filter_element, elements))
return {"FINISHED"}
class SetDefaultContainer(bpy.types.Operator, tool.Ifc.Operator): class SetDefaultContainer(bpy.types.Operator):
bl_idname = "bim.set_default_container" bl_idname = "bim.set_default_container"
bl_label = "Set Default Container" bl_label = "Set Default Container"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Set this as the default container that all new elements will be contained in" bl_description = "Set this as the default container that all new elements will be contained in"
container: bpy.props.IntProperty() container: bpy.props.IntProperty()
def _execute(self, context): def execute(self, context):
core.set_default_container(tool.Spatial, container=tool.Ifc.get().by_id(self.container)) core.set_default_container(tool.Spatial, container=tool.Ifc.get().by_id(self.container))
return {"FINISHED"}
class SetContainerVisibility(bpy.types.Operator, tool.Ifc.Operator): class SetContainerVisibility(bpy.types.Operator):
bl_idname = "bim.set_container_visibility" bl_idname = "bim.set_container_visibility"
bl_label = "Set Container Visibility" bl_label = "Set Container Visibility"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -396,11 +403,11 @@ class SetContainerVisibility(bpy.types.Operator, tool.Ifc.Operator):
self.should_include_children = False self.should_include_children = False
return self.execute(context) return self.execute(context)
def _execute(self, context): def execute(self, context):
if self.mode == "ISOLATE": if self.mode == "ISOLATE":
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
containers = tool.Ifc.get().by_type("IfcSpatialStructureElement") containers = tool.Ifc.get().by_type("IfcSpatialStructureElement")
elif tool.Ifc.get_schema() != "IFC2X3": else:
containers = set(tool.Ifc.get().by_type("IfcSpatialElement")) containers = set(tool.Ifc.get().by_type("IfcSpatialElement"))
containers -= set(tool.Ifc.get().by_type("IfcSpatialZone")) containers -= set(tool.Ifc.get().by_type("IfcSpatialZone"))
for container in containers: for container in containers:
@@ -420,29 +427,31 @@ class SetContainerVisibility(bpy.types.Operator, tool.Ifc.Operator):
collection.hide_viewport = should_hide collection.hide_viewport = should_hide
if self.should_include_children: if self.should_include_children:
queue.extend(ifcopenshell.util.element.get_parts(container)) queue.extend(ifcopenshell.util.element.get_parts(container))
return {"FINISHED"}
class ToggleGrids(bpy.types.Operator, tool.Ifc.Operator): class ToggleGrids(bpy.types.Operator):
bl_idname = "bim.toggle_grids" bl_idname = "bim.toggle_grids"
bl_label = "Toggle Grids" bl_label = "Toggle Grids"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Show or hide grids and grid axes" bl_description = "Show or hide grids and grid axes"
is_visible: bpy.props.BoolProperty(name="Is Visible", default=False, options={"SKIP_SAVE"}) is_visible: bpy.props.BoolProperty(name="Is Visible", default=False, options={"SKIP_SAVE"})
def _execute(self, context): def execute(self, context):
for element in tool.Ifc.get().by_type("IfcGrid") + tool.Ifc.get().by_type("IfcGridAxis"): for element in tool.Ifc.get().by_type("IfcGrid") + tool.Ifc.get().by_type("IfcGridAxis"):
if obj := tool.Ifc.get_object(element): if obj := tool.Ifc.get_object(element):
obj.hide_set(not self.is_visible) obj.hide_set(not self.is_visible)
return {"FINISHED"}
class ToggleSpatialElements(bpy.types.Operator, tool.Ifc.Operator): class ToggleSpatialElements(bpy.types.Operator):
bl_idname = "bim.toggle_spatial_elements" bl_idname = "bim.toggle_spatial_elements"
bl_label = "Toggle Spatial Elements" bl_label = "Toggle Spatial Elements"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
bl_description = "Show or hide spatial elements, such as buildings, sites, etc" bl_description = "Show or hide spatial elements, such as buildings, sites, etc"
is_visible: bpy.props.BoolProperty(name="Is Visible", default=False, options={"SKIP_SAVE"}) is_visible: bpy.props.BoolProperty(name="Is Visible", default=False, options={"SKIP_SAVE"})
def _execute(self, context): def execute(self, context):
if tool.Ifc.get().schema == "IFC2X3": if tool.Ifc.get().schema == "IFC2X3":
elements = tool.Ifc.get().by_type("IfcSpatialStructureElement") elements = tool.Ifc.get().by_type("IfcSpatialStructureElement")
else: else:
@@ -450,3 +459,4 @@ class ToggleSpatialElements(bpy.types.Operator, tool.Ifc.Operator):
for element in elements: for element in elements:
if obj := tool.Ifc.get_object(element): if obj := tool.Ifc.get_object(element):
obj.hide_set(not self.is_visible) obj.hide_set(not self.is_visible)
return {"FINISHED"}
+1 -1
View File
@@ -113,7 +113,7 @@ def regenerate_sheet(drawing: tool.Drawing, sheet: ifcopenshell.entity_instance)
drawing.delete_file(path_layout) drawing.delete_file(path_layout)
def open_sheet(drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None: def open_layout(drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None:
drawing.open_layout_svg(drawing.get_document_uri(sheet, "LAYOUT")) drawing.open_layout_svg(drawing.get_document_uri(sheet, "LAYOUT"))
+4
View File
@@ -262,3 +262,7 @@ def calculate_resource_usage(
) -> None: ) -> None:
ifc.run("resource.calculate_resource_usage", resource=resource) ifc.run("resource.calculate_resource_usage", resource=resource)
resource_tool.load_resources() resource_tool.load_resources()
def calculate_resource_quantity(resource_tool: tool.Resource, resource: ifcopenshell.entity_instance) -> None:
resource_tool.calculate_resource_quantity(resource)
+1
View File
@@ -39,6 +39,7 @@ class Collector(bonsai.core.tool.Collector):
users_collection.objects.unlink(obj) users_collection.objects.unlink(obj)
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
assert element
# Note that tool.Geometry.is_locked is only checked within the if # Note that tool.Geometry.is_locked is only checked within the if
# statements for efficiency as it is a slow check. # statements for efficiency as it is a slow check.
+8 -2
View File
@@ -774,7 +774,9 @@ class Drawing(bonsai.core.tool.Drawing):
def import_drawings(cls) -> None: def import_drawings(cls) -> None:
props = bpy.context.scene.DocProperties props = bpy.context.scene.DocProperties
expanded_target_views = {d.target_view for d in props.drawings if d.is_expanded} expanded_target_views = {d.target_view for d in props.drawings if d.is_expanded}
current_drawings_selection = {d.ifc_definition_id: d.is_selected for d in props.drawings} if not hasattr(cls, "drawing_selected_states"):
cls.drawing_selected_states = {}
cls.drawing_selected_states.update({d.ifc_definition_id: d.is_selected for d in props.drawings if d.is_drawing})
props.drawings.clear() props.drawings.clear()
drawings = [e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"] drawings = [e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"]
grouped_drawings = { grouped_drawings = {
@@ -801,7 +803,7 @@ class Drawing(bonsai.core.tool.Drawing):
for drawing in sorted(drawings, key=lambda x: x.Name or "Unnamed"): for drawing in sorted(drawings, key=lambda x: x.Name or "Unnamed"):
new = props.drawings.add() new = props.drawings.add()
new.name = drawing.Name or "Unnamed" new.name = drawing.Name or "Unnamed"
new.is_selected = current_drawings_selection.get(drawing.id(), True) new.is_selected = cls.drawing_selected_states.setdefault(drawing.id(), True)
new.is_drawing = True new.is_drawing = True
new.ifc_definition_id = drawing.id() # Last, to prevent unnecessary prop callbacks new.ifc_definition_id = drawing.id() # Last, to prevent unnecessary prop callbacks
@@ -828,6 +830,9 @@ class Drawing(bonsai.core.tool.Drawing):
def import_sheets(cls) -> None: def import_sheets(cls) -> None:
props = bpy.context.scene.DocProperties props = bpy.context.scene.DocProperties
expanded_sheets = {s.ifc_definition_id for s in props.sheets if s.is_expanded} expanded_sheets = {s.ifc_definition_id for s in props.sheets if s.is_expanded}
if not hasattr(cls, "sheet_selected_states"):
cls.sheet_selected_states = {}
cls.sheet_selected_states.update({s.ifc_definition_id: s.is_selected for s in props.sheets if s.is_sheet})
props.sheets.clear() props.sheets.clear()
sheets = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SHEET"] sheets = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SHEET"]
for sheet in sorted(sheets, key=lambda s: getattr(s, "Identification", getattr(s, "DocumentId", None))): for sheet in sorted(sheets, key=lambda s: getattr(s, "Identification", getattr(s, "DocumentId", None))):
@@ -840,6 +845,7 @@ class Drawing(bonsai.core.tool.Drawing):
new.name = sheet.Name new.name = sheet.Name
new.is_sheet = True new.is_sheet = True
new.is_expanded = sheet.id() in expanded_sheets new.is_expanded = sheet.id() in expanded_sheets
new.is_selected = cls.sheet_selected_states.setdefault(sheet.id(), True)
if not new.is_expanded: if not new.is_expanded:
continue continue
+8 -2
View File
@@ -747,7 +747,10 @@ class Geometry(bonsai.core.tool.Geometry):
meshes[mesh_name] = mesh meshes[mesh_name] = mesh
old_mesh = obj.data old_mesh = obj.data
cls.change_object_data(obj, mesh, is_global=False) if type(old_mesh) == type(mesh):
cls.change_object_data(obj, mesh, is_global=False)
else:
obj = cls.recreate_object_with_data(obj, mesh, is_global=False)
cls.record_object_materials(obj) cls.record_object_materials(obj)
if not cls.has_data_users(old_mesh): if not cls.has_data_users(old_mesh):
cls.delete_data(old_mesh) cls.delete_data(old_mesh)
@@ -778,7 +781,10 @@ class Geometry(bonsai.core.tool.Geometry):
meshes[mesh_name] = mesh meshes[mesh_name] = mesh
old_mesh = obj.data old_mesh = obj.data
cls.change_object_data(obj, mesh, is_global=False) if type(old_mesh) == type(mesh):
cls.change_object_data(obj, mesh, is_global=False)
else:
obj = cls.recreate_object_with_data(obj, mesh, is_global=False)
cls.record_object_materials(obj) cls.record_object_materials(obj)
if not cls.has_data_users(old_mesh): if not cls.has_data_users(old_mesh):
cls.delete_data(old_mesh) cls.delete_data(old_mesh)
+6
View File
@@ -443,6 +443,12 @@ class Resource(bonsai.core.tool.Resource):
def run_calculate_resource_usage(cls, resource: ifcopenshell.entity_instance) -> None: def run_calculate_resource_usage(cls, resource: ifcopenshell.entity_instance) -> None:
tool.Ifc.run("resource.calculate_resource_usage", resource=resource) tool.Ifc.run("resource.calculate_resource_usage", resource=resource)
@classmethod
def calculate_resource_quantity(cls, resource: ifcopenshell.entity_instance) -> None:
quantity: ifcopenshell.entity_instance = resource.BaseQuantity
quantity_from_products = ifcopenshell.util.resource.get_total_quantity_produced(resource, quantity.Name)
quantity[3] = quantity_from_products
@classmethod @classmethod
def get_task_assignments(cls, resource: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: def get_task_assignments(cls, resource: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
return ifcopenshell.util.resource.get_task_assignments(resource) return ifcopenshell.util.resource.get_task_assignments(resource)
@@ -101,6 +101,7 @@ def calculate_cost_item_resource_value(file: ifcopenshell.file, cost_item: ifcop
for resource in resources: for resource in resources:
cost, unit = ifcopenshell.util.resource.get_cost(resource) cost, unit = ifcopenshell.util.resource.get_cost(resource)
# TODO: cost is never None because get_cost always returns a float.
if cost is None: if cost is None:
# Concept to standardise - Not defined in schema, but this makes manual scheduling of resources 10x faster and less duplicate data. # Concept to standardise - Not defined in schema, but this makes manual scheduling of resources 10x faster and less duplicate data.
parent_cost = ifcopenshell.util.resource.get_parent_cost(resource) parent_cost = ifcopenshell.util.resource.get_parent_cost(resource)
@@ -36,22 +36,25 @@ def remove_resource(file: ifcopenshell.file, resource: ifcopenshell.entity_insta
""" """
settings = {"resource": resource} settings = {"resource": resource}
def remove_consider_history(root: ifcopenshell.entity_instance) -> None:
history = root.OwnerHistory
file.remove(root)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
# TODO: review deep purge # TODO: review deep purge
for inverse in file.get_inverse(settings["resource"]): for inverse in file.get_inverse(settings["resource"]):
if inverse.is_a("IfcRelNests"): if inverse.is_a("IfcRelNests"):
if inverse.RelatingObject == settings["resource"]: if inverse.RelatingObject == settings["resource"]:
for related_object in inverse.RelatedObjects: for related_object in inverse.RelatedObjects:
ifcopenshell.api.resource.remove_resource(file, resource=related_object) ifcopenshell.api.resource.remove_resource(file, resource=related_object)
history = inverse.OwnerHistory remove_consider_history(inverse)
file.remove(inverse) elif inverse.RelatedObjects == (resource,):
if history: remove_consider_history(inverse)
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAssignsToControl"): elif inverse.is_a("IfcRelAssignsToControl"):
if len(inverse.RelatedObjects) == 1: if len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory remove_consider_history(inverse)
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
else: else:
related_objects = list(inverse.RelatedObjects) related_objects = list(inverse.RelatedObjects)
related_objects.remove(settings["resource"]) related_objects.remove(settings["resource"])
@@ -62,17 +65,11 @@ def remove_resource(file: ifcopenshell.file, resource: ifcopenshell.entity_insta
ifcopenshell.api.resource.unassign_resource( ifcopenshell.api.resource.unassign_resource(
file, related_object=related_object, relating_resource=settings["resource"] file, related_object=related_object, relating_resource=settings["resource"]
) )
elif inverse.RelatedObjects == tuple(settings["resource"]): elif inverse.RelatedObjects == (resource,):
history = inverse.OwnerHistory remove_consider_history(inverse)
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
# Usage was added in IFC4. # Usage was added in IFC4.
if usage := getattr(settings["resource"], "Usage", None): if usage := getattr(settings["resource"], "Usage", None):
file.remove(usage) file.remove(usage)
if settings["resource"].BaseQuantity: if settings["resource"].BaseQuantity:
ifcopenshell.api.resource.remove_resource_quantity(file, resource=settings["resource"]) ifcopenshell.api.resource.remove_resource_quantity(file, resource=settings["resource"])
history = settings["resource"].OwnerHistory remove_consider_history(resource)
file.remove(settings["resource"])
if history:
ifcopenshell.util.element.remove_deep2(file, history)