def visualizeHierarchy(self): ''' generate a html file with constraints structure. The html file is in the same folder with the same filename of the assembly ''' out_file = os.path.splitext(self.doc.FileName)[0] + '_asm_hierarchy.html' Msg("Writing visual hierarchy to: {}\n".format(out_file)) f = open(out_file, "w") f.write("<!DOCTYPE html>\n") f.write("<html>\n") f.write("<head>\n") f.write(' <meta charset="utf-8">\n') f.write(' <meta http-equiv="X-UA-Compatible" content="IE=edge">\n') f.write(' <title>A2P assembly hierarchy visualization</title>\n') f.write("</head>\n") f.write("<body>\n") f.write('<div class="mermaid">\n') f.write("graph TD\n") for rig in self.rigids: rigLabel = a2plib.to_str(rig.label).replace(u' ',u'_') # No children, add current rogod as a leaf entry if len(rig.childRigids) == 0: message = u"{}\n".format(rigLabel) if a2plib.PYVERSION < 3: f.write(a2plib.to_bytes(message)) else: f.write(message) else: # Rigid have children, add them based on the dependency list for d in rig.dependencies: if d.dependedRigid in rig.childRigids: dependedRigLabel = a2plib.to_str(d.dependedRigid.label).replace(u' ',u'_') if rig.fixed: message = u"{}({}<br>*FIXED*) -- {} --> {}\n".format(rigLabel, rigLabel, d.Type, dependedRigLabel) if a2plib.PYVERSION < 3: f.write(a2plib.to_bytes(message)) else: f.write(message) else: message = u"{} -- {} --> {}\n".format(rigLabel, d.Type, dependedRigLabel) if a2plib.PYVERSION < 3: f.write(a2plib.to_bytes(message)) else: f.write(message) f.write("</div>\n") f.write(' <script src="https://unpkg.com/[email protected]/dist/mermaid.js"></script>\n') f.write(" <script>\n") f.write(' mermaid.initialize({startOnLoad: true});\n') f.write(" </script>\n") f.write("</body>") f.write("</html>") f.close()
def createUpdateFileList( importPath, parentAssemblyDir, filesToUpdate, recursive=False, selectedFiles=[] #only update parts with these sourceFiles ): # do not update converted parts print("createUpdateFileList importPath = {}".format(importPath)) if a2plib.to_bytes(importPath) == b'converted': return False, filesToUpdate fileNameInProject = a2plib.findSourceFileInProject(importPath, parentAssemblyDir) workingDir, basicFileName = os.path.split(fileNameInProject) docReader1 = FCdocumentReader() docReader1.openDocument(fileNameInProject) needToUpdate = False subAsmNeedsUpdate = False for ob in docReader1.getA2pObjects(): if a2plib.to_bytes(ob.getA2pSource()) == b'converted': print("Did not update converted part '{}'".format(ob.name)) continue #Only update parts which are selected by the user... fDir, fName = os.path.split(ob.getA2pSource()) if len(selectedFiles) > 0 and fName not in selectedFiles: continue if ob.isSubassembly() and recursive: subAsmNeedsUpdate, filesToUpdate = createUpdateFileList( ob.getA2pSource(), workingDir, filesToUpdate, recursive) if subAsmNeedsUpdate: needToUpdate = True objFileNameInProject = a2plib.findSourceFileInProject( ob.getA2pSource(), workingDir) mtime = os.path.getmtime(objFileNameInProject) if ob.getTimeLastImport() < mtime: needToUpdate = True if needToUpdate: if fileNameInProject not in filesToUpdate: filesToUpdate.append(fileNameInProject) return needToUpdate, filesToUpdate
def createPartList( importPath, parentAssemblyDir, partListEntries, recursive=False ): ''' Extract quantities and descriptions of assembled parts from document.xml Is able to analyse subassemblies by recursion It works with a dict. Structure of an entry is: filename: [Quantity,[information,information,....] ] ''' fileNameInProject = a2plib.findSourceFileInProject( importPath, parentAssemblyDir ) workingDir,basicFileName = os.path.split(fileNameInProject) docReader1 = FCdocumentReader() docReader1.openDocument(fileNameInProject) for ob in docReader1.getA2pObjects(): # skip converted parts... if a2plib.to_str(ob.getA2pSource()) == a2plib.to_str('converted'): continue if ob.isSubassembly() and recursive: partListEntries = createPartList( ob.getA2pSource(), workingDir, partListEntries, recursive ) # Process information of this a2p object if not ob.isSubassembly() or not recursive: # Try to get spreadsheetdata _PARTINFO_ from linked source linkedSource1 = ob.getA2pSource() linkedSource = a2plib.findSourceFileInProject( #this returns unicode on py2 systems! linkedSource1, workingDir ) if linkedSource == None: print(u"BOM ERROR: Could not open sourcefile {}".format(linkedSource1)) continue # Is it already processed minimum one time ? entry = partListEntries.get(linkedSource,None) if entry != None: partListEntries.get(linkedSource)[0]+=1 #count sourcefile usage continue # only needed to count imports of this file, information exists yet # There is no entry in dict, need to read out information from importFile... docReader2 = FCdocumentReader() docReader2.openDocument(linkedSource) # Initialize a default parts information... partInformation = [] for i in range(0,len(PARTLIST_COLUMN_NAMES)): partInformation.append("*") # if there is a proper spreadsheet, then read it... for ob in docReader2.getSpreadsheetObjects(): sheetName = PARTINFORMATION_SHEET_NAME if a2plib.PYVERSION > 2: sheetName = a2plib.to_bytes(PARTINFORMATION_SHEET_NAME) if ob.name == sheetName: cells = ob.getCells() for addr in cells.keys(): if addr[:1] == b'B': #column B contains the data, A only the titles idx = int(addr[1:])-1 if idx < len(PARTLIST_COLUMN_NAMES): # don't read further! partInformation[idx] = cells[addr] # last entry of partinformations is reserved for filename partInformation[-1] = os.path.split(linkedSource)[1] #without complete path... # put information to dict and count usage of sourcefiles.. entry = partListEntries.get(linkedSource,None) if entry == None: partListEntries[linkedSource] = [ 1, partInformation ] else: partListEntries.get(linkedSource)[0]+=1 #count sourcefile usage return partListEntries
def Activated(self): doc = FreeCAD.activeDocument() if doc == None: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), u"No active document found!", u"You have to open a FCStd file first." ) return completeFilePath = doc.FileName p,f = os.path.split(completeFilePath) flags = QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No msg = u"Please save before generating a parts list!\nSave now ?" response = QtGui.QMessageBox.information(QtGui.QApplication.activeWindow(), u"Save document?", msg, flags ) if response == QtGui.QMessageBox.No: QtGui.QMessageBox.information( QtGui.QApplication.activeWindow(), u"Parts list generation aborted!", u"You have to save the assembly file first." ) return else: doc.save() flags = QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No msg = u"Do you want to iterate recursively over all included subassemblies?" response = QtGui.QMessageBox.information(QtGui.QApplication.activeWindow(), u"PARTSLIST", msg, flags ) if response == QtGui.QMessageBox.Yes: subAssyRecursion = True else: subAssyRecursion = False partListEntries = createPartList( doc.FileName, p, {}, recursive=subAssyRecursion ) ss = None try: ss = doc.getObject(BOM_SHEET_NAME) except: pass if ss == None: ss = doc.addObject('Spreadsheet::Sheet',BOM_SHEET_NAME) ss.Label = BOM_SHEET_LABEL else: self.clearPartList() # Write Column headers to spreadsheet ss.set('A1',u'POS') ss.set('B1',u'QTY') idx1 = ord('C') idx2 = idx1 + len(PARTLIST_COLUMN_NAMES) i=0 for c in range(idx1,idx2): ss.set(chr(c)+"1",PARTLIST_COLUMN_NAMES[i]) i+=1 # Set the background color of the column headers ss.setBackground('A1:'+chr(idx2-1)+'1', (0.000000,1.000000,0.000000,1.000000)) # Set the columnwith to proper values ss.setColumnWidth('A',40) i=0 for c in range(idx1,idx2): ss.setColumnWidth(chr(c),150) i+=1 # fill entries for partsList... idx3 = ord('A') for idx, k in enumerate(partListEntries.keys()): ss.set('A'+str(idx+2),str(idx+1)) ss.set('B'+str(idx+2),str(partListEntries[k][0])) values = partListEntries[k][1] for j,tx in enumerate(values): # all strings inside values are unicode! #ss.set needs 2. argument as unicode for py3 and utf-8 string for py2!!! if a2plib.PYVERSION > 2: tx2 = tx # preserve unicode else: tx2 = a2plib.to_bytes(tx) # convert to utf-8 ss.set(chr(idx3+2+j)+str(idx+2),tx2) # recompute to finish.. doc.recompute() print("#PARTSLIST# spreadsheet has been created")
def scanForProperties(self): sourceFileFound = False a2pVersionFound = False subAssemblyImportFound = False timeLastImportFound = False spreadSheetCellsFound = False a2pObjectTypeFound = False numLines = len(self.xmlDefs) # Readout object's name and save it (1rst line) if numLines > 0: line = self.xmlDefs[0] segments = line.split(b'"') self.name = segments[1] idx = 0 while idx<numLines: line = self.xmlDefs[idx] if not sourceFileFound and line.startswith(b'<Property name="sourceFile"'): idx+=1 line = self.xmlDefs[idx] segments = line.split(b'"') fileName = segments[1] self.propertyDict[b'sourceFile'] = a2plib.to_str(fileName) sourceFileFound = True elif not a2pVersionFound and line.startswith(b'<Property name="a2p_Version"'): idx+=1 line = self.xmlDefs[idx] segments = line.split(b'"') a2pVersion = segments[1] self.propertyDict[b'a2p_Version'] = a2plib.to_str(a2pVersion) a2pVersionFound = True elif not a2pVersionFound and line.startswith(b'<Property name="assembly2Version"'): idx+=1 line = self.xmlDefs[idx] segments = line.split(b'"') a2pVersion = segments[1] self.propertyDict[b'a2p_Version'] = a2plib.to_str(a2pVersion) a2pVersionFound = True # for very old a2p versions do additionally... elif not subAssemblyImportFound and line.startswith(b'<Property name="subassemblyImport"'): idx+=1 line = self.xmlDefs[idx] segments = line.split(b'"') tmp = segments[1] val = True if tmp == b"false": val = False self.propertyDict[b'subassemblyImport'] = val subAssemblyImportFound = True elif not timeLastImportFound and line.startswith(b'<Property name="timeLastImport"'): idx+=1 line = self.xmlDefs[idx] segments = line.split(b'"') tmp = segments[1] floatVal = float(tmp) self.propertyDict[b'timeLastImport'] = floatVal timeLastImportFound = True elif not spreadSheetCellsFound and line.startswith(b'<Property name="cells" type="Spreadsheet::PropertySheet"'): spreadSheetCellsFound = True idx += 2 cellDict = {} while True: line = self.xmlDefs[idx] if line.startswith(b'</Cells>'): break if line.startswith(b'<Cell address="'): cellAdress,cellContent = self.parseCellLine(line) cellDict[cellAdress] = a2plib.to_str(cellContent) idx += 1 self.propertyDict[b'cells'] = cellDict elif not a2pObjectTypeFound and line.startswith(b'<Property name="objectType"'): idx+=1 line = self.xmlDefs[idx] segments = line.split(b'"') objectType = segments[1] self.propertyDict[b'objectType'] = a2plib.to_bytes(objectType) a2pObjectTypeFound = True idx+=1 self.xmlDefs = [] # we are done, free memory...