#*************************************************************************** #* * #* Copyright (c) 2018 kbwbe * #* * #* Portions of code based on hamish's assembly 2 * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * #* as published by the Free Software Foundation; either version 2 of * #* the License, or (at your option) any later version. * #* for detail see the LICENCE text file. * #* * #* This program is distributed in the hope that it will be useful, * #* but WITHOUT ANY WARRANTY; without even the implied warranty of * #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * #* GNU Library General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * #* License along with this program; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * #*************************************************************************** import FreeCADGui,FreeCAD from PySide import QtGui, QtCore import os import copy import sys import platform import a2plib from a2p_MuxAssembly import muxAssemblyWithTopoNames from a2p_versionmanagement import A2P_VERSION import a2p_solversystem from a2plib import getRelativePathesEnabled import a2p_importedPart_class from a2p_topomapper import ( TopoMapper ) import a2p_lcs_support from a2p_importedPart_class import Proxy_importPart, ImportedPartViewProviderProxy import a2p_constraintServices PYVERSION = sys.version_info[0] #============================================================================== class DataContainer(): def __init__(self): self.tx = None #============================================================================== class ObjectCache: ''' An assembly could use multiple instances of then same importPart. Cache them here so fileImports have to be executed only one time... ''' def __init__(self): self.objects = {} # dict, key=fileName, val=object def cleanUp(self,doc): for key in self.objects.keys(): try: doc.removeObject(self.objects[key].Name) #remove temporaryParts from doc except: pass self.objects = {} # dict, key=fileName def add(self,fileName,obj): # pi_obj = PartInformation-Object self.objects[fileName] = obj def get(self,fileName): obj = self.objects.get(fileName,None) if obj: return obj else: return None def isCached(self,fileName): if fileName in self.objects.keys(): return True else: return False def len(self): return len(self.objects.keys()) objectCache = ObjectCache() #============================================================================== class a2p_shapeExtractDialog(QtGui.QDialog): ''' select a label from shape which has to be imported from a file ''' Deleted = QtCore.Signal() Accepted = QtCore.Signal() def __init__(self,parent,labelList = [], data = None): super(a2p_shapeExtractDialog,self).__init__(parent=parent) #super(a2p_shapeExtractDialog,self).__init__() self.labelList = labelList self.data = data self.initUI() def initUI(self): self.resize(400,100) self.setWindowTitle('select a shape to be imported') self.mainLayout = QtGui.QGridLayout() # a VBoxLayout for the whole form self.shapeCombo = QtGui.QComboBox(self) l = sorted(self.labelList) self.shapeCombo.addItems(l) self.buttons = QtGui.QDialogButtonBox(self) self.buttons.setOrientation(QtCore.Qt.Horizontal) self.buttons.addButton("Cancel", QtGui.QDialogButtonBox.RejectRole) self.buttons.addButton("Choose", QtGui.QDialogButtonBox.AcceptRole) self.connect(self.buttons, QtCore.SIGNAL("accepted()"), self, QtCore.SLOT("accept()")) self.connect(self.buttons, QtCore.SIGNAL("rejected()"), self, QtCore.SLOT("reject()")) self.mainLayout.addWidget(self.shapeCombo,0,0,1,1) self.mainLayout.addWidget(self.buttons,1,0,1,1) self.setLayout(self.mainLayout) def accept(self): if self.data != None: self.data.tx = self.shapeCombo.currentText() self.deleteLater() def reject(self): self.deleteLater() #============================================================================== def importPartFromFile( _doc, filename, extractSingleShape = False, # load only a single user defined shape from file desiredShapeLabel=None, importToCache=False, cacheKey = "" ): doc = _doc #------------------------------------------- # Get the importDocument #------------------------------------------- # look only for filenames, not paths, as there are problems on WIN10 (Address-translation??) importDoc = None importDocIsOpen = False requestedFile = os.path.split(filename)[1] for d in FreeCAD.listDocuments().values(): recentFile = os.path.split(d.FileName)[1] if requestedFile == recentFile: importDoc = d # file is already open... importDocIsOpen = True break if not importDocIsOpen: if filename.lower().endswith('.fcstd'): importDoc = FreeCAD.openDocument(filename) elif filename.lower().endswith('.stp') or filename.lower().endswith('.step'): import ImportGui fname = os.path.splitext(os.path.basename(filename))[0] FreeCAD.newDocument(fname) newname = FreeCAD.ActiveDocument.Name FreeCAD.setActiveDocument(newname) ImportGui.insert(filename,newname) importDoc = FreeCAD.ActiveDocument else: msg = "A part can only be imported from a FreeCAD '*.FCStd' file" QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Value Error", msg ) return #------------------------------------------- # recalculate imported part if requested by preferences # This can be useful if the imported part depends on an # external master-spreadsheet #------------------------------------------- if a2plib.getRecalculateImportedParts(): for ob in importDoc.Objects: ob.recompute() importDoc.save() # useless without saving... #------------------------------------------- # Initialize the new TopoMapper #------------------------------------------- topoMapper = TopoMapper(importDoc) #------------------------------------------- # Get a list of the importable Objects #------------------------------------------- importableObjects = topoMapper.getTopLevelObjects(allowSketches=True) if len(importableObjects) == 0: msg = "No visible Part to import found. Aborting operation" QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Import Error", msg ) return #------------------------------------------- # if only one single shape of the importdoc is wanted.. #------------------------------------------- labelList = [] dc = DataContainer() if extractSingleShape: if desiredShapeLabel is None: # ask for a shape label for io in importableObjects: labelList.append(io.Label) dialog = a2p_shapeExtractDialog( QtGui.QApplication.activeWindow(), labelList, dc) dialog.exec_() if dc.tx is None: msg = "Import of a shape reference aborted by user" QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Import Error", msg ) return else: # use existent shape label dc.tx = desiredShapeLabel #------------------------------------------- # Discover whether we are importing a subassembly or a single part #------------------------------------------- subAssemblyImport = False if all([ 'importPart' in obj.Content for obj in importableObjects]) == 1: subAssemblyImport = True #------------------------------------------- # create new object #------------------------------------------- if importToCache: partName = 'CachedObject_'+str(objectCache.len()) newObj = doc.addObject("Part::FeaturePython",partName) newObj.Label = partName else: partName = a2plib.findUnusedObjectName( importDoc.Label, document=doc ) if extractSingleShape == False: partLabel = a2plib.findUnusedObjectLabel( importDoc.Label, document=doc ) else: partLabel = a2plib.findUnusedObjectLabel( importDoc.Label, document=doc, extension=dc.tx ) if PYVERSION < 3: newObj = doc.addObject( "Part::FeaturePython", partName.encode('utf-8') ) else: newObj = doc.addObject( "Part::FeaturePython", str(partName.encode('utf-8')) ) # works on Python 3.6.5 newObj.Label = partLabel Proxy_importPart(newObj) if FreeCAD.GuiUp: ImportedPartViewProviderProxy(newObj.ViewObject) newObj.a2p_Version = A2P_VERSION assemblyPath = os.path.normpath(os.path.split(doc.FileName)[0]) absPath = os.path.normpath(filename) if getRelativePathesEnabled(): if platform.system() == "Windows": prefix = '.\\' else: prefix = './' relativePath = prefix+os.path.relpath(absPath, assemblyPath) newObj.sourceFile = relativePath else: newObj.sourceFile = absPath if dc.tx is not None: newObj.sourcePart = dc.tx newObj.setEditorMode("timeLastImport",1) newObj.timeLastImport = os.path.getmtime( filename ) if a2plib.getForceFixedPosition(): newObj.fixedPosition = True else: newObj.fixedPosition = not any([i.fixedPosition for i in doc.Objects if hasattr(i, 'fixedPosition') ]) newObj.subassemblyImport = subAssemblyImport newObj.setEditorMode("subassemblyImport",1) if subAssemblyImport: if extractSingleShape: newObj.muxInfo, newObj.Shape, newObj.ViewObject.DiffuseColor, newObj.ViewObject.Transparency = \ muxAssemblyWithTopoNames(importDoc,desiredShapeLabel = dc.tx) else: newObj.muxInfo, newObj.Shape, newObj.ViewObject.DiffuseColor, newObj.ViewObject.Transparency = \ muxAssemblyWithTopoNames(importDoc) else: # TopoMapper manages import of non A2p-Files. It generates the shapes and appropriate topo names... if extractSingleShape: newObj.muxInfo, newObj.Shape, newObj.ViewObject.DiffuseColor, newObj.ViewObject.Transparency = \ topoMapper.createTopoNames(desiredShapeLabel = dc.tx) else: newObj.muxInfo, newObj.Shape, newObj.ViewObject.DiffuseColor, newObj.ViewObject.Transparency = \ topoMapper.createTopoNames() newObj.objectType = 'a2pPart' if extractSingleShape == True: if a2plib.isA2pSketch(newObj): newObj.objectType = 'a2pSketch' newObj.setEditorMode("objectType",1) doc.recompute() if importToCache: # this import is used to update already imported parts objectCache.add(cacheKey, newObj) else: # this is a first time import of a part if not a2plib.getPerFaceTransparency(): # turn of perFaceTransparency by accessing ViewObject.Transparency and set to zero (non transparent) newObj.ViewObject.Transparency = 1 newObj.ViewObject.Transparency = 0 # import assembly first time as non transparent. lcsList = a2p_lcs_support.getListOfLCS(doc,importDoc) if not importDocIsOpen: FreeCAD.closeDocument(importDoc.Name) if len(lcsList) > 0: #========================================= # create a group containing imported LCS's lcsGroupObjectName = 'LCS_Collection' lcsGroupLabel = 'LCS_Collection' if PYVERSION < 3: lcsGroup = doc.addObject( "Part::FeaturePython", lcsGroupObjectName.encode('utf-8') ) else: lcsGroup = doc.addObject( "Part::FeaturePython", str(lcsGroupObjectName.encode('utf-8')) ) # works on Python 3.6.5 lcsGroup.Label = lcsGroupLabel a2p_lcs_support.LCS_Group(lcsGroup) a2p_lcs_support.VP_LCS_Group(lcsGroup.ViewObject) for lcs in lcsList: lcsGroup.addObject(lcs) lcsGroup.Owner = newObj.Name newObj.addProperty("App::PropertyLinkList","lcsLink","importPart").lcsLink = lcsGroup newObj.Label = newObj.Label # this is needed to trigger an update lcsGroup.Label = lcsGroup.Label #========================================= return newObj #============================================================================== toolTip = \ ''' Add a single shape out of an external file to the assembly ''' class a2p_ImportShapeReferenceCommand(): def GetResources(self): return {'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_ShapeReference.svg', #'Accel' : "Shift+A", # a default shortcut (optional) 'MenuText': "Add a single shape out of an external file", 'ToolTip' : toolTip } def Activated(self): if FreeCAD.ActiveDocument is None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "No active Document found", '''First create an empty file and\nsave it under desired name''' ) return # if FreeCAD.ActiveDocument.FileName == '': QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Unnamed document", '''Before inserting first part,\nplease save the empty assembly\nto give it a name''' ) FreeCADGui.SendMsgToActiveView("Save") return doc = FreeCAD.activeDocument() guidoc = FreeCADGui.activeDocument() view = guidoc.activeView() dialog = QtGui.QFileDialog( QtGui.QApplication.activeWindow(), "Select FreeCAD document to import part from" ) # set option "DontUseNativeDialog"=True, as native Filedialog shows # misbehavior on Unbuntu 18.04 LTS. It works case sensitively, what is not wanted... if a2plib.getNativeFileManagerUsage(): dialog.setOption(QtGui.QFileDialog.DontUseNativeDialog, False) else: dialog.setOption(QtGui.QFileDialog.DontUseNativeDialog, True) dialog.setNameFilter("Supported Formats (*.FCStd *.fcstd *.stp *.step);;All files (*.*)") if dialog.exec_(): if PYVERSION < 3: filename = unicode(dialog.selectedFiles()[0]) else: filename = str(dialog.selectedFiles()[0]) else: return if not a2plib.checkFileIsInProjectFolder(filename): msg = \ ''' The part you try to import is outside of your project-folder ! Check your settings of A2plus preferences. ''' QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Import Error", msg ) return #TODO: change for multi separate part import importedObject = importPartFromFile(doc, filename, extractSingleShape=True) if not importedObject: a2plib.Msg("imported Object is empty/none\n") return mw = FreeCADGui.getMainWindow() mdi = mw.findChild(QtGui.QMdiArea) sub = mdi.activeSubWindow() if sub != None: sub.showMaximized() # WF: how will this work for multiple imported objects? # only A2p AI's will have property "fixedPosition" if importedObject and a2plib.isA2pSketch(importedObject): importedObject.fixedPosition = True if importedObject and not a2plib.isA2pSketch(importedObject) and not importedObject.fixedPosition: PartMover( view, importedObject, deleteOnEscape = True ) else: self.timer = QtCore.QTimer() QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.GuiViewFit) self.timer.start( 200 ) #0.2 seconds return def IsActive(self): doc = FreeCAD.activeDocument() if doc is None: return False return True def GuiViewFit(self): FreeCADGui.SendMsgToActiveView("ViewFit") self.timer.stop() FreeCADGui.addCommand('a2p_ImportShapeReferenceCommand',a2p_ImportShapeReferenceCommand()) #============================================================================== toolTip = \ ''' Restore transparency to active document objects ''' class a2p_Restore_Transparency_Command(): def GetResources(self): return {'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_Restore_Transparency.svg', 'Accel' : "Shift+T", # a default shortcut (optional) 'MenuText': "Restore transparency to active document objects", 'ToolTip' : toolTip } def Activated(self): doc = FreeCAD.ActiveDocument if doc is None: FreeCAD.Console.Print("No active document found") return else: for obj in doc.Objects: if hasattr (obj, 'ViewObject'): if hasattr (obj.ViewObject, 'Transparency'): if obj.ViewObject.Transparency < 100: transparency = obj.ViewObject.Transparency obj.ViewObject.Transparency = transparency + 1 obj.ViewObject.Transparency = transparency return def IsActive(self): doc = FreeCAD.activeDocument() if doc is None: return False return True FreeCADGui.addCommand('a2p_Restore_Transparency',a2p_Restore_Transparency_Command()) #============================================================================== toolTip = \ ''' Add a part from an external file to the assembly ''' class a2p_ImportPartCommand(): def GetResources(self): return {'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_ImportPart.svg', 'Accel' : "Shift+A", # a default shortcut (optional) 'MenuText': "Add a part from an external file", 'ToolTip' : toolTip } def Activated(self): if FreeCAD.ActiveDocument is None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "No active Document found", '''First create an empty file and\nsave it under desired name''' ) return # if FreeCAD.ActiveDocument.FileName == '': QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Unnamed document", '''Before inserting first part,\nplease save the empty assembly\nto give it a name''' ) FreeCADGui.SendMsgToActiveView("Save") return doc = FreeCAD.activeDocument() guidoc = FreeCADGui.activeDocument() view = guidoc.activeView() dialog = QtGui.QFileDialog( QtGui.QApplication.activeWindow(), "Select FreeCAD document to import part from" ) # set option "DontUseNativeDialog"=True, as native Filedialog shows # misbehavior on Unbuntu 18.04 LTS. It works case sensitively, what is not wanted... if a2plib.getNativeFileManagerUsage(): dialog.setOption(QtGui.QFileDialog.DontUseNativeDialog, False) else: dialog.setOption(QtGui.QFileDialog.DontUseNativeDialog, True) dialog.setNameFilter("Supported Formats (*.FCStd *.fcstd *.stp *.step);;All files (*.*)") if dialog.exec_(): if PYVERSION < 3: filename = unicode(dialog.selectedFiles()[0]) else: filename = str(dialog.selectedFiles()[0]) else: return if not a2plib.checkFileIsInProjectFolder(filename): msg = \ ''' The part you try to import is outside of your project-folder ! Check your settings of A2plus preferences. ''' QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Import Error", msg ) return #TODO: change for multi separate part import importedObject = importPartFromFile(doc, filename) if not importedObject: a2plib.Msg("imported Object is empty/none\n") return mw = FreeCADGui.getMainWindow() mdi = mw.findChild(QtGui.QMdiArea) sub = mdi.activeSubWindow() if sub != None: sub.showMaximized() # WF: how will this work for multiple imported objects? # only A2p AI's will have property "fixedPosition" if importedObject and not importedObject.fixedPosition: PartMover( view, importedObject, deleteOnEscape = True ) else: self.timer = QtCore.QTimer() QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.GuiViewFit) self.timer.start( 200 ) #0.2 seconds return def IsActive(self): doc = FreeCAD.activeDocument() if doc is None: return False return True def GuiViewFit(self): FreeCADGui.SendMsgToActiveView("ViewFit") self.timer.stop() FreeCADGui.addCommand('a2p_ImportPart',a2p_ImportPartCommand()) #============================================================================== def updateImportedParts(doc, partial=False): if doc is None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "No active document found!", "Before updating parts, you have to open an assembly file." ) return doc.openTransaction("updateImportParts") objectCache.cleanUp(doc) selectedObjects=[] selection = [s for s in FreeCADGui.Selection.getSelection() if s.Document == FreeCAD.ActiveDocument and (a2plib.isA2pPart(s) or a2plib.isA2pSketch()) ] if selection and len(selection)>0: if partial==True: response = QtGui.QMessageBox.Yes else: flags = QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No msg = u"Do you want to update only the selected parts?" response = QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), u"ASSEMBLY UPDATE", msg, flags ) if response == QtGui.QMessageBox.Yes: for s in selection: selectedObjects.append(s) if len(selectedObjects) >0: workingSet = selectedObjects else: workingSet = doc.Objects for obj in workingSet: if hasattr(obj, 'sourceFile') and a2plib.to_str(obj.sourceFile) != a2plib.to_str('converted'): #repair data structures (perhaps an old Assembly2 import was found) if hasattr(obj,"Content") and 'importPart' in obj.Content: # be sure to have an assembly object if obj.Proxy is None: #print (u"Repair Proxy of: {}, Proxy: {}".format(obj.Label, obj.Proxy)) Proxy_importPart(obj) ImportedPartViewProviderProxy(obj.ViewObject) assemblyPath = os.path.normpath(os.path.split(doc.FileName)[0]) absPath = a2plib.findSourceFileInProject(obj.sourceFile, assemblyPath) if absPath is None: QtGui.QMessageBox.critical( QtGui.QApplication.activeWindow(), u"Source file not found", u"Unable to find {}".format( obj.sourceFile ) ) if absPath != None and os.path.exists( absPath ): newPartCreationTime = os.path.getmtime( absPath ) if ( newPartCreationTime > obj.timeLastImport or obj.a2p_Version != A2P_VERSION or a2plib.getRecalculateImportedParts() # open always all parts as they could depend on spreadsheets ): cacheKeyExtension = obj.sourcePart if cacheKeyExtension is None: cacheKeyExtension = "AllShapes" elif cacheKeyExtension == "": cacheKeyExtension = "AllShapes" cacheKeyExtension = '-' + cacheKeyExtension cacheKey = absPath+cacheKeyExtension if not objectCache.isCached(cacheKey): # Load every changed object one time to cache if obj.sourcePart is not None and obj.sourcePart != '': importPartFromFile( doc, absPath, importToCache=True, cacheKey = cacheKey, extractSingleShape = True, desiredShapeLabel = obj.sourcePart ) # the version is now in the cache else: importPartFromFile( doc, absPath, importToCache=True, cacheKey = cacheKey ) # the version is now in the cache newObject = objectCache.get(cacheKey) obj.timeLastImport = newPartCreationTime if hasattr(newObject, 'a2p_Version'): obj.a2p_Version = A2P_VERSION importUpdateConstraintSubobjects( doc, obj, newObject ) # do this before changing shape and mux if hasattr(newObject, 'muxInfo'): obj.muxInfo = newObject.muxInfo # save Placement because following newObject.Shape.copy() isn't resetting it to zeroes... savedPlacement = obj.Placement obj.Shape = newObject.Shape.copy() if a2plib.isA2pSketch(obj): pass else: obj.Placement = savedPlacement # restore the old placement a2plib.copyObjectColors(obj,newObject) #repair constraint directions if for e.g. face-normals flipped around during updating of parts. a2p_constraintServices.redAdjustConstraintDirections(doc) mw = FreeCADGui.getMainWindow() mdi = mw.findChild(QtGui.QMdiArea) sub = mdi.activeSubWindow() if sub != None: sub.showMaximized() objectCache.cleanUp(doc) a2p_solversystem.autoSolveConstraints( doc, useTransaction = False, callingFuncName = "updateImportedParts" ) #transaction is already open... doc.recompute() doc.commitTransaction() toolTip = \ ''' Update parts, which have been imported to the assembly. (If you modify a part in an external file, the new shape is taken to the assembly by this function.) ''' class a2p_UpdateImportedPartsCommand: def Activated(self): doc = FreeCAD.ActiveDocument updateImportedParts(doc) def GetResources(self): return { 'Pixmap' : a2plib.path_a2p + '/icons/a2p_ImportPart_Update.svg', 'MenuText': 'Update parts imported into the assembly', 'ToolTip': toolTip } FreeCADGui.addCommand('a2p_updateImportedParts', a2p_UpdateImportedPartsCommand()) def duplicateImportedPart( part ): doc = FreeCAD.ActiveDocument nameBase = part.Label partName = a2plib.findUnusedObjectName(nameBase,document=doc) partLabel = a2plib.findUnusedObjectLabel(nameBase,document=doc) if PYVERSION >= 3: newObj = doc.addObject("Part::FeaturePython", str(partName.encode("utf-8")) ) else: newObj = doc.addObject("Part::FeaturePython", partName.encode("utf-8") ) newObj.Label = partLabel Proxy_importPart(newObj) ImportedPartViewProviderProxy(newObj.ViewObject) newObj.a2p_Version = part.a2p_Version newObj.sourceFile = part.sourceFile newObj.sourcePart = part.sourcePart newObj.timeLastImport = part.timeLastImport newObj.setEditorMode("timeLastImport",1) newObj.fixedPosition = False newObj.updateColors = getattr(part,'updateColors',True) newObj.muxInfo = part.muxInfo newObj.subassemblyImport = part.subassemblyImport newObj.Shape = part.Shape.copy() for p in part.ViewObject.PropertiesList: #assuming that the user may change the appearance of parts differently depending on their role in the assembly. if hasattr(part.ViewObject, p) and p not in ['DiffuseColor','Proxy','MappedColors']: setattr(newObj.ViewObject, p, getattr( part.ViewObject, p)) newObj.ViewObject.DiffuseColor = copy.copy( part.ViewObject.DiffuseColor ) newObj.ViewObject.Transparency = part.ViewObject.Transparency newObj.Placement.Base = part.Placement.Base newObj.Placement.Rotation = part.Placement.Rotation return newObj toolTip = \ ''' Make a duplicate of a part, which is already imported to the assembly. Select a imported part and hit this button. A duplicate will be created and can be placed somewhere by mouse. Hold "Shift" for doing this multiple times. ''' class a2p_DuplicatePartCommand: def __init__(self): self.partMover = None def Activated(self): doc = FreeCAD.activeDocument() selection = [s for s in FreeCADGui.Selection.getSelectionEx() if s.Document == doc ] self.partMover = PartMover( FreeCADGui.activeDocument().activeView(), duplicateImportedPart(selection[0].Object), deleteOnEscape = True ) self.timer = QtCore.QTimer() QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.onTimer) self.timer.start( 100 ) def onTimer(self): if self.partMover != None: if self.partMover.objectToDelete != None: FreeCAD.activeDocument().removeObject(self.partMover.objectToDelete.Name) self.partMover.objectToDelete = None self.timer.start(100) def IsActive(self): doc = FreeCAD.activeDocument() if doc is None: return False # selection = [s for s in FreeCADGui.Selection.getSelectionEx() if s.Document == doc ] if len(selection) != 1: return False # obj = selection[0].Object if not a2plib.isA2pPart(obj): return False # return True def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_DuplicatePart.svg', 'MenuText': 'Create duplicate of a part', 'ToolTip': toolTip } FreeCADGui.addCommand('a2p_duplicatePart', a2p_DuplicatePartCommand()) toolTip = \ ''' Edit an imported part. Select an imported part and hit this button. The appropriate FCStd file, linked to this part will be opened and you can modify this part at this place. After editing and saving, you have to use the function 'update imported parts' in order to see the new shape within the assembly. ''' class a2p_EditPartCommand: def Activated(self): doc = FreeCAD.activeDocument() #==================================================== # Is there an open Doc ? #==================================================== if doc is None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), u"No active document found!", u"Before editing a part, you have to open an assembly file." ) return #==================================================== # Is something been selected ? #==================================================== selection = [s for s in FreeCADGui.Selection.getSelection() if s.Document == FreeCAD.ActiveDocument ] if not selection: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), u"Selection Error", u"You must select a part to edit first." ) return #==================================================== # Has the selected object an editable a2p file ? #==================================================== obj = selection[0] if not a2plib.isEditableA2pPart(obj): QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), u"Edit: Selection invalid!", u"This object is no imported part!" ) return #==================================================== # Does the file exist ? #==================================================== obj = selection[0] FreeCADGui.Selection.clearSelection() # very important! Avoid Editing the assembly the part was called from! assemblyPath = os.path.normpath(os.path.split(doc.FileName)[0]) fileNameWithinProjectFile = a2plib.findSourceFileInProject(obj.sourceFile, assemblyPath) if fileNameWithinProjectFile is None: msg = \ ''' You want to edit a file which is not found below your project-folder. This is not allowed when using preference "Use project Folder" ''' QtGui.QMessageBox.critical( QtGui.QApplication.activeWindow(), "File error ! ", msg ) return #==================================================== # Open the file for editing and switch the window #==================================================== #Workaround to detect open files on Win10 (Address Translation problem??) importDocIsOpen = False requestedFile = os.path.split(fileNameWithinProjectFile)[1] for d in FreeCAD.listDocuments().values(): recentFile = os.path.split(d.FileName)[1] if requestedFile == recentFile: importDoc = d # file is already open... importDocIsOpen = True break if not importDocIsOpen: if fileNameWithinProjectFile.lower().endswith('.stp') or fileNameWithinProjectFile.lower().endswith('.step'): import ImportGui fname = os.path.splitext(os.path.basename(fileNameWithinProjectFile))[0] FreeCAD.newDocument(fname) newname = FreeCAD.ActiveDocument.Name ImportGui.open(fileNameWithinProjectFile, newname) FreeCAD.ActiveDocument.Label = fname FreeCADGui.SendMsgToActiveView("ViewFit") msg = "Editing a STEP file as '*.FCStd' file\nPlease export the saved file as \'.step\'\n" + fileNameWithinProjectFile QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Info", msg ) else: FreeCAD.open(fileNameWithinProjectFile) else: name = importDoc.Name # Search and activate the corresponding document window.. mw=FreeCADGui.getMainWindow() mdi=mw.findChild(QtGui.QMdiArea) sub=mdi.subWindowList() for s in sub: mdi.setActiveSubWindow(s) if FreeCAD.activeDocument().Name == name: break def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_EditPart.svg', 'MenuText': 'Edit an imported part (open linked FCStd file)', 'ToolTip': toolTip } FreeCADGui.addCommand('a2p_editImportedPart', a2p_EditPartCommand()) #=============================================================================== class PartMover: def __init__(self, view, obj, deleteOnEscape): self.obj = obj self.initialPosition = self.obj.Placement.Base self.view = view self.deleteOnEscape = deleteOnEscape self.callbackMove = self.view.addEventCallback("SoLocation2Event",self.moveMouse) self.callbackClick = self.view.addEventCallback("SoMouseButtonEvent",self.clickMouse) self.callbackKey = self.view.addEventCallback("SoKeyboardEvent",self.KeyboardEvent) self.objectToDelete = None # object reference when pressing the escape key def moveMouse(self, info): newPos = self.view.getPoint( *info['Position'] ) self.obj.Placement.Base = newPos def removeCallbacks(self): self.view.removeEventCallback("SoLocation2Event",self.callbackMove) self.view.removeEventCallback("SoMouseButtonEvent",self.callbackClick) self.view.removeEventCallback("SoKeyboardEvent",self.callbackKey) def clickMouse(self, info): if info['Button'] == 'BUTTON1' and info['State'] == 'DOWN': #if not info['ShiftDown'] and not info['CtrlDown']: #struggles within Inventor Navigation if not info['ShiftDown']: self.removeCallbacks() FreeCAD.activeDocument().recompute() elif info['ShiftDown']: self.obj = duplicateImportedPart(self.obj) self.deleteOnEscape = True def KeyboardEvent(self, info): if info['State'] == 'UP' and info['Key'] == 'ESCAPE': self.removeCallbacks() if not self.deleteOnEscape: self.obj.Placement.Base = self.initialPosition else: self.objectToDelete = self.obj #This can be asked by a timer in a calling func... #This causes a crash in FC0.19/Qt5/Py3 #FreeCAD.activeDocument().removeObject(self.obj.Name) #=============================================================================== toolTip = \ ''' Move the selected part. Select a part and hit this button. The part can be moved around by mouse. If the part is constrained, it will jump back by next solving of the assembly. ''' class a2p_MovePartCommand: def __init__(self): self.partMover = None def Activated(self): doc = FreeCAD.activeDocument() selection = [s for s in FreeCADGui.Selection.getSelectionEx() if s.Document == doc ] FreeCADGui.ActiveDocument.setEdit(selection[0].Object) def IsActive(self): doc = FreeCAD.activeDocument() if doc is None: return False # selection = [s for s in FreeCADGui.Selection.getSelectionEx() if s.Document == doc ] if len(selection) != 1: return False # obj = selection[0].Object if not a2plib.isA2pPart(obj): return False # return True def GetResources(self): return { #'Pixmap' : ':/assembly2/icons/MovePart.svg', 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_MovePart.svg', 'MenuText': 'Move the selected part', 'ToolTip': toolTip } FreeCADGui.addCommand('a2p_movePart', a2p_MovePartCommand()) #=============================================================================== class ConstrainedPartsMover: def __init__(self, view): self.obj = None self.view = view self.doc = FreeCAD.activeDocument() self.callbackMove = self.view.addEventCallback("SoLocation2Event",self.onMouseMove) self.callbackClick = self.view.addEventCallback("SoMouseButtonEvent",self.onMouseClicked) self.callbackKey = self.view.addEventCallback("SoKeyboardEvent",self.KeyboardEvent) self.motionActivated = False def setPreselection(self,doc,obj,sub): if not self.motionActivated: doc = FreeCAD.activeDocument() self.obj = doc.getObject(obj) def addSelection(self,doc,obj,sub,pnt): pass def removeSelection(self,doc,obj,sub): pass def clearSelection(self,doc): pass def onMouseMove(self, info): if self.obj is None: return if self.motionActivated: newPos = self.view.getPoint( *info['Position'] ) self.obj.Placement.Base = newPos a2plib.setSimulationState(True) systemSolved = a2p_solversystem.solveConstraints(self.doc, useTransaction = False) a2plib.setSimulationState(False) if systemSolved == False: self.doc.commitTransaction() QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Animation problem detected", "Use system undo if necessary." ) self.removeCallbacks() def removeCallbacks(self): self.view.removeEventCallback("SoLocation2Event",self.callbackMove) self.view.removeEventCallback("SoMouseButtonEvent",self.callbackClick) self.view.removeEventCallback("SoKeyboardEvent",self.callbackKey) FreeCADGui.Selection.removeObserver(self) def onMouseClicked(self, info): if self.obj is None: return if info['Button'] == 'BUTTON1' and info['State'] == 'DOWN': if hasattr(self.obj, 'fixedPosition') and self.obj.fixedPosition == True: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Invalid selection", '''A2plus will not move a part with property fixedPosition == True''' ) self.removeCallbacks() del self else: self.motionActivated = not self.motionActivated if self.motionActivated == True: self.doc.openTransaction("drag constrained parts") if self.motionActivated == False: # Solve last time with high accuracy to finish a2plib.setSimulationState(False) a2p_solversystem.solveConstraints(self.doc, useTransaction = False) self.doc.commitTransaction() self.removeCallbacks() def KeyboardEvent(self, info): doc = FreeCAD.activeDocument() if info['State'] == 'UP' and info['Key'] == 'ESCAPE': doc.commitTransaction() self.removeCallbacks() #=============================================================================== toolTip = \ ''' Move the a part under rule of constraints. 1) Hit this button 2) Click a part and it is glued to the cursor and can be moved 3) Click again (or press ESC) and the command terminates ''' class a2p_MovePartUnderConstraints: def __init__(self): self.partMover = None def Activated(self): self.partMover = ConstrainedPartsMover( FreeCADGui.activeDocument().activeView() ) FreeCADGui.Selection.addObserver(self.partMover) def IsActive(self): doc = FreeCAD.activeDocument() if doc is None: return False # #selection = [s for s in FreeCADGui.Selection.getSelectionEx() if s.Document == doc ] #if len(selection) != 1: return False # #obj = selection[0].Object #if not a2plib.isA2pPart(obj): return False # return True def GetResources(self): return { #'Pixmap' : ':/assembly2/icons/MovePart.svg', 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_MovePartUnderConstraints.svg', 'MenuText': 'Move the selected part under constraints', 'ToolTip': toolTip } FreeCADGui.addCommand('a2p_MovePartUnderConstraints', a2p_MovePartUnderConstraints()) #=============================================================================== toolTipText = \ ''' Delete all constraints of a selected part. Select exact one part and hit this button. A confirmation dialog pops up, showing all constraints related to the selected part. After confirmation all related constraints are deleted at once. ''' class DeleteConnectionsCommand: def Activated(self): selection = [s for s in FreeCADGui.Selection.getSelection() if s.Document == FreeCAD.ActiveDocument ] #if len(selection) == 1: not required as this check is done in initGui # WF: still get 'list index out of range' if nothing selected. if len(selection) != 1: QtGui.QMessageBox.critical( QtGui.QApplication.activeWindow(), "Selection Error", "Select exactly 1 part") return part = selection[0] deleteList = [] for c in FreeCAD.ActiveDocument.Objects: if 'ConstraintInfo' in c.Content: if part.Name in [ c.Object1, c.Object2 ]: deleteList.append(c) if len(deleteList) == 0: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Info", 'No constraints refer to "%s"' % part.Name) else: flags = QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No msg = u"Delete {}'s constraint(s):\n - {}?".format( part.Label, u'\n - '.join( c.Name for c in deleteList) ) response = QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Delete constraints?", msg, flags ) if response == QtGui.QMessageBox.Yes: doc = FreeCAD.activeDocument() doc.openTransaction("Deleting part's constraints") for c in deleteList: a2plib.removeConstraint(c) doc.commitTransaction() def IsActive(self): selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: return False obj = selection[0] if a2plib.isConstrainedPart(FreeCAD.activeDocument(), obj): return True else: return False def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_DeleteConnections.svg', 'MenuText': 'Delete all constraints of selected parts', 'ToolTip': toolTipText } FreeCADGui.addCommand('a2p_DeleteConnectionsCommand', DeleteConnectionsCommand()) toolTip = \ ''' Highlight both parts, which are related to a selected constraint. Select a constraint within the treeview and hit this button. The whole assembly is switched to transparent mode and you can inspect the desired constraint. ''' class ViewConnectionsCommand: def Activated(self): doc = FreeCAD.ActiveDocument selected = a2plib.getSelectedConstraint() if selected is None: return initialTransparencyState = a2plib.isTransparencyEnabled() if not initialTransparencyState: a2plib.setTransparency() FreeCADGui.Selection.clearSelection() FreeCADGui.Selection.addSelection( doc.getObject(selected.Object1), selected.SubElement1) FreeCADGui.Selection.addSelection( doc.getObject(selected.Object2), selected.SubElement2) # Add observer to remove the transparency when the selection is changing or removing FreeCADGui.Selection.addObserver(ViewConnectionsObserver(initialTransparencyState)) def IsActive(self): #return (a2plib.getSelectedConstraint() is not None and a2plib.isTransparencyEnabled() == False) return (a2plib.getSelectedConstraint() is not None) def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_ViewConnection.svg', 'MenuText': 'Highlight both constrained parts', 'ToolTip': toolTip, } FreeCADGui.addCommand('a2p_ViewConnectionsCommand', ViewConnectionsCommand()) class ViewConnectionsObserver: def __init__(self,initialTransparencyState): self.ignoreClear = False self.initialTransparencyState = initialTransparencyState a2plib.setConstraintViewMode(True) def clearSelection(self, doc): if self.ignoreClear: self.ignoreClear = False else: # remove observer at once, as restoreTransparency would trigger it again... FreeCADGui.Selection.removeObserver(self) # if a2plib.isTransparencyEnabled() and not self.initialTransparencyState: a2plib.restoreTransparency() a2plib.setConstraintViewMode(False) def setSelection(self, doc): selected = a2plib.getSelectedConstraint() if selected is not None: self.ignoreClear = True FreeCADGui.Selection.clearSelection() FreeCADGui.Selection.addSelection( FreeCAD.ActiveDocument.getObject(selected.Object1), selected.SubElement1) FreeCADGui.Selection.addSelection( FreeCAD.ActiveDocument.getObject(selected.Object2), selected.SubElement2) toolTip = \ ''' Show only selected elements, or all if none is selected. Select one or more parts, which are the only ones you want to see in a big assembly. Hit this button, and all other parts will be made invisible. If you select nothing and hit this button, all invisible parts will be made visible again. ''' class a2p_isolateCommand: def hasFaces(self,ob): if hasattr(ob,"Shape") and hasattr(ob.Shape,"Faces") and len(ob.Shape.Faces)>0: return True return False def Activated(self): if FreeCAD.activeDocument() is None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "No active document found!", "You have to open an assembly file first." ) return selection = [s for s in FreeCADGui.Selection.getSelection() if s.Document == FreeCAD.ActiveDocument ] FreeCADGui.Selection.clearSelection() doc = FreeCAD.ActiveDocument if len(selection) == 0: # Show all elements for obj in doc.Objects: if obj.Name == 'PartInformation': continue if obj.Name[:4] == 'Page': continue if obj.Name == 'SimpleAssemblyShape': continue if not self.hasFaces(obj): continue if hasattr(obj,'ViewObject'): if hasattr(obj.ViewObject,'Visibility'): obj.ViewObject.Visibility = True else: # Show only selected elements for obj in doc.Objects: if obj.Name == 'PartInformation': continue if obj.Name[:4] == 'Page': continue if obj.Name == 'SimpleAssemblyShape': continue if a2plib.isA2pConstraint(obj): continue if hasattr(obj,'ViewObject'): if hasattr(obj.ViewObject,'Visibility'): if obj in selection: obj.ViewObject.Visibility = True else: obj.ViewObject.Visibility = False def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_Isolate_Element.svg', 'MenuText': 'Show only selected elements or all if none is selected', 'ToolTip': toolTip } FreeCADGui.addCommand('a2p_isolateCommand', a2p_isolateCommand()) class a2p_ToggleTransparencyCommand: def Activated(self, checked): if FreeCAD.activeDocument() is None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "No active document found!", "You have to open an assembly file first." ) return if a2plib.isTransparencyEnabled(): a2plib.restoreTransparency() else: a2plib.setTransparency() def IsChecked(self): return a2plib.isTransparencyEnabled() def IsActive(self): return not a2plib.getConstraintViewMode() def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_ToggleTransparency.svg', 'MenuText': 'Toggle transparency of assembly', 'ToolTip': 'Toggles transparency of assembly', 'Checkable': self.IsChecked() } FreeCADGui.addCommand('a2p_ToggleTransparencyCommand', a2p_ToggleTransparencyCommand()) toolTipMessage = \ ''' Toggle AutoSolve By pressing this button you can enable or disable automatic solving after a constraint has been edited If automatic solving is disabled you have to start it manually by hitting the solvebutton ''' class a2p_ToggleAutoSolveCommand: def Activated(self, checked): a2plib.setAutoSolve(checked) def IsChecked(self): return a2plib.getAutoSolveState() def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_ToggleAutoSolve.svg', 'MenuText': 'Toggle auto solve', 'ToolTip': toolTipMessage, 'Checkable': self.IsChecked() } FreeCADGui.addCommand('a2p_ToggleAutoSolveCommand', a2p_ToggleAutoSolveCommand()) class a2p_TogglePartialProcessingCommand: def Activated(self, checked): a2plib.setPartialProcessing(checked) def IsChecked(self): return a2plib.isPartialProcessing() def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_TogglePartial.svg', 'MenuText': 'Toggle partial processing', 'ToolTip': 'Toggles partial processing', 'Checkable': self.IsChecked() } FreeCADGui.addCommand('a2p_TogglePartialProcessingCommand', a2p_TogglePartialProcessingCommand()) toolTipMessage = \ ''' Repair the treeview, if it is damaged somehow. After pressing this button, constraints will grouped under corresponding parts again. ''' class a2p_repairTreeViewCommand: def Activated(self): if FreeCAD.activeDocument() is None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "No active document found!", "You have to open an assembly file first." ) return a2plib.a2p_repairTreeView() def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_RepairTree.svg', 'MenuText': 'Repair the tree view if it is somehow damaged', 'ToolTip': toolTipMessage } FreeCADGui.addCommand('a2p_repairTreeViewCommand', a2p_repairTreeViewCommand()) toolTip = \ ''' Flip direction of last constraint. If the last constraint, which has been defined, has a property 'direction', its value will be toggled between 'aligned' and 'opposed' (alignment of axis) ''' class a2p_FlipConstraintDirectionCommand: def Activated(self): if FreeCAD.activeDocument() is None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "No active document found!", "You have to open an assembly file first." ) return a2p_FlipConstraintDirection() def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_FlipConstraint.svg', 'MenuText': 'Flip direction of last constraint', 'ToolTip': toolTip } FreeCADGui.addCommand('a2p_FlipConstraintDirectionCommand', a2p_FlipConstraintDirectionCommand()) def a2p_FlipConstraintDirection(): ''' updating constraints, deactivated at moment''' constraints = [ obj for obj in FreeCAD.ActiveDocument.Objects if 'ConstraintInfo' in obj.Content ] if len(constraints) == 0: QtGui.QMessageBox.information( QtGui.qApp.activeWindow(), "Command Aborted", 'Flip aborted since no a2p constraints in active document.' ) return lastConstraintAdded = constraints[-1] try: if lastConstraintAdded.directionConstraint == 'aligned': lastConstraintAdded.directionConstraint = 'opposed' else: lastConstraintAdded.directionConstraint = 'aligned' a2p_solversystem.autoSolveConstraints(FreeCAD.activeDocument(), callingFuncName="a2p_FlipConstraintDirection") except: pass class a2p_Show_Hierarchy_Command: def Activated(self): doc = FreeCAD.activeDocument() if doc is None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "No active document found!", "You have to open an assembly file first." ) return ss = a2p_solversystem.SolverSystem() ss.loadSystem(doc) ss.assignParentship(doc) ss.visualizeHierarchy() def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_Treeview.svg', 'MenuText': 'Generate HTML file with detailed constraining structure', 'ToolTip': 'Generates HTML file with detailed constraining structure' } FreeCADGui.addCommand('a2p_Show_Hierarchy_Command', a2p_Show_Hierarchy_Command()) class a2p_Show_PartLabels_Command: def Activated(self, index): doc = FreeCAD.activeDocument() if index == 0: '''remove labels from 3D view''' dofGroup = doc.getObject("partLabels") if dofGroup != None: for lbl in dofGroup.Group: doc.removeObject(lbl.Name) doc.removeObject("partLabels") else: '''create or update labels within 3D view''' a2pObjects = [] for ob in doc.Objects: if a2plib.isA2pPart(ob): a2pObjects.append(ob) if len(a2pObjects) == 0: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "Nothing found to be labeled!", "This document does not contain A2p-objects" ) return labelGroup = doc.getObject("partLabels") if labelGroup is None: labelGroup=doc.addObject("App::DocumentObjectGroup", "partLabels") else: for lbl in labelGroup.Group: doc.removeObject(lbl.Name) doc.removeObject("partLabels") labelGroup=doc.addObject("App::DocumentObjectGroup", "partLabels") for ob in a2pObjects: if ob.ViewObject.Visibility == True: bbCenter = ob.Shape.BoundBox.Center partLabel = doc.addObject("App::AnnotationLabel","partLabel") partLabel.LabelText = a2plib.to_str(ob.Label) partLabel.BasePosition.x = bbCenter.x partLabel.BasePosition.y = bbCenter.y partLabel.BasePosition.z = bbCenter.z # partLabel.ViewObject.BackgroundColor = a2plib.YELLOW partLabel.ViewObject.TextColor = a2plib.BLACK labelGroup.addObject(partLabel) def IsChecked(self): doc = FreeCAD.activeDocument() if not doc: return False labelGroup = doc.getObject("partLabels") return labelGroup != None def IsActive(self): doc = FreeCAD.activeDocument() return doc != None def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_PartLabel.svg', 'MenuText': "Show part labels in 3D view", 'ToolTip': "Toggle showing part labels in 3D view", 'Checkable': False } FreeCADGui.addCommand('a2p_Show_PartLabels_Command', a2p_Show_PartLabels_Command()) class a2p_Show_DOF_info_Command: def Activated(self, index): if index == 0: ''' Remove the existing labels from screen''' doc = FreeCAD.activeDocument() dofGroup = doc.getObject("dofLabels") if dofGroup != None: for lbl in dofGroup.Group: doc.removeObject(lbl.Name) doc.removeObject("dofLabels") else: ss = a2p_solversystem.SolverSystem() ss.DOF_info_to_console() def IsActive(self): doc = FreeCAD.activeDocument() return doc != None def IsChecked(self): doc = FreeCAD.activeDocument() if not doc: return False dofGroup = doc.getObject("dofLabels") return dofGroup != None def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_DOFs.svg', 'MenuText': 'Print detailed DOF information', 'ToolTip': 'Toggle printing detailed DOF information', 'Checkable': False } FreeCADGui.addCommand('a2p_Show_DOF_info_Command', a2p_Show_DOF_info_Command()) class a2p_absPath_to_relPath_Command: def Activated(self): doc = FreeCAD.activeDocument() if doc is None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), "No active document found!", "You have to open an assembly file first." ) return assemblyPath = os.path.normpath( os.path.split( os.path.normpath(doc.FileName) )[0]) importParts = [ob for ob in doc.Objects if "mportPart" in ob.Content] for iPart in importParts: if ( iPart.sourceFile.startswith("./") or iPart.sourceFile.startswith("../") or iPart.sourceFile.startswith(".\\") or iPart.sourceFile.startswith("..\\") ): continue # path is already relative filePath = os.path.normpath(iPart.sourceFile) if platform.system() == "Windows": prefix = '.\\' else: prefix = './' iPart.sourceFile = prefix + os.path.relpath(filePath, assemblyPath) def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_SetRelativePathes.svg', 'MenuText': 'Convert absolute paths of imported parts to relative ones', 'ToolTip': 'Converts absolute paths of imported parts to relative ones' } FreeCADGui.addCommand('a2p_absPath_to_relPath_Command', a2p_absPath_to_relPath_Command()) #============================================================================== class a2p_SaveAndExit_Command: def Activated(self): doc = FreeCAD.activeDocument() try: doc.save() FreeCAD.closeDocument(doc.Name) except: FreeCADGui.SendMsgToActiveView("Save") if not FreeCADGui.activeDocument().Modified: # user really saved the file FreeCAD.closeDocument(doc.Name) # mw = FreeCADGui.getMainWindow() mdi = mw.findChild(QtGui.QMdiArea) sub = mdi.activeSubWindow() if sub != None: sub.showMaximized() def IsActive(self): return FreeCAD.activeDocument() != None def GetResources(self): return { 'Pixmap' : a2plib.pathOfModule()+'/icons/a2p_Save_and_exit.svg', 'MenuText': 'Save and exit the active document', 'ToolTip': 'Save and exit the active document' } FreeCADGui.addCommand('a2p_SaveAndExit_Command', a2p_SaveAndExit_Command()) #============================================================================== toolTip = \ ''' Migrate proxies of imported parts Very old A2plus assemblies do not show the correct icons for imported parts and have obsolete properties. With this function, you can migrate the viewProviders of old imported parts to the recent state. After running this function, you should save and reopen your assembly file. ''' class a2p_MigrateProxiesCommand(): def Activated(self): flags = QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No response = QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), u"Migrate proxies of importedParts to recent version", u"Make sure you have a backup of your files. Proceed?", flags ) if response == QtGui.QMessageBox.Yes: doc = FreeCAD.activeDocument() for ob in doc.Objects: if a2plib.isA2pPart(ob): #setup proxies a2p_importedPart_class.Proxy_importPart(ob) if FreeCAD.GuiUp: a2p_importedPart_class.ImportedPartViewProviderProxy(ob.ViewObject) #delete obsolete properties deleteList = [] tmp = ob.PropertiesList for prop in tmp: if prop.startswith('pi_') or prop == 'assembly2Version': deleteList.append(prop) for prop in deleteList: ob.removeProperty(prop) QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), u"The proxies have been migrated.", u"Please save and reopen this assembly file" ) def GetResources(self): return { 'Pixmap' : ':/icons/a2p_Upgrade.svg', 'MenuText': 'Migrate proxies of imported parts', 'ToolTip': toolTip } FreeCADGui.addCommand('a2p_MigrateProxiesCommand', a2p_MigrateProxiesCommand()) #============================================================================== def importUpdateConstraintSubobjects( doc, oldObject, newObject ): if not a2plib.getUseTopoNaming(): return # return if there are no constraints linked to the object if len([c for c in doc.Objects if 'ConstraintInfo' in c.Content and oldObject.Name in [c.Object1, c.Object2] ]) == 0: return # check, whether object is an assembly with muxInformations. # Then find edgenames with mapping in muxinfo... deletionList = [] #for broken constraints if hasattr(oldObject, 'muxInfo'): if hasattr(newObject, 'muxInfo'): # oldVertexNames = [] oldEdgeNames = [] oldFaceNames = [] for item in oldObject.muxInfo: if item[:1] == 'V': oldVertexNames.append(item) if item[:1] == 'E': oldEdgeNames.append(item) if item[:1] == 'F': oldFaceNames.append(item) # newVertexNames = [] newEdgeNames = [] newFaceNames = [] for item in newObject.muxInfo: if item[:1] == 'V': newVertexNames.append(item) if item[:1] == 'E': newEdgeNames.append(item) if item[:1] == 'F': newFaceNames.append(item) # partName = oldObject.Name for c in doc.Objects: if 'ConstraintInfo' in c.Content: if partName == c.Object1: SubElement = "SubElement1" elif partName == c.Object2: SubElement = "SubElement2" else: SubElement = None if SubElement: #same as subElement <> None subElementName = getattr(c, SubElement) if subElementName[:4] == 'Face': try: oldIndex = int(subElementName[4:])-1 oldConstraintString = oldFaceNames[oldIndex] newIndex = newFaceNames.index(oldConstraintString) newSubElementName = 'Face'+str(newIndex+1) except: newIndex = -1 newSubElementName = 'INVALID' elif subElementName[:4] == 'Edge': try: oldIndex = int(subElementName[4:])-1 oldConstraintString = oldEdgeNames[oldIndex] newIndex = newEdgeNames.index(oldConstraintString) newSubElementName = 'Edge'+str(newIndex+1) except: newIndex = -1 newSubElementName = 'INVALID' elif subElementName[:6] == 'Vertex': try: oldIndex = int(subElementName[6:])-1 oldConstraintString = oldVertexNames[oldIndex] newIndex = newVertexNames.index(oldConstraintString) newSubElementName = 'Vertex'+str(newIndex+1) except: newIndex = -1 newSubElementName = 'INVALID' else: newIndex = -1 newSubElementName = 'INVALID' if newIndex >= 0: setattr(c, SubElement, newSubElementName ) print ( "oldConstraintString (KEY) : {}".format( oldConstraintString ) ) print ("Updating by SubElement-Map: {} => {} ".format( subElementName,newSubElementName ) ) continue # # if code coming here, constraint is broken if c.Name not in deletionList: deletionList.append(c.Name) if len(deletionList) > 0: # there are broken constraints.. for cName in deletionList: flags = QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.Abort message = "Constraint %s is broken. Delete constraint? Otherwise check for wrong linkage." % cName #response = QtGui.QMessageBox.critical(QtGui.qApp.activeWindow(), "Broken Constraint", message, flags ) response = QtGui.QMessageBox.critical(None, "Broken Constraint", message, flags ) if response == QtGui.QMessageBox.Yes: FreeCAD.Console.PrintError("Removing constraint %s" % cName) c = doc.getObject(cName) a2plib.removeConstraint(c)