From 477d49e1e8d91d1bc541cbaed7834e14eea306d9 Mon Sep 17 00:00:00 2001 From: Youen Date: Sat, 15 Oct 2022 19:37:29 +0200 Subject: [PATCH] Added interface to add/remove parts on a view (with selection in tree view and/or 3D view) --- InitGui.py | 21 +++++++- ahb_cmd_new_step.py | 8 +-- ahb_cmd_view_annotate.py | 16 +++--- ahb_cmd_view_edit_source_parts.py | 62 +++++++++++++++++++++++ ahb_document_observer.py | 34 +++++++++++++ ahb_techdraw_extensions.py | 82 +++++++++++++++++++++++++------ 6 files changed, 195 insertions(+), 28 deletions(-) create mode 100644 ahb_cmd_view_edit_source_parts.py diff --git a/InitGui.py b/InitGui.py index 826d8ac..6ad8f19 100644 --- a/InitGui.py +++ b/InitGui.py @@ -1,4 +1,4 @@ -#sys.path.append('/usr/local/lib/python3.9/dist-packages/') +sys.path.append('/usr/local/lib/python3.9/dist-packages/') #import pydevd #pydevd.settrace() @@ -25,6 +25,7 @@ class AssemblyHandbookWorkbench(Gui.Workbench): context = None docObserver = None + docLinkObserver = None partsCache = None techDrawExtensions = None @@ -44,8 +45,11 @@ class AssemblyHandbookWorkbench(Gui.Workbench): if self.context is None: self.initializeContext() + import ahb_document_observer + if self.docLinkObserver is None: + self.docLinkObserver = ahb_document_observer.DocLinkObserver() + if self.docObserver is None: - import ahb_document_observer self.docObserver = ahb_document_observer.DocObserver() App.addDocumentObserver(self.docObserver) @@ -62,6 +66,14 @@ class AssemblyHandbookWorkbench(Gui.Workbench): code which should be computed when this workbench is deactivated """ pass + + def ContextMenu(self, recipient): + # This is executed whenever the user right-clicks on an object + # "recipient" will be either "view" or "tree" + contextMenu = ['AHB_view_edit_source_parts'] + self.appendContextMenu("", "Separator") + self.appendContextMenu("", contextMenu) + self.appendContextMenu("", "Separator") def initializeContext(self): import ahb_context @@ -93,6 +105,11 @@ class AssemblyHandbookWorkbench(Gui.Workbench): self.importModule('ahb_cmd_view_annotate') toolbox.append("AHB_view_annotate") + + self.importModule('ahb_cmd_view_edit_source_parts') + toolbox.append("AHB_view_edit_source_parts") + toolbox.append("AHB_view_add_source_parts") + toolbox.append("AHB_view_remove_source_parts") if self.dev: self.importModule('ahb_cmd_reload') diff --git a/ahb_cmd_new_step.py b/ahb_cmd_new_step.py index 5a192e8..5dd97ba 100644 --- a/ahb_cmd_new_step.py +++ b/ahb_cmd_new_step.py @@ -64,9 +64,9 @@ class AHB_New_Step: view = doc.addObject('TechDraw::DrawViewPart', 'View') view.Perspective = False - view.addProperty("App::PropertyString", "AssemblyHandbook_PreviousStepView", "AssemblyHandbook") + view.addProperty("App::PropertyString", "Assembly_handbook_PreviousStepView", "Assembly_handbook") if prev_view is not None: - view.AssemblyHandbook_PreviousStepView = prev_view.Name + view.Assembly_handbook_PreviousStepView = prev_view.Name view.X = prev_view.X view.Y = prev_view.Y view.Direction = prev_view.Direction @@ -82,8 +82,8 @@ class AHB_New_Step: # TODO: re-number next steps if needed if prev_view is not None: for obj in doc.Objects: - if obj != view and obj.TypeId == 'TechDraw::DrawViewPart' and 'AssemblyHandbook_PreviousStepView' in obj.PropertiesList and obj.AssemblyHandbook_PreviousStepView == prev_view.Name: - obj.AssemblyHandbook_PreviousStepView = view.Name + if obj != view and obj.TypeId == 'TechDraw::DrawViewPart' and 'Assembly_handbook_PreviousStepView' in obj.PropertiesList and obj.Assembly_handbook_PreviousStepView == prev_view.Name: + obj.Assembly_handbook_PreviousStepView = view.Name print(obj.Label + ' has been moved after the new step') if not page.KeepUpdated and len(view.XSource) > 0: diff --git a/ahb_cmd_view_annotate.py b/ahb_cmd_view_annotate.py index 170b67b..a9d0722 100644 --- a/ahb_cmd_view_annotate.py +++ b/ahb_cmd_view_annotate.py @@ -29,11 +29,11 @@ class AHB_View_Annotate: # Remove balloons referencing missing objects for balloon in page.Views: - if balloon.TypeId == 'TechDraw::DrawViewBalloon' and "AssemblyHandbook_PartName" in balloon.PropertiesList: + if balloon.TypeId == 'TechDraw::DrawViewBalloon' and "Assembly_handbook_PartName" in balloon.PropertiesList: if balloon.SourceView != view: continue - partLink = doc.getObject(balloon.AssemblyHandbook_PartName) + partLink = doc.getObject(balloon.Assembly_handbook_PartName) if partLink is None or partLink not in view.XSource: - print(balloon.Name + " references missing object " + balloon.AssemblyHandbook_PartName + ", removing balloon") + print(balloon.Name + " references missing object " + balloon.Assembly_handbook_PartName + ", removing balloon") doc.removeObject(balloon.Name) for partLink in view.XSource: @@ -41,7 +41,7 @@ class AHB_View_Annotate: # Search an existing balloon to update for obj in page.Views: - if obj.TypeId == 'TechDraw::DrawViewBalloon' and "AssemblyHandbook_PartName" in obj.PropertiesList and obj.AssemblyHandbook_PartName == partLink.Name: + if obj.TypeId == 'TechDraw::DrawViewBalloon' and "Assembly_handbook_PartName" in obj.PropertiesList and obj.Assembly_handbook_PartName == partLink.Name: if obj.SourceView != view: continue balloon = obj @@ -52,11 +52,11 @@ class AHB_View_Annotate: balloon = doc.addObject("TechDraw::DrawViewBalloon", "Balloon" + partName) balloon.SourceView = view - balloon.addProperty("App::PropertyString", "AssemblyHandbook_PartName", "AssemblyHandbook") - balloon.AssemblyHandbook_PartName = partName + balloon.addProperty("App::PropertyString", "Assembly_handbook_PartName", "Assembly_handbook") + balloon.Assembly_handbook_PartName = partName - balloon.addProperty("App::PropertyFloat", "AssemblyHandbook_OriginOffsetX", "AssemblyHandbook") - balloon.addProperty("App::PropertyFloat", "AssemblyHandbook_OriginOffsetY", "AssemblyHandbook") + balloon.addProperty("App::PropertyFloat", "Assembly_handbook_OriginOffsetX", "Assembly_handbook") + balloon.addProperty("App::PropertyFloat", "Assembly_handbook_OriginOffsetY", "Assembly_handbook") page.addView(balloon) diff --git a/ahb_cmd_view_edit_source_parts.py b/ahb_cmd_view_edit_source_parts.py new file mode 100644 index 0000000..435c4a7 --- /dev/null +++ b/ahb_cmd_view_edit_source_parts.py @@ -0,0 +1,62 @@ +import FreeCADGui as Gui +import FreeCAD as App + +class AHB_EditViewSourceParts: + def GetResources(self): + return {"MenuText": "Edit view source parts", + "ToolTip": "Edits the list of parts that will be rendered in the selected TechDraw view", + "Pixmap": "" + } + + def IsActive(self): + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench + edit_mode = workbench.techDrawExtensions.edited_view is not None + + if edit_mode: + return True + else: + return len(Gui.Selection.getSelection()) == 1 and Gui.Selection.getSelection()[0].TypeId == 'TechDraw::DrawViewPart' + + def Activated(self): + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench + view = None + if len(Gui.Selection.getSelection()) == 1 and Gui.Selection.getSelection()[0].TypeId == 'TechDraw::DrawViewPart': + view = Gui.Selection.getSelection()[0] + workbench.techDrawExtensions.toggleEditViewSourceParts(view) + +class AHB_AddSourcePartsToView: + def GetResources(self): + return {"MenuText": "Add", + "ToolTip": "Adds the selected part(s) to the currently edited view", + "Pixmap": "" + } + + def IsActive(self): + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench + edit_mode = workbench.techDrawExtensions.edited_view is not None + return edit_mode + + def Activated(self): + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench + workbench.techDrawExtensions.editViewSourceParts(Gui.Selection.getSelection(), True) + +class AHB_RemoveSourcePartsToView: + def GetResources(self): + return {"MenuText": "Remove", + "ToolTip": "Removes the selected part(s) from the currently edited view", + "Pixmap": "" + } + + def IsActive(self): + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench + edit_mode = workbench.techDrawExtensions.edited_view is not None + return edit_mode + + def Activated(self): + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench + workbench.techDrawExtensions.editViewSourceParts(Gui.Selection.getSelection(), False) + +from ahb_command import AHB_CommandWrapper +AHB_CommandWrapper.addGuiCommand('AHB_view_edit_source_parts', AHB_EditViewSourceParts()) +AHB_CommandWrapper.addGuiCommand('AHB_view_add_source_parts', AHB_AddSourcePartsToView()) +AHB_CommandWrapper.addGuiCommand('AHB_view_remove_source_parts', AHB_RemoveSourcePartsToView()) diff --git a/ahb_document_observer.py b/ahb_document_observer.py index f1fb0a9..448cb2d 100644 --- a/ahb_document_observer.py +++ b/ahb_document_observer.py @@ -1,6 +1,40 @@ import FreeCAD as App import FreeCADGui as Gui +# Code copied from Assembly4 to select the top-most link instead of a part or sub-object when clicking in the 3D view +class DocLinkObserver: + select_link_mode = False + + def __init__(self): + Gui.Selection.addObserver(self, 0) # 0 forces to resolve the links + + def addSelection(self, doc, obj, sub, pnt): + # Since both 3D view clicks and manual tree selection gets into the same callback + # we will determine by clicked coordinates, for manual tree selections the coordinates are (0,0,0) + if self.select_link_mode and pnt != (0,0,0): + # 3D view click + objList = App.getDocument(doc).getObject(obj).getSubObjectList(sub) + # Build the name of the selected sub-object for multiple sub-assembly levels + subObjName = '' + # first look for the linked object of the selected entity: + # Get linked object name that handles sub-sub-assembly + for subObj in objList: + if subObj.TypeId=='App::Link': + subObjName = subObjName + subObj.Name + '.' + # if no App::Link found, let's look for other things: + if subObjName == '': + for subObj in objList: + if subObj.TypeId=='App::Part' or subObj.TypeId=='PartDesign::Body'or subObj.isDerivedFrom('Part::Feature'): + # the objList contains also the top-level object, don't count it twice + if subObj.Name != obj: + subObjName = subObjName + subObj.Name + '.' + # if we found something, make it the selection + if subObjName != '': + Gui.Selection.removeSelection(doc, obj, sub) + Gui.Selection.addSelection(doc, obj, subObjName) + #FCC.PrintMessage("*"+doc+"*"+obj+"*"+subObjName+"*\n") + +# The main document observer (also observes selections) class DocObserver: changed_object_by_type = {} selection_by_type = {} diff --git a/ahb_techdraw_extensions.py b/ahb_techdraw_extensions.py index 2880467..bdb62a8 100644 --- a/ahb_techdraw_extensions.py +++ b/ahb_techdraw_extensions.py @@ -61,6 +61,8 @@ class TechDrawExtensions: updating_balloon = False + edited_view = None + def __init__(self): workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench workbench.docObserver.onObjectTypeChanged('balloon_changed', 'TechDraw::DrawViewBalloon', lambda obj, prop: self.onBalloonChanged(obj, prop)) @@ -71,8 +73,6 @@ class TechDrawExtensions: QTimer.singleShot(100, self._do_repaint) def _do_repaint(self): - import time - workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench to_repaint = self.views_to_repaint.keys() @@ -83,7 +83,7 @@ class TechDrawExtensions: selected_balloons = [] for obj in Gui.Selection.getSelection(): - if obj.TypeId == 'TechDraw::DrawViewBalloon' and obj.SourceView == view and 'AssemblyHandbook_PartName' in obj.PropertiesList: + if obj.TypeId == 'TechDraw::DrawViewBalloon' and obj.SourceView == view and 'Assembly_handbook_PartName' in obj.PropertiesList: selected_balloons.append(obj) is_first_part = True @@ -94,7 +94,7 @@ class TechDrawExtensions: for balloon in selected_balloons: if balloon is not None: doc = balloon.Document - partLink = doc.getObject(balloon.AssemblyHandbook_PartName) + partLink = doc.getObject(balloon.Assembly_handbook_PartName) part_view = workbench.partsCache.getPart2DView(view, partLink) @@ -130,7 +130,7 @@ class TechDrawExtensions: l1 = App.Vector(line[2], line[3]) #d = abs(edgeData[0] - line[0]) + abs(edgeData[1] - line[1]) + abs(edgeData[2] - line[2]) + abs(edgeData[3] - line[3]) d = v0.distanceToLineSegment(l0, l1).Length + v1.distanceToLineSegment(l0, l1).Length - if d < 0.001: + if d < 0.01: view.formatGeometricEdge(edgeIdx,1,0.25,(0,0.85,0),True) is_first_part = False @@ -150,27 +150,81 @@ class TechDrawExtensions: cursor.setViewPos(App.Vector(selected_balloons[0].OriginX, selected_balloons[0].OriginY)) cursor.setVisible(True) + def toggleEditViewSourceParts(self, view): + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench + + button = self.getToolbarButton('AHB_view_edit_source_parts') + + if self.edited_view is None: + workbench.docLinkObserver.select_link_mode = True + self.edited_view = view + + button.setChecked(True) + button.setText('End source parts edition') + + Gui.Selection.clearSelection() + for obj in view.XSource: + Gui.Selection.addSelection(obj) + else: + workbench.docLinkObserver.select_link_mode = False + + Gui.Selection.clearSelection() + Gui.Selection.addSelection(self.edited_view) + self.edited_view = None + + button.setChecked(False) + button.setText('Edit view source parts') + + def editViewSourceParts(self, parts, add): + if self.edited_view is None: return + xsource = self.edited_view.XSource + modified = False + for part in parts: + if (part in xsource) != add: + if add: + xsource.append(part) + else: + xsource.remove(part) + modified = True + if modified: + self.edited_view.XSource = xsource + + def getToolbarButton(self, buttonName): + mainwin = Gui.getMainWindow() + toolbar = None + for tb in mainwin.findChildren(QtGui.QToolBar): + if tb.objectName()=='Assembly Handbook': + toolbar = tb + + button = None + if toolbar is not None: + for action in toolbar.actions(): + if action.objectName() == buttonName: + button = action + + return button + def onCursorMoved(self, view, new_pos): if len(Gui.Selection.getSelection()) == 0: return balloon = Gui.Selection.getSelection()[0] - if balloon.TypeId != 'TechDraw::DrawViewBalloon' or not 'AssemblyHandbook_PartName' in balloon.PropertiesList: return + if balloon.TypeId != 'TechDraw::DrawViewBalloon' or not 'Assembly_handbook_PartName' in balloon.PropertiesList: return if balloon.SourceView != view: return balloon.OriginX = new_pos.x balloon.OriginY = new_pos.y - obj = balloon.Document.getObject(balloon.AssemblyHandbook_PartName) + obj = balloon.Document.getObject(balloon.Assembly_handbook_PartName) view = balloon.SourceView center = self.computePartCenter(view, obj) - balloon.AssemblyHandbook_OriginOffsetX = new_pos.x - center.x - balloon.AssemblyHandbook_OriginOffsetY = new_pos.y - center.y + balloon.Assembly_handbook_OriginOffsetX = new_pos.x - center.x + balloon.Assembly_handbook_OriginOffsetY = new_pos.y - center.y def onBalloonSelected(self, operation, balloon, sub, point): import time #print(operation, obj.Name, sub, point) - if "AssemblyHandbook_PartName" in balloon.PropertiesList: + if "Assembly_handbook_PartName" in balloon.PropertiesList: #print(operation + " " + balloon.Name) view = balloon.SourceView self.repaint(view) @@ -181,7 +235,7 @@ class TechDrawExtensions: return #print('Balloon changed: ' + obj.Name + '.' + prop) - if prop == 'Y' and "AssemblyHandbook_PartName" in obj.PropertiesList: + if prop == 'Y' and "Assembly_handbook_PartName" in obj.PropertiesList: self.updating_balloon = True self.updateBalloon(obj) self.updating_balloon = False @@ -191,12 +245,12 @@ class TechDrawExtensions: view = balloon.SourceView doc = view.Document - obj = doc.getObject(balloon.AssemblyHandbook_PartName) + obj = doc.getObject(balloon.Assembly_handbook_PartName) objectCenterView = workbench.techDrawExtensions.computePartCenter(view, obj) - balloon.OriginX = objectCenterView.x + balloon.AssemblyHandbook_OriginOffsetX - balloon.OriginY = objectCenterView.y + balloon.AssemblyHandbook_OriginOffsetY + balloon.OriginX = objectCenterView.x + balloon.Assembly_handbook_OriginOffsetX + balloon.OriginY = objectCenterView.y + balloon.Assembly_handbook_OriginOffsetY balloon.Text = obj.LinkedObject.Document.Name if obj.TypeId == 'App::Link' else obj.Name balloon.ViewObject.Font = 'DejaVu Sans'