diff --git a/src/bonsai/bonsai/bim/module/drawing/__init__.py b/src/bonsai/bonsai/bim/module/drawing/__init__.py index a3578ed702..83dd36f5ce 100644 --- a/src/bonsai/bonsai/bim/module/drawing/__init__.py +++ b/src/bonsai/bonsai/bim/module/drawing/__init__.py @@ -21,6 +21,7 @@ from . import ui, prop, operator, handler, gizmos, workspace classes = ( operator.ActivateDrawing, + operator.ActivateDrawingFromSheet, operator.ActivateDrawingStyle, operator.ActivateModel, operator.AddAnnotation, @@ -52,6 +53,7 @@ classes = ( operator.DisableEditingSheets, operator.DisableEditingText, operator.DuplicateDrawing, + operator.DuplicateSheet, operator.EditAssignedProduct, operator.EditElementFilter, operator.EditSheet, @@ -68,6 +70,7 @@ classes = ( operator.LoadSchedules, operator.LoadSheets, operator.OpenDrawing, + operator.OpenLayout, operator.OpenReference, operator.OpenSchedule, operator.OpenSheet, @@ -86,6 +89,7 @@ classes = ( operator.SaveDrawingStyle, operator.SaveDrawingStylesData, operator.SelectAllDrawings, + operator.SelectAllSheets, operator.SelectAssignedProduct, operator.SelectDocIfcFile, operator.OpenDocumentationWebUi, diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index ef16ebd69e..3f0c4378d9 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -154,10 +154,19 @@ class AddDrawing(bpy.types.Operator, tool.Ifc.Operator): class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.duplicate_drawing" bl_label = "Duplicate Drawing" + bl_description = "Make a copy of currently selected drawing" bl_options = {"REGISTER", "UNDO"} drawing: bpy.props.IntProperty() 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): return context.window_manager.invoke_props_dialog(self) @@ -190,10 +199,16 @@ class CreateDrawing(bpy.types.Operator): bl_idname = "bim.create_drawing" bl_label = "Create Drawing" bl_description = ( - "Creates/refreshes a .svg drawing based on currently active camera.\n\n" - + "SHIFT+CLICK to print all selected drawings" + "Creates/refreshes a .svg drawing based on currently active camera\n" + + '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"}) + open_viewer: bpy.props.BoolProperty(name="Open in Viewer", default=False, options={"SKIP_SAVE"}) 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", @@ -223,6 +238,8 @@ class CreateDrawing(bpy.types.Operator): # make sure to use SKIP_SAVE on property, otherwise it might get stuck if event.type == "LEFTMOUSE" and event.shift: self.print_all = True + if event.type == "LEFTMOUSE" and event.ctrl: + self.open_viewer = True return self.execute(context) def execute(self, context): @@ -291,8 +308,11 @@ class CreateDrawing(bpy.types.Operator): with profile("Combine SVG layers"): 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: bpy.ops.bim.activate_drawing(drawing=original_drawing_id, should_view_from_camera=False) 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) -class OpenSheet(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.open_sheet" - bl_label = "Open Sheet Layout" +class DuplicateSheet(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.duplicate_sheet" + bl_label = "Duplicate Sheet" + bl_description = "Make a copy of currently selected sheet" 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): self.props = context.scene.DocProperties sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id) sheet_builder = sheeter.SheetBuilder() 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): 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"} @classmethod @@ -1496,7 +1657,14 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator): attributes = tool.Drawing.generate_reference_attributes( reference, 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, Description="DRAWING", @@ -1512,9 +1680,20 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator): class RemoveDrawingFromSheet(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_drawing_from_sheet" bl_label = "Remove Drawing From Sheet" + bl_description = "Remove currently selected drawing from sheet" bl_options = {"REGISTER", "UNDO"} 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): reference = tool.Ifc.get().by_id(self.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): bl_idname = "bim.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"} + 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 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): scene = context.scene 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 svg2dxf_command = tool.Blender.get_addon_preferences().svg2dxf_command - 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 svg2pdf_command: - tool.Drawing.open_with_user_command(tool.Blender.get_addon_preferences().pdf_command, pdf) + if self.create_all: + sheets = [tool.Ifc.get().by_id(s.ifc_definition_id) for s in props.sheets if s.is_sheet and s.is_selected] 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): @@ -1651,12 +1861,20 @@ class OpenDrawing(bpy.types.Operator): bl_label = "Open Drawing" view: bpy.props.StringProperty() 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' - + "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"}) + @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): # opening all drawings on shift+click # 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) def execute(self, context): + self.props = context.scene.DocProperties if self.open_all: 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: - 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 = [] drawings_not_found = [] @@ -1682,7 +1901,7 @@ class OpenDrawing(bpy.types.Operator): drawings_not_found.append(drawing.Name) 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) ) self.report({"ERROR"}, msg) @@ -1755,20 +1974,7 @@ class ActivateModel(bpy.types.Operator): return {"FINISHED"} -class ActivateDrawing(bpy.types.Operator): - 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"}) - +class ActivateDrawingBase: def invoke(self, context, event): if event.type == "LEFTMOUSE" and event.alt: self.should_view_from_camera = False @@ -1817,6 +2023,54 @@ class ActivateDrawing(bpy.types.Operator): 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): bl_idname = "bim.select_doc_ifc_file" bl_label = "Select Documentation IFC File" @@ -1856,6 +2110,14 @@ class RemoveDrawing(bpy.types.Operator, tool.Ifc.Operator): drawing: bpy.props.IntProperty() 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): # removing all selected drawings on shift+click # 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): if self.remove_all: 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: if not self.drawing: @@ -2192,6 +2456,7 @@ class RemoveSheet(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_sheet" bl_label = "Remove Sheet" bl_options = {"REGISTER", "UNDO"} + bl_description = "Remove currently selected sheet" sheet: bpy.props.IntProperty() def _execute(self, context): @@ -2260,11 +2525,15 @@ class BuildSchedule(bpy.types.Operator, tool.Ifc.Operator): class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.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"} @classmethod def poll(cls, context): 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 def _execute(self, context): @@ -2299,7 +2568,14 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator): attributes = tool.Drawing.generate_reference_attributes( reference, 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, Description="SCHEDULE", @@ -2316,11 +2592,15 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator): class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.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"} @classmethod def poll(cls, context): 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 def _execute(self, context): @@ -2355,7 +2635,13 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator): attributes = tool.Drawing.generate_reference_attributes( reference, 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 ), Location=extref_location, @@ -2720,7 +3006,8 @@ class LoadSheets(bpy.types.Operator, tool.Ifc.Operator): class EditSheet(bpy.types.Operator, tool.Ifc.Operator): 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"} identification: bpy.props.StringProperty() name: bpy.props.StringProperty() @@ -3069,20 +3356,30 @@ class ConvertSVGToDXF(bpy.types.Operator): bl_label = "Convert SVG to DXF" bl_options = {"REGISTER", "UNDO"} 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"}) + @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): # convert all drawings 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 + self.convert_all = True return self.execute(context) def execute(self, context): if self.convert_all: 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: drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)] diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index 710d1f3918..882e4b3165 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -290,6 +290,7 @@ class Sheet(PropertyGroup): identification: StringProperty(name="Identification") name: StringProperty(name="Name") is_sheet: BoolProperty(name="Is Sheet", default=False) + is_selected: BoolProperty(name="Is Selected", default=True) reference_type: StringProperty(name="Reference Type") is_expanded: BoolProperty(name="Is Expanded", default=False) diff --git a/src/bonsai/bonsai/bim/module/drawing/ui.py b/src/bonsai/bonsai/bim/module/drawing/ui.py index 367ef612fc..58d1a990f2 100644 --- a/src/bonsai/bonsai/bim/module/drawing/ui.py +++ b/src/bonsai/bonsai/bim/module/drawing/ui.py @@ -92,11 +92,6 @@ class BIM_PT_camera(Panel): row = self.layout.row() 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): bl_label = "Element Filters" @@ -252,43 +247,29 @@ class BIM_PT_drawings(Panel): if self.props.active_drawing_index < len(self.props.drawings): active_drawing = self.props.drawings[self.props.active_drawing_index] row = self.layout.row(align=True) - col = row.column() - col.alignment = "LEFT" - row2 = col.row(align=True) + row2 = row.row(align=True) 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 ) - col = row.column() - col.alignment = "RIGHT" + row3 = row.row(align=True) + row3.alignment = "RIGHT" - convert_to_dxf = row.row(align=True) - op = convert_to_dxf.operator("bim.convert_svg_to_dxf", text="", icon="IMAGE_DATA") - op.view = active_drawing.name - convert_to_dxf.enabled = active_drawing.ifc_definition_id > 0 + row3.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="").drawing = ( + active_drawing.ifc_definition_id + ) + 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) - op = open_drawing_button.operator("bim.open_drawing", icon="URL", text="") - op.view = active_drawing.name - open_drawing_button.enabled = active_drawing.ifc_definition_id > 0 - - 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="") + row3.operator("bim.select_all_drawings", icon="CHECKBOX_HLT", text="") + row3.operator("bim.create_drawing", text="", icon="OUTPUT") + row3.operator("bim.convert_svg_to_dxf", text="", icon="SEQ_PREVIEW").view = active_drawing.name + row3.operator("bim.open_drawing", icon="HIDE_OFF", text="").view = active_drawing.name self.layout.template_list( "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: 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") return @@ -375,7 +356,7 @@ class BIM_PT_references(Panel): if not self.props.is_editing_references: 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") return @@ -429,7 +410,23 @@ class BIM_PT_sheets(Panel): if self.props.sheets and self.props.active_sheet_index < len(self.props.sheets): active_sheet = self.props.sheets[self.props.active_sheet_index] 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": drawingnamesvg = active_sheet.name @@ -445,23 +442,21 @@ class BIM_PT_sheets(Panel): break 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 - else: - print(f"No matching drawing ID found for {drawingname}") - row.operator("bim.edit_sheet", icon="GREASEPENCIL", text="") - row.operator("bim.open_sheet", icon="URL", text="") - row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="") - row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="") - row.operator("bim.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="") - row.operator("bim.create_sheets", icon="FILE_REFRESH", text="") - if active_sheet.is_sheet: - row.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id - else: - op = row.operator("bim.remove_drawing_from_sheet", icon="X", text="") - op.reference = active_sheet.ifc_definition_id + row3.separator(factor=0.5, type="SPACE") + + row3.operator("bim.edit_sheet", icon="GREASEPENCIL", text="") + row3.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="") + row3.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="") + row3.operator("bim.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="") + row3.operator("bim.open_layout", icon="CURRENT_FILE", text="") + + row3.separator(factor=0.5, type="SPACE") + + 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") @@ -600,6 +595,13 @@ class BIM_UL_drawinglist(bpy.types.UIList): 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, "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: if item.target_view == "PLAN_VIEW": icon = "UV_FACESEL" @@ -641,13 +643,16 @@ class BIM_UL_sheets(bpy.types.UIList): 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}") else: row.label(text="", icon="BLANK1") if item.reference_type == "DRAWING": row.label(text="", icon="IMAGE_DATA") elif item.reference_type == "SCHEDULE": - row.label(text="", icon="LONGDISPLAY") + row.label(text="", icon="PRESET") elif item.reference_type == "TITLEBLOCK": row.label(text="", icon="MENU_PANEL") elif item.reference_type == "REVISION": diff --git a/src/bonsai/bonsai/core/drawing.py b/src/bonsai/bonsai/core/drawing.py index 4e0c15a714..b9a0b99e6c 100644 --- a/src/bonsai/bonsai/core/drawing.py +++ b/src/bonsai/bonsai/core/drawing.py @@ -113,7 +113,7 @@ def regenerate_sheet(drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) 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")) diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index 88ce914f5b..cdb6e91334 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -774,7 +774,9 @@ class Drawing(bonsai.core.tool.Drawing): def import_drawings(cls) -> None: props = bpy.context.scene.DocProperties 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() drawings = [e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"] grouped_drawings = { @@ -801,7 +803,7 @@ class Drawing(bonsai.core.tool.Drawing): for drawing in sorted(drawings, key=lambda x: x.Name or "Unnamed"): new = props.drawings.add() 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.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: props = bpy.context.scene.DocProperties 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() 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))): @@ -840,6 +845,7 @@ class Drawing(bonsai.core.tool.Drawing): new.name = sheet.Name new.is_sheet = True new.is_expanded = sheet.id() in expanded_sheets + new.is_selected = cls.sheet_selected_states.setdefault(sheet.id(), True) if not new.is_expanded: continue