From 4292411262789314fa170571fbc8446b792db423 Mon Sep 17 00:00:00 2001 From: kbwbe Date: Sat, 10 Nov 2018 11:42:00 +0100 Subject: [PATCH 1/4] file importer cleaned up --- a2p_bom.py | 3 +- a2p_convertPart.py | 15 ++------ a2p_importpart.py | 87 ------------------------------------------ a2p_partlistglobals.py | 1 + 4 files changed, 7 insertions(+), 99 deletions(-) diff --git a/a2p_bom.py b/a2p_bom.py index 4e9045a6..c642cef4 100644 --- a/a2p_bom.py +++ b/a2p_bom.py @@ -34,6 +34,7 @@ BOM_SHEET_NAME, BOM_SHEET_LABEL, PARTINFORMATION_SHEET_NAME, + BOM_MAX_COLS, BOM_MAX_LENGTH ) @@ -122,7 +123,7 @@ def clearPartList(self): alphabet_list = list(string.ascii_uppercase) doc = FreeCAD.activeDocument() ss = doc.getObject(BOM_SHEET_NAME) - for i in range(0,12): #12 Rows enought for a partlist + for i in range(0,BOM_MAX_COLS): for k in range(0,BOM_MAX_LENGTH): cellAdress = alphabet_list[i]+str(k+1) ss.set(cellAdress,'') diff --git a/a2p_convertPart.py b/a2p_convertPart.py index c8e10dee..575d599c 100644 --- a/a2p_convertPart.py +++ b/a2p_convertPart.py @@ -26,7 +26,6 @@ from PySide import QtGui, QtCore import os, copy, time import a2plib -from a2p_importpart import filterImpParts from a2p_MuxAssembly import createTopoInfo from a2p_viewProviderProxies import * from a2p_versionmanagement import SubAssemblyWalk, A2P_VERSION @@ -41,17 +40,11 @@ def execute(self, shape): pass def convertToImportedPart(doc, obj): - '''convertToImportedPart(document, documentObject) - changes a regular FreeCAD object into an A2plus + ''' + convertToImportedPart(document, documentObject) - changes a regular FreeCAD object into an A2plus importedPart, adds the importedPart to the document and removes the FreeCAD object from the - document. Returns None''' - -# objExpand = filterImpParts(obj) -# if not objExpand: -# msg = obj.Name + " was not converted." -# FreeCADGui.Console.Message(msg) -# for oe in objExpand: - - #partName = obj.Name + document. Returns None + ''' partName = a2plib.findUnusedObjectName( obj.Label, document=doc ) partLabel = a2plib.findUnusedObjectLabel( obj.Label, document=doc ) filename = "converted" #or none? if obj is already in this doc, we don't know it's original filename diff --git a/a2p_importpart.py b/a2p_importpart.py index 39e0d12d..e3119950 100644 --- a/a2p_importpart.py +++ b/a2p_importpart.py @@ -94,78 +94,6 @@ def len(self): objectCache = ObjectCache() -def globalVisibility(doc, imp): - if not imp.InList: - return imp.ViewObject.Visibility - else: - for parent in imp.InList: - if not parent.ViewObject.Visibility: - return parent.ViewObject.Visibility - else: - return globalVisibility(doc, parent) - -def getImpPartsFromDoc(doc, visibleOnly = True): - objsIn = doc.Objects - impPartsOut = list() - for obj in objsIn: - impPartList = filterImpParts(obj) - if (impPartList): - if (visibleOnly): - vizParts = list() - for imp in impPartList: - if imp.isDerivedFrom("PartDesign::Body"): - if hasattr(imp,'ViewObject') and imp.ViewObject.isVisible() and \ - hasattr(imp.Tip,'ViewObject') and imp.Tip.ViewObject.isVisible(): - gv = globalVisibility(doc, imp) - if gv: - vizParts.append(imp) - else: - if hasattr(imp,'ViewObject') and imp.ViewObject.isVisible(): - gv = globalVisibility(doc, imp) - if gv: - vizParts.append(imp) - impPartsOut.extend(vizParts) - else: - impPartsOut.extend(impPartList) - return impPartsOut - -def filterImpParts(obj): - impPartsOut = list() - if obj.isDerivedFrom("Sketcher::SketchObject"): - pass - elif obj.isDerivedFrom("PartDesign::Body"): - # we want bodies that are top level in the document or top level in a container(App::Part) - # we don't want bodies that are inside other bodies. - if ((not(obj.InList)) or \ - ((len(obj.InList) == 1) and (obj.InList[0].hasExtension("App::GroupExtension")))): #top of group - impPartsOut.append(obj) - elif obj.hasExtension("App::GroupExtension"): # App::Part container. GroupEx contents are already in list, - pass # don't need to find them - elif obj.isDerivedFrom("PartDesign::Feature"): - if not obj.getParentGeoFeatureGroup(): # this is v016 PD::F. 017+ would have PGFG = Body - if ((not obj.InList) or - ((len(obj.InList) == 1) and (hasattr(obj.InList[0], "Group")))): # not part of any other object - if ( - hasattr(obj,"ViewObject") and - obj.ViewObject.isVisible() and - hasattr(obj,"Shape") and - len(obj.Shape.Faces) > 0 - ): - impPartsOut.append(obj) - elif obj.isDerivedFrom("Part::Feature"): - if not(obj.InList): - impPartsOut.append(obj) # top levelwithin Document - elif (len(obj.InList) == 1) and (obj.InList[0].hasExtension("App::GroupExtension")): - obj.Placement = plmGlobal - impPartsOut.append(obj) # top level within Group - elif a2plib.isA2pPart(obj): # imported part - impPartsOut.append(obj) - else: - pass # more odd PF cases?? BaseFeature in body?? - else: - pass # garbage objects - Origins, Axis, etc - return impPartsOut - def importPartFromFile(_doc, filename, importToCache=False): doc = _doc #------------------------------------------- @@ -217,21 +145,6 @@ def importPartFromFile(_doc, filename, importToCache=False): if all([ 'importPart' in obj.Content for obj in importableObjects]) == 1: subAssemblyImport = True - ''' - #------------------------------------------- - # Discover whether we are importing a subassembly or a single part - #------------------------------------------- - #if any([ 'importPart' in obj.Content for obj in importDoc.Objects]) and not len(visibleObjects) == 1: - subAssemblyImport = False - Msg("A2P importPartFromFile: importableObjects: {}\n".format(len(importableObjects))) - if len(importableObjects) == 1: - DebugMsg(A2P_DEBUG_3,"a2p importPartFromFile: first and only file: {}\n".format(importableObjects[0])) - else: - DebugMsg(A2P_DEBUG_3,"a2p importPartFromFile: importableObjects:\n{}\n".format(importableObjects)) - if len(importableObjects) > 1: - subAssemblyImport = True - ''' - #------------------------------------------- # create new object #------------------------------------------- diff --git a/a2p_partlistglobals.py b/a2p_partlistglobals.py index f4520609..6f559e4f 100644 --- a/a2p_partlistglobals.py +++ b/a2p_partlistglobals.py @@ -25,6 +25,7 @@ BOM_SHEET_NAME = '_PARTSLIST_' #BOM = BillOfMaterials... BOM_SHEET_LABEL = '#PARTSLIST#' +BOM_MAX_COLS = 10 BOM_MAX_LENGTH = 150 From 440297b172bb4299f77a99dc02097a1bf5fe08b6 Mon Sep 17 00:00:00 2001 From: kbwbe Date: Sat, 10 Nov 2018 12:57:11 +0100 Subject: [PATCH 2/4] code refactoring, fix BOM for very old a2p versions --- a2p_bom.py | 1 + a2p_fcdocumentreader.py | 5 +++++ a2p_rigid.py | 10 +--------- a2p_solversystem.py | 17 ++--------------- a2plib.py | 13 ------------- 5 files changed, 9 insertions(+), 37 deletions(-) diff --git a/a2p_bom.py b/a2p_bom.py index c642cef4..8dbed7c5 100644 --- a/a2p_bom.py +++ b/a2p_bom.py @@ -60,6 +60,7 @@ def createPartList( ) workingDir,basicFileName = os.path.split(fileNameInProject) docReader1 = FCdocumentReader() + docReader1.openDocument(fileNameInProject) for ob in docReader1.getA2pObjects(): #print(u'{}, Subassembly? = {}'.format(ob,ob.isSubassembly())) diff --git a/a2p_fcdocumentreader.py b/a2p_fcdocumentreader.py index 5f5eec61..387a2128 100644 --- a/a2p_fcdocumentreader.py +++ b/a2p_fcdocumentreader.py @@ -218,6 +218,11 @@ def getA2pObjects(self): for ob in self.objects: if ob.propertyDict.get('a2p_Version',None) != None: out.append(ob) + continue + elif ob.propertyDict.get('assembly2Version',None) != None: # for very old a2p projects... + out.append(ob) + continue + return out def getSpreadsheetObjects(self): diff --git a/a2p_rigid.py b/a2p_rigid.py index 741d16e1..a7ae6107 100644 --- a/a2p_rigid.py +++ b/a2p_rigid.py @@ -45,17 +45,9 @@ A2P_DEBUG_1, A2P_DEBUG_2, A2P_DEBUG_3, - ) -import a2p_libDOF - -from a2plib import ( PARTIAL_SOLVE_STAGE1, - PARTIAL_SOLVE_STAGE2, - PARTIAL_SOLVE_STAGE3, - PARTIAL_SOLVE_STAGE4, - PARTIAL_SOLVE_STAGE5, - PARTIAL_SOLVE_END ) +import a2p_libDOF from a2p_libDOF import ( SystemOrigin, diff --git a/a2p_solversystem.py b/a2p_solversystem.py index 800b4160..c1ff2376 100644 --- a/a2p_solversystem.py +++ b/a2p_solversystem.py @@ -45,6 +45,8 @@ A2P_DEBUG_1, A2P_DEBUG_2, A2P_DEBUG_3, + + PARTIAL_SOLVE_STAGE1, ) from a2p_dependencies import Dependency from a2p_rigid import Rigid @@ -72,15 +74,6 @@ SOLVER_CONVERGENCY_FACTOR = 0.99 SOLVER_CONVERGENCY_ERROR_INIT_VALUE = 1.0e+20 -from a2plib import ( - PARTIAL_SOLVE_STAGE1, - PARTIAL_SOLVE_STAGE2, - PARTIAL_SOLVE_STAGE3, - PARTIAL_SOLVE_STAGE4, - PARTIAL_SOLVE_STAGE5, - PARTIAL_SOLVE_END - ) - #------------------------------------------------------------------------------ class SolverSystem(): ''' @@ -338,27 +331,21 @@ def solveAccuracySteps(self,doc): self.mySOLVER_POS_ACCURACY = SOLVER_CONTROLDATA[self.level_of_accuracy][0] self.mySOLVER_SPIN_ACCURACY = SOLVER_CONTROLDATA[self.level_of_accuracy][1] - #startTime = int(round(time.time() * 1000)) self.loadSystem(doc) if self.status == "loadingDependencyError": return self.assignParentship(doc) - #loadTime = int(round(time.time() * 1000)) while True: systemSolved = self.calculateChain(doc) - #totalTime = int(round(time.time() * 1000)) if systemSolved: self.level_of_accuracy+=1 if self.level_of_accuracy > len(SOLVER_CONTROLDATA): self.solutionToParts(doc) break - #self.prepareRestart() self.mySOLVER_POS_ACCURACY = SOLVER_CONTROLDATA[self.level_of_accuracy][0] self.mySOLVER_SPIN_ACCURACY = SOLVER_CONTROLDATA[self.level_of_accuracy][1] - #self.solutionToParts(doc) self.loadSystem(doc) else: - #self.solutionToParts(doc) completeSolvingRequired = SOLVER_CONTROLDATA[self.level_of_accuracy][2] if not completeSolvingRequired: systemSolved = True break diff --git a/a2plib.py b/a2plib.py index d795d317..4ad71798 100644 --- a/a2plib.py +++ b/a2plib.py @@ -63,12 +63,6 @@ A2P_DEBUG_LEVEL = A2P_DEBUG_NONE PARTIAL_SOLVE_STAGE1 = 1 #solve all rigid fully constrained to tempfixed rigid, enable only involved dep, then set them as tempfixed -PARTIAL_SOLVE_STAGE2 = 2 #solve all rigid constrained only to tempfixed rigids, it doesn't matter if fully constrained or not. - #in case more than one tempfixed rigid -PARTIAL_SOLVE_STAGE3 = 3 #repeat stage 1 and stage2 as there are rigids that match -PARTIAL_SOLVE_STAGE4 = 4 #look for block of rigids, if a rigid is fully constrained to one rigid, solve them and create a superrigid (disabled at the moment) -PARTIAL_SOLVE_STAGE5 = 5 #take all remaining rigid and dependencies not done and try to solve them all together -PARTIAL_SOLVE_END = 6 #------------------------------------------------------------------------------ def getUseTopoNaming(): @@ -106,13 +100,6 @@ def setTransparency(): if hasattr(obj,'ViewObject'): if hasattr(obj.ViewObject,'Transparency'): # if hasattr(obj.ViewObject,'DiffuseColor'): - if ( len(obj.ViewObject.DiffuseColor) == 1 ) : - DebugMsg(A2P_DEBUG_3,"a2p setTransparency: ONE ShapeColor and Transparency detected:\n{}" \ - .format(obj.ViewObject.DiffuseColor)) - else: - DebugMsg(A2P_DEBUG_3,"a2p setTransparency: muxed assembly detected:\n{}" \ - .format(obj.ViewObject.DiffuseColor)) - DebugMsg(A2P_DEBUG_3,"A2P setTransparency: Saving transparency!\n") SAVED_TRANSPARENCY.append( (obj.Name, obj.ViewObject.Transparency, obj.ViewObject.DiffuseColor) ) From 99e237725dcaf6e3bed9a8a5f667a817ea5ee4a0 Mon Sep 17 00:00:00 2001 From: kbwbe Date: Sat, 10 Nov 2018 14:17:49 +0100 Subject: [PATCH 3/4] userinterface: more checks to avoid misusage --- a2p_importpart.py | 117 +++++++++++++++++++++++++++++++++++----------- a2plib.py | 13 +++++- 2 files changed, 100 insertions(+), 30 deletions(-) diff --git a/a2p_importpart.py b/a2p_importpart.py index e3119950..47df7f8b 100644 --- a/a2p_importpart.py +++ b/a2p_importpart.py @@ -453,23 +453,45 @@ def duplicateImportedPart( part ): class a2p_DuplicatePartCommand: def Activated(self): + #==================================================== + # Is there an open Doc ? + #==================================================== if FreeCAD.activeDocument() == None: - QtGui.QMessageBox.critical( + QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), - "No active Document error", - "First please open an assembly file!" + u"No active Document error", + u"First please open an assembly file!" ) return + + #==================================================== + # Is something been selected ? + #==================================================== selection = [s for s in FreeCADGui.Selection.getSelectionEx() if s.Document == FreeCAD.ActiveDocument ] - if len(selection) == 1: - PartMover( FreeCADGui.activeDocument().activeView(), duplicateImportedPart( selection[0].Object ) ) - else: - QtGui.QMessageBox.critical( + if len(selection) != 1: + QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), - "Selection error", - "Before duplicating, first please select a part!" + u"Selection error", + u"Before duplicating, first please select a part!" ) + return + #==================================================== + # Is the selection an a2p part ? + #==================================================== + obj = selection[0].Object + if not a2plib.isA2pPart(obj): + QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), + u"Duplicate: Selection invalid!", + u"This object is no imported part!" + ) + return + + #==================================================== + # Duplicate the part + #==================================================== + PartMover( FreeCADGui.activeDocument().activeView(), duplicateImportedPart( selection[0].Object ) ) + def GetResources(self): return { @@ -487,24 +509,42 @@ def GetResources(self): class a2p_EditPartCommand: def Activated(self): doc = FreeCAD.activeDocument() + #==================================================== + # Is there an open Doc ? + #==================================================== if doc == None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), - "No active document found!", - "Before editing a part, you have to open an assembly file." + 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: - msg = \ -''' -You must select a part to edit first. -''' QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), - "Selection Error", - msg + 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]) @@ -523,7 +563,10 @@ def Activated(self): msg ) return - #TODO: WF fails if "use folder" = false here + + #==================================================== + # Open the file for editing and switch the window + #==================================================== docs = [] for d in FreeCAD.listDocuments().values(): #dict_values not indexable, docs now is... docs.append(d) @@ -542,10 +585,6 @@ def Activated(self): for s in sub: mdi.setActiveSubWindow(s) if FreeCAD.activeDocument().Name == name: break - # This does not work somehow... - # FreeCAD.setActiveDocument( name ) - # FreeCAD.ActiveDocument=FreeCAD.getDocument( name ) - # FreeCADGui.ActiveDocument=FreeCADGui.getDocument( name ) def GetResources(self): @@ -603,19 +642,41 @@ def addSelection( self, docName, objName, sub, pnt ): class a2p_MovePartCommand: def Activated(self): + #==================================================== + # Is there an open Doc ? + #==================================================== if FreeCAD.activeDocument() == None: QtGui.QMessageBox.critical( QtGui.QApplication.activeWindow(), - "No active Document error", - "First please open an assembly file!" + u"No active Document error", + u"First please open an assembly file!" ) return - + + #==================================================== + # Is something been selected ? + #==================================================== selection = [s for s in FreeCADGui.Selection.getSelectionEx() if s.Document == FreeCAD.ActiveDocument ] - if len(selection) == 1: + if len(selection) != 1: + QtGui.QMessageBox.information( + QtGui.QApplication.activeWindow(), + u"Selection error", + u"Before moving, first please select exact 1 part!" + ) + return + + #==================================================== + # Move object, if possible + #==================================================== + try: PartMover( FreeCADGui.activeDocument().activeView(), selection[0].Object ) - else: - PartMoverSelectionObserver() + except: + QtGui.QMessageBox.information( + QtGui.QApplication.activeWindow(), + u"Wrong selection", + u"Cannot move selected object!" + ) + def GetResources(self): return { diff --git a/a2plib.py b/a2plib.py index 4ad71798..72fabc73 100644 --- a/a2plib.py +++ b/a2plib.py @@ -537,15 +537,24 @@ def isA2pPart(obj): result = True # -> otherwise toggle transparency won't work elif hasattr(obj,"subassemblyImport"): # another possible assembly item result = True + elif hasattr(obj,"assembly2Version"): # another possible assembly item (very old a2p versions) + result = True return result - +#------------------------------------------------------------------------------ +def isEditableA2pPart(obj): + if not isA2pPart(obj): return False + if hasattr(obj,"sourceFile"): + if obj.sourceFile == "converted": return False + if obj.sourceFile == "": return False + return True +#------------------------------------------------------------------------------ def isA2pConstraint(obj): result = False if hasattr(obj,"Content"): if ('ConstraintInfo' in obj.Content) or ('ConstraintNfo'in obj.Content): result = True return result - +#------------------------------------------------------------------------------ def isA2pObject(obj): result = False if isA2pPart(obj) or isA2pConstraint(obj): From ac8d9fadc5060f081ec8ee39f288d3b4b8826e51 Mon Sep 17 00:00:00 2001 From: kbwbe Date: Sun, 11 Nov 2018 15:20:59 +0100 Subject: [PATCH 4/4] toponaming for PartWB modified --- a2p_topomapper.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/a2p_topomapper.py b/a2p_topomapper.py index 5e6a3b3e..b4e55b6d 100644 --- a/a2p_topomapper.py +++ b/a2p_topomapper.py @@ -111,6 +111,7 @@ def __init__(self,doc): self.totalNumVertexes = 0 self.totalNumEdges = 0 self.totalNumFaces = 0 + self.isPartDesignDocument = False def calcFloatKey(self,val): return "%014.3f;" % val @@ -278,11 +279,14 @@ def populateShapeDict(self,objName): numNewlyCreatedVertexes = len(shape.Vertexes) - self.totalNumVertexes self.totalNumVertexes = len(shape.Vertexes) vertexNamePrefix = 'V;'+objName + ';' - vertexNameSuffix = str(numNewlyCreatedVertexes)+';' + vertexNameSuffix = str(numNewlyCreatedVertexes)+';' #only correct for PartDesign, PartWB gives false counts i = 1 # do not enumerate the following, count new vertexes ! for vertex in vertexes: vertexKey = self.calcVertexKey(pl.multVec(vertex.Point)) - vertexName = vertexNamePrefix + str(i) + ';' + vertexNameSuffix + if self.isPartDesignDocument: + vertexName = vertexNamePrefix + str(i) + ';' + vertexNameSuffix + else: + vertexName = vertexNamePrefix + str(i) + ';' vertexFound = self.shapeDict.get(vertexKey,False) if vertexFound == False: self.shapeDict[vertexKey] = vertexName @@ -293,7 +297,7 @@ def populateShapeDict(self,objName): numNewlyCreatedEdges = len(edges) - self.totalNumEdges self.totalNumEdges = len(edges) edgeNamePrefix = 'E;' + objName + ';' - edgeNameSuffix = str(numNewlyCreatedEdges)+';' + edgeNameSuffix = str(numNewlyCreatedEdges)+';' #only correct for PartDesign, PartWB gives false counts i = 1 # do not enumerate the following, count new Edges ! for edge in edges: edgeKeys = self.calcEdgeKeys(edge, pl) # 2 keys for a linear edge, 1 key per circular edge @@ -304,7 +308,10 @@ def populateShapeDict(self,objName): entryFound = True break if not entryFound: - edgeName = edgeNamePrefix + str(i) + ';' + edgeNameSuffix + if self.isPartDesignDocument: + edgeName = edgeNamePrefix + str(i) + ';' + edgeNameSuffix + else: + edgeName = edgeNamePrefix + str(i) + ';' i+=1 else: edgeName = tmp # the old edge name... @@ -317,7 +324,7 @@ def populateShapeDict(self,objName): numNewlyCreatedFaces = len(faces) - self.totalNumFaces self.totalNumFaces = len(faces) faceNamePrefix = 'F;' + objName + ';' - faceNameSuffix = str(numNewlyCreatedFaces)+';' + faceNameSuffix = str(numNewlyCreatedFaces)+';' #only correct for PartDesign, PartWB gives false counts i = 1 # do not enumerate the following, count new Faces ! for face in faces: faceKeys = self.calcFaceKeys(face, pl) # one key per vertex of a face @@ -329,7 +336,10 @@ def populateShapeDict(self,objName): entryFound = True break if not entryFound: - faceName = faceNamePrefix + str(i) + ';' + faceNameSuffix + if self.isPartDesignDocument: + faceName = faceNamePrefix + str(i) + ';' + faceNameSuffix + else: + faceName = faceNamePrefix + str(i) + ';' i+=1 else: faceName = tmp # the old face name... @@ -407,6 +417,12 @@ def getTopLevelObjects(self): outObs.append(self.doc.getObject(objName)) return outObs + def detectPartDesignDocument(self): + self.isPartDesignDocument = False + for ob in self.doc.Objects: + if ob.Name.startswith('Body'): + self.isPartDesignDocument = True + break def createTopoNames(self,withColor=False): ''' @@ -414,6 +430,7 @@ def createTopoNames(self,withColor=False): assigns toponames to its geometry if toponaming is enabled. ''' + self.detectPartDesignDocument() self.getTopLevelObjects() #------------------------------------------- # analyse the toplevel shapes