diff --git a/.gitignore b/.gitignore index 6e341fc4..2708f228 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,3 @@ __pycache__/* *.directory AnimationLib_translated.py .gitignore -InfoKeys.py -InfoScript.py diff --git a/InfoKeys.py b/InfoKeys.py new file mode 100644 index 00000000..7c00c732 --- /dev/null +++ b/InfoKeys.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +# coding: utf-8 +# +# LGPL +# +# libraries for FreeCAD's Assembly 4 workbench + +import os, json + +import FreeCAD as App +import infoPartCmd + + +# Autofilling info ref +partInfo =[ 'LabelDoc', \ + 'LabelPart', \ + 'PadLength', \ + 'ShapeLength'] + +infoToolTip = {'LabelDoc':'Return the Label of Document','LabelPart':'Return the Label of Part','PadLength':'Return the Length of Pad','ShapeLength':'Return the Length of Shape'} + +# protection against update of user configuration +### to have the dir of external configuration file +ConfUserDir = os.path.join(App.getUserAppDataDir(),'Templates') +ConfUserFilename = "Asm4_infoPartConf.json" +ConfUserFilejson = os.path.join(ConfUserDir, ConfUserFilename) + + +### try to open existing external configuration file of user +try : + file = open(ConfUserFilejson, 'r') + file.close() +### else make the default external configuration file +except : + partInfoDef = dict() + for prop in partInfo: + partInfoDef.setdefault(prop,{'userData':prop + 'User','active':True}) + try: + os.mkdir(ConfUserDir) + except: + pass + file = open(ConfUserFilejson, 'x') + json.dump(partInfoDef,file) + file.close() + + +### now user configuration is : +file = open(ConfUserFilejson, 'r') +infoKeysUser = json.load(file).copy() +file.close() + +def infoDefault(self): + ### auto filling module + ### load infoKeysUser + file = open(ConfUserFilejson, 'r') + infoKeysUser = json.load(file).copy() + file.close() + ### part variable creation + try : + self.TypeId + PART=self + except AttributeError: + PART=self.part + ### you have PART + DOC=PART.Document + ### you have DOC + ### research + for i in range(len(PART.Group)): + if PART.Group[i].TypeId == 'PartDesign::Body' : + BODY=PART.Group[i] + ### you have BODY + for i in range(len(BODY.Group)): + if BODY.Group[i].TypeId == 'PartDesign::Pad' : + PAD=BODY.Group[i] + ### you have PAD + try : + SKETCH=PAD.Profile[0] + ### you have SKETCH + except NameError : + print('there is no Sketch on a Pad of : ',PART.FullName ) + + ### start all autoinfofield + LabelDoc(self,PART,DOC) + LabelPart(self,PART) + PadLength(self,PART,PAD) + ShapeLength(self,PART,SKETCH) +""" +how make a new autoinfofield : + +ref newautoinfofield name in partInfo[] + +make a description in infoToolTip = {} + +put newautoinfofield name in infoDefault() at the end with the right arg (PAD,SKETCH...) + +write new def like that : + +def newautoinfofieldname(self,PART(option : DOC , BODY , PAD , SKETCH): +###you can use DOC - PART - BODY - PAD - SKETCH + auto_info_field = infoKeysUser.get('newautoinfofieldname').get('userData') + auto_info_fill = newautoinfofield information + try: + ### if the command comes from makeBom write autoinfo directly on Part + self.TypeId + setattr(PART,auto_info_field,str(auto_info_fill)) + except AttributeError: + ### if the command comes from infoPartUI write autoinfo on autofilling field on UI + try : + ### if field is actived + for i in range(len(self.infoTable)): + if self.infoTable[i][0]== auto_info_field : + self.infos[i].setText(str(auto_info_fill)) + except AttributeError: + ### if field is not actived + pass + +""" + +def ShapeLength(self,PART,SKETCH): +###you can use DOC - PART - BODY - PAD - SKETCH + auto_info_field = infoKeysUser.get('ShapeLength').get('userData') + try : + auto_info_fill = SKETCH.Shape.Length + except AttributeError: + return + try: + ### if the command comes from makeBom write autoinfo directly on Part + self.TypeId + setattr(PART,auto_info_field,str(auto_info_fill)) + except AttributeError: + ### if the command comes from infoPartUI write autoinfo on autofilling field on UI + try : + ### if field is actived + for i in range(len(self.infoTable)): + if self.infoTable[i][0]== auto_info_field : + self.infos[i].setText(str(auto_info_fill)) + except AttributeError: + ### if field is not actived + pass + + +def PadLength(self,PART,PAD): +###you can use DOC - PART - BODY - PAD - SKETCH + auto_info_field = infoKeysUser.get('PadLength').get('userData') + try : + auto_info_fill = PAD.Length + except AttributeError: + return + try: + ### if the command comes from makeBom write autoinfo directly on Part + self.TypeId + setattr(PART,auto_info_field,str(auto_info_fill)) + except AttributeError: + ### if the command comes from infoPartUI write autoinfo on autofilling field on UI + try : + ### if field is actived + for i in range(len(self.infoTable)): + if self.infoTable[i][0]== auto_info_field : + self.infos[i].setText(str(auto_info_fill)) + except AttributeError: + ### if field is not actived + pass + + + +def LabelDoc(self,PART,DOC): + docLabel = infoKeysUser.get('LabelDoc').get('userData') + try: + ### if the command comes from makeBom write autoinfo directly on Part + self.TypeId + setattr(PART,docLabel,DOC.Label) + except AttributeError: + ### if the command comes from infoPartUI write autoinfo on autofilling field on UI + try : + ### if field is actived + for i in range(len(self.infoTable)): + if self.infoTable[i][0]==docLabel: + self.infos[i].setText(DOC.Label) + except AttributeError: + ### if field is not actived + pass + +def LabelPart(self,PART): + partLabel = infoKeysUser.get('LabelPart').get('userData') + try: + ### if the command comes from makeBom write autoinfo directly on Part + self.TypeId + setattr(PART,partLabel,PART.Label) + except AttributeError: + ### if the command comes from infoPartUI write autoinfo on autofilling field on UI + try : + ### if field is actived + for i in range(len(self.infoTable)): + if self.infoTable[i][0]== partLabel: + self.infos[i].setText(PART.Label) + except AttributeError: + ### if field is not actived + pass + + + + pass \ No newline at end of file diff --git a/InfoKeysInit.py b/InfoKeysInit.py deleted file mode 100644 index 3f713718..00000000 --- a/InfoKeysInit.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -# coding: utf-8 -# -# LGPL -# -# InfoKeysInit.py - - - -""" - +-----------------------------------------------+ - | customizable file | - +-----------------------------------------------+ -""" - -# Entitled of the informations for your part - -partInfo = [ 'PartID', \ - 'PartName', \ - 'PartDescription', \ - 'PartSupplier'] - - diff --git a/README.md b/README.md index c3a6744a..caaabc1e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # FreeCAD Assembly 4 workbench -Current version 0.11.1 +Current version 0.11.2 @@ -52,6 +52,10 @@ You can get more information in the [user instructions](INSTRUCTIONS.md), the [t ## Release notes +* 2021.10.08 (**0.11.2**) : +added "Open File" to insertLink +BOM and infoPart improvements + * 2021.10.01 (**0.11.1**) : reverted Asm4.QUnitSpinBox() to QtGui.QDoubleSpinBox() because of incompatibilites with some locale (',' comma decimal separators) diff --git a/VERSION b/VERSION index 21e4e39d..362883de 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,5 @@ Assembly 4 Workbench for FreeCAD -v0.11.1 +v0.11.2 + diff --git a/infoPartCmd.py b/infoPartCmd.py index 5c5b1b94..9a4ae1ae 100644 --- a/infoPartCmd.py +++ b/infoPartCmd.py @@ -8,7 +8,7 @@ -import os, shutil +import os, json from PySide import QtGui, QtCore import FreeCADGui as Gui @@ -16,71 +16,65 @@ from FreeCAD import Console as FCC import Asm4_libs as Asm4 +import InfoKeys + +# protection against update of user configuration ### to have the dir of external configuration file -#wbPath = os.path.dirname(__file__) -wbPath = Asm4.wbPath -InfoKeysFile = os.path.join( wbPath, 'InfoKeys.py' ) -# InfoScript = os.path.join( wbPath, 'InfoScript.py' ) -InfoKeysFileInit = os.path.join( wbPath, 'InfoKeysInit.py' ) -# InfoScriptInit = os.path.join( wbPath, 'InfoScriptInit.py' ) +ConfUserDir = os.path.join(App.getUserAppDataDir(),'Templates') +ConfUserFilename = "Asm4_infoPartConf.json" +ConfUserFilejson = os.path.join(ConfUserDir, ConfUserFilename) ### try to open existing external configuration file of user try : - fichier = open(InfoKeysFile, 'r') - fichier.close() - import InfoKeys as InfoKeys -### else make the default external configuration file -except : - shutil.copyfile( InfoKeysFileInit , InfoKeysFile ) - import InfoKeys as InfoKeys -### try to open existing external configuration file of user -''' -try : - fichier = open(InfoScript, 'r') - fichier.close() - import InfoScript as autoInfo + file = open(ConfUserFilejson, 'r') + file.close() ### else make the default external configuration file except : - shutil.copyfile( InfoScriptInit ,InfoScript) - import InfoScript as autoInfo -''' - + partInfoDef = dict() + for prop in InfoKeys.partInfo: + partInfoDef.setdefault(prop,{'userData':prop + 'User','active':True}) + os.mkdir(ConfUserDir) + file = open(ConfUserFilejson, 'x') + json.dump(partInfoDef,file) + file.close() + +""" +now user configuration is : +file = open(ConfUserFilejson, 'r') +user configuration = json.load(file).copy() +file.close() +""" """ +-----------------------------------------------+ - | Helper functions | + | The Help Tools | +-----------------------------------------------+ """ +def writeXml(text): + text=text.encode('unicode_escape').decode().replace('\\','_x_m_l_') + return text -''' -def checkPart(): - # allowed types to edit info - partTypes = [ 'App::Part', 'PartDesign::Body'] - selectedPart = None - # if an App::Part is selected - if len(Gui.Selection.getSelection())==1: - selObj = Gui.Selection.getSelection()[0] - if selObj.TypeId in partTypes: - selectedPart = selObj - return selectedPart -''' - +def decodeXml(text): + text=text.replace('_x_m_l_','\\').encode().decode('unicode_escape') + return text """ +-----------------------------------------------+ | The command | +-----------------------------------------------+ """ + + class infoPartCmd(): def __init__(self): super(infoPartCmd,self).__init__() def GetResources(self): tooltip = "Edit part information. " - tooltip += "The default part information keys are in the file " - tooltip += "\"FreeCAD/Mod/Assembly4/InfoKeys.py\", edit as you need" + tooltip += "The default part information can be configured" + tooltip += "use the Config button" iconFile = os.path.join( Asm4.iconPath, 'Asm4_PartInfo.svg' ) return {"MenuText": "Edit Part Information", "ToolTip": tooltip, "Pixmap": iconFile } @@ -96,7 +90,6 @@ def Activated(self): - """ +-----------------------------------------------+ | The UI and functions in the Task panel | @@ -106,18 +99,17 @@ class infoPartUI(): def __init__(self): self.base = QtGui.QWidget() - self.form = self.base + self.form = self.base iconFile = os.path.join( Asm4.iconPath , 'Asm4_PartInfo.svg') self.form.setWindowIcon(QtGui.QIcon( iconFile )) self.form.setWindowTitle("Edit Part Information") # hey-ho, let's go self.part = Asm4.getSelectedContainer() - self.infoKeys = InfoKeys.partInfo - # the attribute PartName is mandatory in the info-keys - if not 'PartName' in self.infoKeys: - self.infoKeys.append('PartName') - self.makePartInfo() + file = open(ConfUserFilejson, 'r') + self.infoKeysUser = json.load(file).copy() + file.close() + self.makePartInfo(self,self.part) self.infoTable = [] self.getPartInfo() @@ -130,21 +122,26 @@ def getPartInfo(self): for prop in self.part.PropertiesList: if self.part.getGroupOfProperty(prop)=='PartInfo' : if self.part.getTypeIdOfProperty(prop)=='App::PropertyString' : - value = self.part.getPropertyByName(prop) - self.infoTable.append([prop,value]) + for propuser in self.infoKeysUser : + if self.infoKeysUser.get(propuser).get('userData') == prop : + if self.infoKeysUser.get(propuser).get('active'): + value = self.part.getPropertyByName(prop) + self.infoTable.append([prop,value]) # add the default part information - def makePartInfo( self, reset=False ): - for info in self.infoKeys: - try : - self.part - if not hasattr(self.part,info): - #object with part - self.part.addProperty( 'App::PropertyString', info, 'PartInfo' ) - except AttributeError : - if self.TypeId == 'App::Part' : - #object part - self.addProperty( 'App::PropertyString', info, 'PartInfo' ) + def makePartInfo( self, object , reset=False ): + for info in self.infoKeysUser: + if self.infoKeysUser.get(info).get('active'): + try : + object.part + if not hasattr(object.part,self.infoKeysUser.get(info).get('userData')): + #object with part + object.part.addProperty( 'App::PropertyString', self.infoKeysUser.get(info).get('userData'), 'PartInfo' ) + except AttributeError : + if object.TypeId == 'App::Part' : + #object part + if not hasattr(object,self.infoKeysUser.get(info).get('userData')): + object.addProperty( 'App::PropertyString', self.infoKeysUser.get(info).get('userData'), 'PartInfo' ) return # AddNew @@ -152,17 +149,42 @@ def addNew(self): for i,prop in enumerate(self.infoTable): if self.part.getGroupOfProperty(prop[0])=='PartInfo' : if self.part.getTypeIdOfProperty(prop[0])=='App::PropertyString' : - text=self.infos[i].text() - setattr(self.part,prop[0],str(text)) + for propuser in self.infoKeysUser : + if self.infoKeysUser.get(propuser).get('userData') == prop[0] : + if self.infoKeysUser.get(propuser).get('active'): + text=self.infos[i].text() + setattr(self.part,prop[0],str(text)) # edit info keys def editKeys(self): - pass + Gui.Control.closeDialog() + Gui.Control.showDialog( infoPartConfUI() ) + #pass + + def reInit(self): + #init of list of all Properties + List = self.part.PropertiesList + listpi=[] + #make list of PartInfo Properties + for prop in List : + if self.part.getGroupOfProperty(prop) == 'PartInfo' : + listpi.append(prop) + # delete all PartInfo Properties + for suppr in listpi : + self.part.removeProperty(suppr) + # message for user + mb = QtGui.QMessageBox() + mb.setText("Your fields \n has been re-initilize") + mb.setWindowTitle("RE-INITIALISATION") + mb.exec_() + # close + self.finish() + # InfoDefault def infoDefault(self): - #autoInfo.infoDefault(self) - pass + InfoKeys.infoDefault(self) + #pass # close def finish(self): @@ -174,11 +196,13 @@ def getStandardButtons(self): # Cancel def reject(self): + print("info cancel") self.finish() # OK: we insert the selected part def accept(self): self.addNew() + print("info save") self.finish() @@ -189,36 +213,296 @@ def drawUI(self): self.formLayout = QtGui.QFormLayout() self.infos=[] for i,prop in enumerate(self.infoTable): - checkLayout = QtGui.QHBoxLayout() - propValue = QtGui.QLineEdit() - propValue.setText( prop[1] ) - checked = QtGui.QCheckBox() - checkLayout.addWidget(propValue) - checkLayout.addWidget(checked) - self.formLayout.addRow(QtGui.QLabel(prop[0]),checkLayout) - self.infos.append(propValue) + for propuser in self.infoKeysUser : + if self.infoKeysUser.get(propuser).get('userData') == prop[0] : + if self.infoKeysUser.get(propuser).get('active'): + checkLayout = QtGui.QHBoxLayout() + propValue = QtGui.QLineEdit() + propValue.setText( prop[1] ) + checkLayout.addWidget(propValue) + self.formLayout.addRow(QtGui.QLabel(decodeXml(prop[0])),checkLayout) + self.infos.append(propValue) self.mainLayout.addLayout(self.formLayout) self.mainLayout.addWidget(QtGui.QLabel()) # Buttons self.buttonsLayout = QtGui.QHBoxLayout() - self.editFields = QtGui.QPushButton('Edit Fields') - self.loadTemplate = QtGui.QPushButton('Load Template') - self.buttonsLayout.addWidget(self.editFields) - self.buttonsLayout.addStretch() - self.buttonsLayout.addWidget(self.loadTemplate) + self.confFields = QtGui.QPushButton('Config') + self.reinit = QtGui.QPushButton('re-init') + self.reinit.setToolTip('To re-initialize your PartInfo Field of your part') + self.autoFill = QtGui.QPushButton('auto-filling') + self.buttonsLayout.addWidget(self.confFields) + self.buttonsLayout.addWidget(self.reinit) + self.buttonsLayout.addWidget(self.autoFill) self.mainLayout.addLayout(self.buttonsLayout) - self.form.setLayout(self.mainLayout) + #self.form.setLayout(self.mainLayout) # Actions - self.editFields.clicked.connect(self.editKeys) - self.loadTemplate.clicked.connect(self.infoDefault) + self.confFields.clicked.connect(self.editKeys) + self.reinit.clicked.connect(self.reInit) + self.autoFill.clicked.connect(self.infoDefault) + test=False + try: + if self.infoTable[0][1]=='': + test=True + except IndexError: + test=True + if test : + self.infoDefault() + self.addNew() + +class infoPartConfUI(): + def __init__(self): + self.base = QtGui.QWidget() + self.form = self.base + iconFile = os.path.join( Asm4.iconPath , 'Asm4_PartInfo.svg') + self.form.setWindowIcon(QtGui.QIcon( iconFile )) + self.form.setWindowTitle("Edit Part Info Configuration") + + # hey-ho, let's go + self.infoKeysDefault = InfoKeys.partInfo.copy() + self.infoToolTip = InfoKeys.infoToolTip.copy() + file = open(ConfUserFilejson, 'r') + self.infoKeysUser = json.load(file).copy() + file.close() + # create a dict() of defaultinfokeys and userinfokeys + self.confTemplate = dict() + self.confTemplate = self.infoKeysUser.copy() + + # the GUI objects are defined later down + self.drawConfUI() + + + # close + def finish(self): + Gui.Control.closeDialog() + print('exit config') + # standard panel UI buttons + def getStandardButtons(self): + return int(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok) + # Cancel + def reject(self): + print("Cancel") + self.finish() + # OK: we write a new config + def accept(self): + # init i + i=0 + # verification of field + for prop in self.confTemplate: + if self.infos[i].text() == '': + mb = QtGui.QMessageBox() + mb.setText("YOU CAN NOT LEAVE A FIELD BLANK \n DISABLE IT OR DELETE IT") + mb.setWindowTitle("WRITTING OF NEW CONFIG") + mb.exec_() + return + i+=1 + # init i + i=0 + # creation of dict() for user config file + config=dict() + for prop in self.confTemplate: + uData=writeXml(self.infos[i].text()) + config.setdefault(prop,{'userData':uData.replace(" ", "_"),'active':self.checker[i].isChecked()}) + i+=1 + # write user config file + file = open(ConfUserFilejson, 'w') + json.dump(config,file) + file.close() + # write in infoKeysUser + file = open(ConfUserFilejson, 'r') + self.infoKeysUser = json.load(file).copy() + file.close() + # message for user + mb = QtGui.QMessageBox() + mb.setText("Your configuration \n has been saved") + mb.setWindowTitle("WRITTING OF NEW CONFIG") + mb.exec_() + # close + self.finish() + + def addNewManField(self): + # init new default name + nameref='man' + indexref=1 + newref = nameref + str(indexref) + while newref in self.confTemplate : + indexref+=1 + newref = nameref + str(indexref) + # init new user name + newField=self.newOne.text() + self.newOne.setText('') + self.addNewField(newref,newField) + + def addNewField(self,newref,newField): + # write new field on confTemplate + self.confTemplate.setdefault(newref,{'userData': newField,'active': True }) + # write new field on line + # Label + newLab = QtGui.QLabel(newref) + self.gridLayout.addWidget(newLab,self.i,0) + self.label.append(newLab) + # Qline + newOne = QtGui.QLineEdit() + newOne.setText(newField) + self.gridLayout.addWidget(newOne,self.i,1) + self.infos.append(newOne) + # checkbox + checkLayout = QtGui.QVBoxLayout() + checked = QtGui.QCheckBox() + checked.setChecked(self.confTemplate.get(newref).get('active') ) + self.gridLayout.addWidget(checked,self.i,2) + self.checker.append(checked) + # suppcombo + self.suppCombo.addItem(newField) + self.i+=1 + + # Define the deleteField action + def deleteField(self): + # delete all ref line and infos.text() + delField = writeXml(self.suppCombo.currentText()) + i=0 + for prop in self.confTemplate: + if self.confTemplate.get(prop).get('userData') == delField: + self.label[i].deleteLater() + self.label.remove( self.label[i]) + self.infos[i].deleteLater() + self.infos.remove(self.infos[i]) + self.checker[i].deleteLater() + self.checker.remove(self.checker[i]) + self.suppCombo.removeItem(i-len(self.infoKeysDefault)) + self.refField = str(prop) + i+=1 + # delete it on confTemplate + self.confTemplate.pop(self.refField) + return + + # fonction of return if autofield list is update or no and what is new + def updateAutoFieldlist(self): + # init list + listUser=[] + for li in self.infoKeysUser : + listUser.append(li) + listDefault=self.infoKeysDefault.copy() + for li in listUser: + try : + listDefault.remove(li) + except: + pass + if listDefault == [] : + return None + else : + return listDefault + + # Define the update autoField module + # if infoKeys.py Update have new-autoField + # the user must be able to install them + def updateAutoField(self): + upField = self.upCombo.currentText() + self.upCombo.removeItem(self.upCombo.currentIndex()) + self.addNewField(upField,upField) + + + # Define the iUI + def drawConfUI(self): + # init container + self.label=[] + self.infos=[] + self.checker=[] + self.combo=[] + self.upcombo=[] + # Place the widgets with layouts + # make multiple layout + self.mainLayout = QtGui.QVBoxLayout(self.form) + self.gridLayout = QtGui.QGridLayout() + self.gridLayoutButtons = QtGui.QGridLayout() + self.gridLayoutUpdate = QtGui.QGridLayout() + + # make a first column with default data + default = QtGui.QLabel('default Data') + self.gridLayout.addWidget(default,0,0) + i=1 + for prop in self.confTemplate: + default = QtGui.QLabel(prop) + default.setToolTip(self.infoToolTip.get(prop)) + self.gridLayout.addWidget(default,i,0) + self.label.append(default) + i+=1 + # make add and delete label + self.addnewLab = QtGui.QLabel('add New') + self.gridLayoutButtons.addWidget(self.addnewLab,0,0) + self.suppLab = QtGui.QLabel('Delete') + self.gridLayoutButtons.addWidget(self.suppLab,1,0) + + # make a second column with user data in QLinEdit + user = QtGui.QLabel('user Data') + self.gridLayout.addWidget(user,0,1) + i=1 + for prop in self.confTemplate: + propValue = QtGui.QLineEdit() + propValue.setText( decodeXml(self.confTemplate.get(prop).get('userData')) ) + self.gridLayout.addWidget(propValue,i,1) + self.infos.append(propValue) + i+=1 + # make add qline and delete combobox + # add + self.newOne = QtGui.QLineEdit() + self.i=i + self.gridLayoutButtons.addWidget(self.newOne,0,1) + # delete + self.suppCombo = QtGui.QComboBox() + for prop in self.confTemplate: + if prop[0:3] == 'man' : + self.suppCombo.addItem(decodeXml(self.confTemplate.get(prop).get('userData'))) + self.gridLayoutButtons.addWidget(self.suppCombo,1,1) + + # make a third column of QCheckBox for activate or not + active = QtGui.QLabel('active') + self.gridLayout.addWidget(active,0,2) + i=1 + for prop in self.confTemplate: + checkLayout = QtGui.QVBoxLayout() + checked = QtGui.QCheckBox() + checked.setChecked(self.confTemplate.get(prop).get('active') ) + self.gridLayout.addWidget(checked,i,2) + self.checker.append(checked) + i+=1 + self.addnew = QtGui.QPushButton('add New') + self.gridLayoutButtons.addWidget(self.addnew,0,2) + self.suppBut = QtGui.QPushButton('Delete') + self.gridLayoutButtons.addWidget(self.suppBut,1,2) + # Actions + self.addnew.clicked.connect(self.addNewManField) + self.suppBut.clicked.connect(self.deleteField) + + # insert layout in mainlayout + self.mainLayout.addLayout(self.gridLayout) + self.mainLayout.addLayout(self.gridLayoutButtons) + + # If are there update show there + if self.updateAutoFieldlist() != None : + updateLab = QtGui.QLabel('Update New Auto-infoField') + self.gridLayoutUpdate.addWidget(updateLab,0,0) + self.upCombo = QtGui.QComboBox() + self.gridLayoutUpdate.addWidget(self.upCombo,1,0) + for prop in self.updateAutoFieldlist() : + self.upCombo.addItem(prop) + self.upBut = QtGui.QPushButton('Update') + self.gridLayoutUpdate.addWidget(self.upBut,1,1) + # Actions + self.upBut.clicked.connect(self.updateAutoField) + + # insert layout in mainlayout + self.mainLayout.addLayout(self.gridLayoutUpdate) + + + """ +-----------------------------------------------+ diff --git a/insertLinkCmd.py b/insertLinkCmd.py index 6689d2e2..6919de02 100644 --- a/insertLinkCmd.py +++ b/insertLinkCmd.py @@ -67,10 +67,10 @@ def Activated(self): self.rootAssembly = None self.origLink = None self.brokenLink = False - self.allParts = [] - self.partsDoc = [] + #self.allParts = [] + #self.partsDoc = [] self.filterPartList.clear() - self.partList.clear() + #self.partList.clear() self.linkNameInput.clear() # if an Asm4 Assembly is present, that's where we put the link @@ -106,6 +106,10 @@ def Activated(self): Asm4.warningBox( 'Please create an Assembly' ) return + # build the list of available parts + self.lookForParts() + + ''' # Search for all App::Parts and PartDesign::Body in all open documents # Also store the document of the part for doc in App.listDocuments().values(): @@ -130,6 +134,7 @@ def Activated(self): newItem.setText( part.Document.Name +"#"+ Asm4.labelName(part) ) newItem.setIcon(part.ViewObject.Icon) self.partList.addItem(newItem) + ''' # if an existing valid App::Link was selected if self.origLink and not self.brokenLink: @@ -156,6 +161,74 @@ def Activated(self): # show the UI self.UI.show() + # Search for all App::Parts and PartDesign::Body in all open documents + # Also store the document of the part + def lookForParts( self, doc=None ): + self.allParts = [] + self.partsDoc = [] + self.partList.clear() + if doc is None: + docList = App.listDocuments().values() + else: + docList = [doc] + for doc in docList: + # don't consider temporary documents + if not doc.Temporary: + for obj in doc.findObjects("App::Part"): + # we don't want to link to itself to the 'Model' object + # other App::Part in the same document are OK + # but only those at top level (not nested inside other containers) + if obj != self.rootAssembly and obj.getParentGeoFeatureGroup() is None: + self.allParts.append( obj ) + self.partsDoc.append( doc ) + for obj in doc.findObjects("PartDesign::Body"): + # but only those at top level (not nested inside other containers) + if obj.getParentGeoFeatureGroup() is None: + self.allParts.append( obj ) + self.partsDoc.append( doc ) + # build the list + for part in self.allParts: + newItem = QtGui.QListWidgetItem() + newItem.setText( part.Document.Name +"#"+ Asm4.labelName(part) ) + newItem.setIcon(part.ViewObject.Icon) + self.partList.addItem(newItem) + + + # from A2+ + def openFile(self): + filename = None + importDoc = None + importDocIsOpen = False + 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 (*.FCStd *.fcstd);;All files (*.*)") + if dialog.exec_(): + filename = str(dialog.selectedFiles()[0]) + # look only for filenames, not paths, as there are problems on WIN10 (Address-translation??) + requestedFile = os.path.split(filename)[1] + # see whether the file is already open + for d in App.listDocuments().values(): + recentFile = os.path.split(d.FileName)[1] + if requestedFile == recentFile: + importDoc = d # file is already open... + importDocIsOpen = True + break + # if not, open it + if not importDocIsOpen: + if filename.lower().endswith('.fcstd'): + importDoc = App.openDocument(filename) + App.setActiveDocument( self.activeDoc.Name ) + # update the part list + self.lookForParts(importDoc) + return """ @@ -279,6 +352,8 @@ def drawUI(self): self.linkNameInput = QtGui.QLineEdit(self.UI) # Cancel button self.cancelButton = QtGui.QPushButton('Cancel', self.UI) + # Cancel button + self.openFileButton = QtGui.QPushButton('Open file', self.UI) # Insert Link button self.insertButton = QtGui.QPushButton('Insert', self.UI) self.insertButton.setDefault(True) @@ -295,12 +370,15 @@ def drawUI(self): self.buttonsLayout = QtGui.QHBoxLayout() self.buttonsLayout.addWidget(self.cancelButton) self.buttonsLayout.addStretch() + self.buttonsLayout.addWidget(self.openFileButton) + self.buttonsLayout.addStretch() self.buttonsLayout.addWidget(self.insertButton) self.mainLayout.addLayout(self.buttonsLayout) self.UI.setLayout(self.mainLayout) # Actions self.cancelButton.clicked.connect(self.onCancel) + self.openFileButton.clicked.connect(self.openFile) self.insertButton.clicked.connect(self.onCreateLink) self.partList.itemClicked.connect( self.onItemClicked) self.filterPartList.textChanged.connect(self.onFilterChange) diff --git a/makeBomCmd.py b/makeBomCmd.py index 23999885..8daadcac 100644 --- a/makeBomCmd.py +++ b/makeBomCmd.py @@ -8,17 +8,45 @@ import os +import json from PySide import QtGui, QtCore import FreeCADGui as Gui import FreeCAD as App -from FreeCAD import Console as FCC import Asm4_libs as Asm4 -#import infoPartCmd +import infoPartCmd import InfoKeys -#crea = infoPartCmd.infoPartUI.makePartInfo -#rempli = infoPartCmd.infoPartUI.infoDefault + +# protection against update of user configuration + +### to have the dir of external configuration file +ConfUserDir = os.path.join(App.getUserAppDataDir(),'Templates') +ConfUserFilename = "Asm4_infoPartConf.json" +ConfUserFilejson = os.path.join(ConfUserDir, ConfUserFilename) + + +### try to open existing external configuration file of user +try : + file = open(ConfUserFilejson, 'r') + file.close() +### else make the default external configuration file +except : + partInfoDef = dict() + for prop in InfoKeys.partInfo: + partInfoDef.setdefault(prop,{'userData':prop + 'User','active':True}) + os.mkdir(ConfUserDir) + file = open(ConfUserFilejson, 'x') + json.dump(partInfoDef,file) + file.close() + +### now user configuration is : +file = open(ConfUserFilejson, 'r') +infoKeysUser = json.load(file).copy() +file.close() + +crea = infoPartCmd.infoPartUI.makePartInfo +fill = infoPartCmd.infoPartUI.infoDefault @@ -34,140 +62,19 @@ +-----------------------------------------------+ | prints a parts list | +-----------------------------------------------+ -def Partlist(object,level=0): - indent = ' '*level - # list the Variables - if object.Name=='Variables': - print(indent+'Variables:') - vars = object - for prop in vars.PropertiesList: - if vars.getGroupOfProperty(prop)=='Variables' : - propValue = vars.getPropertyByName(prop) - print(indent+' '+prop+' = '+str(propValue)) - # if its a link, look for the linked object - elif object.TypeId=='App::Link': - print(indent+object.Label+' -> '+object.LinkedObject.Document.Name+'#'+object.LinkedObject.Label) - Partlist(object.LinkedObject,level+1) - # everything else - else: - print(indent+object.Label+' ('+object.TypeId+')') - # if it's a part, look for sub-objects - if object.TypeId=='App::Part': - for objname in object.getSubObjects(): - subobj = object.Document.getObject( objname[0:-1] ) - Partlist(subobj,level+1) - return - - - - -import FreeCAD,Draft -import csv - -forbbox = ('PartDesign::Body', 'Part::Feature', 'Part::FeaturePython') - -def Partlist(object,level=0,tab=None): - indent = ' '*level - if tab is None: - tab = {} - if object.TypeId=='App::Link': - print(indent+object.Label+' -> '+object.LinkedObject.Document.Name+'#'+object.LinkedObject.Label+' => '+object.LinkedObject.FullName) - Partlist(object.LinkedObject,level+1,tab) - else: - print(indent+object.Label+' ('+object.TypeId+')') - if hasattr(object, 'Shape') and object.TypeId in forbbox: - if object.FullName in tab: - tab[object.FullName]["count"] = tab[object.FullName]["count"] + 1 - else: - tab[object.FullName] = {} - tab[object.FullName]["var"] = {} - tab[object.FullName]["count"] = 1 - - tab[object.FullName]['label'] = object.Label - tab[object.FullName]['fullname'] = object.FullName - tab[object.FullName]['Id'] = object.Label - bb=object.Shape.BoundBox - tab[object.FullName]['xlen'] = bb.XLength - tab[object.FullName]['ylen'] = bb.YLength - tab[object.FullName]['zlen'] = bb.ZLength - tab[object.FullName]['volume'] = str(object.Shape.Volume) - - if hasattr(object, 'AttachedTo'): - tab[object.FullName]['attachedto'] = object.AttachedTo - - print (indent+" => BBox: "+str(object.Shape.BoundBox)) - print (indent+" => Volume: "+str(object.Shape.Volume)) - if object.TypeId=='App::Part': - # look for Variables - if object.Document.getObject( 'Variables' ): - print(indent+' Variables:') - vars = object.Document.getObject( 'Variables' ) - for prop in vars.PropertiesList: - if vars.getGroupOfProperty(prop)=='Variables' : - propValue = vars.getPropertyByName(prop) - print(indent+' '+prop+' = '+str(propValue)) - if not object.FullName in tab: - tab[object.FullName] = {} - tab[object.FullName]['fullname'] = object.FullName - tab[object.FullName]["var"] = {} - tab[object.FullName]["var"][prop] = str(propValue) - # look for sub-objects - for objname in object.getSubObjects(): - subobj = object.Document.getObject( objname[0:-1] ) - Partlist(subobj,level+1, tab) - return tab - - -def dictoarr(tab): - keys = {} - for obj in tab.keys(): - if isinstance(tab[obj], dict): - for key in tab[obj].keys(): - if isinstance(tab[obj][key], dict): - for inner_key in tab[obj][key].keys(): - keys[key+"."+inner_key] = {}; - keys[key+"."+inner_key][0] = key; - keys[key+"."+inner_key][1] = inner_key; - else: - keys[key] = 1; - headings = sorted(keys.keys()) - - arr = [ headings ] - for obj in sorted(tab.keys()): - line = [] - for head in headings: - value = '' - lookup = keys[head] - if isinstance(lookup, dict): - if lookup[0] in tab[obj] and lookup[1] in tab[obj][lookup[0]]: - value = tab[obj][lookup[0]][lookup[1]] - else: - if head in tab[obj]: - value = tab[obj][head] - line.append(value) - arr.append(line) - return arr - - -a = Partlist(FreeCAD.ActiveDocument.getObject("Model"), 0) -print("\n\n") -print(a) -t = dictoarr(a) -print("\n\n") -print(t) - -with open('/tmp/table.csv', 'w') as csvfile: - writer = csv.writer(csvfile, delimiter="\t") - writer.writerows(t) - """ + class makeBOM: def __init__(self): super(makeBOM,self).__init__() + file = open(ConfUserFilejson, 'r') + self.infoKeysUser = json.load(file).copy() + file.close() def GetResources(self): - tooltip = "EXPERIMENTAL !!! " + tooltip = "Bill of Materials" tooltip += "Create the Bill of Materials of an Assembly" + tooltip += "With the Info and Config of Edit Part Information" iconFile = os.path.join( Asm4.iconPath, 'Asm4_PartsList.svg' ) return {"MenuText": "Create Part List", "ToolTip": tooltip, "Pixmap": iconFile } @@ -192,201 +99,101 @@ def Activated(self): print("legacy Assembly4 Model") except: print("Hum, this might not work") - ''' - try : - self.model = self.modelDoc.Model - except: - print("nouveau Assembly") - ''' self.drawUI() self.UI.show() self.BOM.clear() + self.Verbose=str() self.PartsList = {} self.listParts(self.model) - self.BOM.setPlainText(str(self.PartsList)) + self.inSpreadsheet() + self.BOM.setPlainText(self.Verbose) -### def listParts by FarmingSoul - use of Part info Edit +### def listParts use of Part info Edit def listParts(self,object,level=0): - forbbox = ('PartDesign::Body', 'Part::Feature', 'Part::FeaturePython') + file = open(ConfUserFilejson, 'r') + self.infoKeysUser = json.load(file).copy() + file.close() if object == None: return if self.PartsList == None: self.PartsList = {} + # research App::Part because the partInfo attribute is on if object.TypeId=='App::Link': self.listParts(object.LinkedObject,level+1) else: if object.TypeId=='App::Part': if level > 0: + # write PartsList + # test if the part already exist on PartsList if object.Label in self.PartsList: + # if already exist =+ 1 in qty of this part count self.PartsList[object.Label]['Qty.'] = self.PartsList[object.Label]['Qty.'] + 1 else: + # if not exist , create a dict() for this part self.PartsList[object.Label] = dict() + for prop in self.infoKeysUser: + if self.infoKeysUser.get(prop).get('active'): + try: + # try to get partInfo in part + getattr(object,self.infoKeysUser.get(prop).get('userData')) + except AttributeError: + self.Verbose+='you don\'t have fill the info of this Part :'+ object.Label +'\n' + crea(self,object) + self.Verbose+='info create for :'+ object.Label +'\n' + fill(object) + self.Verbose+='info auto filled for :'+ object.Label+'\n' + self.PartsList[object.Label][self.infoKeysUser.get(prop).get('userData')] = getattr(object,self.infoKeysUser.get(prop).get('userData')) self.PartsList[object.Label]['Qty.'] = 1 - for prop in InfoKeys.partInfo: - try: - getattr(object,prop) - self.PartsList[object.Label][prop] = getattr(object,prop) - except AttributeError: - print ('L\'object \"',object.Label,'\" n\'a pas d\'info \"',prop,'\"') - # create an entry for that part under its name - if prop=='PartName': - self.PartsList[object.Label][prop] = object.Label - # crea(object) - # rempli(object) - - '''tab[object.FullName]["var"] = {} ''' - - '''# look for Variables - if object.Document.getObject( 'Variables' ): - print(indent+' Variables:') - vars = object.Document.getObject( 'Variables' ) - for prop in vars.PropertiesList: - if vars.getGroupOfProperty(prop)=='Variables' : - propValue = vars.getPropertyByName(prop) - print(indent+' '+prop+' = '+str(propValue)) - if not object.FullName in tab: - tab[object.FullName] = {} - tab[object.FullName]['fullname'] = object.FullName - tab[object.FullName]["var"] = {} - tab[object.FullName]["var"][prop] = str(propValue) ''' # look for sub-objects for objname in object.getSubObjects(): subobj = object.Document.getObject( objname[0:-1] ) self.listParts(subobj,level+1) - return - -### def listParts by Zolko - not use of Part info Edit -### def listParts( self, obj, level=0 ): -### indent = '\n'+'\t'*level -### if obj.Document == self.modelDoc: -### docName = '' -### else: -### docName = obj.Document.Name+'#' -### #partBB = App.BoundBox() -### # list the Variables -### if obj.Name=='Variables': -### #print(indent+'Variables:') -### self.PartsList += indent+'Variables:' -### for prop in obj.PropertiesList: -### if obj.getGroupOfProperty(prop)=='Variables' : -### propValue = obj.getPropertyByName(prop) -### self.PartsList += indent+'\t'+prop+' = '+str(propValue) -### # if it's part we look for sub-objects -### elif obj.TypeId=='App::Part': -### self.PartsList += indent +docName +obj.Label -### for objname in obj.getSubObjects(): -### subobj = obj.Document.getObject( objname[0:-1] ) -### self.listParts( subobj, level+1 ) -### # if its a link, look for the linked object -### elif obj.TypeId=='App::Link': -### self.PartsList += indent+obj.Label+' -> ' -### self.listParts( obj.LinkedObject, level ) -### # if its a Body container we also add the document name and the size -### elif obj.TypeId=='PartDesign::Body': -### self.PartsList += indent +docName +obj.Label -### if obj.Label2: -### self.PartsList += ' ('+obj.Label2+')' -### bb = obj.Shape.BoundBox -### if abs(max(bb.XLength,bb.YLength,bb.ZLength)) < 1e+10: -### Xsize = str(int((bb.XLength * 10)+0.099)/10) -### Ysize = str(int((bb.YLength * 10)+0.099)/10) -### Zsize = str(int((bb.ZLength * 10)+0.099)/10) -### self.PartsList += ', Size: '+Xsize+' x '+Ysize+' x '+Zsize -### # everything else except datum objects -### elif obj.TypeId not in Asm4.datumTypes: -### self.PartsList += indent+obj.Label -### if obj.Label2: -### self.PartsList += ' ('+obj.Label2+')' -### else: -### self.PartsList += ' ('+obj.TypeId+')' -### # if the object has a shape, add it at the end of the line -### if hasattr(obj,'Shape') and obj.Shape.BoundBox.isValid(): -### bb = obj.Shape.BoundBox -### if max(bb.XLength,bb.YLength,bb.ZLength) < 1e+10: -### Xsize = str(int((bb.XLength * 10)+0.099)/10) -### Ysize = str(int((bb.YLength * 10)+0.099)/10) -### Zsize = str(int((bb.ZLength * 10)+0.099)/10) -### self.PartsList += ', Size: '+Xsize+' x '+Ysize+' x '+Zsize -### return - - def onSave(self): - #pass - """Saves ASCII tree to user system file""" - _path = QtGui.QFileDialog.getSaveFileName() - if _path[0]: - save_file = QtCore.QFile(_path[0]) - if save_file.open(QtCore.QFile.ReadWrite): - save_fileContent = QtCore.QTextStream(save_file) - save_fileContent << self.BOM - save_file.flush() - save_file.close() - self.BOM.setPlainText("Saved to file : " + _path[0]) - else: - #FCC.PrintError("ERROR : Can't open file : "+ _path[0]+'\n') - self.BOM.setPlainText("ERROR : Can't open file : " + _path[0]) - else: - self.BOM.setPlainText("ERROR : Can't open file : " + _path[0]) - QtCore.QTimer.singleShot(3000, lambda:self.BOM.setPlainText(self.PartsList)) - #self.UI.close() - - - def isReal( bb ): - # check if the BoundingBox is a real one - if bb.isValid() and abs(max(bb.XLength,bb.YLength,bb.ZLength)) < 1e+10: - return True - else: - return False - - ''' - def checkModel(self): - # check whether there is already a Model in the document - # Returns True if there is an object called 'Assembly' or 'Model' for old version - if App.ActiveDocument and App.ActiveDocument.getObject('Assembly') and App.ActiveDocument.Assembly.TypeId == 'App::Part': - return(True) - elif App.ActiveDocument and App.ActiveDocument.getObject('Model') and App.ActiveDocument.Model.TypeId == 'App::Part': - return(True) - else: - return(False) - ''' + + return + self.Verbose+='Your Bill of Materials is Done\n' -### def onCopy by FarmingSoul - Copy on Spreadsheet +### def Copy - Copy on Spreadsheet - def onCopy(self): - """Copies Parts List to Spreadsheet""" + def inSpreadsheet(self): + #Copies Parts List to Spreadsheet document = App.ActiveDocument + # init plist whit dict() PartsList plist = self.PartsList if len(plist) == 0: return - + # BOM on Spreadsheet if not hasattr(document, 'BOM'): spreadsheet = document.addObject('Spreadsheet::Sheet','BOM') else: spreadsheet = document.BOM spreadsheet.Label = "BOM" + # clean the BOM spreadsheet.clearAll() - + # to write line in spreadsheet def wrow(drow: [str], row: int): for i, d in enumerate(drow): - spreadsheet.set(str(chr(ord('a') + i)).upper()+str(row+1), str(d)) - - for i, (_,data) in enumerate(plist.items(), start=1): - wrow(data.keys(),0) - wrow(data.values(),i) + if row==0: + spreadsheet.set(str(chr(ord('a') + i)).upper()+str(row+1),infoPartCmd.decodeXml(str(d))) + else : + spreadsheet.set(str(chr(ord('a') + i)).upper()+str(row+1),str(d)) + # to make list of values of dict() plist + data = list(plist.values()) + # to write first line with keys + wrow(data[0].keys(),0) + # to write line by line BoM in Spreadsheet + for i,_ in enumerate(data): + wrow(data[i].values(),i+1) document.recompute() -### def onCopy by Zolko - Copy on clipboard -### def onCopy(self): -### '''Copies Parts List to clipboard''' -### self.BOM.selectAll() -### self.BOM.copy() -### self.BOM.setPlainText("Copied BoM to clipboard") -### QtCore.QTimer.singleShot(3000, lambda:self.BOM.setPlainText(self.PartsList)) + self.Verbose+='Your Bill of Materials is Write on BOM Spreadsheet\n' def onOK(self): + document = App.ActiveDocument + Gui.Selection.addSelection(document.Name,'BOM') self.UI.close() @@ -403,24 +210,21 @@ def drawUI(self): self.UI.setModal(False) # set main window widgets layout self.mainLayout = QtGui.QVBoxLayout(self.UI) - - # The list, is a plain text field + + # Help and Log : + self.LabelBOM = QtGui.QLabel('BoM:\n\nThis tool make BoM with the Info and Config of Edit Part Information. \n\nIf you have auto-infoField in your Config you can use BoM directly.\nBoM complet automaticaly your auto-infoField.\n\nLog :') + self.mainLayout.addWidget(self.LabelBOM) + + # The Log (Verbose) is a plain text field self.BOM = QtGui.QPlainTextEdit() - self.BOM.setMinimumSize(600,500) self.BOM.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) self.mainLayout.addWidget(self.BOM) # the button row definition self.buttonLayout = QtGui.QHBoxLayout() - self.buttonLayout.addStretch() - # Save button - self.CopyButton = QtGui.QPushButton('In Spreadsheet') - self.buttonLayout.addWidget(self.CopyButton) - # Save button - #self.SaveButton = QtGui.QPushButton('Save') - #self.buttonLayout.addWidget(self.SaveButton) + # OK button - self.OkButton = QtGui.QPushButton('Close') + self.OkButton = QtGui.QPushButton('OK') self.OkButton.setDefault(True) self.buttonLayout.addWidget(self.OkButton) self.mainLayout.addLayout(self.buttonLayout) @@ -429,8 +233,6 @@ def drawUI(self): self.UI.setLayout(self.mainLayout) # Actions - self.CopyButton.clicked.connect(self.onCopy) - #self.SaveButton.clicked.connect(self.onSave) self.OkButton.clicked.connect(self.onOK) # add the command to the workbench