From 028bcfa7ddc9ba335faa715e64d39423a21dd4b3 Mon Sep 17 00:00:00 2001 From: Dan Miel <42722171+DanMiel@users.noreply.github.com> Date: Tue, 15 Mar 2022 14:20:29 -0700 Subject: [PATCH 1/9] Update InitGui.py Import for update program and icon code for viewer and update. --- InitGui.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/InitGui.py b/InitGui.py index f78aa1fd..84fb5a4b 100644 --- a/InitGui.py +++ b/InitGui.py @@ -79,7 +79,7 @@ def Initialize(self): import a2p_bom # bom == bill of materials == partslist import a2p_constraintServices import a2p_searchConstraintConflicts - + import CD_A2plusupdater #for CD function if a2plib.getRecursiveUpdateEnabled(): partCommands = [ 'a2p_ImportPart', @@ -224,6 +224,15 @@ def Initialize(self): '/GuiA2p/Resources/ui/a2p_prefs.ui','A2plus' ) + DiagnosticCommands = [ + 'rnp_Constraint_Viewer', + 'rnp_Update_A2pParts', + ] + + self.appendToolbar( + 'A2Diagnostics', + DiagnosticCommands + ) def Activated(self): import a2p_observers From df200c70148685fd87b88777f93cd82094b23d47 Mon Sep 17 00:00:00 2001 From: luz paz Date: Wed, 16 Mar 2022 22:30:10 -0400 Subject: [PATCH 2/9] Add Constrain diagonostic files --- CD_A2plusupdater.py | 705 +++++++++++++ CD_ConstraintDiagnostics.py | 1368 ++++++++++++++++++++++++ CD_checkconstraints.py | 505 +++++++++ CD_featurelabels.py | 217 ++++ CD_importpart.py | 1967 +++++++++++++++++++++++++++++++++++ 5 files changed, 4762 insertions(+) create mode 100644 CD_A2plusupdater.py create mode 100644 CD_ConstraintDiagnostics.py create mode 100644 CD_checkconstraints.py create mode 100644 CD_featurelabels.py create mode 100644 CD_importpart.py diff --git a/CD_A2plusupdater.py b/CD_A2plusupdater.py new file mode 100644 index 00000000..37962cc5 --- /dev/null +++ b/CD_A2plusupdater.py @@ -0,0 +1,705 @@ + + + + + + + +import sys +import os +import FreeCADGui +import FreeCAD +from PySide import QtUiTools +from PySide.QtGui import * +from PySide import QtGui, QtCore +import a2plib +import a2p_solversystem +from a2p_solversystem import solveConstraints +import CD_importpart +import CD_checkconstraints +import CD_ConstraintDiagnostics +class globaluseclass: + def __init__(self,name): + self.roundto = 5 + self.partlabel = '' + self.partname = '' + self.notfoundfeatures =[] + self.foundfeatures =[] + self.dictOldNew ={} + self.alldicts = {} + self.clist = [] + self.partobj = None + self.test = [] + self.cylfaces = [] + self.notcylfaces = [] + self.partlog =[] + def resetvars(self): + self.partlabel = '' + self.partname = '' + self.notfoundfeatures =[] + self.foundfeatures =[] + self.dictOldNew ={} + self.alldicts = {} + #self.usedfeatures = [] + self.clist = [] + self.partobj = None + self.test = [] + self.cylfaces = [] + self.notcylfaces = [] + self.repaired = 0 +g=globaluseclass("g") + + +class sideFuncs1(): + def __init__(self): + pass + def opendoccheck(self): + doc = None + doc = FreeCAD.activeDocument() + + if doc == None: + msg = 'A file must be selected to start this selector\nPlease open a file and try again' + mApp(msg) + return('Nostart') + + return() +sideFuncs = sideFuncs1() + + + +class classFuncs(): + def __init__(self): + pass + + + def runinorder(self): + partname = 'fff' + partname = self.selectfiles() + if partname == 'No': + return + if partname == 'fff': + return + self.secondrun(partname,False) + + + def selectfiles(self): + ret =sideFuncs.opendoccheck() + if ret == 'Nostart': + return('No') + doc = FreeCAD.activeDocument() + partslist= FreeCADGui.Selection.getSelection() + if len(partslist) == 0: + mApp('1 No parts were selected to update.\nSelect one part and try again.') + return('No') + if len(partslist) > 1: + mApp('I have limited the number of parts that can be updaated to 1.\nSelect one part and try again.') + return('No') + CD_ConstraintDiagnostics.statusform.show() + CD_ConstraintDiagnostics.statusform.txtboxstatus.setText('Updating Assembly.') + CD_ConstraintDiagnostics.statusform.update() + + for num in range(0,len(partslist)): + partobj = partslist[num] + partname = self.firstrun(partobj) + self.secondrun(False) + + def firstrun(self,partobj): + g.resetvars() #reset Variables + partname = 'none' + g.partobj = partobj + g.partlabel =partobj.Label + partname = partobj.Name + g.partname = partobj.Name + g.shape1 = partobj.Shape + self.getfeatstomove() + FreeCADGui.updateGui() + return(partname) + + def secondrun(self,newpart = False): + doc = FreeCAD.activeDocument() + + + if newpart == False: + newobj = None + CD_importpart.updateImportedParts(doc,True) + newobj = g.partobj + FreeCADGui.updateGui() + g.shape2 = newobj.Shape + getfacelists() + self.runpostchange() + doc.recompute() + FreeCADGui.updateGui() + CD_ConstraintDiagnostics.statusform.Closeme() + + def runpostchange(self): + + + doc = FreeCAD.activeDocument() + self.findfeats_attempt1() + doc.recompute() + FreeCADGui.updateGui() + doc = FreeCAD.activeDocument() + FreeCADGui.updateGui() + + clist = [] + for e in g.notfoundfeatures: + cobj = FreeCAD.ActiveDocument.getObject(e[0]) + clist.append(cobj) + if len(clist) != 0: + CD_ConstraintDiagnostics.form1.show() + CD_ConstraintDiagnostics.form1.loadtable(clist) + else: + mApp('Update complete. All surfaces found') + print('update complete') + + print('Repaired constraints = ' + (str(g.repaired - len(g.notfoundfeatures)))) + + def getfeatstomove(self): + doc = FreeCAD.activeDocument() + clist = selectforpart(g.partlabel) + g.clist = clist + featname = '' + di ={} + for cobj in clist: + #get feature info before update + partname = g.partname + featname = '' + subElement = "" + subElement = "" + subobj1 = doc.getObject(cobj.Object1) + subobj2 = doc.getObject(cobj.Object2) + frompart = [g.partlabel,g.partname] + for i in range(0,len(frompart)): + partname = frompart[i] + if subobj1.Label == partname: + subElement = "SubElement1" + featname = cobj.SubElement1 + if subobj2.Label == partname: + subElement = "SubElement2" + featname = cobj.SubElement2 + if featname != '': + break + dir ='N' + if hasattr(cobj,'directionConstraint'): + dir = cobj.directionConstraint + # dict is basic info for constraint + # these next functions adds info for the subelements + if 'Face' in featname: + #add face info + facenum = int(featname[4:]) + di = self.getfacebynum(facenum-1,g.shape1) + if 'Edge' in featname: + #add edge info + num = int(featname[4:]) + num = num - 1 + di = self.getedgebynum(num,g.shape1) + if 'V' in featname: + #add Vertex info + num = int(featname[6:]) + num = num - 1 + di = self.getvertexbynum(num,g.shape1) + dict = {'Name':cobj.Name,'cname':cobj.Name,'featname':featname,'subElement':subElement,'dir':dir,'newname':''} + dict.update(di) + + g.alldicts[cobj.Name] = dict #Save the info to a larger dictionary + + + def getfacebynum (self,facenum,shape): + # get face info. + face = shape.Faces[facenum] + area = rondnum(face.Area) + surftype = face.Surface + facepoints =[] + points =[] + center = -1 + + + edge1 = face.Edges[0] + + eeee=face.Edges + numofpoints = len(face.Vertexes) + for f0 in face.Vertexes: # Search the Vertexes of the face + point=FreeCAD.Vector(f0.Point.x,f0.Point.y,f0.Point.z) + x,y,z =point + loc = rondlist([x,y,z]) + facepoints.append(loc) + volume =rondnum(face.Volume) + radius = -1 + + surftype = face.Surface + surfstr = str(surftype) + + if'Cylinder' in surfstr: + surfstr = 'Cylinder' + radius = rondnum(surftype.Radius) + center = rondlist(face.Edges[0].CenterOfMass) + + if'Plane' in surfstr: + surfstr = 'Plane' + dict ={'surftype':surfstr,'area':area,'facepoints':facepoints,'center':center,'radius':radius,'edges':eeee} + + return(dict) + + + + def getedgebynum (self,num,shape): + + pnt1 = None + pnt2 = None + edge = shape.Edges[num] + length = edge.Length + length = rondnum(edge.Length) + center =edge.CenterOfMass + center = rondlist(center) + pnt1 = edge.Vertexes[0] #Basepoints + x1=pnt1.Point.x + y1=pnt1.Point.y + z1=pnt1.Point.z + startpoint = rondlist([x1,y1,z1]) + a = FreeCAD.Vector(x1,y1,z1) + b = FreeCAD.Vector() + try: + pnt2 = edge.Vertexes[1] #Basepoints + x2=pnt2.Point.x + y2=pnt2.Point.y + z2=pnt2.Point.z + endpoint = [x2,y2,z2] + b=FreeCAD.Vector(x2,y2,z2) + endpoint = rondlist([x2,y2,z2]) + except: + endpoint = ["-","-","-"] + + + radius =-1 + vector = None + curvetype = '' + center = -1 + tstr = str(edge.Curve) + if 'Line' in tstr: + curvetype ='line' + if 'Circle' in tstr: + curvetype ='circle' + radius = rondnum(edge.Curve.Radius) + center = rondlist(edge.CenterOfMass) + if 'Spline' in tstr: + curvetype ='spline' #A2 is not using these + dict = {'curvetype':curvetype,'obj':edge,'length':length,'startpoint':startpoint,'center':center,'endpoint':endpoint,'radius':radius,'vector':vector} + + + return(dict) + + + def getvertexbynum(self,num,shape): + v=shape.Vertexes[num] + x=v.Point.x + y=v.Point.y + z=v.Point.z + xyz = [x,y,z] + + xyz= rondlist([x,y,z]) + return({'xyz':xyz}) + + ## post functions*********************************** + + def findfeats_attempt1(self): + + # Try to find features after the update + doc = FreeCAD.activeDocument() + for k,dict in g.alldicts.items(): + dict = dict + newfeat = '' + featname =dict.get('featname') + if featname in g.foundfeatures: + newfeat = g.dictOldNew.get(featname) + else: + if 'Face' in featname: + newfeat = self.findnewface_attemt1(dict) + if 'Edge' in featname: + newfeat = self.findnewedge_attemt1(dict) + if 'Vertex' in featname: + newfeat = self.findnewvertex_attemt1(dict) + if newfeat =='' or newfeat == 'No': + g.notfoundfeatures.append([dict.get('Name'),dict]) + pass + else: + if newfeat in g.foundfeatures == False: + g.foundfeatures.append(newfeat) # + g.dictOldNew[featname] = newfeat + + self.swapfeature(newfeat,dict) + doc.recompute() + + + if len(g.notfoundfeatures) > 0: + self.findfeats_attempt2() + + + def swapfeature(self,newfeat,dict): + #add the new feature to the constraint + cname = dict.get('cname') + g.partlog.append('Found ' + newfeat) + cobj = FreeCAD.ActiveDocument.getObject(cname) + mobj = FreeCAD.ActiveDocument.getObject(cname+'_mirror') + SubElement = dict.get('subElement') + if SubElement == 'SubElement1': + if cobj.SubElement1 != newfeat: + cobj.SubElement1 = newfeat + mobj.SubElement1 = newfeat + g.repaired = g.repaired + 1 + if SubElement == 'SubElement2': + if cobj.SubElement2 != newfeat: + cobj.SubElement2 = newfeat + mobj.SubElement2 = newfeat + g.repaired = g.repaired + 1 + dir = dict.get('dir') + if hasattr(cobj,'directionConstraint'): + cobj.directionConstraint = dir + if hasattr(mobj,'directionConstraint'): + mobj.directionConstraint = dir + return + + #If not found on first attemt try aagain + def findfeats_attempt2(self): + newfeat = '' + notfoundtemp = g.notfoundfeatures + + + g.notfoundfeatures = [] + for ea in notfoundtemp: + dict= ea[1] + featname =dict.get('featname') + if featname in g.foundfeatures: + newfeat = g.dictOldNew.get(featname) + else: + if 'Face' in featname: + newfeat = self.findnewface_attemt2(dict) + if 'Edge' in featname: + newfeat = self.findnewedge_attemt2(dict) + if newfeat =='No' or newfeat == '': + g.notfoundfeatures.append([dict.get('Name'),dict]) + newfeat ='None' + else: + if newfeat in g.foundfeatures == False: + g.foundfeatures.append(newfeat) + g.dictOldNew[featname] = newfeat + self.swapfeature(newfeat,dict) + + + def findnewface_attemt1(self,dict): + #First attement to find a face. Perfect fit is area the same all points the ssame + face = '' + #newfeat = '' + #surftype = dict.get('surftype') + if dict.get('surftype') == 'Cylinder': + face = self.findCylinderattemt1(dict) + else: + for num in range(0,len(g.shape2.Faces)): + testdict = self.getfacebynum(num,g.shape2) + if testdict.get('surftype') != 'Cylinder': + if dict.get('area') == testdict.get('area')\ + and dict.get('facepoints') == testdict.get('facepoints'): + face = 'Face' + str(num +1) + break + return(face) + + + + def findnewface_attemt2(self,dict): + face = '' + #second attempt ignors area looks for points + face = '' + if dict.get('surftype') == 'Cylinder': + face = self.findCylinderattemt2(dict) + + + else: + + for num in range(0,len(g.shape2.Faces)): + testdict = self.getfacebynum(num,g.shape2) + if dict.get('surftype') != 'Cylinder': + + points = dict.get('facepoints') + testpoints = testdict.get('facepoints') + + # points = points[1] + # testpoints =testpoints [1] + if len(points) < len(testpoints): + list1 = points + list2 = testpoints + else: + list1 = testpoints + list2 = points + match = 0 + for vert in list1: + if vert in list2: + match=match+1 + if match == len(list1): + face = 'Face' + str(num+1) + break + + if face == '': + + dedges = dict.get('edges') + edge=dedges[0] + pnt1 = edge.Vertexes[0] #Basepoints + x=pnt1.Point.x + y=pnt1.Point.y + z=pnt1.Point.z + dlist = [rondnum(edge.Length),x,y,z] + for num in range(0,len(g.shape2.Faces)): + + testdict = self.getfacebynum(num,g.shape2) + if dict.get('surftype') != 'Cylinder': + ed =testdict.get('edges') + ary = [] + for e in ed: + pnt1 = e.Vertexes[0] #Basepoints + x=pnt1.Point.x + y=pnt1.Point.y + z=pnt1.Point.z + tlist = [e.Length,x,y,z] + if dlist == tlist: + face = 'Face' + str(num+1) + break + return(face) + + + + def findCylinderattemt1(self,dict): + face = '' + for num in g.cylfaces: + testdict = self.getfacebynum(num,g.shape2) + if dict.get('facepoints') == testdict.get('facepoints') and\ + dict.get('radius') == testdict.get('radius'): + face = 'Face' + str(num +1) + + break + return(face) + + + + def findCylinderattemt2(self,dict): + #First attement to find a face. Perfect fit is area the same all points the ssame + face = '' + ver1 = dict.get('center') + for num in g.cylfaces: + testdict = self.getfacebynum(num,g.shape2) + ver2 = testdict.get('center') + if ver1 == ver2: + face = 'Face' + str(num+1) + break + return(face) + + + + + + def findnewedge_attemt1(self,dict): + edge = '' + if dict.get('curvetype') == 'circle': + center1 = dict.get('center') + for num in range(0,len(g.shape2.Edges)): + testdict = self.getedgebynum(num,g.shape2) + center2 = testdict.get('center') + if dict.get('radius') == testdict.get('radius')\ + and dict.get('center') == testdict.get('center'): + edge ='Edge' + str(num +1) + break + + + + + else: + for num in g.notcylfaces: + testdict = self.getfacebynum(num,g.shape2) + if dict.get('length') == testdict.get('length')\ + and dict.get('center') == testdict.get('center'): + edge ='Edge' + str(num +1) + break + + return(edge) + + + def findnewedge_attemt2(self,dict): + edge = '' + if dict.get('curvetype') == 'circle': + center1 = dict.get('center') + for num in range(0,len(g.shape2.Edges)): + + testdict = self.getedgebynum(num,g.shape2) + + center2 = testdict.get('center') + if center1 == center2: + edge ='Edge' + str(num +1) + break + + + for num in range(0,len(g.shape2.Edges)): + testdict = self.getedgebynum(num,g.shape2) + if dict.get('curvetype') == 'circle': + if testdict.get('curvetype') == 'circle': + + if dict.get('startpoint') == testdict.get('startpoint'): + edge ='Edge' + str(num +1) + break + + return(edge) + + + #pnts = g.shape2.Vertexes + #mypoint = dict.get('mypoint') + #ent = None + #for p in g.shape2.Vertexes: + # if p.Point == mypoint.Point: + # ent = p + # break + #mApp('found mypoint') + #allegdes = [] + #for e in g.shape2.edges: + # if e.Vertex[0].isSame(ent) or e.Vertex[1].isSame(ent): + # alledges.append(e) + # FreeCADGui.Selection.addSelection(e) + #return(edge) + + + + + def findnewvertex_attemt1(self,dict): + vertex = '' + featname = dict.get('featname') + for num in range(0,len(g.shape2.Vertexes)): + test = self.getvertexbynum(num,g.shape2) + if dict.get('xyz') == test.get('xyz'): + vertex = Vertex + str(num +1) + return(vertex) + +funcs = classFuncs() + +def getfacelists(): + g.cylfaces = [] + g.notcylfaces=[] + + for num in range(0,len(g.shape2.Faces)): + if str(g.shape2.Faces[num].Surface) == '': + g.cylfaces.append(num) + elif str(g.shape2.Faces[num].Surface) == '': + g.notcylfaces.append(num) + + + +def selectforpart(partlabel,selectType = 'std'): + #find the constraints for the part selected + doc = FreeCAD.activeDocument() + clist = [] + pnamelist =[] + pnamelist.append(partlabel) + for obj in FreeCAD.ActiveDocument.Objects: #Select constraints + if 'ConstraintInfo' in obj.Content: + if '_mirror' not in obj.Name: + subobj1 = doc.getObject(obj.Object1) + subobj2 = doc.getObject(obj.Object2) + part1name = subobj1.Label + part2name = subobj2.Label + + if selectType == 'betweenonly': + clist.append(obj) + else: + if part1name in pnamelist or part2name in pnamelist: + clist.append(obj) + return(clist) + + +def rondnum(num,rndto = g.roundto,mmorin='mm'): + #round a number to digits in global + #left in mm for accuracy. + rn = round(num,rndto) + if mmorin == 'in': + rn=rn/25.4 + + return(rn) + + +def rondlist(list,inch = False): + x = list[0] + y = list[1] + z = list[2] + x=rondnum(x) + y=rondnum(y) + z=rondnum(z) + if inch: + x=x/25.4 + y=y/25.4 + z=z/25.4 + + + return([x,y,z]) + + + +class mApp(QWidget): + # for error messages + def __init__(self,msg,msgtype = 'ok'): + super().__init__() + self.title = 'PyQt5 messagebox - pythonspot.com' + self.left = 600 + self.top = 100 + self.width = 320 + self.height = 200 + self.initUI(msg) + + def initUI(self,msg,msgtype = 'ok'): + self.setWindowTitle(self.title) + self.setGeometry(800, 300, 300, 400) + if msgtype == 'ok': + buttonReply = QMessageBox.question(self, 'PyQt5 message', msg, QMessageBox.Ok | QMessageBox.Ok) + if msgtype == 'yn': + buttonReply = QMessageBox.question(self, 'PyQt5 message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if buttonReply == QMessageBox.Yes: + return('y') + else: + return('n') + + + + + +toolTipText = \ +''' +Updates the A2plus.assembly when parts are modified. +To update the assembly, select the part that you have modified and press the icon. +When the update has finished run the A2plus solver to vereify if there are broken constraints. +This is an attempt to reduce the number of broken constraints caused +when modifying a part from FreeCAD’s A2plus assembly program. This records the +constraint’s mating surfaces immediately before the update and tries to +reconnect them after the update. +If this fails you can undo this update by using the undo button +and running the standard A2plus updater. +''' + +class rnp_Update_A2pParts: + + def Activated(self): + #funcs.runinorder() + funcs.selectfiles() + + def Deactivated(): + """This function is executed when the workbench is deactivated""" + FreeCADGui.Selection.clearSelection() + return + + + + def GetResources(self): + mypath = os.path.dirname(__file__) + return { + 'Pixmap' : mypath + "/icons/updateA2.svg", + 'MenuText': 'Updates parts from the A2plus program that has been modified', + 'ToolTip': 'Updates modifed parts.' + } + +FreeCADGui.addCommand('rnp_Update_A2pParts',rnp_Update_A2pParts()) +#============================================================================== + +#2020-08-06 Changed Lines 162 to 172 to open the viewer if the are missing features. + + diff --git a/CD_ConstraintDiagnostics.py b/CD_ConstraintDiagnostics.py new file mode 100644 index 00000000..c8f6184c --- /dev/null +++ b/CD_ConstraintDiagnostics.py @@ -0,0 +1,1368 @@ + +#*************************************************************************** +#* * +#* Copyright (c) 2020 Dan Miel * +#* * +#* 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 * +#* * +#*************************************************************************** +# This is to be used with A2plus Assembly WorkBench +# Tries to find constraints that are conflicting with each other. + +__version__ = 0.1 + +import sys +import os +import FreeCAD +import FreeCADGui +import math + + +from PySide import QtGui, QtCore +from PySide import QtUiTools +from PySide.QtGui import * +import a2plib +import CD_checkconstraints +import a2p_solversystem +import CD_featurelabels + + + +class globaluseclass: + def __init__(self,name): + self.checkingnum =0 + self.roundto = 4 + self.labelexist = False + self.movedconsts = [] + self.exname = '' + self.chgpart = '' + self.clist = [] + self.selfeat = [] +g = globaluseclass("g") + + +class mApp(QWidget): + + # for error messages + def __init__(self,msg,msgtype = 'ok'): + super().__init__() + self.title = 'PyQt5 messagebox - pythonspot.com' + self.left = 100 + self.top = 100 + self.width = 320 + self.height = 200 + self.initUI(msg) + + def initUI(self,msg,msgtype = 'ok'): + self.setWindowTitle(self.title) + self.setGeometry(self.left, self.top, self.width, self.height) + self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + if msgtype == 'ok': + buttonReply = QMessageBox.question(self, 'PyQt5 message', msg, QMessageBox.Ok | QMessageBox.Ok) + if msgtype == 'yn': + buttonReply = QMessageBox.question(self, 'PyQt5 message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if buttonReply == QMessageBox.Yes: + pass + #print('Yes clicked.') + else: + pass + #print('No clicked.') + + self.show() + def clearvector(): + o.pointlist = [] + + + +class ShowPartProperties(QtGui.QWidget): + + def __init__(self): + QtGui.QWidget.__init__(self) + self.drt() + self.oldcell = '' + #self.listObjects = None + + def drt(self): + self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + self.setGeometry(100, 50, 700, 280)#xy,wh + self.setWindowTitle("Constraint Viewer") + self.setStyleSheet("font: 11pt arial MS") + + formx=self.width() + formy = self.height()-30 + + + + layout = QtGui.QHBoxLayout() + bar = QtGui.QMenuBar(self) + + + file = bar.addMenu("Labels") + file.addAction("Open Dialog") + file.addAction("Delete labels") + file.triggered[QAction].connect(self.process_menus) + + tbinfo = bar.addMenu("Info") + tbinfo.addAction("Places of accuracy = " + str(g.roundto)) + tbinfo.triggered[QAction].connect(self.process_menus) + + file = bar.addMenu("Help") + file.addAction("Open Help") + file.triggered[QAction].connect(self.process_menus) + + file = bar.addMenu("Misc") + file.addAction("Change Part") + file.triggered[QAction].connect(self.process_menus) + + #self.chkboxrefreshonopen=QtGui.QCheckBox('refresh on open',self) + ##Saves to a text file whether to refresh the tree on open + #self.chkboxrefreshonopen.move(200, 5) + #self.chkboxrefreshonopen.setChecked(False) + #self.chkboxrefreshonopen.setFixedHeight(18) + #self.chkboxrefreshonopen.released.connect(lambda:self.saveRefresh()) + + + self.txtboxMainerror = QtGui.QLineEdit(self) + self.txtboxMainerror.move(250,60) + self.txtboxMainerror.setFixedHeight(80) + self.txtboxMainerror.setFixedWidth(250) + self.txtboxMainerror.setText('Status') + self.txtboxMainerror.hide() + self.txtboxMainerror.setStyleSheet("font: 20pt arial MS") + + #Main Table + self.tm = QtGui.QTableWidget(self) + self.tm.setGeometry(10, 120, 650, 50) # xy,wh + self.tm.setWindowTitle("Broken Mates") + self.tm.setEditTriggers(QtGui.QTableWidget.NoEditTriggers) + self.tm.setRowCount(0) + self.tm.setColumnCount(10) + self.tm.setMouseTracking(True) + #self.tm.cellEntered.connect(self.onCellEntered) + self.tm.cellClicked.connect(self.cell_was_clicked) + self.tm.setHorizontalHeaderLabels(['Direction', 'Suppress', 'Run','Constraint name','Prt1 feat', 'Prt2 feat', 'F1', 'Part1', 'F2', 'Part2']) #, 'Part moved', 'Dist x', 'Dist y', 'Dist z']) + self.tm.horizontalHeader().sectionClicked.connect(self.fun) + for row in range(self.tm.rowCount()): + item = QtGui.QStandardItem(text) + model.setItem(row, column, item) + + + #ready for buttons + + + updatemsg=\ +''' +When a part is edited select it and press.\n +more that one can be selected but 1 is recommended. +''' + + self.btns=[] + btnLabels = [ + ['Transparent On','Toggles Transparency of parts'], + ['Find w label','Press to toggle a label for selected feature.'] + ] + self.createButtonColumn(5,btnLabels) + + + btnLabels = [ + + ['Import from part','Select a part and import \nall of the constraints for that part'], + ['Import from Tree','Copy selected constraints from the Tree'] + ] + self.createButtonColumn(140,btnLabels) + + + btnLabels = [ + ['Clear Table','Clear the table'], + ['Attach to','Select the feature to change in table.\nselect surface to change to.\n'] + ] + self.createButtonColumn(280,btnLabels) + + btnLabels = [ + ['Std Solver','Same as the solver above.\n'], + #['Solver No Chk','Does not check for errors while solving constraints.'] + ['No Option','Does not check for errors while solving constraints.'] + ] + self.createButtonColumn(420,btnLabels) + + + btnLabels = [ + ['Find in Tree','Finds the constraint in the tree\nfor the select row in table.'], + ['Clear Tree','Remove search color from tree.\n'] + ] + self.createButtonColumn(550,btnLabels) + + def createButtonColumn(self,xloc,btnLabels): + for row in range(0,len(btnLabels)): + btny = 30 +(26*row) + self.btn= QtGui.QPushButton( str(btnLabels[row][0]), self) + self.btn.move(xloc,btny) + self.btn.setFixedWidth(130) + self.btn.setFixedHeight(25) + self.btn.setToolTip(btnLabels[row][1]) + self.btn.released.connect(self.button_pushed) # pressed + self.btns.append(self.btn) + + def button_pushed(self): + index = self.btns.index(self.sender()) + buttext=self.btns[index].text() + if 'Trans' in buttext: + self.transparentOn(index) + if buttext == 'Import from part': + conflicts.selectforpart('std') + if buttext == 'Import from Tree': + conflicts.selectforTree() + + if 'Find in Tree' in buttext: + print('in') + searchterm =lastclc.cname + print('searchterm' + searchterm) + search.startsearch(searchterm,0) + + if 'Clear Tree' in buttext: + search.reset1() + if 'Clear Table' in buttext: + self.clearTable() + + + if buttext == 'Attach to': + # attaches leg to selected surface + sidefuncs.swapselectedleg() + + + if buttext == 'Std Solver': + self.stdSolve() + + if buttext == 'Solver No Chk': + mApp('Option not available') + print('Not working') + #conflicts.solveNOoerrorchecking() + cons = CD_checkconstraints.CheckConstraints.getallconstraints() + print(cons) + CD_checkconstraints.CheckConstraints.checkformovement(cons,False) + + if buttext == 'Find w label': + #createlabel for single part + if g.labelexist: + CD_featurelabels.labels.deletelabels() + g.labelexist = False + return + fname = lastclc.text + if lastclc.column == 4: + pname = self.tm.item(lastclc.row,7).text() + elif lastclc.column == 5: + pname = self.tm.item(lastclc.row,9).text() + else: + mApp('A part feature must be selected in the table') + return + + doc = FreeCAD.activeDocument() + sels =partobj = FreeCAD.ActiveDocument.getObjectsByLabel(pname) + for e in sels: + try: + partobj=e + except: + mApp('The table has lost focus. \nPlease reselect in the table.') + return + s= FreeCADGui.Selection.getSelectionEx()[0] + try: + ent = s.SubObjects[0] + except: + mApp('The selected text in the table is not a proper feature name.\n' + fname + ' ' + pname) + return + #featurelabels.labels.attachtotable(fname,ent) + CD_featurelabels.labels.labelForTable(ent,fname) + g.labelexist = True + if buttext == 'rotatepart': + move.startrotate() + + if buttext == 'Find Constraint': + + search.startsearch(lastclc.cname,0) + + def rotatepart(self): + rotate.tm.setRowCount(0) + + + def clearTable(self): + self.tm.setRowCount(0) + + + + def process_menus(self,q): + #process the menu according to their text + menutext = q.text() + if q.text() == "Open Dialog": + CD_featurelabels.form1.showme() + if q.text() == "Delete labels": + CD_featurelabels.labels.deletelabels() + if q.text() == "Change Part": + sidefuncs.replacepart1() + + + + def process_misc_menus(self,q): + menutext = q.text() + + if menutext == "Solve without error checking": + conflicts.solveNOerrorchecking() + + + + + + def stdSolve(self): + print('B') + doc = FreeCAD.activeDocument() + a2p_solversystem.solveConstraints(doc) + + + def fun4(self,Ncol): + self.tm = self.tm.sort_values(self.tm.headers[Ncol],ascending= Qt.AscendingOrder) + + + def fun(self,i): + # click in column header to sort column + self.tm.sortByColumn(i) + + def loadtable(self,listObjects): + # fill the table with infomation from a list of constraints + self.tm.setRowCount(0) + + doc = FreeCAD.activeDocument() + + row = 0 + rnum = 0 + for constraint in reversed(listObjects): + try: + cname = constraint.Name + mate = doc.getObject(cname) + except: + print('no name') + continue + ob1 = doc.getObject(mate.Object1) + if hasattr(ob1,'fixedPosition') ==False: + fixed1 = 'N' + else: + fixed1 = str(ob1.fixedPosition) + fixed1 = fixed1[0:1] + ob2 = doc.getObject(mate.Object2) + if hasattr(ob2,'fixedPosition') ==False: + fixed2 = 'N' + else: + ob2 = doc.getObject(mate.Object2) + fixed2 = str(ob2.fixedPosition) + fixed2 = fixed2[0:1] + + part1 = doc.getObject(mate.Object1) + part2 = doc.getObject(mate.Object2) + + try: + dirs = mate.directionConstraint + except: + dirs = 'None' + self.tm.insertRow(0) + + fn1 = mate.SubElement1 + fn2 = mate.SubElement2 + if len(fn1 ) == 0: + fn1 = 'None' + if len(fn2) == 0: + fn2 = 'None' + + dir =QtGui.QTableWidgetItem(str(dirs[0])) + rnum =QtGui.QTableWidgetItem(str('')) + sup = QtGui.QTableWidgetItem(str(mate.Suppressed)) + run = QtGui.QTableWidgetItem(str('Run')) # hhh + name = QtGui.QTableWidgetItem(cname) + fixed1 = QtGui.QTableWidgetItem(fixed1[0]) + Part1 = QtGui.QTableWidgetItem(part1.Label) + fname1 = QtGui.QTableWidgetItem(fn1) + fixed2 = QtGui.QTableWidgetItem(fixed2[0]) + Part2 = QtGui.QTableWidgetItem(part2.Label) + fname2 = QtGui.QTableWidgetItem(fn2) + + + self.tm.setItem(0, 0, dir) + self.tm.setItem(0, 1, sup) + self.tm.setItem(0, 2, run) + self.tm.setItem(0, 3, name) + self.tm.setItem(0, 4, fname1) + self.tm.setItem(0, 5, fname2) + self.tm.setItem(0, 6, fixed1) + self.tm.setItem(0, 7, Part1) + self.tm.setItem(0, 8, fixed2) + self.tm.setItem(0, 9, Part2) + + + + if self.tm.item(0, 4).text() == 'None': + self.tm.item(0, 4).setBackground(QBrush(QColor('yellow'))) + + if self.tm.item(0, 5).text() == 'None': + self.tm.item(0, 5).setBackground(QBrush(QColor('yellow'))) + row=row+1 + + if cname in CD_checkconstraints.g.allErrors: + if CD_checkconstraints.g.allErrors[cname].get('errortype') == 'Direction': + self.tm.item(0, 3).setBackground(QBrush(QColor('yellow'))) + # print(checkconstraints.g.allErrors[cname].get('errortype')) + #print(checkconstraints.g.allErrors) + header = self.tm.horizontalHeader() + header.setResizeMode(QtGui.QHeaderView.ResizeToContents) + self.current_hover = [0, 0] + self.hoveronoff(True) + + self.oldcell = self.tm.item(2, 1) + self.tm.current_hover = [0, 0] + for row in range(self.tm.rowCount()): + self.tm.setRowHeight(row, 15) + + + def onCellEntered(self, row, column): + return # i don't want the surfaces flickering + # cells highlight the constraint as mouse moved over + header = self.tm.horizontalHeaderItem(column).text() + item = self.tm.item(row, column) + txt = item.text() + citem = self.tm.item(row, 2) + cname = citem.text() + constraint = FreeCAD.ActiveDocument.getObject(cname) + FreeCADGui.Selection.clearSelection() + partobj1 = FreeCAD.ActiveDocument.getObject(constraint.Object1) + partobj2 = FreeCAD.ActiveDocument.getObject(constraint.Object2) + + + if header == 'Constraint name': + FreeCADGui.Selection.addSelection(partobj1,constraint.SubElement1) + FreeCADGui.Selection.addSelection(partobj2,constraint.SubElement2) + Print('Constraint name header found ') + if header == 'Prt1 feat': + FreeCADGui.Selection.addSelection(partobj1,constraint.SubElement1) + Print('Prt1 header found ') + if header == 'Prt2 feat': + FreeCADGui.Selection.addSelection(partobj2,constraint.SubElement2) + + if header == 'Part1 Label': + FreeCADGui.Selection.addSelection(partobj1) + + if header == 'Part2 Label': + FreeCADGui.Selection.addSelection(partobj2) + + if header == 'F1 Label': + pass + if header == 'F2 Label': + pass + + if str(self.oldcell) is 'None': + self.oldcell = self.tm.item(0, 0) + if column >-1 and column < form1.tm.columnCount()-1: + self.oldcell.setBackground(QBrush(QColor('white'))) + + item.setBackground(QBrush(QColor('yellow'))) + self.oldcell = item + + + def hoveronoff(self,bool): + self.tm.setMouseTracking(bool) + + def cell_was_clicked(self, row, column): + header = self.tm.horizontalHeaderItem(column).text() + item = self.tm.item(row, 3) + #g.lastclickeditem = [row,column,header] + lastclc.cellpicked(row,column) + cname = item.text() + try: + constraint = FreeCAD.ActiveDocument.getObject(cname) + partobj1 = FreeCAD.ActiveDocument.getObject(constraint.Object1) + partobj2 = FreeCAD.ActiveDocument.getObject(constraint.Object2) + except: + mApp('Constraint is not in file. Was it deleted?') + return + FreeCADGui.Selection.clearSelection() + if header == 'Run': + print(constraint.Name) + conflicts.checkformovement([constraint]) + FreeCADGui.Selection.addSelection(partobj1,constraint.SubElement1) + FreeCADGui.Selection.addSelection(partobj2,constraint.SubElement2) + + + if header == 'Constraint name': + FreeCADGui.Selection.addSelection(partobj1,constraint.SubElement1) + FreeCADGui.Selection.addSelection(partobj2,constraint.SubElement2) + if header == 'Prt1 feat': + FreeCADGui.Selection.addSelection(partobj1,constraint.SubElement1) + g.lastclickedFeat = FreeCADGui.Selection.getSelection() + if header == 'Prt2 feat': + FreeCADGui.Selection.addSelection(partobj2,constraint.SubElement2) + g.lastclickedFeat = FreeCADGui.Selection.getSelection() + FreeCADGui.Selection.setPreselection + if header == 'Part1': + FreeCADGui.Selection.addSelection(partobj1) + + if header == 'Part2': + FreeCADGui.Selection.addSelection(partobj2) + + + + if header == 'Suppress': + + if constraint.Suppressed == False: + constraint.Suppressed = True + else: + constraint.Suppressed = False + bool=constraint.Suppressed + boo=str(bool) + item2 = self.tm.item(row, column) + #item2.setText(boo[0]) + item2.setText(boo) + if header == 'Direction': + item2 = self.tm.item(row, column) + if item2.text()!= 'N': + dir=constraint.directionConstraint + if dir =='opposed': + newdir='aligned' + else: + newdir='opposed' + constraint.directionConstraint=newdir + dir=constraint.directionConstraint + item2 = self.tm.item(row, column) + item2.setText(dir[0]) + conflicts.checkformovement([constraint]) + + + + + def transparentOnold(self,index): + buttext =self.btns[index].text() + initialTransparencyState = a2plib.isTransparencyEnabled() + if 'On' in buttext: + doc = FreeCAD.ActiveDocument + initialTransparencyState = a2plib.isTransparencyEnabled() + if not initialTransparencyState: + a2plib.setTransparency() + self.btns[index].setText('Transparent Off') + if 'Off' in buttext: + self.btns[index].setText('Transparent On') + a2plib.restoreTransparency( ) + + def transparentOn(self,index): + buttext = self.btns[index].text() + tval = 0 + if 'On' in buttext: + tval = 80 + self.btns[index].setText('Transparent Off') + + if 'Off' in buttext: + tval = 0 + self.btns[index].setText('Transparent On') + for obj in FreeCAD.ActiveDocument.Objects: + if hasattr(obj,'ViewObject'): + if hasattr(obj.ViewObject,'Transparency'): + if obj.Name.startswith("Line") == False: + if obj.Name.startswith("Sketch") == False: + obj.ViewObject.Transparency = tval + + + + + + def showme(self): + #watch.onAcceptConstraint() + ret = self.checkforactiveview()# checks to see if a file is opened + if ret == 'Nostart': + return + + self.clearTable() + self.show() + lastclc.clear + + + def checkforactiveview(self): + # check that a file is open + try: + self.view =FreeCADGui.activeDocument().activeView() + except: + msg = 'A file must be opened to start this selector\nPlease open a file and try again' + mApp(msg) + return('Nostart') + return + + + def Closeme(self): + #close window and ensure that obsever is off + selObv.SelObserverOFF() + self.close() + + + + def closeEvent(self, event): + selObv.SelObserverOFF() + form1.Closeme() + self.close() + + + def resizeEvent(self, event): + #resize table + formx = self.width() + formy = self.height() + + self.tm.resize(formx -20,formy -120) + +form1=ShowPartProperties() + + + + +class classconflictreport(): + def __init__(self,name): + self.name = None + self.dictAllPlacements = {} + self.ConstraintsAll =[] + self.ConstraintsBad=[] + + self.Listofallparts = [] + self.worklist = [] + + def selectforTree(self,selectType = 'std'): + doc = FreeCAD.activeDocument() + clist = [] + sels = FreeCADGui.Selection.getSelectionEx() + if len(sels) == 0: + form1.clearTable() + mApp('Nothing was selected in the Tree.') + return + numofParts = len(sels) + for sel in sels: + cname = sel.Object.Name + cname = cname.replace('_mirror','') + cobj =doc.getObject(cname) + if 'ConstraintInfo' in cobj.Content: + clist.append(cobj) + if len(clist) == 0: + form1.clearTable() + mApp('There were no constraints selected in the Tree.\nSelect one or more constraints and try again.') + return + form1.loadtable(clist) + + + #select a part in the Gui and the attached constraints are sent to the form. + def selectforpart(self,selectType = 'std'): + pnamelist =[] + doc = FreeCAD.activeDocument() + clist = [] + #partlist = [] + sels = FreeCADGui.Selection.getSelectionEx() + if len(sels) ==0: + mApp('No parts weres selected in the window.') + return + numofParts = len(sels) + + if numofParts == 2 and selectType == 'betweenonly': + msg44 = 'Two or more parts needed for constraints to go between them' + form1.txtboxMainerror.setText(msg44) + mApp(msg44) + return + + + sels = FreeCADGui.Selection.getSelectionEx() + for sel in sels: + pnamelist.append(sel.Object.Label) + + for obj in FreeCAD.ActiveDocument.Objects: # Select constraints + if 'ConstraintInfo' in obj.Content: + if '_mirror' not in obj.Name: + subobj1 = doc.getObject(obj.Object1) + subobj2 = doc.getObject(obj.Object2) + part1name = subobj1.Label + part2name = subobj2.Label + + if selectType == 'betweenonly': + clist.append(obj) + else: + if part1name in pnamelist or part2name in pnamelist: + clist.append(obj) + if len(clist) == 0: + mApp('There are no constraints for this part.') + return + if selectType == 'list': + return(clist) + form1.loadtable(clist) + + + + def getallconstraints(self): + doc = FreeCAD.activeDocument() + constraints = [] + for obj in doc.Objects: + if 'ConstraintInfo' in obj.Content: + if not 'mirror' in obj.Name: + constraints.append(obj) + if len(constraints) == 0: + mApp('I can not find any contraints in this file.') + return(None) + return(constraints) + + + + def checkformovement(self,constraintlist,putPartBack = False,continuelist =False): + shakenpart =[] + movedobj = [] + g.movedconsts= [] + doc = FreeCAD.activeDocument() + failedcObjects= [] + failedpartnames = [] + partmoved = '' + partsmoved = [] + typemoved = '' + Bothpartsfixed=[] +# g.movedconsts =[] + if continuelist: + start = g.checkingnum + else: + start = 0 + for g.checkingnum in range(start,len(constraintlist)): + #for g.checkingnum in range(len(constraintlist),start,-1): + + cobj = constraintlist[g.checkingnum] + #corder.append(cobj.Name) + #norder.append(g.checkingnum) + subobj1 = cobj.getPropertyByName('Object1') + subobj2 = cobj.getPropertyByName('Object2') + part1 = doc.getObject(subobj1) # Save Position and fixed + part2 = doc.getObject(subobj2) + p1fix = part1.fixedPosition + p2fix = part2.fixedPosition + + + + if part1.fixedPosition and part2.fixedPosition: + Bothpartsfixed.append(cobj.Name) + elif part1.fixedPosition or part2.fixedPosition: + pass + elif part1.Label not in partsmoved and part2.Label not in partsmoved: + part1.fixedPosition = True + elif part1.Label not in partsmoved: + part1.fixedPosition = True + else: + part2.fixedPosition = True + + + #recording the location of part before move*** + preloc1 = part1.Placement.Base + preloc2 = part2.Placement.Base + + preBase1 = part1.Placement.Base # Round vectors to 6 places + preBase2 = part2.Placement.Base + preRot1 = part1.Placement.Rotation.Axis + preRot2 = part2.Placement.Rotation.Axis + preAngle1 = part1.Placement.Rotation.Angle + preAngle2 = part2.Placement.Rotation.Angle + + + + + preBasePt1 = part1.Placement.Base # Round vectors to 6 places + preBasePt2 = part2.Placement.Base + preRotPt1 = part1.Placement.Rotation.Axis + preRotPt2 = part2.Placement.Rotation.Axis + preAnglePt1 = part1.Placement.Rotation.Angle + preAnglePt2 = part2.Placement.Rotation.Angle + #xx + solved = self.solvelist([cobj]) # solve a single constraint + #doc.recompute() + #FreeCADGui.updateGui() + part1.fixedPosition = p1fix # reset parts fixed + part2.fixedPosition = p2fix + # Recording location after move + postBasePt1 = part1.Placement.Base # Round vectors to 6 places + postBasePt2 = part2.Placement.Base + postRotPt1 = part1.Placement.Rotation.Axis + postRotPt2 = part2.Placement.Rotation.Axis + postAnglePt1 = part1.Placement.Rotation.Angle + postAnglePt2 = part2.Placement.Rotation.Angle + + + disMoved = 0.0 + # Compares before and after the constraint is run + # Did part move and what kind of movment + #if self.isMoved(preBasePt1,postBasePt1,cobj,part1.Label): + if self.partMoved(preBasePt1.sub(postBasePt1),' ',cobj,part1.Label): + typemoved = 'xyz' + + if self.partMoved(preBasePt2.sub(postBasePt2),' ',cobj,part2.Label): + #failedpartnames.append(part2.Label) + typemoved = 'xyz' + + + + + if self.partMoved(preRotPt1.sub(postRotPt1),' ',cobj,part1.Label): + #failedpartnames.append(part1.Label) + typemoved = 'Rotate' + + if self.partMoved(preRotPt2.sub(postRotPt2),' ',cobj,part2.Label): + #failedpartnames.append(part2.Label) + typemoved = 'Rotate' + + + if self.partMoved(preAnglePt1,postAnglePt1,cobj,part1.Label): + typemoved = 'Angle' + + if self.partMoved(preAnglePt2,postAnglePt2,cobj,part2.Label): + typemoved = 'Angle' + + if putPartBack: + #Places part back in original location if put back is True + part1.Placement.Base = preBase1 + part1.Placement.Rotation.Axis = preRot1 + part1.Placement.Rotation.Angle = preAngle1 + part2.Placement.Base = preBase2 + part2.Placement.Rotation.Axis = preRot2 + part2.Placement.Rotation.Angle = preAngle2 + + + + #create list with cobj names + failedcObjects = g.movedconsts + + for e in failedcObjects: + failedpartnames.append(e.Name) + + + return([g.movedconsts,failedpartnames,Bothpartsfixed]) + + def partMoved(self,vec1,vec2,cobj,pname22): + dis = 'a' + if cobj in g.movedconsts: + return(False) + + if vec2 == ' ': + dis = vec1.Length + else: + dis = vec1 - vec2 + disMoved = rondnum(dis) + if disMoved != 0.0: + if cobj not in g.movedconsts: + g.movedconsts.append(cobj) + + #checks if part moved more than once + return(True) + return(False) + + + def solveNOoerrorchecking(self): + cons = self.getallconstraints() + self.checkformovement(cons,False) + + + def solvelist(self,list): + #add 1 at a time then solve allSolve + workList =[] + solved = 'no run' + doc = FreeCAD.activeDocument() + for c in list: + workList.append(c) + print(workList) + solved = a2p_solversystem.solveConstraints(doc,matelist = workList, showFailMessage=False) +conflicts = classconflictreport('conflicts') + +class classsidefunctions(): + def __init__(self,name): + self.name = name + self.sel1 = '' + self.featname1 = '' + + def swapselectedleg(self): + #starts observer to select a new enity + if lastclc.column < 4 or lastclc.column > 5: + mApp('Surfaces can only be replaced in columns/nPart1 feat or Part2 feat') + return + if len(FreeCADGui.Selection.getSelectionEx()) == 0 and lastclc.text is not 'None': + mApp('No feature has been selected') + return + + else: + sel =FreeCADGui.Selection.getSelectionEx()[0] + featname = sel.SubElementNames[0] + selObv.SelObserverON() + + + def trunoffobserv(self,objname, sub): + #Turns observer off and selects both features + + doc = FreeCAD.activeDocument() + selObv.SelObserverOFF() + if g.chgpart == 'part': + self.replacepart2() + else: + self.swap1leg() + + def swap1leg(self): + print('In swap one leg***') + feat2name = "" + if len(FreeCADGui.Selection.getSelectionEx()) == 0: + return + sel = FreeCADGui.Selection.getSelectionEx()[0] + if lastclc.text is 'None': + feat2name = sel.SubElementNames[0] + else: + feat2name = sel.SubElementNames[0] + cname = lastclc.cname + + FreeCADGui.Selection.clearSelection() + dict = {'cname':cname,'SubElement':lastclc.SubElement,'dir':lastclc.dir,'newfeat':feat2name} + self.swapfeature(dict) + cobj =FreeCAD.ActiveDocument.getObject(cname) + + + partobj1 = FreeCAD.ActiveDocument.getObject(cobj.Object1) + partobj2 = FreeCAD.ActiveDocument.getObject(cobj.Object2) + print('cobj.object1') + print(cobj.Object1) + print(cobj.Object2) + + if sel.Object.Name != partobj1.Name and sel.Object.Name != partobj2.Name: + mApp('The constraint can only be moved to another surface of the same part') + return + FreeCADGui.Selection.addSelection(partobj1,cobj.SubElement1) + FreeCADGui.Selection.addSelection(partobj2,cobj.SubElement2) + + item = form1.tm.item(lastclc.row, lastclc.column).setText(feat2name) + + def swapfeature(self,dict): + print('In swapfeatuer') + #changes a legs mating feature + newfeat = dict.get('newfeat') + leg = '' + mleg = '' + cname = dict.get('cname') + cobj = FreeCAD.ActiveDocument.getObject(cname) + mobj = FreeCAD.ActiveDocument.getObject(cname+'_mirror') + + SubElement = dict.get('SubElement') + + if SubElement == 'SubElement1': + leg = cobj.SubElement1 = newfeat + mleg = mobj.SubElement1 = newfeat + if SubElement == 'SubElement2': + leg = cobj.SubElement2 = newfeat + mleg = mobj.SubElement2 = newfeat + dir = dict.get('dir') + if hasattr(cobj,'directionConstraint'): + cobj.directionConstraint = dir + if hasattr(mobj,'directionConstraint'): + mobj.directionConstraint = dir + + return + + def replacepart1(self): + if len(FreeCADGui.Selection.getSelectionEx()) == 0: + mApp('No part has been selected') + return + g.clist = conflicts.selectforpart('list') + sel =FreeCADGui.Selection.getSelectionEx()[0] + g.exname = sel.Object + g.chgpart = 'part' + g.selfeat = [sel.Object,] #.append([sel.object,featname]) + selObv.SelObserverON() + + + #def trunoffpartobserv(self, obj): + # selObv.SelObserverOFF() + + + def replacepart2(self): + if object is None: + return + g.chgpart = '' + doc = FreeCAD.activeDocument() + + oldpart = g.exname + oldname = oldpart.Name + + sel = FreeCADGui.Selection.getSelectionEx()[0] + newpart = sel.Object + newname = newpart.Name + + base1 = oldpart.Placement.Base + rotate = oldpart.Placement.Rotation.Axis + angle = oldpart.Placement.Rotation.Angle + tempshape = oldpart.Shape + + tempshape = oldpart.Shape + tempbase = newpart.Placement.Base + tempRotation = newpart.Placement.Rotation.Axis + tempangle = newpart.Placement.Rotation.Angle + FreeCADGui.Selection.clearSelection() + FreeCADGui.Selection.addSelection(oldpart) + + oldpart.Shape = newpart.Shape + oldpart.Placement.Base = base1# = newpart.Placement.Base + oldpart.Placement.Rotation.Axis = rotate #= newpart.Placement.Rotation.Axis + oldpart.Placement.Rotation.Angle = angle + + g.chgpart == '' + newpart.Shape = tempshape + newpart.Placement.Base = tempbase + newpart.Placement.Rotation.Axis = tempRotation + newpart.Placement.Rotation.Angle = tempangle + + def ChangePart(self): + sels = FreeCADGui.Selection.getSelectionEx() + if len(sels) != 2: + mApp('You need to select 2 parts for this feature') + return + part1 = sels[0] + part2 = sels[1] + shape1 = part1.Shape + shape2 = part2.Shape + part1.shape = shape2 + part2.shape = shape1 + +sidefuncs = classsidefunctions('sidefuncs') + + +class SelObserver: + def __init__(self): + pass + + def SelObserverON(self): + o=FreeCADGui.Selection.addObserver(selObv) + def SelObserverOFF(self): + print('SelObserverOFF') + try: + FreeCADGui.Selection.removeObserver(selObv) + except: + print('removeObserver failed in C checker') + + def setPreselection(self,doc,obj,sub): # Preselection object + pass + def addSelection(self,doc,obj,sub,pnt): # Selection object + sidefuncs.trunoffobserv( obj, sub) + + + pass + def removeSelection(self,doc,obj,sub): # Delete the selected object + pass + def setSelection(self,doc): + #this is sent from menu + funcs.constraintselected('table') + +selObv =SelObserver() +#selObv.SelObserverON() +#selObv.SelObserverOFF() + + +class classsearch(): + def __init__(self): + self.founditems = [] + def startsearch(self,searchterm,colnum): + #col0 = Labcobj Label + #col2 = cobj.Name + #self.reset1() + mw = FreeCADGui.getMainWindow() + tab = mw.findChild(QtGui.QTabWidget, u'combiTab') + tree = tab.widget(0).findChildren(QtGui.QTreeWidget)[0] + top = tree.topLevelItem(0) + for idx in range(top.childCount()): + self.searchTreeItem(tree, top.child(idx), searchterm,colnum) + + def searchTreeItem(self, tree, item, searchterm,colnum): + for idx in range(item.childCount()): + itm = item.child(idx) + if searchterm in itm.text(colnum): + itm.setBackground(0, QtGui.QColor(255, 255, 0, 100)) + self.expandParent(tree, itm) + self.searchTreeItem(tree, item.child(idx),searchterm,colnum) + + def expandParent(self, tree, item): + parent = item.parent() + if parent: + tree.expandItem(parent) + self.expandParent(tree, parent) + + + def resetAll(self, item): + for idx in range(item.childCount()): + itm = item.child(idx) + self.founditems.append(itm) + itm.setBackground(0, QBrush()) + #itm.setBackground(QBrush(QColor('white'))) + self.resetAll(itm) + + + def reset1(self): + + mw = FreeCADGui.getMainWindow() + tab = mw.findChild(QtGui.QTabWidget, u'combiTab') + tree = tab.widget(0).findChildren(QtGui.QTreeWidget)[0] + top = tree.topLevelItem(0) + for idx in range(top.childCount()): + self.resetAll(top.child(idx)) + + def removecolor(self): + for e in self.founditems: + itm.setBackground(0, QBrush()) + #self.oldcell.setBackground(QBrush(QColor('white'))) +search = classsearch() + + + + +def rondlist(list,inch = False): + x = list[0] + y = list[1] + z = list[2] + x = rondnum(x) + y = rondnum(y) + z = rondnum(z) + if inch: + x = x/25.4 + y = y/25.4 + z = z/25.4 + + + return([x,y,z]) + + +def rondnum(num,rndto = g.roundto,mmorin='mm'): + #round a number to digits in global + #left in mm for accuracy. + rn = round(num,g.roundto) + if mmorin == 'in': + rn=rn/25.4 + + return(rn) + + + + + +class classlastclickeditem: + def __init__(self,name): + self.row = -1 + self.column = -1 + self.header = '' + self.cname = '' + self.cobj = None + self.dir = 'N' + self.text = '' + self.SubElement = '' + + def clear(self): + self.row = -1 + self.column = -1 + self.header = '' + self.cname = '' + self.cobj = None + self.dir= 'N' + + def cellpicked(self,row,column): + doc = FreeCAD.activeDocument() + item = form1.tm.item(row, column) + + self.item = item + self.row = row + self.column = column + self.text = item.text() + + + + self.header = form1.tm.horizontalHeaderItem(column).text() + citem = form1.tm.item(self.row, 3) + cname = citem.text() + self.cname = cname + + self.cobj = FreeCAD.ActiveDocument.getObject(self.cname) + if hasattr(self.cobj,'directionConstraint'): + self.dir = self.cobj.directionConstraint +# def attachto(self): + + if self.column == 4: + self.SubElement = 'SubElement1' + if self.column == 5: + self.SubElement = 'SubElement2' + + return(self.SubElement) +lastclc=classlastclickeditem("lastclc") + + +#This class looks for mouse clicks +class ViewObserver: + def __init__(self): + + self.view =None + self.o = None + self.c = None + def vostart(self): + try: + self.view =FreeCADGui.activeDocument().activeView() + #print(str(type(FreeCADGui.activeDocument().activeView()))) + #if 'None' in str(type(FreeCADGui.activeDocument().activeView())): + except: + msg = 'A file must be opened to start this selector\nPlease open a file and try again' + mApp(msg) + return('Nostart') + #self.view=FreeCADGui.activeDocument().activeView() + self.o=ViewObserver() + self.c = self.view.addEventCallback("SoMouseButtonEvent",self.o.logPosition) + def vooff(self): + print('vooff') + try: + self.view.removeEventCallback("SoMouseButtonEvent",self.c) + except Exception as e: + print(str(e)) + + def logPosition(self, myinfo): + down = (myinfo["State"] == "DOWN") + up = (myinfo["State"] == "UP") + pos = myinfo["Position"] + if up: + pass + if (down): + + if myinfo['Button']=='BUTTON1': + selcontrol + info.readTreeclick() + partinfo =FreeCADGui.activeDocument().activeView().getObjectInfo(pos) + if partinfo == None: + + info.clearsels() + + + else: + pass + + if up: + pass +viewob = ViewObserver() #(None) +#viewob = ViewObserver(FreeCADGui.activeDocument().activeView()) +#viewob =ViewObserver(FreeCADGui.activeDocument().activeView()) + +class formReport(QtGui.QDialog): + + def __init__(self,name): + self.name = name + row1 = 30 + row2 = 65 + + super(formReport,self).__init__() + self.setWindowTitle('Constraint Checker') + self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + self.setGeometry(300,100,300,200)#xy,wh + self.setStyleSheet("font: 10pt arial MS") + #self.setmoldal(True) + + self.txtboxstatus = QtGui.QTextEdit(self) + self.txtboxstatus.move(5,30) + self.txtboxstatus.setFixedWidth(250) + self.txtboxstatus.setFixedHeight(60) + + self.lblviewlabel = QtGui.QLabel(self) + self.lblviewlabel.setText('Status"') + self.lblviewlabel.move(5,5) + self.lblviewlabel.setFixedWidth(250) + self.lblviewlabel.setFixedHeight(20) + self.lblviewlabel.setStyleSheet("font: 13pt arial MS") + + + + + #def resizeEvent(self, event): + # #resize table + # formx=self.width() + # formy = self.height() + # form1.tm.resize(formx -20,formy -200) + + def showme(self,msg): + print('show') + self.show() + #self.txtboxstatus.setText(msg) + #self.txtboxstatus.update() + #self.txtboxstatus.repaint() + + #return + + def Closeme(self): + self.close() + + def closeEvent(self, event): + self.close() + +statusform = formReport('statusform') + + +toolTipText = \ +''' +Select geometry to be constrained +within 3D View ! + +Suitable Constraint buttons will +get activated. + +Please also read tooltips of each +button. +''' + + +toolTipText = \ +''' +View constraints +''' + +class rnp_Constraint_Viewer: + + def Activated(self): + process.startprog() + + def onDeleteConstraint(self): + self.constraintValueBox.deleteLater() + a2plib.setConstraintEditorRef(None) + FreeCADGui.Selection.clearSelection() + + + def Deactivated(): + """This function is executed when the workbench is deactivated""" + #FreeCADGui.Selection.clearSelection() + + return + + + + def GetResources(self): + mypath = os.path.dirname(__file__) + return { + 'Pixmap' : mypath + "/icons/ConstraintDiagnostics.svg", + 'MenuText': 'Edit selected constraint', + 'ToolTip': toolTipText + } + +FreeCADGui.addCommand('rnp_Constraint_Viewer',rnp_Constraint_Viewer()) +#============================================================================== + + +class process: + def __init__(self,name): + self.name = name + + def startprog(): + # formg.write('startprog') + form1.showme() + +#form1.showme() + diff --git a/CD_checkconstraints.py b/CD_checkconstraints.py new file mode 100644 index 00000000..bc27672d --- /dev/null +++ b/CD_checkconstraints.py @@ -0,0 +1,505 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2020 Dan Miel * +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU Lesser General Public License (LGPL) * +#* 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 * +#* * +#*************************************************************************** +# This is to be used with A2plus Assembly WorkBench +# Tries to find constraints that are conflicting with each other. + +__version__ = 0.1 + +import sys +import os +import FreeCAD +import FreeCADGui +from PySide import QtGui, QtCore +from PySide.QtGui import * +import a2p_solversystem +import a2p_constraintServices +import CD_ConstraintDiagnostics + +class globaluseclass: + def __init__(self,name): + self.checkingnum =0 + self.roundto = 6 + self.labelexist =False + self.movedconsts =[] + self.test =[] + self.allErrors = {} +g = globaluseclass("g") + + +class mApp(QWidget): + + # for error messages + def __init__(self,msg): + super().__init__() + self.title = 'PyQt5 messagebox - pythonspot.com' + self.left = 100 + self.top = 100 + self.width = 320 + self.height = 200 + self.initUI(msg) + + def initUI(self,msg,msgtype = 'ok'): + self.setWindowTitle(self.title) + self.setGeometry(self.left, self.top, self.width, self.height) + self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + if msgtype == 'ok': + buttonReply = QMessageBox.question(self, 'PyQt5 message', msg, QMessageBox.Ok | QMessageBox.Ok) + if msgtype == 'yn': + buttonReply = QMessageBox.question(self, 'PyQt5 message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if buttonReply == QMessageBox.Yes: + pass + #print('Yes clicked.') + else: + pass + #print('No clicked.') + + self.show() + + def clearvector(): + o.pointlist = [] + +class formMain(QtGui.QMainWindow): + + def __init__(self,name): + self.name = name + super(formMain,self).__init__() + self.setWindowTitle('Constraint Checker') + self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + self.setGeometry(300,100,600,140) # xy,wh + self.setStyleSheet("font: 11pt arial MS") + + self.txtboxReport = QtGui.QTextEdit(self) + self.txtboxReport.move(5,5) + self.txtboxReport.setFixedWidth(600) + self.txtboxReport.setFixedHeight(75) + + self.lblviewlabel = QtGui.QLabel(self) + self.lblviewlabel.setText('To view the constraints, press "Open Viewer"') + self.lblviewlabel.move(5,90) + self.lblviewlabel.setFixedWidth(250) + self.lblviewlabel.setFixedHeight(20) + self.lblviewlabel.setStyleSheet("font: 13pt arial MS") + + self.btnOpenViewer = QtGui.QPushButton(self) + self.btnOpenViewer.move(365,90) + self.btnOpenViewer.setFixedWidth(100) + self.btnOpenViewer.setFixedHeight(28) + self.btnOpenViewer.setIcon(QtGui.QIcon('./icons/ConstraintDiagnostics.svg')) + self.btnOpenViewer.setToolTip("View constraints the assembly.") + self.btnOpenViewer.setText("Open Viewer") + self.btnOpenViewer.clicked.connect(lambda:self.openViewer()) + + self.btnCloseForm = QtGui.QPushButton(self) + self.btnCloseForm.move(475,90) + self.btnCloseForm.setFixedWidth(100) + self.btnCloseForm.setFixedHeight(28) + self.btnCloseForm.setToolTip("Close this form.") + self.btnCloseForm.setText("Close") + self.btnCloseForm.clicked.connect(lambda:self.Closeme()) + + def openViewer(self): + clist = [] + doc = FreeCAD.activeDocument() + for (k,v) in g.allErrors.items(): + cobj = doc.getObject(k) + clist.append(cobj) + + CD_ConstraintDiagnostics.form1.show() + CD_ConstraintDiagnostics.form1.loadtable(clist) + + + def resizeEvent(self): + #resize table + formx = self.width() + formy = self.height() + self.txtboxReport.resize(formx -20, formy -120) + + def showme(self,msg): + self.txtboxReport.setText(msg) + self.show() + + + def Closeme(self): + self.close() + + def closeEvent(self, event): + form1.Closeme() + self.close() + +form1 = formMain('form1') + + +class classfilecheck(): + def __init__(self): + pass + def opendoccheck(self): + doc = None + doc = FreeCAD.activeDocument() + + if doc is None: + msg = 'A file must be selected to start this selector\nPlease open a file and try again' + mApp(msg) + return('Nostart') + + return() +filecheck = classfilecheck() + + +class classCheckConstraints(): + def __init__(self): + self.name = None + + self.dictAllPlacements = {} + self.ConstraintsAll = [] + self.ConstraintsBad = [] + + self.Listofallparts = [] + self.worklist = [] + self.test = [] + self.dir_errors = [] + self.rigids = [] + + + def startcheck(self,constraints= 'all'): + if filecheck.opendoccheck() == 'Nostart': + return + doc = FreeCAD.activeDocument() + #g.allErrors = {} + if constraints == 'all': + constraints = self.getallconstraints() + if len(constraints) == 0: + return + + #ConstraintDiagnostics.statusform.showme('messg') + CD_ConstraintDiagnostics.statusform.show() + CD_ConstraintDiagnostics.statusform.txtboxstatus.setText('Running Checker.') + + CD_ConstraintDiagnostics.statusform.update() + + + ss = a2p_solversystem.SolverSystem() + + ss.loadSystem(doc) + ss.assignParentship(doc) + rigids=ss.rigids + + for e in rigids: # get rigid parts + self.rigids.append(e.objectName) + + + + + self.dir_errors = a2p_constraintServices.redAdjustConstraintDirections(doc, constraints) + self.checkformovement(constraints, True) + if len(g.allErrors) != 0: + msg = '' + for e in g.allErrors: + line = str(g.allErrors.get(e)) + msg = msg + line + '\n' + form1.showme(msg) + else: + print('Zero errors') + CD_ConstraintDiagnostics.statusform.Closeme() + + + def checkformovement(self,constraintlist,putPartBack = True): + doc = FreeCAD.activeDocument() + partmoved = '' + partsmoved = [] + typemoved = '' + Bothpartsfixed = False + + for checkingnum in range(0,len(constraintlist)): + cobj = constraintlist[checkingnum] + + if cobj.Name in self.dir_errors: + errortype = '' + len1 = 0 + len2 = 0 + + if len(cobj.SubElement1) == 0: + errortype = 'Feat 1 missing' + if len(cobj.SubElement2) == 0: + errortype = 'Feat 2 missing' + if errortype == '': + errortype = 'Direction' + self.addError(cobj,errortype,'') + continue + subobj1 = cobj.getPropertyByName('Object1') + subobj2 = cobj.getPropertyByName('Object2') + part1 = doc.getObject(subobj1) # Save Position and fixed + part2 = doc.getObject(subobj2) + p1fix = False + p2fix = False + if hasattr(part1,"fixedPosition"): + p1fix =part1.fixedPosition + if hasattr(part2,"fixedPosition"): + p2fix = part2.fixedPosition + + if hasattr(part1,"fixedPosition") and hasattr(part2,"fixedPosition"): + if part1.fixedPosition and part2.fixedPosition: + Bothpartsfixed = True + self.addError(cobj,'Both fixed','') + elif part1.fixedPosition or part2.fixedPosition: + pass + if part1.Label not in partsmoved and part2.Label not in partsmoved: + part1.fixedPosition = True + elif part1.Label not in partsmoved: + part1.fixedPosition = True + else: + part2.fixedPosition = True + + + elif hasattr(part1,"fixedPosition") and hasattr(part2,"fixedPosition") == False: + if part1.Label not in partsmoved: + part1.fixedPosition =True + elif hasattr(part1,"fixedPosition") == False and hasattr(part2,"fixedPosition"): + if part2.Label not in partsmoved and part1.Label not in partsmoved: + part2.fixedPosition =True + + + #recording the location of part before move*** + preBase1 = part1.Placement.Base + preBase2 = part2.Placement.Base + preRot1 = part1.Placement.Rotation.Axis + preRot2 = part2.Placement.Rotation.Axis + preAngle1 = part1.Placement.Rotation.Angle + preAngle2 = part2.Placement.Rotation.Angle + + + + + preBasePt1 = part1.Placement.Base + preBasePt2 = part2.Placement.Base + preRotPt1 = part1.Placement.Rotation.Axis + preRotPt2 = part2.Placement.Rotation.Axis + preAnglePt1 = part1.Placement.Rotation.Angle + preAnglePt2 = part2.Placement.Rotation.Angle + #xx + + solved = self.solvelist([cobj]) # solve a single constraint + if hasattr(part1,"fixedPosition"): + part1.fixedPosition = p1fix #reset parts fixed + if hasattr(part2,"fixedPosition"): + part2.fixedPosition = p2fix + + + # Recording location after move + postBasePt1 = part1.Placement.Base # Round vectors to 6 places + postBasePt2 = part2.Placement.Base + postRotPt1 = part1.Placement.Rotation.Axis + postRotPt2 = part2.Placement.Rotation.Axis + postAnglePt1 = part1.Placement.Rotation.Angle + postAnglePt2 = part2.Placement.Rotation.Angle + localmove = False + + moved = self.partMoved(preBasePt1,postBasePt1,'xyz',cobj,part1.Label) + if moved: + localmove = True + pass + + moved = self.partMoved(preBasePt2,postBasePt2,'xyz',cobj,part2.Label) + if moved: + localmove = True + pass + + moved = self.partMoved(preRotPt1,postRotPt1,'Rotate',cobj,part1.Label) + if moved: + localmove = True + pass + + moved = self.partMoved(preRotPt2,postRotPt2,'Rotate',cobj,part2.Label) + if moved: + localmove = True + pass + + moved = self.partMoved(preAnglePt1, postAnglePt1,'Angle',cobj,part1.Label) + + if moved: + localmove = True + pass + moved = self.partMoved(preAnglePt2, postAnglePt2,'Angle',cobj,part2.Label) + if moved: + localmove = True + pass + partsmoved.append(part1.Label) + partsmoved.append(part2.Label) + + + if putPartBack: + #Places part back in origial location if put back is True + part1.Placement.Base = preBase1 + part1.Placement.Rotation.Axis = preRot1 + part1.Placement.Rotation.Angle = preAngle1 + part2.Placement.Base = preBase2 + part2.Placement.Rotation.Axis = preRot2 + part2.Placement.Rotation.Angle = preAngle2 + + + + def partMoved(self,vec1,vec2,movetype,cobj): + + if cobj.Name in g.allErrors.keys(): + return(False) + #dis = 'a' + errortype = '' + foundError = False + moved = '' + + if movetype == 'Angle': + dis1=rondnum(vec1) + dis2=rondnum(vec2) + if dis1 != dis2: + foundError = True + errortype = 'Conflict' + moved = movetype + + else: + + v1=FreeCAD.Vector(rondlist(vec1)) + v2=FreeCAD.Vector(rondlist(vec2)) + x,y,z = vec1 + v1 = [x,y,z] + v1=FreeCAD.Vector(rondlist(v1)) + + x,y,z = vec2 + v2 = [x,y,z] + v2=FreeCAD.Vector(rondlist(v2)) + if v1 != v2: + self.test.append(cobj.Name) + self.test.append([v1]) + self.test.append([v2]) + foundError = True + errortype = 'Conflict' + moved = movetype + if foundError: + self.addError(cobj,errortype,moved) + return(foundError) + def addError(self,cobj,errortype,movetype): + dict = {'Name':cobj.Name,'errortype':errortype,'movetype':movetype} + g.allErrors[cobj.Name] = dict + def getallconstraints(self): + doc = FreeCAD.activeDocument() + constraints = [] + for obj in doc.Objects: + if 'ConstraintInfo' in obj.Content: + if not 'mirror' in obj.Name: + constraints.append(obj) + + if len(constraints) == 0: + mApp('Cannot find any contraints in this file.') + return(None) + return(constraints) + + + def solveNOoerrorchecking(self): + cons = self.getallconstraints() + print(cons) + + self.checkformovement(cons,False) + + def solvelist(self,list): + # add 1 at a time then solve allSolve + workList =[] + solved = 'no run' + doc = FreeCAD.activeDocument() + for c in list: + print(c.Name) + workList.append(c) + solved = a2p_solversystem.solveConstraints(doc,None,False,matelist = workList, showFailMessage=False) + return(solved) +CheckConstraints = classCheckConstraints() + + + +def rondlist(list,inch = False): + x = list[0] + y = list[1] + z = list[2] + x=rondnum(x) + y=rondnum(y) + z=rondnum(z) + if inch: + x=x/25.4 + y=y/25.4 + z=z/25.4 + + + return([x,y,z]) + + +def rondnum(num,rndto = g.roundto,mmorin='mm'): + # round a number to digits in global + # left in mm for accuracy. + rn = round(num,g.roundto) + if mmorin == 'in': + rn=rn/25.4 + + return(rn) + + + + +toolTipText = \ +''' +Select geometry to be constrained +within 3D View ! + +Suitable Constraint buttons will +get activated. + +Please also read tooltips of each +button. +''' + + +toolTipText = \ +''' +check constraints. +''' + +class rnp_Constraint_Checkeralone: + + def Activated(self): + CheckConstraints.startcheck() + + def onDeleteConstraint(self): + pass + + def Deactivated(): + pass + #"""This function is executed when the workbench is deactivated""" + + + def GetResources(self): + mypath = os.path.dirname(__file__) + return { + 'Pixmap' : mypath + "/icons/ConflictCheckeralone.svg", + 'MenuText': 'Checks constraints', + 'ToolTip': 'Checks constraints' + } + +FreeCADGui.addCommand('rnp_Constraint_Checkeralone', rnp_Constraint_Checkeralone()) +#============================================================================== + + + + diff --git a/CD_featurelabels.py b/CD_featurelabels.py new file mode 100644 index 00000000..dc969c04 --- /dev/null +++ b/CD_featurelabels.py @@ -0,0 +1,217 @@ + + + + + + +import sys +import os +from FreeCAD import Console +import FreeCADGui +import FreeCAD +from PySide import QtUiTools +from PySide.QtGui import * +from PySide import QtGui, QtCore + +import PySide + + +class formMain(QtGui.QMainWindow): + + def __init__(self,name): + self.name = name + super(formMain,self).__init__() + self.setWindowTitle('Create Labels') + self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + self.setGeometry(300,100,200,300)#xy,wh + self.setStyleSheet("font: 11pt arial MS") + + self.btnLabels = [['Add Face Labels','Add labels to all of the faces on a selected part'], + ['Add Edge Labels','Add labels to all of the edges on a selected part'], + ['Add Vertex Labels','Add labels to all of the vertexes on a selected part'], + ['Delete Labels','Delet all labels'], + ['Attach to','Enter a name in the texbox and press to add a label'], + ['Selected Labels','Labels added to selected features'], + ['Close',''] # + ] + + + + self.txtboxaddlabel = QtGui.QLineEdit(self) + self.txtboxaddlabel.move(10, 25) + self.txtboxaddlabel.setFixedHeight(25) + self.txtboxaddlabel.setFixedWidth(180) + self.txtboxaddlabel.setText('Feature name') + + + + self.btns=[] + BtnNum = 0 + for row in range(0,len(self.btnLabels)) : + btny = 70 +(28*row) + + self.btn= QtGui.QPushButton( str(self.btnLabels[row][0]), self) + self.btn.move(5,btny) + self.btn.setFixedWidth(190) + self.btn.setFixedHeight(25) + self.btn.setToolTip(self.btnLabels[row][1]) + self.btn.released.connect(self.button_pushed) # pressed + self.btns.append(self.btn) + BtnNum = BtnNum + 1 + + + def button_pushed(self): + index = self.btns.index(self.sender()) + btext=self.btns[index].text() + print(btext) + if btext == 'Add Face Labels': + labels.addlabels('Face') + if btext == 'Add Edge Labels': + labels.addlabels('Edge') + if btext == 'Add Vertex Labels': + labels.addlabels('Vertex') + + if btext == 'Delete Labels': + labels.deletelabels() + if btext == 'Attach to': + labels.attachto() + if btext == 'Close': + self.Closeme() + + if btext == 'Selected Labels': + labels.selectedlabels() + + + def hideMe(self): + Gui.Selection.clearSelection() + self.close() + + + def showme(self): + self.show() + + def Closeme(self): + self.close() + + def closeEvent(self, event): + form1.Closeme() + self.close() + +form1 = formMain('form1') + +class classLabels(): + def __init__(self): + self.labelGroup = None + pass + #self.name = name + + + def checkselection(self): + #checks to see if labels already exist + doc = FreeCAD.activeDocument() + #loc = None + print('checking for label') + self.labelGroup = doc.getObject("partLabels") + if self.labelGroup is None: + self.labelGroup=doc.addObject("App::DocumentObjectGroup", "partLabels") + + if len(FreeCADGui.Selection.getSelection()) == 0: + #dlib.mApp('Please select One part.') + return(False) + return(True) + + + def addlabels(self,feat): + sel = self.checkselection() + if not sel: + return + sel = FreeCADGui.Selection.getSelection() # Select an object + if feat == 'Face': + features = sel[0].Shape.Faces + if feat == 'Edge': + features = sel[0].Shape.Edges + if feat == 'Vertex': + features = sel[0].Shape.Vertexes + + + for num in range(0,len(features)): + ent = features[num] + if feat == 'Vertex': + loc = ent.Point + else: + loc = ent.CenterOfMass + partLabel = self.makelabel(ent,feat+str(num+1),loc) + self.labelGroup.addObject(partLabel) + + + def makelabel(self,ent,name,loc): + partLabel = FreeCAD.ActiveDocument.addObject("App::AnnotationLabel","partLabel") + partLabel.LabelText = name + partLabel.BasePosition.x = loc[0] + partLabel.BasePosition.y = loc[1] + partLabel.BasePosition.z = loc[2] + partLabel.ViewObject.BackgroundColor = (1.0,1.0,0.0) + partLabel.ViewObject.TextColor = (0.0,0.0,0.0) + return(partLabel) + + + + def deletelabels(self): + for obj in FreeCAD.ActiveDocument.Objects: + if "partLabel" in obj.Label: + FreeCAD.ActiveDocument.removeObject(obj.Name) + + + def attachto(self,sel = None,featname = ''): + + sel = self.checkselection() + if not sel: + return + if featname == '': + featname = form1.txtboxaddlabel.text() + if sel is None: + sel = FreeCADGui.Selection.getSelection()[0] + FreeCADGui.Selection.clearSelection() + FreeCADGui.Selection.addSelection(sel,featname) + s= FreeCADGui.Selection.getSelectionEx()[0] + ent = s.SubObjects[0] + self.makelabel(ent,name,loc) + + + + + + def getEntLoc(self,ent,featname): + if 'V' in featname: + loc = ent.Point + else: + loc = ent.CenterOfMass + partLabel = self.makelabel(ent,featname,loc) + self.labelGroup.addObject(partLabel) + self.makelabel(ent,featname,loc) + + + #Create a label to find a part + def labelForTable(self,ent, featname): + sel = self.checkselection() + self.getEntLoc(ent,featname) + + + + def selectedlabels(self): + sel = self.checkselection() + if not sel: + return + sels = FreeCADGui.Selection.getSelectionEx() # Select an object + for sel in sels: + featname= sel.SubElementNames[0] + ent = sel.SubObjects[0] + self.getentloc(ent,featname) + + + +labels=classLabels() + + +#form1.showme() + diff --git a/CD_importpart.py b/CD_importpart.py new file mode 100644 index 00000000..b8118f73 --- /dev/null +++ b/CD_importpart.py @@ -0,0 +1,1967 @@ +#*************************************************************************** +#* * +#* 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 os.path +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): #changed to true Dan + 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) + From 77473dc31d246afa6190c384cfcaf11657e6db88 Mon Sep 17 00:00:00 2001 From: Dan Miel <42722171+DanMiel@users.noreply.github.com> Date: Tue, 15 Mar 2022 14:33:58 -0700 Subject: [PATCH 3/9] Update a2p_constraintServices.py Added 3 lines for the part updater in constraint diagnostics. --- a2p_constraintServices.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/a2p_constraintServices.py b/a2p_constraintServices.py index b2b59d80..7c4a467d 100644 --- a/a2p_constraintServices.py +++ b/a2p_constraintServices.py @@ -36,6 +36,7 @@ def redAdjustConstraintDirections(doc): a2p-constraints of a document, in order to reach a solvable state if possible, especially used after updating of imported parts. """ + result = [] #added for CD unknown_constraints = [] constraints = [ obj for obj in doc.Objects if 'ConstraintInfo' in obj.Content] for c in constraints: @@ -72,12 +73,14 @@ def redAdjustConstraintDirections(doc): unknown_constraints.append(c.Type) except: print("Errors occurred during processing of {}".format(c.Label)) - + result.append(c.Name) #added for CD + if len(unknown_constraints) > 0: print("redefineConstraintDirections(): Found unknown constraints: {}".format( set(unknown_constraints) ) ) + return(result) #added for CD #============================================================================== class a2p_reAdjustConstraintDirectionsCommand: From a05754b0592493a08348988549d72146825a402c Mon Sep 17 00:00:00 2001 From: Dan Miel <42722171+DanMiel@users.noreply.github.com> Date: Tue, 15 Mar 2022 14:37:44 -0700 Subject: [PATCH 4/9] Icons for constraint diagnostics. updating part and viewing. --- icons/ConstraintDiagnostics.svg | 1109 +++++++++++++++++++++++++++++++ icons/updateA2.svg | 222 +++++++ 2 files changed, 1331 insertions(+) create mode 100644 icons/ConstraintDiagnostics.svg create mode 100644 icons/updateA2.svg diff --git a/icons/ConstraintDiagnostics.svg b/icons/ConstraintDiagnostics.svg new file mode 100644 index 00000000..58fa29d6 --- /dev/null +++ b/icons/ConstraintDiagnostics.svg @@ -0,0 +1,1109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/updateA2.svg b/icons/updateA2.svg new file mode 100644 index 00000000..4503bd35 --- /dev/null +++ b/icons/updateA2.svg @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + + + + + + + + + From 2bfbc0d7a6bcf937ee984fd7efa2ce3c029b19e0 Mon Sep 17 00:00:00 2001 From: luz paz Date: Fri, 18 Mar 2022 09:48:26 -0400 Subject: [PATCH 5/9] Expanded comment abbreviation for more clarity --- InitGui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InitGui.py b/InitGui.py index 84fb5a4b..6a4d1762 100644 --- a/InitGui.py +++ b/InitGui.py @@ -79,7 +79,7 @@ def Initialize(self): import a2p_bom # bom == bill of materials == partslist import a2p_constraintServices import a2p_searchConstraintConflicts - import CD_A2plusupdater #for CD function + import CD_A2plusupdater # for Constraint Diagnostic function if a2plib.getRecursiveUpdateEnabled(): partCommands = [ 'a2p_ImportPart', From 0602d9eb513977353b7d7318db496451d6eef80d Mon Sep 17 00:00:00 2001 From: luz paz Date: Fri, 18 Mar 2022 09:49:16 -0400 Subject: [PATCH 6/9] CD_featurelabels.py: add header + typo fix and formatting tweaks --- CD_featurelabels.py | 66 ++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/CD_featurelabels.py b/CD_featurelabels.py index dc969c04..0185b331 100644 --- a/CD_featurelabels.py +++ b/CD_featurelabels.py @@ -1,8 +1,25 @@ - - - - - +#*************************************************************************** +#* * +#* Copyright (c) 2020 Dan Miel * +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU Lesser General Public License (LGPL) * +#* 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 * +#* * +#*************************************************************************** +# This is to be used with A2plus Assembly WorkBench +# import sys import os @@ -17,22 +34,22 @@ class formMain(QtGui.QMainWindow): - - def __init__(self,name): + + def __init__(self,name): self.name = name super(formMain,self).__init__() self.setWindowTitle('Create Labels') self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - self.setGeometry(300,100,200,300)#xy,wh + self.setGeometry(300,100,200,300) # xy,wh self.setStyleSheet("font: 11pt arial MS") - self.btnLabels = [['Add Face Labels','Add labels to all of the faces on a selected part'], - ['Add Edge Labels','Add labels to all of the edges on a selected part'], - ['Add Vertex Labels','Add labels to all of the vertexes on a selected part'], - ['Delete Labels','Delet all labels'], - ['Attach to','Enter a name in the texbox and press to add a label'], - ['Selected Labels','Labels added to selected features'], - ['Close',''] # + self.btnLabels = [['Add Face Labels', 'Add labels to all of the faces on a selected part'], + ['Add Edge Labels', 'Add labels to all of the edges on a selected part'], + ['Add Vertex Labels', 'Add labels to all of the vertexes on a selected part'], + ['Delete Labels', 'Delete all labels'], + ['Attach to', 'Enter a name in the texbox and press to add a label'], + ['Selected Labels', 'Labels added to selected features'], + ['Close', ''] # ] @@ -103,20 +120,19 @@ class classLabels(): def __init__(self): self.labelGroup = None pass - #self.name = name - + # self.name = name def checkselection(self): - #checks to see if labels already exist + """Checks to see if labels already exist.""" doc = FreeCAD.activeDocument() - #loc = None + # loc = None print('checking for label') self.labelGroup = doc.getObject("partLabels") if self.labelGroup is None: self.labelGroup=doc.addObject("App::DocumentObjectGroup", "partLabels") if len(FreeCADGui.Selection.getSelection()) == 0: - #dlib.mApp('Please select One part.') + # dlib.mApp('Please select One part.') return(False) return(True) @@ -129,11 +145,11 @@ def addlabels(self,feat): if feat == 'Face': features = sel[0].Shape.Faces if feat == 'Edge': - features = sel[0].Shape.Edges + features = sel[0].Shape.Edges if feat == 'Vertex': features = sel[0].Shape.Vertexes - - + + for num in range(0,len(features)): ent = features[num] if feat == 'Vertex': @@ -163,7 +179,7 @@ def deletelabels(self): def attachto(self,sel = None,featname = ''): - + sel = self.checkselection() if not sel: return @@ -191,8 +207,8 @@ def getEntLoc(self,ent,featname): self.makelabel(ent,featname,loc) - #Create a label to find a part def labelForTable(self,ent, featname): + """Create a label to find a part.""" sel = self.checkselection() self.getEntLoc(ent,featname) From 8ca0c6314d34d6ea2c7d337a988c90f20b4c0069 Mon Sep 17 00:00:00 2001 From: luz paz Date: Fri, 18 Mar 2022 10:18:34 -0400 Subject: [PATCH 7/9] CD_A2plusupdater.py: Add header + formatting --- CD_A2plusupdater.py | 228 ++++++++++++++++++++++++-------------------- 1 file changed, 122 insertions(+), 106 deletions(-) diff --git a/CD_A2plusupdater.py b/CD_A2plusupdater.py index 37962cc5..384b7ab7 100644 --- a/CD_A2plusupdater.py +++ b/CD_A2plusupdater.py @@ -1,9 +1,25 @@ - - - - - - +#*************************************************************************** +#* * +#* Copyright (c) 2020 Dan Miel * +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU Lesser General Public License (LGPL) * +#* 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 * +#* * +#*************************************************************************** +# This is to be used with A2plus Assembly WorkBench +# import sys import os @@ -12,33 +28,36 @@ from PySide import QtUiTools from PySide.QtGui import * from PySide import QtGui, QtCore -import a2plib +import a2plib import a2p_solversystem from a2p_solversystem import solveConstraints import CD_importpart import CD_checkconstraints import CD_ConstraintDiagnostics + + class globaluseclass: - def __init__(self,name): + + def __init__(self, name): self.roundto = 5 self.partlabel = '' self.partname = '' - self.notfoundfeatures =[] - self.foundfeatures =[] - self.dictOldNew ={} + self.notfoundfeatures = [] + self.foundfeatures = [] + self.dictOldNew = {} self.alldicts = {} self.clist = [] self.partobj = None self.test = [] self.cylfaces = [] self.notcylfaces = [] - self.partlog =[] + self.partlog = [] def resetvars(self): self.partlabel = '' self.partname = '' - self.notfoundfeatures =[] - self.foundfeatures =[] - self.dictOldNew ={} + self.notfoundfeatures = [] + self.foundfeatures = [] + self.dictOldNew = {} self.alldicts = {} #self.usedfeatures = [] self.clist = [] @@ -47,17 +66,17 @@ def resetvars(self): self.cylfaces = [] self.notcylfaces = [] self.repaired = 0 -g=globaluseclass("g") +g = globaluseclass("g") -class sideFuncs1(): +class sideFuncs1(): def __init__(self): pass def opendoccheck(self): doc = None doc = FreeCAD.activeDocument() - if doc == None: + if doc is None: msg = 'A file must be selected to start this selector\nPlease open a file and try again' mApp(msg) return('Nostart') @@ -87,7 +106,7 @@ def selectfiles(self): if ret == 'Nostart': return('No') doc = FreeCAD.activeDocument() - partslist= FreeCADGui.Selection.getSelection() + partslist = FreeCADGui.Selection.getSelection() if len(partslist) == 0: mApp('1 No parts were selected to update.\nSelect one part and try again.') return('No') @@ -99,12 +118,12 @@ def selectfiles(self): CD_ConstraintDiagnostics.statusform.update() for num in range(0,len(partslist)): - partobj = partslist[num] + partobj = partslist[num] partname = self.firstrun(partobj) self.secondrun(False) - + def firstrun(self,partobj): - g.resetvars() #reset Variables + g.resetvars() # reset Variables partname = 'none' g.partobj = partobj g.partlabel =partobj.Label @@ -115,13 +134,13 @@ def firstrun(self,partobj): FreeCADGui.updateGui() return(partname) - def secondrun(self,newpart = False): + def secondrun(self, newpart = False): doc = FreeCAD.activeDocument() if newpart == False: newobj = None - CD_importpart.updateImportedParts(doc,True) + CD_importpart.updateImportedParts(doc, True) newobj = g.partobj FreeCADGui.updateGui() g.shape2 = newobj.Shape @@ -133,7 +152,6 @@ def secondrun(self,newpart = False): def runpostchange(self): - doc = FreeCAD.activeDocument() self.findfeats_attempt1() doc.recompute() @@ -154,76 +172,76 @@ def runpostchange(self): print('Repaired constraints = ' + (str(g.repaired - len(g.notfoundfeatures)))) - def getfeatstomove(self): + def getfeatstomove(self): doc = FreeCAD.activeDocument() clist = selectforpart(g.partlabel) g.clist = clist featname = '' - di ={} + di = {} for cobj in clist: - #get feature info before update + # get feature info before update partname = g.partname featname = '' subElement = "" subElement = "" subobj1 = doc.getObject(cobj.Object1) subobj2 = doc.getObject(cobj.Object2) - frompart = [g.partlabel,g.partname] + frompart = [g.partlabel, g.partname] for i in range(0,len(frompart)): partname = frompart[i] if subobj1.Label == partname: subElement = "SubElement1" featname = cobj.SubElement1 - if subobj2.Label == partname: + if subobj2.Label == partname: subElement = "SubElement2" featname = cobj.SubElement2 if featname != '': break - dir ='N' + dir = 'N' if hasattr(cobj,'directionConstraint'): dir = cobj.directionConstraint # dict is basic info for constraint # these next functions adds info for the subelements if 'Face' in featname: - #add face info + # add face info facenum = int(featname[4:]) di = self.getfacebynum(facenum-1,g.shape1) if 'Edge' in featname: - #add edge info + # add edge info num = int(featname[4:]) num = num - 1 di = self.getedgebynum(num,g.shape1) if 'V' in featname: - #add Vertex info + # add Vertex info num = int(featname[6:]) num = num - 1 - di = self.getvertexbynum(num,g.shape1) - dict = {'Name':cobj.Name,'cname':cobj.Name,'featname':featname,'subElement':subElement,'dir':dir,'newname':''} + di = self.getvertexbynum(num, g.shape1) + dict = {'Name':cobj.Name,'cname':cobj.Name,'featname':featname,'subElement':subElement,'dir':dir,'newname':''} dict.update(di) - g.alldicts[cobj.Name] = dict #Save the info to a larger dictionary + g.alldicts[cobj.Name] = dict # Save the info to a larger dictionary def getfacebynum (self,facenum,shape): - # get face info. + """Get face info.""" face = shape.Faces[facenum] - area = rondnum(face.Area) + area = rondnum(face.Area) surftype = face.Surface - facepoints =[] - points =[] + facepoints = [] + points = [] center = -1 - + edge1 = face.Edges[0] - eeee=face.Edges + eeee = face.Edges numofpoints = len(face.Vertexes) - for f0 in face.Vertexes: # Search the Vertexes of the face - point=FreeCAD.Vector(f0.Point.x,f0.Point.y,f0.Point.z) - x,y,z =point + for f0 in face.Vertexes: # Search the Vertexes of the face + point = FreeCAD.Vector(f0.Point.x,f0.Point.y,f0.Point.z) + x,y,z = point loc = rondlist([x,y,z]) facepoints.append(loc) - volume =rondnum(face.Volume) + volume = rondnum(face.Volume) radius = -1 surftype = face.Surface @@ -249,20 +267,20 @@ def getedgebynum (self,num,shape): edge = shape.Edges[num] length = edge.Length length = rondnum(edge.Length) - center =edge.CenterOfMass + center = edge.CenterOfMass center = rondlist(center) - pnt1 = edge.Vertexes[0] #Basepoints - x1=pnt1.Point.x - y1=pnt1.Point.y - z1=pnt1.Point.z + pnt1 = edge.Vertexes[0] # Basepoints + x1 = pnt1.Point.x + y1 = pnt1.Point.y + z1 = pnt1.Point.z startpoint = rondlist([x1,y1,z1]) a = FreeCAD.Vector(x1,y1,z1) b = FreeCAD.Vector() try: - pnt2 = edge.Vertexes[1] #Basepoints - x2=pnt2.Point.x - y2=pnt2.Point.y - z2=pnt2.Point.z + pnt2 = edge.Vertexes[1] # Basepoints + x2 = pnt2.Point.x + y2 = pnt2.Point.y + z2 = pnt2.Point.z endpoint = [x2,y2,z2] b=FreeCAD.Vector(x2,y2,z2) endpoint = rondlist([x2,y2,z2]) @@ -270,22 +288,21 @@ def getedgebynum (self,num,shape): endpoint = ["-","-","-"] - radius =-1 + radius = -1 vector = None curvetype = '' center = -1 tstr = str(edge.Curve) if 'Line' in tstr: - curvetype ='line' + curvetype ='line' if 'Circle' in tstr: curvetype ='circle' radius = rondnum(edge.Curve.Radius) center = rondlist(edge.CenterOfMass) if 'Spline' in tstr: - curvetype ='spline' #A2 is not using these + curvetype ='spline' # A2 is not using these dict = {'curvetype':curvetype,'obj':edge,'length':length,'startpoint':startpoint,'center':center,'endpoint':endpoint,'radius':radius,'vector':vector} - return(dict) @@ -302,12 +319,11 @@ def getvertexbynum(self,num,shape): ## post functions*********************************** def findfeats_attempt1(self): - - # Try to find features after the update + """Try to find features after the update.""" doc = FreeCAD.activeDocument() for k,dict in g.alldicts.items(): dict = dict - newfeat = '' + newfeat = '' featname =dict.get('featname') if featname in g.foundfeatures: newfeat = g.dictOldNew.get(featname) @@ -335,11 +351,11 @@ def findfeats_attempt1(self): def swapfeature(self,newfeat,dict): - #add the new feature to the constraint + """Add the new feature to the constraint.""" cname = dict.get('cname') g.partlog.append('Found ' + newfeat) cobj = FreeCAD.ActiveDocument.getObject(cname) - mobj = FreeCAD.ActiveDocument.getObject(cname+'_mirror') + mobj = FreeCAD.ActiveDocument.getObject(cname+'_mirror') SubElement = dict.get('subElement') if SubElement == 'SubElement1': if cobj.SubElement1 != newfeat: @@ -354,11 +370,11 @@ def swapfeature(self,newfeat,dict): dir = dict.get('dir') if hasattr(cobj,'directionConstraint'): cobj.directionConstraint = dir - if hasattr(mobj,'directionConstraint'): + if hasattr(mobj,'directionConstraint'): mobj.directionConstraint = dir return - #If not found on first attemt try aagain + # If not found on first attempt try again def findfeats_attempt2(self): newfeat = '' notfoundtemp = g.notfoundfeatures @@ -366,8 +382,8 @@ def findfeats_attempt2(self): g.notfoundfeatures = [] for ea in notfoundtemp: - dict= ea[1] - featname =dict.get('featname') + dict = ea[1] + featname = dict.get('featname') if featname in g.foundfeatures: newfeat = g.dictOldNew.get(featname) else: @@ -377,7 +393,7 @@ def findfeats_attempt2(self): newfeat = self.findnewedge_attemt2(dict) if newfeat =='No' or newfeat == '': g.notfoundfeatures.append([dict.get('Name'),dict]) - newfeat ='None' + newfeat = 'None' else: if newfeat in g.foundfeatures == False: g.foundfeatures.append(newfeat) @@ -388,8 +404,8 @@ def findfeats_attempt2(self): def findnewface_attemt1(self,dict): #First attement to find a face. Perfect fit is area the same all points the ssame face = '' - #newfeat = '' - #surftype = dict.get('surftype') + # newfeat = '' + # surftype = dict.get('surftype') if dict.get('surftype') == 'Cylinder': face = self.findCylinderattemt1(dict) else: @@ -421,8 +437,8 @@ def findnewface_attemt2(self,dict): points = dict.get('facepoints') testpoints = testdict.get('facepoints') - # points = points[1] - # testpoints =testpoints [1] + # points = points[1] + # testpoints =testpoints [1] if len(points) < len(testpoints): list1 = points list2 = testpoints @@ -432,31 +448,31 @@ def findnewface_attemt2(self,dict): match = 0 for vert in list1: if vert in list2: - match=match+1 + match = match+1 if match == len(list1): face = 'Face' + str(num+1) break if face == '': - + dedges = dict.get('edges') - edge=dedges[0] - pnt1 = edge.Vertexes[0] #Basepoints - x=pnt1.Point.x - y=pnt1.Point.y - z=pnt1.Point.z + edge = dedges[0] + pnt1 = edge.Vertexes[0] # Basepoints + x = pnt1.Point.x + y = pnt1.Point.y + z = pnt1.Point.z dlist = [rondnum(edge.Length),x,y,z] for num in range(0,len(g.shape2.Faces)): testdict = self.getfacebynum(num,g.shape2) if dict.get('surftype') != 'Cylinder': - ed =testdict.get('edges') + ed = testdict.get('edges') ary = [] for e in ed: - pnt1 = e.Vertexes[0] #Basepoints - x=pnt1.Point.x - y=pnt1.Point.y - z=pnt1.Point.z + pnt1 = e.Vertexes[0] # Basepoints + x = pnt1.Point.x + y = pnt1.Point.y + z = pnt1.Point.z tlist = [e.Length,x,y,z] if dlist == tlist: face = 'Face' + str(num+1) @@ -577,7 +593,7 @@ def findnewvertex_attemt1(self,dict): def getfacelists(): g.cylfaces = [] - g.notcylfaces=[] + g.notcylfaces= [] for num in range(0,len(g.shape2.Faces)): if str(g.shape2.Faces[num].Surface) == '': @@ -585,7 +601,7 @@ def getfacelists(): elif str(g.shape2.Faces[num].Surface) == '': g.notcylfaces.append(num) - + def selectforpart(partlabel,selectType = 'std'): #find the constraints for the part selected @@ -593,7 +609,7 @@ def selectforpart(partlabel,selectType = 'std'): clist = [] pnamelist =[] pnamelist.append(partlabel) - for obj in FreeCAD.ActiveDocument.Objects: #Select constraints + for obj in FreeCAD.ActiveDocument.Objects: # Select constraints if 'ConstraintInfo' in obj.Content: if '_mirror' not in obj.Name: subobj1 = doc.getObject(obj.Object1) @@ -604,17 +620,17 @@ def selectforpart(partlabel,selectType = 'std'): if selectType == 'betweenonly': clist.append(obj) else: - if part1name in pnamelist or part2name in pnamelist: + if part1name in pnamelist or part2name in pnamelist: clist.append(obj) return(clist) def rondnum(num,rndto = g.roundto,mmorin='mm'): - #round a number to digits in global - #left in mm for accuracy. + # round a number to digits in global + # left in mm for accuracy. rn = round(num,rndto) if mmorin == 'in': - rn=rn/25.4 + rn = rn/25.4 return(rn) @@ -623,13 +639,13 @@ def rondlist(list,inch = False): x = list[0] y = list[1] z = list[2] - x=rondnum(x) - y=rondnum(y) - z=rondnum(z) + x = rondnum(x) + y = rondnum(y) + z = rondnum(z) if inch: - x=x/25.4 - y=y/25.4 - z=z/25.4 + x = x/25.4 + y = y/25.4 + z = z/25.4 return([x,y,z]) @@ -640,13 +656,13 @@ class mApp(QWidget): # for error messages def __init__(self,msg,msgtype = 'ok'): super().__init__() - self.title = 'PyQt5 messagebox - pythonspot.com' + self.title = 'PyQt5 messagebox' self.left = 600 self.top = 100 self.width = 320 self.height = 200 self.initUI(msg) - + def initUI(self,msg,msgtype = 'ok'): self.setWindowTitle(self.title) self.setGeometry(800, 300, 300, 400) @@ -665,15 +681,15 @@ def initUI(self,msg,msgtype = 'ok'): toolTipText = \ ''' -Updates the A2plus.assembly when parts are modified. -To update the assembly, select the part that you have modified and press the icon. +Updates the A2plus.assembly when parts are modified. +To update the assembly, select the part that you have modified and press the icon. When the update has finished run the A2plus solver to vereify if there are broken constraints. This is an attempt to reduce the number of broken constraints caused -when modifying a part from FreeCAD’s A2plus assembly program. This records the -constraint’s mating surfaces immediately before the update and tries to +when modifying a part from FreeCAD’s A2plus assembly program. This records the +constraint’s mating surfaces immediately before the update and tries to reconnect them after the update. If this fails you can undo this update by using the undo button -and running the standard A2plus updater. +and running the standard A2plus updater. ''' class rnp_Update_A2pParts: From ce10230339e968837a55003e23bbeafa37767d0f Mon Sep 17 00:00:00 2001 From: luz paz Date: Fri, 18 Mar 2022 10:19:39 -0400 Subject: [PATCH 8/9] CD_A2plusupdater.py: rename functions with typos `attemt`->`attempt` --- CD_A2plusupdater.py | 45 ++++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/CD_A2plusupdater.py b/CD_A2plusupdater.py index 384b7ab7..b5bb237b 100644 --- a/CD_A2plusupdater.py +++ b/CD_A2plusupdater.py @@ -138,7 +138,7 @@ def secondrun(self, newpart = False): doc = FreeCAD.activeDocument() - if newpart == False: + if newpart is False: newobj = None CD_importpart.updateImportedParts(doc, True) newobj = g.partobj @@ -329,11 +329,11 @@ def findfeats_attempt1(self): newfeat = g.dictOldNew.get(featname) else: if 'Face' in featname: - newfeat = self.findnewface_attemt1(dict) + newfeat = self.findnewface_attempt1(dict) if 'Edge' in featname: - newfeat = self.findnewedge_attemt1(dict) + newfeat = self.findnewedge_attempt1(dict) if 'Vertex' in featname: - newfeat = self.findnewvertex_attemt1(dict) + newfeat = self.findnewvertex_attempt1(dict) if newfeat =='' or newfeat == 'No': g.notfoundfeatures.append([dict.get('Name'),dict]) pass @@ -388,10 +388,10 @@ def findfeats_attempt2(self): newfeat = g.dictOldNew.get(featname) else: if 'Face' in featname: - newfeat = self.findnewface_attemt2(dict) + newfeat = self.findnewface_attempt2(dict) if 'Edge' in featname: - newfeat = self.findnewedge_attemt2(dict) - if newfeat =='No' or newfeat == '': + newfeat = self.findnewedge_attempt2(dict) + if newfeat is 'No' or newfeat == '': g.notfoundfeatures.append([dict.get('Name'),dict]) newfeat = 'None' else: @@ -401,13 +401,13 @@ def findfeats_attempt2(self): self.swapfeature(newfeat,dict) - def findnewface_attemt1(self,dict): - #First attement to find a face. Perfect fit is area the same all points the ssame + def findnewface_attempt1(self,dict): + # First attempt to find a face. Perfect fit is area the same all points the same face = '' # newfeat = '' # surftype = dict.get('surftype') if dict.get('surftype') == 'Cylinder': - face = self.findCylinderattemt1(dict) + face = self.findCylinderattempt1(dict) else: for num in range(0,len(g.shape2.Faces)): testdict = self.getfacebynum(num,g.shape2) @@ -420,12 +420,12 @@ def findnewface_attemt1(self,dict): - def findnewface_attemt2(self,dict): + def findnewface_attempt2(self,dict): face = '' - #second attempt ignors area looks for points + # second attempt ignores area; looks for points face = '' if dict.get('surftype') == 'Cylinder': - face = self.findCylinderattemt2(dict) + face = self.findCylinderattempt2(dict) else: @@ -480,8 +480,7 @@ def findnewface_attemt2(self,dict): return(face) - - def findCylinderattemt1(self,dict): + def findCylinderattempt1(self,dict): face = '' for num in g.cylfaces: testdict = self.getfacebynum(num,g.shape2) @@ -493,9 +492,8 @@ def findCylinderattemt1(self,dict): return(face) - - def findCylinderattemt2(self,dict): - #First attement to find a face. Perfect fit is area the same all points the ssame + def findCylinderattempt2(self,dict): + #First attempt to find a face. Perfect fit is area the same all points the same face = '' ver1 = dict.get('center') for num in g.cylfaces: @@ -507,10 +505,7 @@ def findCylinderattemt2(self,dict): return(face) - - - - def findnewedge_attemt1(self,dict): + def findnewedge_attempt1(self,dict): edge = '' if dict.get('curvetype') == 'circle': center1 = dict.get('center') @@ -527,7 +522,7 @@ def findnewedge_attemt1(self,dict): else: for num in g.notcylfaces: - testdict = self.getfacebynum(num,g.shape2) + testdict = self.getfacebynum(num,g.shape2) if dict.get('length') == testdict.get('length')\ and dict.get('center') == testdict.get('center'): edge ='Edge' + str(num +1) @@ -536,7 +531,7 @@ def findnewedge_attemt1(self,dict): return(edge) - def findnewedge_attemt2(self,dict): + def findnewedge_attempt2(self,dict): edge = '' if dict.get('curvetype') == 'circle': center1 = dict.get('center') @@ -580,7 +575,7 @@ def findnewedge_attemt2(self,dict): - def findnewvertex_attemt1(self,dict): + def findnewvertex_attempt1(self,dict): vertex = '' featname = dict.get('featname') for num in range(0,len(g.shape2.Vertexes)): From 805a2b9ce10305a441f2bc661c4133ab9be5f213 Mon Sep 17 00:00:00 2001 From: luz paz Date: Fri, 18 Mar 2022 10:31:15 -0400 Subject: [PATCH 9/9] a2p_constraintServices.py: clarify functionality related to CD --- a2p_constraintServices.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/a2p_constraintServices.py b/a2p_constraintServices.py index 7c4a467d..097e18a8 100644 --- a/a2p_constraintServices.py +++ b/a2p_constraintServices.py @@ -36,11 +36,11 @@ def redAdjustConstraintDirections(doc): a2p-constraints of a document, in order to reach a solvable state if possible, especially used after updating of imported parts. """ - result = [] #added for CD + result = [] # Added for Constraint Diagnostic function unknown_constraints = [] constraints = [ obj for obj in doc.Objects if 'ConstraintInfo' in obj.Content] for c in constraints: - try: #process as much constraints as possible + try: # process as much constraints as possible if c.Type == 'pointIdentity': a2p_constraints.PointIdentityConstraint.recalculateMatingDirection(c) elif c.Type == 'pointOnLine': @@ -73,14 +73,14 @@ def redAdjustConstraintDirections(doc): unknown_constraints.append(c.Type) except: print("Errors occurred during processing of {}".format(c.Label)) - result.append(c.Name) #added for CD - + result.append(c.Name) # Added for Constraint Diagnostic function + if len(unknown_constraints) > 0: print("redefineConstraintDirections(): Found unknown constraints: {}".format( set(unknown_constraints) ) ) - return(result) #added for CD + return(result) # Added for Constraint Diagnostic function #============================================================================== class a2p_reAdjustConstraintDirectionsCommand: