├── Gui └── Resources │ ├── dd_resources.rcc │ ├── compile_resources_pack.py │ └── icons │ ├── welding │ ├── Flachennaht.svg │ ├── square_groove.svg │ ├── J_groove.svg │ ├── Bevel_groove.svg │ ├── Stirnflachnaht.svg │ ├── Y_groove.svg │ ├── Schragnaht.svg │ ├── V_groove.svg │ ├── Steilflankennaht.svg │ ├── J_groove_(alt).svg │ ├── fillet.svg │ ├── double_square_groove.svg │ ├── Plug_or_slot.svg │ ├── fillet_with_dashed_line.svg │ ├── U_groove.svg │ ├── Double_J_groove.svg │ ├── Double_bevel_groove.svg │ ├── Double_V_groove.svg │ ├── double_fillet.svg │ ├── flange_edge.svg │ ├── Double_Y_groove.svg │ ├── Double_J_groove_(alt).svg │ ├── VU_groove.svg │ ├── fillet_with_circle.svg │ ├── Spot_or_projection.svg │ ├── Double_U_groove.svg │ ├── Surfacing.svg │ ├── Falznnaht.svg │ ├── plain.svg │ └── Seam.svg │ ├── drawLine.svg │ ├── drawLineWithArrow.svg │ ├── table_dd.svg │ ├── bendingNote.svg │ ├── help.svg │ ├── drawCircularArc.svg │ ├── drawCircle.svg │ └── noteCircle.svg ├── .gitignore ├── escapeDimensioning.py ├── grabPointAdd.py ├── crudeDebugger.py ├── textMove.py ├── README.md ├── textAdd.py ├── InitGui.py ├── deleteDimension.py ├── dimensionSvgConstructor.py ├── cgpr.py ├── table_dd.py ├── toleranceDialog.py ├── freeDrawing.py ├── textEdit.py ├── dimensionSvgConstructor_testCenterLines.py ├── toleranceDialog.ui ├── previewDimension.ui ├── centerView.py ├── noteCircle.py ├── toleranceAdd.py ├── radiusDimension.py ├── circularDimension.py └── lineSearches.py /Gui/Resources/dd_resources.rcc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easyw/FreeCAD_drawing_dimensioning/master/Gui/Resources/dd_resources.rcc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | crudeDebugger_output* 4 | crudeDebugger_output/* 5 | .syncthing.* 6 | \#* 7 | testCases_selectionOverlay/ 8 | dxfwrite/ -------------------------------------------------------------------------------- /escapeDimensioning.py: -------------------------------------------------------------------------------- 1 | 2 | from dimensioning import * 3 | 4 | class EscapeDimensioning: 5 | def Activated(self): 6 | drawingVars = getDrawingPageGUIVars() 7 | drawingVars.page.touch() 8 | App.ActiveDocument.recompute() 9 | 10 | def GetResources(self): 11 | return { 12 | 'Pixmap' : ':/dd/icons/escape.svg' , 13 | 'MenuText': 'escape Dimensioning', 14 | 'ToolTip': 'escape Dimensioning' 15 | } 16 | 17 | FreeCADGui.addCommand('dd_escapeDimensioning', EscapeDimensioning()) 18 | -------------------------------------------------------------------------------- /Gui/Resources/compile_resources_pack.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import os, glob 3 | 4 | qrc_filename = 'drawing_dimensioning.qrc' 5 | assert not os.path.exists(qrc_filename) 6 | 7 | qrc = ''' 8 | ''' 9 | for fn in glob.glob('icons/*.svg') + glob.glob('icons/welding/*.svg') + glob.glob('ui/*.ui'): 10 | qrc = qrc + '\n\t\t%s' % fn 11 | qrc = qrc + '''\n\t 12 | ''' 13 | 14 | print(qrc) 15 | 16 | f = open(qrc_filename,'w') 17 | f.write(qrc) 18 | f.close() 19 | 20 | os.system('rcc -binary %s -o dd_resources.rcc' % qrc_filename) 21 | os.remove(qrc_filename) 22 | -------------------------------------------------------------------------------- /grabPointAdd.py: -------------------------------------------------------------------------------- 1 | 2 | from dimensioning import * 3 | import previewDimension 4 | 5 | 6 | def grabPointDrawSVG( x, y, preview=False): # draw a cross 7 | if preview: 8 | return ''' 9 | 10 | 11 | ''' % ( x-1, y, x+1, y, x, y-1, x, y+1) 12 | else: 13 | return ' ''' % ( x, y, x, y) 14 | 15 | 16 | def grabPoint_preview( x, y): 17 | return grabPointDrawSVG( x, y, preview=True) 18 | 19 | def grabPoint_clickHandler( x, y): 20 | d.selections = [ PlacementClick( x, y) ] 21 | return 'createDimension:%s' % findUnusedObjectName('grabPoint') 22 | 23 | class Proxy_grabPoint( Proxy_DimensionObject_prototype ): 24 | def dimensionProcess( self ): 25 | return d 26 | d = DimensioningProcessTracker( grabPointDrawSVG ) 27 | d.ProxyClass = Proxy_grabPoint 28 | 29 | class AddGrabPoint: 30 | def Activated(self): 31 | V = getDrawingPageGUIVars() 32 | d.activate( V, dialogTitle='Add Grab Point', dialogIconPath=':/dd/icons/grabPointAdd.svg', endFunction=self.Activated ) 33 | previewDimension.initializePreview( 34 | d, 35 | grabPoint_preview, 36 | grabPoint_clickHandler ) 37 | 38 | def GetResources(self): 39 | return { 40 | 'Pixmap' : ':/dd/icons/grabPointAdd.svg' , 41 | 'MenuText': 'Add grab point to draw a free dimension', 42 | 'ToolTip': 'Add grab point to draw a free dimension' 43 | } 44 | FreeCADGui.addCommand('dd_grabPoint', AddGrabPoint()) 45 | 46 | 47 | -------------------------------------------------------------------------------- /crudeDebugger.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python Code for generating crude debugging functionality for the Dimensioning FreeCAD module. 3 | ''' 4 | 5 | import os, sys 6 | 7 | __dir__ = os.path.dirname(__file__) 8 | 9 | debug_output_directory = os.path.join( __dir__ , 'crudeDebugger_output' ) 10 | if not os.path.exists(debug_output_directory): 11 | os.mkdir(debug_output_directory) 12 | sys.path.append(debug_output_directory) 13 | 14 | global crudeDebuggerOutputFile 15 | crudeDebuggerOutputFile = None 16 | 17 | def crudeDebuggerPrint( txt, epilogue='\n' ): 18 | global crudeDebuggerOutputFile 19 | if crudeDebuggerOutputFile == None: 20 | fn = os.path.join(debug_output_directory, 'output') 21 | crudeDebuggerOutputFile = open(fn, 'w') 22 | crudeDebuggerOutputFile.write(txt+epilogue) 23 | crudeDebuggerOutputFile.flush() 24 | 25 | 26 | def printingDebugging( pythonFile, debugExt='_crudeDebugging.py' ): 27 | #headers = [] #i.e. [class , function, loop, ... #nah no nessary for now 28 | assert pythonFile.endswith('.py') 29 | output = ['from crudeDebugger import crudeDebuggerPrint' ] 30 | insideTextblock = False 31 | bracketBalance = 0 32 | bB2 = 0 #for ''' special bracket case 33 | for lineNo, line in enumerate(open(pythonFile)): 34 | indent = line[: len(line)-len(line.lstrip())] 35 | prev_bracketBalance = bracketBalance 36 | bracketBalance = prev_bracketBalance + line.count('(') - line.count(')') + line.count('{') - line.count('}') + line.count('[') - line.count(']') 37 | prev_bB2 = bB2 38 | bB2 = (prev_bB2 + line.count("'''"))%2 39 | if len(line.strip()) == 0: 40 | pass 41 | elif line.strip().startswith("'''"): 42 | insideTextblock = not insideTextblock 43 | if insideTextblock: 44 | pass 45 | elif insideTextblock: 46 | pass 47 | elif any( line.lstrip().startswith(s) for s in ['elif','def','class','else','except','"',"'"] ): 48 | pass 49 | elif prev_bB2 <> 0: 50 | pass 51 | elif prev_bracketBalance == 0: #and bracketBalance == 0: 52 | lineCleaned = line.rstrip().replace("'''", "''' +" + '"' + "'''" + '"' + " +'''") 53 | 54 | output.append('%scrudeDebuggerPrint(%s%s:%i \t%s %s)' % (indent,"'''", os.path.basename(pythonFile), lineNo, lineCleaned, "'''")) 55 | output.append(line[:-1]) #remove \n caracter 56 | 57 | output_fn = os.path.join(debug_output_directory, os.path.basename(pythonFile[:-3]) + debugExt) 58 | f = open(output_fn,'w') 59 | f.write('\n'.join(output)) 60 | f.close() 61 | 62 | 63 | 64 | if __name__ == '__main__': 65 | print('testing crudeDebugger for Dimensioning module') 66 | printingDebugging('crudeDebugger.py') 67 | -------------------------------------------------------------------------------- /textMove.py: -------------------------------------------------------------------------------- 1 | 2 | from dimensioning import * 3 | import selectionOverlay, previewDimension 4 | import XMLlib 5 | 6 | d = DimensioningProcessTracker() 7 | 8 | def moveTextSvg( x, y): 9 | e = d.elementXML 10 | xml = e.XML[e.pStart:e.pEnd] 11 | xml = XMLlib.replaceParm(xml, 'x', '%f' % x ) 12 | xml = XMLlib.replaceParm(xml, 'y', '%f' % y ) 13 | if e.parms.has_key('transform'): 14 | xml = XMLlib.replaceParm(xml, 'transform', "rotate(%s %f,%f)" % (d.textRotation,x,y) ) 15 | return e.XML[:e.pStart] + xml + e.XML[e.pEnd:] 16 | 17 | def placeText( x, y): 18 | FreeCAD.ActiveDocument.openTransaction("move text") 19 | d.dimToEdit.ViewResult = moveTextSvg(x, y ) 20 | FreeCAD.ActiveDocument.commitTransaction() 21 | return 'stopPreview' 22 | 23 | def MoveDimensionText( event, referer, elementXML, elementParms, elementViewObject ): 24 | d.dimToEdit = elementViewObject 25 | d.elementXML = elementXML 26 | debugPrint(2, 'moving %s' % elementViewObject.Name) 27 | if elementXML.parms.has_key('transform'): 28 | transform = elementXML.parms['transform'] 29 | t = transform[ XMLlib.findOffset(transform,'rotate(',0): ] 30 | d.textRotation = XMLlib.splitMultiSep(t, ', ')[0] 31 | debugPrint(3, 'd.textRotation %s' % d.textRotation) 32 | else: 33 | d.textRotation = None 34 | selectionOverlay.hideSelectionGraphicsItems() 35 | previewDimension.initializePreview( d, moveTextSvg, placeText ) 36 | 37 | maskBrush = QtGui.QBrush( QtGui.QColor(0,160,0,100) ) 38 | maskPen = QtGui.QPen( QtGui.QColor(0,160,0,100) ) 39 | maskPen.setWidth(0.0) 40 | maskHoverPen = QtGui.QPen( QtGui.QColor(0,255,0,255) ) 41 | maskHoverPen.setWidth(0.0) 42 | 43 | class MoveText: 44 | def Activated(self): 45 | V = getDrawingPageGUIVars() #needs to be done before dialog show, else Qt active is dialog and not freecads 46 | d.activate( V, dialogTitle='Move Text', dialogIconPath=':/dd/icons/textMove.svg', endFunction=self.Activated ) 47 | selectGraphicsItems = selectionOverlay.generateSelectionGraphicsItems( 48 | [obj for obj in V.page.Group if obj.Name.startswith('dim')], 49 | MoveDimensionText , 50 | sceneToAddTo = V.graphicsScene, 51 | transform = V.transform, 52 | doTextItems = True, 53 | pointWid=2.0, 54 | maskPen=maskPen, 55 | maskHoverPen=maskHoverPen, 56 | maskBrush = maskBrush 57 | ) 58 | 59 | def GetResources(self): 60 | msg = "Move a dimension's text" 61 | return { 62 | 'Pixmap' : ':/dd/icons/textMove.svg' , 63 | 'MenuText': msg, 64 | 'ToolTip': msg 65 | } 66 | FreeCADGui.addCommand('dd_moveText', MoveText()) 67 | 68 | 69 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Flachennaht.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/square_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/J_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Bevel_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Stirnflachnaht.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Y_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Schragnaht.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/V_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Steilflankennaht.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/J_groove_(alt).svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/fillet.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/double_square_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FreeCAD_drawing_dimensioning 2 | ============================ 3 | 4 | Drawing dimensioning workbench for FreeCAD v0.15 and v0.16. 5 | 6 | Take note that hamish2014/FreeCAD_drawing_dimensioning is no longer maintained. 7 | 8 | Intended work-flow: 9 | * create a drawing page and a drawing of the part using the drawing workbench 10 | * switch to the drawing dimensioning workbench to add dimensions to that drawing 11 | 12 | Features 13 | * linear dimensioning 14 | * circular and radial dimensioning 15 | * angular dimension 16 | * center lines 17 | * adding, editing and moving dimension text 18 | * deleting dimensions 19 | * Draw a dimension or a symbol anywhere on the drawing with "Add grab point" 20 | 21 | Limitations 22 | * No parametric updating, if the drawing is updated the dimensions need to be redone 23 | * only works with FreeCAD version 0.15+ 24 | 25 | 26 | 27 | Linux Installation Instructions 28 | ------------------------------- 29 | 30 | To use this workbench clone this git repository under your FreeCAD MyScripts directory, and install the pyside and numpy python libraries. 31 | On a Linux Debian based system such as Ubuntu, installation can be done through BASH as follows 32 | 33 | ```bash 34 | $ sudo apt-get install git python-numpy python-pyside 35 | $ mkdir ~/.FreeCAD/Mod 36 | $ cd ~/.FreeCAD/Mod 37 | $ git clone https://github.com/hamish2014/FreeCAD_drawing_dimensioning.git 38 | ``` 39 | 40 | Once installed, use git to easily update to the latest version: 41 | ```bash 42 | $ cd ~/.FreeCAD/Mod/FreeCAD_drawing_dimensioning 43 | $ git pull 44 | $ rm *.pyc 45 | ``` 46 | Windows Installation Instructions 47 | --------------------------------- 48 | 49 | Tested with 015.4415 Development Snapshot on a Windows 7 64bit-System (thanks BPLRFE ) 50 | 51 | * download the git repository as ZIP 52 | * assuming FreeCAD is installed in "C:\PortableApps\FreeCAD 0_15", go to "C:\PortableApps\FreeCAD 0_15\Mod" within Windows Explorer 53 | * create new directory named "DrawingDimensioning" 54 | * unzip downloaded repository in "C:\PortableApps\FreeCAD 0_15\Mod\DrawingDimensioning" 55 | 56 | FreeCAD will now have a new workbench-entry called "DrawingDimensioning". 57 | 58 | *Pyside and Numpy are integrated in the FreeCAD dev-Snapshots 0.15, so these Python packages do not need to be installed individually* 59 | 60 | To update to the latest version, delete the DrawingDimensioning folder and redownload the git repository. 61 | 62 | Mac Installation Instructions 63 | ----------------------------- 64 | 65 | Copy or unzip the drawing dimensioning folder to the directory *FreeCAD.app*/Contents/Mod 66 | 67 | where *FreeCAD.app* is the folder where FreeCAD is installed. (thanks PLChris) 68 | 69 | Setting your dimensioning preferences 70 | ------------------------------------- 71 | 72 | Unit preferences are taken from the General unit preferences (excluding number of decimal places!). 73 | To set unit preferences goto edit -> preferences -> general -> units 74 | 75 | To set up your desired dimensioning style 76 | 1. open FreeCAD 77 | 2. switch to the Drawing dimensioning workbench 78 | 3. edit -> preferences -> drawing dimensioning 79 | 80 | 81 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Plug_or_slot.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/fillet_with_dashed_line.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/U_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Double_J_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Double_bevel_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Double_V_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/double_fillet.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/flange_edge.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Double_Y_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /textAdd.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | from dimensioning import * 3 | import previewDimension 4 | from dimensionSvgConstructor import * 5 | 6 | d = DimensioningProcessTracker() 7 | 8 | def textSVG( x, y, text='text', rotation=0.0, textRenderer_addText= defaultTextRenderer): 9 | return ' %s ' % textRenderer_addText(x,y,text,rotation=rotation) 10 | 11 | d.registerPreference( 'textRenderer_addText', ['inherit','5', 0], 'text properties (AddText)', kind='font' ) 12 | 13 | class text_widget: 14 | def valueChanged( self, arg1): 15 | d.text = arg1 16 | def generateWidget( self, dimensioningProcess ): 17 | self.lineEdit = QtGui.QLineEdit() 18 | self.lineEdit.setText('text') 19 | d.text = 'text' 20 | self.lineEdit.textChanged.connect(self.valueChanged) 21 | return self.lineEdit 22 | def add_properties_to_dimension_object( self, obj ): 23 | obj.addProperty("App::PropertyString", 'text', 'Parameters') 24 | obj.text = d.text.encode('utf8') 25 | def get_values_from_dimension_object( self, obj, KWs ): 26 | KWs['text'] = obj.text #should be unicode 27 | d.dialogWidgets.append( text_widget() ) 28 | class rotation_widget: 29 | def valueChanged( self, arg1): 30 | d.rotation = arg1 31 | def generateWidget( self, dimensioningProcess ): 32 | self.spinbox = QtGui.QDoubleSpinBox() 33 | self.spinbox.setValue(0) 34 | d.rotation = 0 35 | self.spinbox.setMinimum( -180 ) 36 | self.spinbox.setMaximum( 180 ) 37 | self.spinbox.setDecimals( 1 ) 38 | self.spinbox.setSingleStep( 5 ) 39 | self.spinbox.setSuffix(unicode('°','utf8')) 40 | self.spinbox.valueChanged.connect(self.valueChanged) 41 | return DimensioningTaskDialog_generate_row_hbox('rotation', self.spinbox) 42 | def add_properties_to_dimension_object( self, obj ): 43 | obj.addProperty("App::PropertyAngle", 'rotation', 'Parameters') 44 | obj.rotation = d.rotation 45 | def get_values_from_dimension_object( self, obj, KWs ): 46 | KWs['rotation'] = obj.rotation #should be unicode 47 | d.dialogWidgets.append( rotation_widget() ) 48 | 49 | 50 | def addText_preview(mouseX, mouseY): 51 | return textSVG(mouseX, mouseY, d.text, d.rotation, **d.dimensionConstructorKWs ) 52 | 53 | def addText_clickHandler( x, y ): 54 | d.selections = [ PlacementClick( x, y) ] 55 | return 'createDimension:%s' % findUnusedObjectName('text') 56 | 57 | class Proxy_textAdd( Proxy_DimensionObject_prototype ): 58 | def dimensionProcess( self ): 59 | return d 60 | d.ProxyClass = Proxy_textAdd 61 | d.proxy_svgFun = textSVG 62 | 63 | 64 | class AddText: 65 | def Activated(self): 66 | V = getDrawingPageGUIVars() 67 | d.activate( V, dialogTitle='Add Text', dialogIconPath= ':/dd/icons/textAdd.svg', endFunction=self.Activated ) 68 | previewDimension.initializePreview( d, addText_preview, addText_clickHandler) 69 | 70 | def GetResources(self): 71 | return { 72 | 'Pixmap' : ':/dd/icons/textAdd.svg' , 73 | 'MenuText': 'Add text to drawing', 74 | 'ToolTip': 'Add text to drawing' 75 | } 76 | FreeCADGui.addCommand('dd_addText', AddText()) 77 | 78 | 79 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Double_J_groove_(alt).svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/VU_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/fillet_with_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Spot_or_projection.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Double_U_groove.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Surfacing.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/drawLine.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 34 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 69 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /InitGui.py: -------------------------------------------------------------------------------- 1 | import dimensioning #QtCore.QResource.registerResource happens in there 2 | 3 | class DrawingDimensioningWorkbench (Workbench): 4 | Icon = ':/dd/icons/linearDimension.svg' 5 | MenuText = 'Drawing Dimensioning' 6 | def Initialize(self): 7 | import linearDimension 8 | import linearDimension_stack 9 | import deleteDimension 10 | import circularDimension 11 | import grabPointAdd 12 | import textAdd 13 | import textEdit 14 | import textMove 15 | import escapeDimensioning 16 | import angularDimension 17 | import radiusDimension 18 | import centerLines 19 | import noteCircle 20 | import table_dd 21 | import centerView 22 | import toleranceAdd 23 | import recomputeDimensions 24 | from drawing_wb_shortcuts import newpageShortcuts 25 | self.appendToolbar('Drawing Workbench shortcuts', newpageShortcuts + [ 26 | 'dd_new_drawing_page_preferences', 27 | 'dd_Drawing_OrthoViews', 28 | ] ) 29 | # copy the Drawing toolbar 30 | import DrawingGui 31 | self.appendToolbar('Drawing Workbench Commands',["Drawing_NewPage", 32 | "Drawing_NewView","Drawing_OrthoViews","Drawing_OpenBrowserView", 33 | "Drawing_Annotation","Drawing_Clip","Drawing_Symbol", 34 | "Drawing_DraftView","Drawing_ExportPage"]) 35 | 36 | commandslist = [ 37 | 'dd_linearDimension', #where dd is short-hand for drawing dimensioning 38 | 'dd_linearDimensionStack', 39 | 'dd_circularDimension', 40 | 'dd_radiusDimension', 41 | 'dd_angularDimension', 42 | 'dd_centerLines', 43 | 'dd_centerLine', 44 | 'dd_noteCircle', 45 | 'dd_grabPoint', 46 | 'dd_addText', 47 | # 'dd_editText', # no longer available to user, else to complicated! In particular multiple avenues available to user to change text properties 48 | # 'dd_moveText', # therefore sticking with the FreeCAD way of doing things 49 | 'dd_addTolerance', 50 | 'dd_addTable', 51 | 'dd_deleteDimension', 52 | 'dd_escapeDimensioning', 53 | 'dd_recomputeDimensions', 54 | ] 55 | self.appendToolbar('Drawing Dimensioning', commandslist) 56 | import unfold 57 | import unfold_bending_note 58 | import unfold_export_to_dxf 59 | unfold_cmds = [ 60 | 'dd_unfold', 61 | 'dd_bendingNote', 62 | 'dd_centerView', 63 | 'dd_exportToDxf' 64 | ] 65 | self.appendToolbar( 'Drawing Dimensioning Folding', unfold_cmds ) 66 | import weldingSymbols 67 | git_commit_no = int( FreeCAD.Version()[2].split()[0] ) 68 | if git_commit_no > 5166: 69 | weldingCommandList = ['dd_weldingGroupCommand'] 70 | else: 71 | weldingCommandList = weldingSymbols.weldingCmds 72 | self.appendToolbar('Drawing Dimensioning Welding Symbols', weldingCommandList) 73 | self.appendToolbar('Drawing Dimensioning Help', [ 'dd_help' ]) 74 | FreeCADGui.addIconPath(':/dd/icons') 75 | FreeCADGui.addPreferencePage( ':/dd/ui/drawing_dimensioing_prefs-base.ui','Drawing Dimensioning' ) 76 | 77 | 78 | Gui.addWorkbench(DrawingDimensioningWorkbench()) 79 | 80 | 81 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Falznnaht.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /Gui/Resources/icons/drawLineWithArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 34 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 69 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/plain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 34 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 69 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /deleteDimension.py: -------------------------------------------------------------------------------- 1 | 2 | from dimensioning import * 3 | import selectionOverlay, previewDimension 4 | 5 | d = DimensioningProcessTracker() 6 | 7 | def deleteDimension( event, referer, elementXML, elementParms, elementViewObject ): 8 | debugPrint(2, 'deleting dimension %s' % elementViewObject.Name) 9 | FreeCAD.ActiveDocument.openTransaction("Delete %s" % elementViewObject.Name) 10 | FreeCAD.ActiveDocument.removeObject( elementViewObject.Name ) 11 | FreeCAD.ActiveDocument.commitTransaction() 12 | recomputeWithOutViewReset(d.drawingVars) 13 | FreeCADGui.Control.closeDialog() 14 | if d.endFunction <> None: 15 | previewDimension.preview.dimensioningProcessTracker = d 16 | previewDimension.timer.start( 1 ) 17 | 18 | class deleteAllButton: 19 | def deleteAllDimensions( self, arg1=None): 20 | try : 21 | FreeCAD.ActiveDocument.openTransaction("Delete All Dimensions") 22 | debugPrint(2,'Deleting all dimensioning objects') 23 | #FreeCAD.ActiveDocument.openTransaction("Delete All Dim. Objects") 24 | for obj in d.drawingVars.page.Group: 25 | if hasattr(obj,'Proxy') and isinstance(obj.Proxy, Proxy_DimensionObject_prototype): 26 | FreeCAD.ActiveDocument.removeObject( obj.Name ) 27 | FreeCAD.ActiveDocument.commitTransaction() 28 | #FreeCAD.ActiveDocument.commitTransaction()# ah undo not working ... 29 | recomputeWithOutViewReset(d.drawingVars) 30 | FreeCADGui.Control.closeDialog() 31 | except: 32 | errorMessagebox_with_traceback() 33 | def generateWidget( self, dimensioningProcess ): 34 | button = QtGui.QPushButton('Delete All') 35 | button.clicked.connect( self.deleteAllDimensions ) 36 | return button 37 | d.dialogWidgets.append( deleteAllButton() ) 38 | class UndoInfoText: 39 | def generateWidget( self, dimensioningProcess ): 40 | vbox = QtGui.QVBoxLayout() 41 | vbox.addWidget( QtGui.QLabel('To undo a deletion:') ) 42 | vbox.addWidget( QtGui.QLabel(' 1) Undo') ) 43 | vbox.addWidget( QtGui.QLabel(' 2) Recompute Document') ) 44 | return vbox 45 | d.dialogWidgets.append( UndoInfoText() ) 46 | 47 | 48 | maskBrush = QtGui.QBrush( QtGui.QColor(160,0,0,100) ) 49 | maskPen = QtGui.QPen( QtGui.QColor(160,0,0,100) ) 50 | maskPen.setWidth(0.0) 51 | maskHoverPen = QtGui.QPen( QtGui.QColor(255,0,0,255) ) 52 | maskHoverPen.setWidth(0.0) 53 | 54 | class DeleteDimension: 55 | def Activated(self): 56 | V = getDrawingPageGUIVars() 57 | d.activate(V, dialogTitle='Delete Dimension', dialogIconPath=':/dd/icons/deleteDimension.svg' , endFunction=self.Activated, grid=False) 58 | selectionOverlay.generateSelectionGraphicsItems( 59 | [obj for obj in V.page.Group if hasattr(obj,'Proxy') and isinstance(obj.Proxy, Proxy_DimensionObject_prototype)], 60 | doSelectViewObjectPoints = True, 61 | onClickFun=deleteDimension, 62 | sceneToAddTo = V.graphicsScene, 63 | transform = V.transform, 64 | pointWid=1.0, 65 | maskPen=maskPen, 66 | maskHoverPen=maskHoverPen, 67 | maskBrush = maskBrush 68 | ) 69 | selectionOverlay.addProxyRectToRescaleGraphicsSelectionItems( V.graphicsScene, V.graphicsView, V.width, V.height) 70 | 71 | def GetResources(self): 72 | return { 73 | 'Pixmap' : ':/dd/icons/deleteDimension.svg', 74 | 'MenuText': 'Delete Dimension', 75 | 'ToolTip': 'Delete a dimension' 76 | } 77 | 78 | FreeCADGui.addCommand('dd_deleteDimension', DeleteDimension()) 79 | -------------------------------------------------------------------------------- /Gui/Resources/icons/table_dd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 34 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 69 | 75 | 81 | 87 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /dimensionSvgConstructor.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | ''' 3 | library containing commonly used SVGs construction functions 4 | ''' 5 | 6 | 7 | import numpy 8 | from numpy import dot, pi, arctan2, sin, cos, arccos 9 | import math 10 | from math import atan2 11 | from numpy.linalg import norm 12 | from svgLib_dd import SvgTextRenderer 13 | 14 | def directionVector( A, B ): 15 | if norm(B-A) == 0: 16 | return numpy.array([0.0,0.0]) 17 | else: 18 | return (B-A)/norm(B-A) 19 | 20 | def dimensionSVG_trimLine(A,B,trimA, trimB): 21 | d = directionVector( A, B) 22 | return (A + d*trimA).tolist() + (B - d*trimB).tolist() 23 | 24 | def rotate2D( v, angle ): 25 | return numpy.dot( [[ cos(angle), -sin(angle)],[ sin(angle), cos(angle)]], v) 26 | 27 | def arrowHeadSVG( tipPos, d, L1, L2, W, clr='blue'): 28 | d2 = numpy.dot( [[ 0, -1],[ 1, 0]], d) #same as rotate2D( d, pi/2 ) 29 | R = numpy.array( [d, d2]).transpose() 30 | p2 = numpy.dot( R, [ L1, W/2.0 ]) + tipPos 31 | p3 = numpy.dot( R, [ L1+L2, 0 ]) + tipPos 32 | p4 = numpy.dot( R, [ L1, -W/2.0 ]) + tipPos 33 | return '' % (tipPos[0], tipPos[1], p2[0], p2[1], p3[0], p3[1], p4[0], p4[1], clr, clr) 34 | 35 | def remove_tailing_zeros(s): 36 | if '.' in s: 37 | return s.rstrip('0').rstrip('.') 38 | else: 39 | return s 40 | 41 | def dimensionText( V, formatStr, roundingDigit=6, comma=False): 42 | try: 43 | s1 = remove_tailing_zeros(formatStr % {'value':V}) 44 | except TypeError: 45 | s1 = remove_tailing_zeros(formatStr % V) 46 | Vrounded = numpy.round(V, roundingDigit) 47 | try: 48 | s2 = remove_tailing_zeros(formatStr % {'value':Vrounded}) 49 | except TypeError: 50 | s2 = remove_tailing_zeros(formatStr % Vrounded) 51 | s = s2 if len(s2) < len(s1) else s1 52 | if comma: s = s.replace('.',',') 53 | return s 54 | 55 | defaultTextRenderer = SvgTextRenderer(font_family='Verdana', font_size='5pt', fill="red") 56 | 57 | def svgLine( x1, y1, x2, y2, lineColor, strokeWidth): 58 | return '' % (x1, y1, x2, y2, lineColor, strokeWidth ) 59 | 60 | def lineIntersection(line1, line2): 61 | x1,y1 = line1[0:2] 62 | dx1 = line1[2] - x1 63 | dy1 = line1[3] - y1 64 | x2,y2 = line2[0:2] 65 | dx2 = line2[2] - x2 66 | dy2 = line2[3] - y2 67 | # x1 + dx1*t1 = x2 + dx2*t2 68 | # y1 + dy1*t1 = y2 + dy2*t2 69 | A = numpy.array([ 70 | [ dx1, -dx2 ], 71 | [ dy1, -dy2 ], 72 | ]) 73 | b = numpy.array([ x2 - x1, y2 - y1]) 74 | t1,t2 = numpy.linalg.solve(A,b) 75 | x_int = x1 + dx1*t1 76 | y_int = y1 + dy1*t1 77 | #assert x1 + dx1*t1 == x2 + dx2*t2 78 | return x_int, y_int 79 | 80 | def textPlacement_common_procedure( A, B, text, x_text, y_text, textRotation, textRenderer, autoPlaceText, autoPlaceOffset): 81 | if textRotation > 90: 82 | textRotation = textRotation - 180 83 | if textRotation > 88: 84 | textRotation = textRotation - 180 85 | elif textRotation > 12 : 86 | textRotation = textRotation - 90 87 | elif textRotation < -92: 88 | textRotation = textRotation + 90 89 | if not autoPlaceText: 90 | if x_text <> None and y_text <> None: 91 | return textRenderer( x_text, y_text, text, rotation=textRotation ) 92 | else : 93 | return '' 94 | else: 95 | theta = (textRotation - 90)/180.0*pi 96 | pos_text = (A + B)/2 + autoPlaceOffset * numpy.array([ cos(theta), sin(theta)]) 97 | return textRenderer( pos_text[0], pos_text[1], text, rotation=textRotation, text_anchor='middle' ) 98 | -------------------------------------------------------------------------------- /Gui/Resources/icons/bendingNote.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 34 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 69 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Gui/Resources/icons/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 45 | 46 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | 76 | 77 | 81 | 87 | ? 99 | 100 | 101 | -------------------------------------------------------------------------------- /cgpr.py: -------------------------------------------------------------------------------- 1 | """conjugate gradient optimisation method using Polak-Ribiere search directions (CGPR)""" 2 | from numpy import array, zeros, nan, dot, inf 3 | from numpy.linalg import norm 4 | from numpy.random import rand 5 | from lineSearches import quadraticLineSearch 6 | 7 | 8 | def toStdOut(txt): 9 | print(txt) 10 | 11 | def CGPR( x0, f, grad_f, x_tol=10**-6, f_tol=None, maxIt=100, 12 | debugPrintLevel=0, printF=toStdOut, lineSearchIt=20): 13 | ''' 14 | search for minimum using conjugate gradient optimisation with Polak-Ribiere search directions 15 | ''' 16 | n = len(x0) 17 | x = array(x0) 18 | x_c = zeros(n) * nan 19 | u_hist = [] 20 | g = nan 21 | for i in range(maxIt): 22 | f_x = f(x) 23 | printF('cgpr it %02i: norm(prev. step) %1.1e, f(x) %1.3e' % (i, norm(x_c), f_x)) 24 | if debugPrintLevel > 1: 25 | printF(' x %s' % x) 26 | printF(' f(x) %s' % f_x) 27 | if norm(x_c) <= x_tol: 28 | break 29 | if f_x < f_tol: 30 | break 31 | grad_prev = g 32 | g = grad_f(x, f0=f_x) 33 | if debugPrintLevel > 1: 34 | printF(' grad_f : %s' % g) 35 | if i == 0: 36 | u = -g 37 | u_hist = [u] 38 | else: 39 | beta = dot(g - grad_prev,g) / norm(grad_prev)**2 40 | u = -g + beta*u_hist[-1] 41 | u_hist.append(u) 42 | if debugPrintLevel > 1: 43 | printF(' u %s' % u) 44 | x_next = quadraticLineSearch( f, x, f_x, u, lineSearchIt, debugPrintLevel-2, printF, tol_x=x_tol, tol_stag=inf ) 45 | x_c = x_next - x 46 | x = x_next 47 | return x 48 | 49 | 50 | 51 | class GradientApproximatorForwardDifference: 52 | def __init__(self, f): 53 | self.f = f 54 | def __call__(self, x, f0, eps=10**-7): 55 | n = len(x) 56 | grad_f = zeros(n) 57 | for i in range(n): 58 | x_eps = x.copy() 59 | x_eps[i] = x_eps[i] + eps 60 | grad_f[i] = (self.f(x_eps) - f0)/eps 61 | return grad_f 62 | 63 | if __name__ == '__main__': 64 | print('Testing CGPR algorithm') 65 | print('-GradientApproximator-') 66 | def f(X) : 67 | y,z=X 68 | return y + y*z + (1.0-y)**3 69 | def grad_f(X): 70 | y,z=X 71 | return array([ 1 + z - 3*(1.0-y)**2, y ]) 72 | grad_f_fd = GradientApproximatorForwardDifference(f) 73 | for i in range(2): 74 | X = rand(2)*10-5 75 | print(' X %s' % X) 76 | print(' grad_f(X) analytical: %s' % grad_f(X)) 77 | print(' grad_f(X) forwardDiff.: %s' % grad_f_fd(X, f(X))) 78 | print(' norm(analytical-forwardDiff) %e' % norm(grad_f(X) - grad_f_fd(X, f(X))) ) 79 | 80 | def f_basic(X): 81 | y,z = X 82 | return 2*(y-3)**2 + 2*(z+1)**2 83 | 84 | CGPR( -10+rand(2), f_basic, GradientApproximatorForwardDifference(f_basic), 85 | debugPrintLevel=3, printF=toStdOut, lineSearchIt=5) 86 | 87 | def f1(x) : 88 | "Rosenbrocks's parabloic valley " 89 | x1,x2 = x[0],x[1] 90 | return 100*(x2 -x1 **2) ** 2 + (1 - x1)**2 91 | def f2(x) : 92 | "Quadratic function" 93 | x1,x2 = x[0],x[1] 94 | return (x1 + 2*x2 - 7)**2 + (2*x1 + x2 - 5)**2 95 | def f3(x) : 96 | "Powells Quadratic function" 97 | x1,x2,x3,x4 = x[0],x[1],x[2],x[3] 98 | return (x1 + 10*x2)**2 + 5 * (x3 - x4)**2 + (x2 - 2*x3)**4 + 10*(x1-x4)**4 99 | 100 | for t,n in zip( [f1,f2,f3], [2,2,4]): 101 | print(t.__doc__) 102 | x0 = -10+20*rand(n) 103 | CGPR( x0, t, GradientApproximatorForwardDifference(t), 104 | debugPrintLevel=1, printF=toStdOut, lineSearchIt=20) 105 | -------------------------------------------------------------------------------- /Gui/Resources/icons/welding/Seam.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 55 | 56 | -------------------------------------------------------------------------------- /table_dd.py: -------------------------------------------------------------------------------- 1 | 2 | from dimensioning import * 3 | import selectionOverlay, previewDimension 4 | from dimensionSvgConstructor import * 5 | 6 | d = DimensioningProcessTracker() 7 | 8 | def tableSVG( top_left_x, top_left_y, column_widths, contents, row_heights, 9 | border_width=0.5, border_color='black', padding_x=1.0, padding_y=1.0, extra_rows=0, 10 | textRenderer_table=defaultTextRenderer): 11 | no_columns = len(column_widths) 12 | no_rows = int( numpy.ceil( len(contents) / float(no_columns) ) + extra_rows ) 13 | XML_body = [ ] 14 | def addLine(x1,y1,x2,y2): 15 | 'relative to top_left corner of table' 16 | XML_body.append('' % (x1, y1, x2, y2, border_color, border_width) ) 17 | #building table body 18 | width = sum( column_widths ) 19 | addLine( 0, 0, width, 0 ) 20 | y_offset = 0 21 | row_y = [] 22 | for i in range(no_rows): 23 | y_offset += row_heights[ i % len(row_heights) ] 24 | row_y.append( y_offset ) 25 | addLine( 0, y_offset, width, y_offset ) 26 | #debugPrint(1, 'row y : ' + str(row_y)) 27 | x_offset = 0 28 | column_x = [] 29 | addLine( 0, 0, 0, y_offset ) 30 | for j in range(no_columns): 31 | column_x.append( x_offset ) 32 | x_offset += column_widths[j] 33 | addLine( x_offset, 0, x_offset, y_offset ) 34 | for i, text in enumerate( contents ): 35 | row = int(i / no_columns) 36 | col = i % no_columns 37 | XML_body.append( textRenderer_table( 38 | column_x[col] + padding_x, 39 | row_y[row] - padding_y, 40 | text ) ) 41 | return ''' %s ''' % ( top_left_x, top_left_y, '\n'.join(XML_body) ) 42 | 43 | d.registerPreference( 'column_widths', [20.0, 30.0], kind='float_list') 44 | d.registerPreference( 'contents', 'number of rows adjusted to fit table contents'.split(' '), kind='string_list') 45 | d.registerPreference( 'row_heights', [7.0], kind='float_list') 46 | d.registerPreference( 'border_width', 0.3, increment=0.05 ) 47 | d.registerPreference( 'border_color', RGBtoUnsigned(0, 0, 0), kind='color') 48 | d.registerPreference( 'padding_x', 1.0 ) 49 | d.registerPreference( 'padding_y', 1.0 ) 50 | d.registerPreference( 'extra_rows', 0, decimals=0 ) 51 | d.registerPreference( 'textRenderer_table', ['inherit','5', 0], 'text properties (table)', kind='font' ) 52 | 53 | d.max_selections = 1 54 | def table_preview(mouseX, mouseY): 55 | selections = d.selections + [ PlacementClick( mouseX, mouseY) ] if len(d.selections) < d.max_selections else d.selections 56 | return tableSVG( *selections_to_svg_fun_args(selections), **d.dimensionConstructorKWs ) 57 | 58 | def table_clickHandler( x, y ): 59 | d.selections.append( PlacementClick( x, y) ) 60 | if len(d.selections) == d.max_selections: 61 | return 'createDimension:%s' % findUnusedObjectName('table') 62 | 63 | def selectFun( event, referer, elementXML, elementParms, elementViewObject ): 64 | viewInfo = selectionOverlay.DrawingsViews_info[elementViewObject.Name] 65 | d.selections = [ PointSelection( elementParms, elementXML, viewInfo ) ] 66 | selectionOverlay.hideSelectionGraphicsItems() 67 | previewDimension.initializePreview( d, noteCircle_preview, noteCircle_clickHandler) 68 | 69 | 70 | class Proxy_table( Proxy_DimensionObject_prototype ): 71 | def dimensionProcess( self ): 72 | return d 73 | d.ProxyClass = Proxy_table 74 | d.proxy_svgFun = tableSVG 75 | 76 | 77 | class AddTable: 78 | def Activated(self): 79 | V = getDrawingPageGUIVars() 80 | d.activate( V, dialogTitle='Add Table', dialogIconPath= ':/dd/icons/table_dd.svg', endFunction=self.Activated ) 81 | previewDimension.initializePreview( d, table_preview, table_clickHandler) 82 | 83 | def GetResources(self): 84 | return { 85 | 'Pixmap' : ':/dd/icons/table_dd.svg' , 86 | 'MenuText': 'Add table to drawing', 87 | 'ToolTip': 'Add table to drawing' 88 | } 89 | FreeCADGui.addCommand('dd_addTable', AddTable()) 90 | 91 | -------------------------------------------------------------------------------- /toleranceDialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'toleranceDialog.ui' 4 | # 5 | # Created: Mon Jun 8 14:50:14 2015 6 | # by: pyside-uic 0.2.15 running on PySide 1.2.1 7 | # 8 | # WARNING! All changes made in this file will be lost! 9 | 10 | from PySide import QtCore, QtGui 11 | 12 | class Ui_Dialog(object): 13 | def setupUi(self, Dialog): 14 | Dialog.setObjectName("Dialog") 15 | Dialog.setWindowModality(QtCore.Qt.ApplicationModal) 16 | Dialog.resize(209, 149) 17 | self.gridLayout_2 = QtGui.QGridLayout(Dialog) 18 | self.gridLayout_2.setObjectName("gridLayout_2") 19 | self.placeButton = QtGui.QPushButton(Dialog) 20 | self.placeButton.setObjectName("placeButton") 21 | self.gridLayout_2.addWidget(self.placeButton, 2, 0, 1, 1) 22 | self.gridLayout = QtGui.QGridLayout() 23 | self.gridLayout.setSizeConstraint(QtGui.QLayout.SetMaximumSize) 24 | self.gridLayout.setObjectName("gridLayout") 25 | self.label = QtGui.QLabel(Dialog) 26 | self.label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) 27 | self.label.setObjectName("label") 28 | self.gridLayout.addWidget(self.label, 0, 0, 1, 1) 29 | self.label_2 = QtGui.QLabel(Dialog) 30 | sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) 31 | sizePolicy.setHorizontalStretch(1) 32 | sizePolicy.setVerticalStretch(0) 33 | sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) 34 | self.label_2.setSizePolicy(sizePolicy) 35 | self.label_2.setMinimumSize(QtCore.QSize(61, 0)) 36 | self.label_2.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) 37 | self.label_2.setObjectName("label_2") 38 | self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) 39 | self.upperLineEdit = QtGui.QLineEdit(Dialog) 40 | self.upperLineEdit.setObjectName("upperLineEdit") 41 | self.gridLayout.addWidget(self.upperLineEdit, 0, 1, 1, 1) 42 | self.lowerLineEdit = QtGui.QLineEdit(Dialog) 43 | self.lowerLineEdit.setObjectName("lowerLineEdit") 44 | self.gridLayout.addWidget(self.lowerLineEdit, 1, 1, 1, 1) 45 | self.label_3 = QtGui.QLabel(Dialog) 46 | self.label_3.setObjectName("label_3") 47 | self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1) 48 | self.scaleDoubleSpinBox = QtGui.QDoubleSpinBox(Dialog) 49 | self.scaleDoubleSpinBox.setMinimum(0.05) 50 | self.scaleDoubleSpinBox.setSingleStep(0.05) 51 | self.scaleDoubleSpinBox.setProperty("value", 0.8) 52 | self.scaleDoubleSpinBox.setObjectName("scaleDoubleSpinBox") 53 | self.gridLayout.addWidget(self.scaleDoubleSpinBox, 2, 1, 1, 1) 54 | self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 1) 55 | 56 | self.retranslateUi(Dialog) 57 | QtCore.QObject.connect(self.placeButton, QtCore.SIGNAL("released()"), Dialog.accept) 58 | QtCore.QObject.connect(self.upperLineEdit, QtCore.SIGNAL("returnPressed()"), Dialog.accept) 59 | QtCore.QObject.connect(self.lowerLineEdit, QtCore.SIGNAL("returnPressed()"), Dialog.accept) 60 | QtCore.QMetaObject.connectSlotsByName(Dialog) 61 | 62 | def retranslateUi(self, Dialog): 63 | Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Add tolerance", None, QtGui.QApplication.UnicodeUTF8)) 64 | self.placeButton.setText(QtGui.QApplication.translate("Dialog", "Add", None, QtGui.QApplication.UnicodeUTF8)) 65 | self.label.setText(QtGui.QApplication.translate("Dialog", "upper", None, QtGui.QApplication.UnicodeUTF8)) 66 | self.label_2.setText(QtGui.QApplication.translate("Dialog", "lower", None, QtGui.QApplication.UnicodeUTF8)) 67 | self.upperLineEdit.setText(QtGui.QApplication.translate("Dialog", "+0", None, QtGui.QApplication.UnicodeUTF8)) 68 | self.lowerLineEdit.setText(QtGui.QApplication.translate("Dialog", "-0", None, QtGui.QApplication.UnicodeUTF8)) 69 | self.label_3.setText(QtGui.QApplication.translate("Dialog", "font scale", None, QtGui.QApplication.UnicodeUTF8)) 70 | 71 | -------------------------------------------------------------------------------- /freeDrawing.py: -------------------------------------------------------------------------------- 1 | from dimensioning import * 2 | import previewDimension 3 | import textAddDialog 4 | 5 | dimensioning = DimensioningProcessTracker() 6 | 7 | def lineSVG( x1, y1, x2, y2, svgTag='g', svgParms='', strokeWidth=0.5, lineColor='blue'): 8 | XML = '''<%s %s > 9 | 10 | ''' % ( svgTag, svgParms, x1, y1, x2, y2, lineColor, strokeWidth, svgTag ) 11 | return XML 12 | 13 | def line_ClickEvent( x, y): 14 | if dimensioning.stage == 0: 15 | dimensioning.x1 = x 16 | dimensioning.y1 = y 17 | dimensioning.stage = 1 18 | return None, None 19 | else: #dimensioning.stage == 1 : 20 | viewName = findUnusedObjectName('dimLine') 21 | XML = lineSVG( dimensioning.x1, dimensioning.y1, 22 | x, y, **dimensioning.dimensionConstructorKWs ) 23 | return viewName, XML 24 | 25 | def line_hoverEvent( x, y): 26 | if dimensioning.stage == 1 : 27 | return lineSVG( dimensioning.x1, dimensioning.y1, 28 | x, y, **dimensioning.svg_preview_KWs ) 29 | 30 | class lineFreeDrawing: 31 | def Activated(self): 32 | V = getDrawingPageGUIVars() 33 | dimensioning.activate( V, ['strokeWidth'],['lineColor'] ) 34 | previewDimension.initializePreview( 35 | dimensioning.drawingVars, 36 | line_ClickEvent, 37 | line_hoverEvent, 38 | ) 39 | 40 | def GetResources(self): 41 | return { 42 | 'Pixmap' : ':/dd/icons/drawLine.svg', 43 | 'MenuText': 'Draw a line' 44 | } 45 | FreeCADGui.addCommand('DrawingDimensioning_drawLine', lineFreeDrawing()) 46 | 47 | 48 | from dimensionSvgConstructor import arrowHeadSVG, numpy, directionVector 49 | 50 | def ArrowWithTail_SVG( c_x, c_y, radialLine_x=None, radialLine_y=None, tail_x=None, tail_y=None, arrowL1=3,arrowL2=1,arrowW=2, svgTag='g', svgParms='', strokeWidth=0.5, lineColor='blue'): 51 | XML_body = [] 52 | if radialLine_x <> None and radialLine_y <> None: 53 | XML_body.append( '' % (radialLine_x, radialLine_y, c_x, c_y, lineColor, strokeWidth) ) 54 | d = directionVector( 55 | numpy.array([ c_x, c_y]), 56 | numpy.array([radialLine_x, radialLine_y]), 57 | ) 58 | XML_body.append( arrowHeadSVG( numpy.array([c_x, c_y]), d, arrowL1, arrowL2, arrowW, lineColor ) ) 59 | if tail_x <> None and tail_y <> None: 60 | XML_body.append( '' % (radialLine_x, radialLine_y, tail_x, radialLine_y, lineColor, strokeWidth) ) 61 | return '''<%s %s > 62 | %s 63 | ''' % ( svgTag, svgParms, "\n".join(XML_body), svgTag ) 64 | 65 | def ArrowWithTail_ClickEvent( x, y): 66 | dimensioning.dArgs = dimensioning.dArgs + [x,y] 67 | dimensioning.stage = dimensioning.stage + 1 68 | if dimensioning.stage == 3: 69 | viewName = findUnusedObjectName('dimLine') 70 | XML = ArrowWithTail_SVG( 71 | *dimensioning.dArgs, 72 | **dimensioning.dimensionConstructorKWs ) 73 | return viewName, XML 74 | else: 75 | return None,None 76 | 77 | def ArrowWithTail_hoverEvent( x, y): 78 | if dimensioning.stage > 0 : 79 | return ArrowWithTail_SVG( 80 | *(dimensioning.dArgs + [x, y]), 81 | **dimensioning.svg_preview_KWs 82 | ) 83 | 84 | class ArrowWithTail_Drawing: 85 | def Activated(self): 86 | V = getDrawingPageGUIVars() 87 | dimensioning.activate( V, ['strokeWidth','arrowL1','arrowL2','arrowW'],['lineColor'] ) 88 | dimensioning.dArgs = [] 89 | previewDimension.initializePreview( 90 | dimensioning.drawingVars, 91 | ArrowWithTail_ClickEvent, 92 | ArrowWithTail_hoverEvent, 93 | ) 94 | 95 | def GetResources(self): 96 | return { 97 | 'Pixmap' : ':/dd/icons/drawLineWithArrow.svg', 98 | 'MenuText': 'Draw an arrow with a tail', 99 | } 100 | FreeCADGui.addCommand('DrawingDimensioning_drawArrowWithTail', ArrowWithTail_Drawing()) 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Gui/Resources/icons/drawCircularArc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 34 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 69 | 74 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /textEdit.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Dialog notes 3 | Use Qt Designer to edit the textAddDialog.ui 4 | Once completed $ pyside-uic textAddDialog.ui > textAddDialog.py 5 | 6 | To test inside Freecad 7 | from addTextDialog import DialogWidget 8 | dialog = DialogWidget() 9 | dialogUi = addTextDialog.Ui_Dialog() 10 | dialogUi.setupUi(dialog) 11 | dialog.show() 12 | 13 | ''' 14 | 15 | from dimensioning import * 16 | import selectionOverlay 17 | import textAddDialog 18 | from svgLib_dd import SvgTextParser 19 | import previewDimension 20 | 21 | d = DimensioningProcessTracker() 22 | 23 | def EditDimensionText( event, referer, elementXML, elementParms, elementViewObject ): 24 | d.dimToEdit = elementViewObject 25 | d.elementXML = elementXML 26 | selectionOverlay.hideSelectionGraphicsItems() 27 | e = elementXML 28 | debugPrint(3, e.XML[e.pStart:e.pEnd] ) 29 | svgText = SvgTextParser( e.XML[e.pStart:e.pEnd] ) 30 | d.svgText = svgText 31 | debugPrint(3, u'editing %s' % unicode(svgText)) 32 | widgets = dict( [c.objectName(), c] for c in dialog.children() ) 33 | widgets['textLineEdit'].setText( svgText.text ) 34 | widgets['sizeLineEdit'].setText( svgText.font_size) 35 | widgets['colorLineEdit'].setText( svgText.fill ) 36 | widgets['familyLineEdit'].setText( svgText.font_family ) 37 | widgets['doubleSpinBox_rotation'].setValue(svgText.rotation) 38 | widgets['placeButton'].setText('Change') 39 | dialog.setWindowTitle('Editing %s' % elementViewObject.Name) 40 | dialog.show() 41 | 42 | class EditTextDialogWidget( QtGui.QWidget ): 43 | def accept( self ): 44 | debugPrint(3, 'EditTextDialogWidget accept pressed') 45 | widgets = dict( [c.objectName(), c] for c in self.children() ) 46 | debugPrint(4, 'widgets %s' % widgets) 47 | if widgets['textLineEdit'].text() == '': 48 | debugPrint(1, 'Aborting placing empty text.') 49 | return 50 | self.hide() 51 | svgText = d.svgText 52 | svgText.text = widgets['textLineEdit'].text() 53 | widgets['textLineEdit'].setText('') 54 | svgText.font_size = widgets['sizeLineEdit'].text() 55 | svgText.font_family = widgets['familyLineEdit'].text() 56 | svgText.fill = widgets['colorLineEdit'].text() 57 | svgText.rotation = widgets['doubleSpinBox_rotation'].value() 58 | debugPrint(3,'updating XML in %s to' % d.dimToEdit.Name) 59 | xml = svgText.toXML() 60 | debugPrint(4,xml) 61 | e = d.elementXML 62 | newXML = e.XML[:e.pStart] + xml + e.XML[e.pEnd:] 63 | debugPrint(3,newXML) 64 | d.dimToEdit.ViewResult = newXML 65 | recomputeWithOutViewReset(d.drawingVars) 66 | if d.taskDialog <> None: #unessary check 67 | FreeCADGui.Control.closeDialog() 68 | if d.endFunction <> None: 69 | previewDimension.preview.dimensioningProcessTracker = d 70 | previewDimension.timer.start( 100 ) # 100 ms, need some time for dialog to close 71 | 72 | dialog = EditTextDialogWidget() 73 | dialogUi = textAddDialog.Ui_Dialog() 74 | dialogUi.setupUi(dialog) 75 | 76 | maskBrush = QtGui.QBrush( QtGui.QColor(0,160,0,100) ) 77 | maskPen = QtGui.QPen( QtGui.QColor(0,160,0,100) ) 78 | maskPen.setWidth(0.0) 79 | maskHoverPen = QtGui.QPen( QtGui.QColor(0,255,0,255) ) 80 | maskHoverPen.setWidth(0.0) 81 | 82 | class EditText: 83 | def Activated(self): 84 | V = getDrawingPageGUIVars() 85 | d.activate( V, dialogTitle='Edit Text', dialogIconPath= ':/dd/icons/textEdit.svg', endFunction=self.Activated, grid=False ) 86 | selectGraphicsItems = selectionOverlay.generateSelectionGraphicsItems( 87 | [obj for obj in V.page.Group if obj.Name.startswith('dim')], 88 | EditDimensionText , 89 | sceneToAddTo = V.graphicsScene, 90 | transform = V.transform, 91 | doTextItems = True, 92 | pointWid=2.0, 93 | maskPen=maskPen, 94 | maskHoverPen=maskHoverPen, 95 | maskBrush = maskBrush 96 | ) 97 | 98 | def GetResources(self): 99 | msg = "Edit a dimension's text" 100 | return { 101 | 'Pixmap' : ':/dd/icons/textEdit.svg', 102 | 'MenuText': msg, 103 | 'ToolTip': msg 104 | } 105 | FreeCADGui.addCommand('dd_editText', EditText()) 106 | 107 | 108 | -------------------------------------------------------------------------------- /dimensionSvgConstructor_testCenterLines.py: -------------------------------------------------------------------------------- 1 | print('Testing dimensionSvgConstructor.py centerLines') 2 | 3 | from dimensionSvgConstructor import * 4 | import sys 5 | from PySide import QtGui, QtCore, QtSvg 6 | 7 | app = QtGui.QApplication(sys.argv) 8 | width = 640 9 | height = 480 10 | 11 | graphicsScene = QtGui.QGraphicsScene(0,0,width,height) 12 | graphicsScene.addText("Center Lines testing app.\nEsc to Exit") 13 | 14 | dimensions = [] 15 | 16 | class DimensioningRect(QtGui.QGraphicsRectItem): 17 | def __init__(self,*args): 18 | super(DimensioningRect, self).__init__(*args) 19 | svgRenderer = QtSvg.QSvgRenderer() 20 | self.action_ind = 0 21 | self.dimPreview = QtSvg.QGraphicsSvgItem() 22 | self.dimSVGRenderer = QtSvg.QSvgRenderer() 23 | self.dimSVGRenderer.load( QtCore.QByteArray( '''''' % (args[2],args[3]))) 24 | self.dimPreview.setSharedRenderer( self.dimSVGRenderer ) 25 | self.dimPreview.setZValue(100) 26 | graphicsScene.addItem( self.dimPreview ) 27 | self.dim_svg_KWs = dict( 28 | svgTag='svg', svgParms='width="%i" height="%i"' % (args[2],args[3]), 29 | centerLine_width=2.0, centerLine_len_dot=5, centerLine_len_dash=15, centerLine_len_gap=5 30 | ) 31 | assert not hasattr(self, 'topLeft') 32 | assert not hasattr(self, 'bottomRight') 33 | assert not hasattr(self, 'center') 34 | 35 | def mousePressEvent( self, event ): 36 | if event.button() == QtCore.Qt.MouseButton.LeftButton: 37 | pos = event.scenePos() 38 | x, y = pos.x(), pos.y() 39 | if self.action_ind == 0: 40 | self.center = x, y 41 | print('center set to x=%3.1f y=%3.1f' % (x,y)) 42 | self.action_ind = self.action_ind + 1 43 | elif self.action_ind == 1: 44 | self.topLeft = x, y 45 | print('topLeft set to x=%3.1f y=%3.1f' % (x,y)) 46 | self.action_ind = self.action_ind + 1 47 | elif self.action_ind == 2: # then place 48 | self.bottomRight = x, y 49 | self.action_ind = 0 50 | XML = centerLinesSVG( self.center, self.topLeft, self.bottomRight, 51 | **self.dim_svg_KWs ) 52 | if XML <> None: 53 | print(XML) 54 | newSvg = QtSvg.QGraphicsSvgItem( ) 55 | svgRenderer = QtSvg.QSvgRenderer() 56 | svgRenderer.load( QtCore.QByteArray( XML )) 57 | newSvg.setSharedRenderer( svgRenderer ) 58 | dimensions.append([ newSvg, svgRenderer]) #as to prevent the garbage collector from freeing these resources (which causes a crash) 59 | self.scene().addItem( newSvg ) 60 | 61 | def hoverMoveEvent(self, event): 62 | if self.action_ind == 0: 63 | return 64 | pos = event.scenePos() 65 | x, y = pos.x(), pos.y() 66 | XML = None 67 | if self.action_ind == 1: # then placeDimensionBaseLine action 68 | XML = centerLinesSVG( self.center, [x, y], 69 | **self.dim_svg_KWs ) 70 | elif self.action_ind == 2: # then placeDimensionText 71 | XML = centerLinesSVG( self.center, self.topLeft, [ x, y ], **self.dim_svg_KWs ) 72 | if XML <> None: 73 | self.dimSVGRenderer.load( QtCore.QByteArray( XML ) ) 74 | self.dimPreview.update() 75 | self.dimPreview.show() 76 | else: 77 | self.dimPreview.hide() 78 | 79 | def wheelEvent( self, event): 80 | if event.delta() > 0: 81 | view.scale(1.1, 1.1) 82 | else: 83 | view.scale(0.9, 0.9) 84 | 85 | def keyPressEvent(self, event): 86 | if len(event.text()) == 1: 87 | print('key pressed: event.text %s (ord %i)' % (event.text(), ord(event.text()))) 88 | if event.text() == chr(27): #escape key 89 | sys.exit(2) 90 | 91 | dimensioningRect = DimensioningRect(0,0,width,height) 92 | dimensioningRect.setAcceptHoverEvents(True) 93 | dimensioningRect.setFlag( QtGui.QGraphicsItem.GraphicsItemFlag.ItemIsFocusable, True ) 94 | graphicsScene.addItem(dimensioningRect) 95 | 96 | view = QtGui.QGraphicsView(graphicsScene) 97 | #view.scale(2, 2) 98 | view.show() 99 | 100 | sys.exit(app.exec_()) 101 | -------------------------------------------------------------------------------- /Gui/Resources/icons/drawCircle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 34 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 69 | 74 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /toleranceDialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | Qt::ApplicationModal 7 | 8 | 9 | 10 | 0 11 | 0 12 | 209 13 | 149 14 | 15 | 16 | 17 | Add tolerance 18 | 19 | 20 | 21 | 22 | 23 | Add 24 | 25 | 26 | 27 | 28 | 29 | 30 | QLayout::SetMaximumSize 31 | 32 | 33 | 34 | 35 | upper 36 | 37 | 38 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 1 47 | 0 48 | 49 | 50 | 51 | 52 | 61 53 | 0 54 | 55 | 56 | 57 | lower 58 | 59 | 60 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 61 | 62 | 63 | 64 | 65 | 66 | 67 | +0 68 | 69 | 70 | 71 | 72 | 73 | 74 | -0 75 | 76 | 77 | 78 | 79 | 80 | 81 | font scale 82 | 83 | 84 | 85 | 86 | 87 | 88 | 0.050000000000000 89 | 90 | 91 | 0.050000000000000 92 | 93 | 94 | 0.800000000000000 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | placeButton 106 | released() 107 | Dialog 108 | accept() 109 | 110 | 111 | 177 112 | 149 113 | 114 | 115 | 142 116 | 96 117 | 118 | 119 | 120 | 121 | upperLineEdit 122 | returnPressed() 123 | Dialog 124 | accept() 125 | 126 | 127 | 177 128 | 43 129 | 130 | 131 | 142 132 | 96 133 | 134 | 135 | 136 | 137 | lowerLineEdit 138 | returnPressed() 139 | Dialog 140 | accept() 141 | 142 | 143 | 140 144 | 56 145 | 146 | 147 | 104 148 | 74 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /previewDimension.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 338 10 | 400 11 | 12 | 13 | 14 | Preview Dialog 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 1 23 | 24 | 25 | 26 | Unit Used 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | scheme 35 | 36 | 37 | 38 | 39 | 40 | 41 | Qt::Horizontal 42 | 43 | 44 | 45 | 40 46 | 20 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 1 56 | 0 57 | 58 | 59 | 60 | 61 | default 62 | 63 | 64 | 65 | 66 | mm 67 | 68 | 69 | 70 | 71 | inch 72 | 73 | 74 | 75 | 76 | m 77 | 78 | 79 | 80 | 81 | custom 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | default Unit: 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | custom/mm 105 | 106 | 107 | 108 | 109 | 110 | 111 | Qt::Horizontal 112 | 113 | 114 | 115 | 40 116 | 20 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 3 125 | 126 | 127 | 0.001000000000000 128 | 129 | 130 | 1000000.000000000000000 131 | 132 | 133 | 0.500000000000000 134 | 135 | 136 | 1.000000000000000 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /centerView.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import FreeCAD,FreeCADGui,os,re 4 | from XMLlib import SvgXMLTreeNode 5 | from svgLib_dd import SvgPath 6 | from dimensioning import debugPrint 7 | 8 | 9 | def getPoints(svg): 10 | "returns a series of (x,y) points from an SVG fragment" 11 | # adapted from selectionOverlay.py 12 | points = [] 13 | XML_tree = SvgXMLTreeNode(svg,0) 14 | scaling = XML_tree.scaling() 15 | SelectViewObjectPoint_loc = None 16 | for element in XML_tree.getAllElements(): 17 | if element.tag == 'circle': 18 | x, y = element.applyTransforms( float( element.parms['cx'] ), float( element.parms['cy'] ) ) 19 | points.append((x,y)) 20 | elif element.tag == 'ellipse': 21 | x, y = element.applyTransforms( float( element.parms['cx'] ), float( element.parms['cy'] ) ) 22 | points.append((x,y)) 23 | elif element.tag == 'text' and element.parms.has_key('x'): 24 | x, y = element.applyTransforms( float( element.parms['x'] ), float( element.parms['y'] ) ) 25 | points.append((x,y)) 26 | elif element.tag == 'path': 27 | path = SvgPath( element ) 28 | for p in path.points: 29 | points.append((p.x, p.y)) 30 | elif element.tag == 'line': 31 | x1, y1 = element.applyTransforms( float( element.parms['x1'] ), float( element.parms['y1'] ) ) 32 | x2, y2 = element.applyTransforms( float( element.parms['x2'] ), float( element.parms['y2'] ) ) 33 | points.append((x1, y1)) 34 | points.append((x2, y2)) 35 | return points 36 | 37 | 38 | def getCenterPoint(viewObject): 39 | "returns the (x,y) center point of a DrawingView object" 40 | if not hasattr(viewObject, 'ViewResult'): 41 | return None 42 | if viewObject.ViewResult.strip() == '': 43 | return None 44 | points = getPoints(viewObject.ViewResult) 45 | xmin = 9999999999999999 46 | xmax = -9999999999999999 47 | ymin = 9999999999999999 48 | ymax = -9999999999999999 49 | for p in points: 50 | if p[0] < xmin: 51 | xmin = p[0] 52 | if p[0] > xmax: 53 | xmax = p[0] 54 | if p[1] < ymin: 55 | ymin = p[1] 56 | if p[1] > ymax: 57 | ymax = p[1] 58 | x = xmin + (xmax-xmin)/2 59 | y = ymin + (ymax-ymin)/2 60 | return (x,y) 61 | 62 | 63 | def getPageDimensions(pageObject): 64 | "returns the (x,y) dimensions of a page" 65 | if not pageObject.PageResult: 66 | return None 67 | if not os.path.exists(pageObject.PageResult): 68 | return None 69 | f = open(pageObject.PageResult) 70 | svg = f.read() 71 | f.close() 72 | svg = svg.replace("\n"," ") 73 | width = re.findall("",svg) 74 | if width: 75 | width = float(width[0].strip("mm").strip("px")) 76 | else: 77 | return None 78 | height = re.findall("",svg) 79 | if height: 80 | height = float(height[0].strip("mm").strip("px")) 81 | else: 82 | return None 83 | return (width,height) 84 | 85 | 86 | class CenterView: 87 | "Defines the CenterView command" 88 | 89 | def GetResources(self): 90 | return { 91 | 'Pixmap' : ':/dd/icons/centerView.svg' , 92 | 'MenuText': 'Centers a view on its page', 93 | 'ToolTip': 'Centers a view on its page' 94 | } 95 | 96 | def Activated(self): 97 | sel = FreeCADGui.Selection.getSelection() 98 | for obj in sel: 99 | done = False 100 | if obj.isDerivedFrom("Drawing::FeatureView"): 101 | for parent in obj.InList: 102 | if parent.isDerivedFrom("Drawing::FeaturePage"): 103 | pagedims = getPageDimensions(parent) 104 | if pagedims: 105 | viewcenter = getCenterPoint(obj) 106 | if viewcenter: 107 | debugPrint( 2, "current center point: %3.3f, %3.3f" % viewcenter ) 108 | debugPrint( 2, "page center: %3.3f, %3.3f" % (pagedims[0]/2, pagedims[1]/2) ) 109 | dx = pagedims[0]/2 - viewcenter[0] 110 | dy = pagedims[1]/2 - viewcenter[1] 111 | debugPrint( 2, "delta: %3.3f, %3.3f" % (dx, dy) ) 112 | FreeCAD.ActiveDocument.openTransaction("Center View") 113 | obj.X = obj.X+dx 114 | obj.Y = obj.Y+dy 115 | FreeCAD.ActiveDocument.commitTransaction() 116 | done = True 117 | if not done: 118 | FreeCAD.Console.PrintError("Unable to move view "+obj.Label+"\n") 119 | FreeCAD.ActiveDocument.recompute() 120 | 121 | 122 | FreeCADGui.addCommand('dd_centerView', CenterView()) 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /noteCircle.py: -------------------------------------------------------------------------------- 1 | 2 | from dimensioning import * 3 | import selectionOverlay, previewDimension 4 | from dimensionSvgConstructor import * 5 | 6 | d = DimensioningProcessTracker() 7 | 8 | def noteCircleSVG( start_x, start_y, radialLine_x=None, radialLine_y=None, tail_x=None, tail_y=None, 9 | noteCircleText= '0', strokeWidth=0.5, lineColor='blue', noteCircle_radius=4.5, noteCircle_fill='white', 10 | textRenderer_noteCircle=defaultTextRenderer): 11 | XML_body = [ ] 12 | if radialLine_x <> None and radialLine_y <> None: 13 | XML_body.append( svgLine(radialLine_x, radialLine_y, start_x, start_y, lineColor, strokeWidth) ) 14 | if tail_x <> None and tail_y <> None: 15 | XML_body.append( svgLine(radialLine_x, radialLine_y, tail_x, radialLine_y, lineColor, strokeWidth) ) 16 | XML_body.append(' ' % (tail_x, radialLine_y, noteCircle_radius, lineColor, noteCircle_fill) ) 17 | XML_body.append( textRenderer_noteCircle( tail_x - 1.5, radialLine_y + 1.5, noteCircleText ) ) 18 | return ' %s ' % '\n'.join(XML_body) 19 | 20 | d.registerPreference( 'strokeWidth') 21 | d.registerPreference( 'lineColor' ) 22 | d.registerPreference( 'noteCircle_radius', 4.5, increment=0.5, label='radius') 23 | d.registerPreference( 'noteCircle_fill', RGBtoUnsigned(255, 255, 255), kind='color', label='fill' ) 24 | d.registerPreference( 'textRenderer_noteCircle', ['inherit','5', 150<<16], 'text properties (Note Circle)', kind='font' ) 25 | d.max_selections = 3 26 | 27 | class NoteCircleText_widget: 28 | def __init__(self): 29 | self.counter = 1 30 | def valueChanged( self, arg1): 31 | d.noteCircleText = '%i' % arg1 32 | self.counter = arg1 + 1 33 | def generateWidget( self, dimensioningProcess ): 34 | self.spinbox = QtGui.QSpinBox() 35 | self.spinbox.setValue(self.counter) 36 | d.noteCircleText = '%i' % self.counter 37 | self.spinbox.valueChanged.connect(self.valueChanged) 38 | self.counter = self.counter + 1 39 | return DimensioningTaskDialog_generate_row_hbox('no.', self.spinbox) 40 | def add_properties_to_dimension_object( self, obj ): 41 | obj.addProperty("App::PropertyString", 'noteText', 'Parameters') 42 | obj.noteText = d.noteCircleText.encode('utf8') 43 | def get_values_from_dimension_object( self, obj, KWs ): 44 | KWs['noteCircleText'] = obj.noteText #should be unicode 45 | d.dialogWidgets.append( NoteCircleText_widget() ) 46 | 47 | 48 | 49 | def noteCircle_preview(mouseX, mouseY): 50 | selections = d.selections + [ PlacementClick( mouseX, mouseY) ] if len(d.selections) < d.max_selections else d.selections 51 | return noteCircleSVG( *selections_to_svg_fun_args(selections), noteCircleText=d.noteCircleText, **d.dimensionConstructorKWs ) 52 | 53 | def noteCircle_clickHandler( x, y ): 54 | d.selections.append( PlacementClick( x, y) ) 55 | if len(d.selections) == d.max_selections: 56 | return 'createDimension:%s' % findUnusedObjectName('noteCircle') 57 | 58 | def selectFun( event, referer, elementXML, elementParms, elementViewObject ): 59 | viewInfo = selectionOverlay.DrawingsViews_info[elementViewObject.Name] 60 | d.selections = [ PointSelection( elementParms, elementXML, viewInfo ) ] 61 | selectionOverlay.hideSelectionGraphicsItems() 62 | previewDimension.initializePreview( d, noteCircle_preview, noteCircle_clickHandler) 63 | 64 | class Proxy_noteCircle( Proxy_DimensionObject_prototype ): 65 | def dimensionProcess( self ): 66 | return d 67 | d.ProxyClass = Proxy_noteCircle 68 | d.proxy_svgFun = noteCircleSVG 69 | 70 | maskBrush = QtGui.QBrush( QtGui.QColor(0,160,0,100) ) 71 | maskPen = QtGui.QPen( QtGui.QColor(0,160,0,100) ) 72 | maskPen.setWidth(0.0) 73 | maskHoverPen = QtGui.QPen( QtGui.QColor(0,255,0,255) ) 74 | maskHoverPen.setWidth(0.0) 75 | 76 | class NoteCircle: 77 | def Activated(self): 78 | V = getDrawingPageGUIVars() 79 | d.activate(V, dialogTitle='Add Note Circle', dialogIconPath=':/dd/icons/noteCircle.svg', endFunction=self.Activated ) 80 | from grabPointAdd import Proxy_grabPoint 81 | selectionOverlay.generateSelectionGraphicsItems( 82 | dimensionableObjects( V.page ) + [obj for obj in V.page.Group if hasattr(obj,'Proxy') and isinstance( obj.Proxy, Proxy_grabPoint) ], 83 | selectFun, 84 | transform = V.transform, 85 | sceneToAddTo = V.graphicsScene, 86 | doPoints=True, doMidPoints=True, doSelectViewObjectPoints = True, 87 | pointWid=1.0, 88 | maskPen=maskPen, 89 | maskHoverPen=maskHoverPen, 90 | maskBrush = maskBrush 91 | ) 92 | selectionOverlay.addProxyRectToRescaleGraphicsSelectionItems( V.graphicsScene, V.graphicsView, V.width, V.height) 93 | 94 | def GetResources(self): 95 | return { 96 | 'Pixmap' : ':/dd/icons/noteCircle.svg' , 97 | 'MenuText': 'Notation', 98 | 'ToolTip': 'Creates a notation indicator' 99 | } 100 | 101 | FreeCADGui.addCommand('dd_noteCircle', NoteCircle()) 102 | -------------------------------------------------------------------------------- /toleranceAdd.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Dialog notes 3 | ------------ 4 | Use Qt Designer to edit the toleranceDialog.ui 5 | Once completed 6 | $ pyside-uic toleranceDialog.ui > toleranceDialog.py 7 | ''' 8 | 9 | from dimensioning import * 10 | import previewDimension, selectionOverlay 11 | import toleranceDialog 12 | from textEdit import maskBrush, maskPen, maskHoverPen 13 | from dimensionSvgConstructor import * 14 | 15 | d = DimensioningProcessTracker() 16 | 17 | def _textSVG_sub(x_offset, y_offset, text, comma_decimal_place, rotation, text_x, text_y ): 18 | offset = rotate2D([x_offset, y_offset], rotation*numpy.pi/180) 19 | x = text_x + offset[0] 20 | y = svgText.y + offset[1] 21 | return d.textRenderer(x, y, text if not comma_decimal_place else text.replace('.',','), 22 | text_anchor='end', rotation=svgText.rotation ) 23 | 24 | def textSVG( text_x, text_y, text, font_size, rotation, font_family, font_fill, x, y, text_upper, text_lower, toleranceText_sizeRatio=0.8, comma_decimal_place=False ): 25 | fS = float(font_size) * toleranceText_sizeRatio 26 | textRenderer = SvgTextRenderer( 27 | font_family = font_family, 28 | fill = font_fill, 29 | font_size = fS 30 | ) 31 | w = rotate2D([x - text_x, y - text_y], -rotation*numpy.pi/180)[0] 32 | def _textSVG_sub(x_offset, y_offset, text ): 33 | offset = rotate2D([x_offset, y_offset], rotation*numpy.pi/180) 34 | x = text_x + offset[0] 35 | y = text_y + offset[1] 36 | return textRenderer(x, y, text if not comma_decimal_place else text.replace('.',','), 37 | text_anchor='end', rotation=rotation ) 38 | textXML_lower = _textSVG_sub( w, 0.0*fS, text_lower ) 39 | textXML_upper = _textSVG_sub( w, -fS, text_upper ) 40 | return ' %s \n %s ' % ( textXML_lower, textXML_upper ) 41 | d.registerPreference( 'toleranceText_sizeRatio', 0.8, increment=0.1, label='size ratio') 42 | d.registerPreference( 'comma_decimal_place') 43 | 44 | class boundText_widget: 45 | def __init__(self, name, default): 46 | self.name = name 47 | self.default = default 48 | def valueChanged( self, arg1): 49 | setattr(d, self.name, arg1) 50 | def generateWidget( self, dimensioningProcess ): 51 | self.lineEdit = QtGui.QLineEdit() 52 | self.lineEdit.setText(self.default) 53 | setattr(d, self.name, self.default) 54 | self.lineEdit.textChanged.connect(self.valueChanged) 55 | return DimensioningTaskDialog_generate_row_hbox(self.name, self.lineEdit) 56 | def add_properties_to_dimension_object( self, obj ): 57 | obj.addProperty("App::PropertyString", self.name+'_text', 'Parameters') 58 | setattr( obj, self.name+'_text', getattr( d, self.name ).encode('utf8') ) 59 | def get_values_from_dimension_object( self, obj, KWs ): 60 | KWs['text_'+self.name] = getattr( obj, self.name+'_text') #should be unicode 61 | 62 | d.dialogWidgets.append( boundText_widget('upper','+0.0') ) 63 | d.dialogWidgets.append( boundText_widget('lower','-0.0') ) 64 | 65 | 66 | def toleranceAdd_preview( mouse_x, mouse_y ): 67 | s = d.selections + [PlacementClick( mouse_x, mouse_y)] if len(d.selections) == 1 else d.selections 68 | return textSVG( *selections_to_svg_fun_args(s), text_upper=d.upper, text_lower=d.lower, **d.dimensionConstructorKWs ) 69 | 70 | def toleranceAdd_clickHandler(x, y): 71 | d.selections.append( PlacementClick( x, y) ) 72 | return 'createDimension:%s' % findUnusedObjectName('tolerance') 73 | 74 | def AddToleranceToText( event, referer, elementXML, elementParms, elementViewObject ): 75 | viewInfo = selectionOverlay.DrawingsViews_info[elementViewObject.Name] 76 | d.selections = [ TextSelection( elementParms, elementXML, viewInfo ) ] 77 | selectionOverlay.hideSelectionGraphicsItems() 78 | previewDimension.initializePreview(d, toleranceAdd_preview, toleranceAdd_clickHandler) 79 | 80 | class Proxy_toleranceAdd( Proxy_DimensionObject_prototype ): 81 | def dimensionProcess( self ): 82 | return d 83 | d.ProxyClass = Proxy_toleranceAdd 84 | d.proxy_svgFun = textSVG 85 | 86 | class AddTolerance: 87 | def Activated(self): 88 | V = getDrawingPageGUIVars() 89 | d.activate( V, dialogTitle='Add Tolerance', dialogIconPath=':/dd/icons/toleranceAdd.svg', endFunction=self.Activated ) 90 | selectGraphicsItems = selectionOverlay.generateSelectionGraphicsItems( 91 | [obj for obj in V.page.Group if hasattr(obj,'Proxy') and isinstance(obj.Proxy, Proxy_DimensionObject_prototype)], 92 | AddToleranceToText , 93 | sceneToAddTo = V.graphicsScene, 94 | transform = V.transform, 95 | doTextItems = True, 96 | pointWid=2.0, 97 | maskPen=maskPen, 98 | maskHoverPen=maskHoverPen, 99 | maskBrush = maskBrush 100 | ) 101 | 102 | def GetResources(self): 103 | return { 104 | 'Pixmap' : ':/dd/icons/toleranceAdd.svg' , 105 | 'MenuText': 'Add tolerance super and subscript to dimension', 106 | } 107 | FreeCADGui.addCommand('dd_addTolerance', AddTolerance()) 108 | 109 | 110 | -------------------------------------------------------------------------------- /radiusDimension.py: -------------------------------------------------------------------------------- 1 | 2 | from dimensioning import * 3 | import selectionOverlay, previewDimension 4 | from dimensionSvgConstructor import * 5 | 6 | d = DimensioningProcessTracker() 7 | 8 | def radiusDimensionSVG( center_x, center_y, radius, radialLine_x=None, radialLine_y=None, tail_x=None, tail_y=None, text_x=None, text_y=None, autoPlaceText=False, autoPlaceOffset=2.0, 9 | textFormat_radial='R%3.3f', comma_decimal_place=False, 10 | centerPointDia = 1, arrowL1=3, arrowL2=1, arrowW=2, strokeWidth=0.5, scale=1.0, lineColor='blue', arrow_scheme='auto', 11 | textRenderer=defaultTextRenderer): 12 | XML_body = [ ' ' % (center_x, center_y, centerPointDia*0.5, lineColor) ] 13 | if radialLine_x <> None and radialLine_y <> None: 14 | theta = math.atan2( radialLine_y - center_y, radialLine_x - center_x ) 15 | A = numpy.array([ center_x + radius*numpy.cos(theta) , center_y + radius*numpy.sin(theta) ]) 16 | B = numpy.array([ center_x - radius*numpy.cos(theta) , center_y - radius*numpy.sin(theta) ]) 17 | XML_body.append( svgLine(radialLine_x, radialLine_y, center_x, center_y, lineColor, strokeWidth) ) 18 | if radius > 0: 19 | if arrow_scheme <> 'off': 20 | if arrow_scheme == 'auto': 21 | s = 1 if radius > arrowL1 + arrowL2 + 0.5*centerPointDia else -1 22 | elif arrow_scheme == 'in': 23 | s = 1 24 | elif arrow_scheme == 'out': 25 | s = -1 26 | XML_body.append( arrowHeadSVG( A, s*directionVector(A,B), arrowL1, arrowL2, arrowW, lineColor ) ) 27 | if tail_x <> None and tail_y <> None: 28 | XML_body.append( svgLine(radialLine_x, radialLine_y, tail_x, radialLine_y, lineColor, strokeWidth) ) 29 | text = dimensionText( radius*scale,textFormat_radial, comma=comma_decimal_place) 30 | XML_body.append( textPlacement_common_procedure(numpy.array([radialLine_x, radialLine_y]), numpy.array([tail_x, radialLine_y]), text, text_x, text_y, 0, textRenderer, autoPlaceText, autoPlaceOffset) ) 31 | return ' %s ' % "\n".join(XML_body) 32 | 33 | d.dialogWidgets.append( unitSelectionWidget ) 34 | d.registerPreference( 'textFormat_radial', 'R%(value)3.3f', 'format mask') 35 | d.registerPreference( 'arrow_scheme') 36 | d.registerPreference( 'autoPlaceText') 37 | d.registerPreference( 'comma_decimal_place') 38 | d.registerPreference( 'centerPointDia') 39 | d.registerPreference( 'arrowL1') 40 | d.registerPreference( 'arrowL2') 41 | d.registerPreference( 'arrowW') 42 | d.registerPreference( 'strokeWidth') 43 | d.registerPreference( 'lineColor') 44 | d.registerPreference( 'textRenderer' ) 45 | d.registerPreference( 'autoPlaceOffset') 46 | d.max_selections = 4 47 | 48 | def radiusDimensionSVG_preview(mouseX, mouseY): 49 | selections = d.selections + [ PlacementClick( mouseX, mouseY ) ] if len(d.selections) < d.max_selections else d.selections 50 | return radiusDimensionSVG( *selections_to_svg_fun_args(selections), scale=d.viewScale*d.unitConversionFactor, **d.dimensionConstructorKWs ) 51 | 52 | def radiusDimensionSVG_clickHandler( x, y ): 53 | d.selections.append( PlacementClick( x, y ) ) 54 | if len(d.selections) == d.max_selections - 1 and d.dimensionConstructorKWs['autoPlaceText']: 55 | d.selections.append( PlacementClick( x, y ) ) # to avoid crash when auto place turned off 56 | return 'createDimension:%s' % findUnusedObjectName('rad') 57 | elif len(d.selections) == d.max_selections : 58 | return 'createDimension:%s' % findUnusedObjectName('rad') 59 | 60 | def selectFun( event, referer, elementXML, elementParms, elementViewObject ): 61 | viewInfo = selectionOverlay.DrawingsViews_info[elementViewObject.Name] 62 | d.viewScale = 1/elementXML.rootNode().scaling() 63 | d.selections.append( CircularArcSelection( elementParms, elementXML, viewInfo ) ) 64 | selectionOverlay.hideSelectionGraphicsItems() 65 | previewDimension.initializePreview( d, radiusDimensionSVG_preview, radiusDimensionSVG_clickHandler) 66 | 67 | class Proxy_RadiusDimension( Proxy_DimensionObject_prototype ): 68 | def dimensionProcess( self ): 69 | return d 70 | d.ProxyClass = Proxy_RadiusDimension 71 | d.proxy_svgFun = radiusDimensionSVG 72 | 73 | maskPen = QtGui.QPen( QtGui.QColor(0,255,0,100) ) 74 | maskPen.setWidth(2.0) 75 | maskHoverPen = QtGui.QPen( QtGui.QColor(0,255,0,255) ) 76 | maskHoverPen.setWidth(2.0) 77 | 78 | class RadiusDimension: 79 | def Activated(self): 80 | V = getDrawingPageGUIVars() 81 | d.activate(V, 'Add Radial Dimension', dialogIconPath=':/dd/icons/radiusDimension.svg', endFunction=self.Activated) 82 | selectionOverlay.generateSelectionGraphicsItems( 83 | dimensionableObjects( V.page ), 84 | selectFun , 85 | transform = V.transform, 86 | sceneToAddTo = V.graphicsScene, 87 | doCircles=True, doFittedCircles=True, 88 | maskPen=maskPen, 89 | maskHoverPen=maskHoverPen, 90 | maskBrush = QtGui.QBrush() #clear 91 | ) 92 | selectionOverlay.addProxyRectToRescaleGraphicsSelectionItems( V.graphicsScene, V.graphicsView, V.width, V.height) 93 | 94 | def GetResources(self): 95 | return { 96 | 'Pixmap' : ':/dd/icons/radiusDimension.svg', 97 | 'MenuText': 'Radius Dimension', 98 | 'ToolTip': 'Creates a radius dimension' 99 | } 100 | 101 | FreeCADGui.addCommand('dd_radiusDimension', RadiusDimension()) 102 | -------------------------------------------------------------------------------- /Gui/Resources/icons/noteCircle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 35 | 38 | 42 | 46 | 47 | 49 | 53 | 57 | 58 | 67 | 68 | 87 | 89 | 90 | 92 | image/svg+xml 93 | 95 | 96 | 97 | 98 | 99 | 103 | 113 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /circularDimension.py: -------------------------------------------------------------------------------- 1 | # This Python file uses the following encoding: utf-8 2 | 3 | from dimensioning import * 4 | import selectionOverlay, previewDimension 5 | from dimensionSvgConstructor import * 6 | 7 | d = DimensioningProcessTracker() 8 | 9 | def circularDimensionSVG( center_x, center_y, radius, radialLine_x=None, radialLine_y=None, tail_x=None, tail_y=None, text_x=None, text_y=None, autoPlaceText=False, autoPlaceOffset=2.0, 10 | scale=1.0, textFormat_circular='Ø%3.3f', comma_decimal_place=False, 11 | centerPointDia = 1, arrowL1=3, arrowL2=1, arrowW=2, strokeWidth=0.5, lineColor='blue', arrow_scheme='auto', 12 | textRenderer=defaultTextRenderer): 13 | XML_body = [ ' ' % (center_x, center_y, centerPointDia*0.5, lineColor) ] 14 | #XML_body.append( '' % (center_x, center_y, radius, strokeWidth) ) 15 | if radialLine_x <> None and radialLine_y <> None: 16 | theta = math.atan2( radialLine_y - center_y, radialLine_x - center_x ) 17 | A = numpy.array([ center_x + radius*numpy.cos(theta) , center_y + radius*numpy.sin(theta) ]) 18 | B = numpy.array([ center_x - radius*numpy.cos(theta) , center_y - radius*numpy.sin(theta) ]) 19 | XML_body.append( svgLine(radialLine_x, radialLine_y, B[0], B[1], lineColor, strokeWidth) ) 20 | if radius > 0: 21 | if arrow_scheme <> 'off': 22 | if arrow_scheme == 'auto': 23 | s = 1 if radius > arrowL1 + arrowL2 + 0.5*centerPointDia else -1 24 | elif arrow_scheme == 'in': 25 | s = 1 26 | elif arrow_scheme == 'out': 27 | s = -1 28 | XML_body.append( arrowHeadSVG( A, s*directionVector(A,B), arrowL1, arrowL2, arrowW, lineColor ) ) 29 | XML_body.append( arrowHeadSVG( B, s*directionVector(B,A), arrowL1, arrowL2, arrowW, lineColor ) ) 30 | if tail_x <> None and tail_y <> None: 31 | XML_body.append( svgLine( radialLine_x, radialLine_y, tail_x, radialLine_y, lineColor, strokeWidth ) ) 32 | text = dimensionText(2*radius*scale,textFormat_circular, comma=comma_decimal_place) 33 | XML_body.append( textPlacement_common_procedure(numpy.array([radialLine_x, radialLine_y]), numpy.array([tail_x, radialLine_y]), text, text_x, text_y, 0, textRenderer, autoPlaceText, autoPlaceOffset) ) 34 | return ' %s ' % "\n".join(XML_body) 35 | 36 | d.dialogWidgets.append( unitSelectionWidget ) 37 | d.registerPreference( 'textFormat_circular', 'Ø%(value)3.3f', 'format mask') 38 | d.registerPreference( 'arrow_scheme') 39 | d.registerPreference( 'autoPlaceText') 40 | d.registerPreference( 'comma_decimal_place') 41 | d.registerPreference( 'centerPointDia', 0.5, increment=0.5) 42 | d.registerPreference( 'arrowL1') 43 | d.registerPreference( 'arrowL2') 44 | d.registerPreference( 'arrowW') 45 | d.registerPreference( 'strokeWidth') 46 | d.registerPreference( 'lineColor') 47 | d.registerPreference( 'textRenderer' ) 48 | d.registerPreference( 'autoPlaceOffset') 49 | d.max_selections = 4 50 | 51 | 52 | def circularDimensionSVG_preview(mouseX, mouseY): 53 | selections = d.selections + [ PlacementClick( mouseX, mouseY ) ] if len(d.selections) < d.max_selections else d.selections 54 | return circularDimensionSVG( *selections_to_svg_fun_args(selections), scale=d.viewScale*d.unitConversionFactor, **d.dimensionConstructorKWs ) 55 | 56 | def circularDimensionSVG_clickHandler( x, y ): 57 | d.selections.append( PlacementClick( x, y ) ) 58 | if len(d.selections) == d.max_selections - 1 and d.dimensionConstructorKWs['autoPlaceText']: 59 | d.selections.append( PlacementClick( x, y ) ) # to avoid crash when auto place turned off 60 | return 'createDimension:%s' % findUnusedObjectName('dia') 61 | elif len(d.selections) == d.max_selections : 62 | return 'createDimension:%s' % findUnusedObjectName('dia') 63 | 64 | def selectFun( event, referer, elementXML, elementParms, elementViewObject ): 65 | viewInfo = selectionOverlay.DrawingsViews_info[elementViewObject.Name] 66 | d.viewScale = 1/elementXML.rootNode().scaling() 67 | d.selections.append( CircularArcSelection( elementParms, elementXML, viewInfo ) ) 68 | selectionOverlay.hideSelectionGraphicsItems() 69 | previewDimension.initializePreview( d, circularDimensionSVG_preview, circularDimensionSVG_clickHandler ) 70 | 71 | class Proxy_CircularDimension( Proxy_DimensionObject_prototype ): 72 | def dimensionProcess( self ): 73 | return d 74 | d.ProxyClass = Proxy_CircularDimension 75 | d.proxy_svgFun = circularDimensionSVG 76 | 77 | 78 | 79 | maskPen = QtGui.QPen( QtGui.QColor(0,255,0,100) ) 80 | maskPen.setWidth(2.0) 81 | maskHoverPen = QtGui.QPen( QtGui.QColor(0,255,0,255) ) 82 | maskHoverPen.setWidth(2.0) 83 | 84 | 85 | class CircularDimension: 86 | def Activated(self): 87 | V = getDrawingPageGUIVars() 88 | d.activate(V, dialogTitle='Add Circular Dimension', dialogIconPath=':/dd/icons/circularDimension.svg', endFunction=self.Activated ) 89 | selectionOverlay.generateSelectionGraphicsItems( 90 | dimensionableObjects( V.page ), 91 | selectFun , 92 | transform = V.transform, 93 | sceneToAddTo = V.graphicsScene, 94 | doCircles=True, doFittedCircles=True, 95 | maskPen=maskPen, 96 | maskHoverPen=maskHoverPen, 97 | maskBrush = QtGui.QBrush() #clear 98 | ) 99 | selectionOverlay.addProxyRectToRescaleGraphicsSelectionItems( V.graphicsScene, V.graphicsView, V.width, V.height) 100 | 101 | def GetResources(self): 102 | return { 103 | 'Pixmap' : ':/dd/icons/circularDimension.svg' , 104 | 'MenuText': 'Circular Dimension', 105 | 'ToolTip': 'Creates a circular dimension' 106 | } 107 | 108 | FreeCADGui.addCommand('dd_circularDimension', CircularDimension()) 109 | -------------------------------------------------------------------------------- /lineSearches.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy, math 3 | from numpy.linalg import norm 4 | 5 | class LineSearchEvaluation: 6 | def __init__(self, f, x, searchDirection, lam, fv=None): 7 | self.lam = lam 8 | self.xv = x + lam*searchDirection 9 | self.fv = f(x + lam*searchDirection) if not fv else fv 10 | def __lt__(self, b): 11 | return self.fv < b.fv 12 | def __eq__(self, b): 13 | return self.lam == b.lam 14 | def str(self, prefix='LSEval', lamFmt='%1.6f', fvFmt='%1.2e'): 15 | return '%s %s,%s' % (prefix, lamFmt % self.lam, fvFmt % self.fv ) 16 | 17 | 18 | 19 | phi = (5.0**0.5 - 1)/2 20 | def goldenSectionSearch( f, x1, f1, intialStep, it, debugPrintLevel, printF, it_min_at_x1=12): #f1 added to save resources... 21 | def LSEval(lam, fv=None): 22 | return LineSearchEvaluation( f, x1, intialStep, lam, fv ) 23 | y1 = LSEval( 0.0, f1) 24 | y2 = LSEval( phi**2 ) 25 | y3 = LSEval( phi ) 26 | y4 = LSEval( 1.0 ) 27 | if debugPrintLevel > 0: 28 | printF(' goldenSection search it 0: lam %1.3f %1.3f %1.3f %1.3f f(lam) %1.2e %1.2e %1.2e %1.2e' % ( y1.lam, y2.lam, y3.lam, y4.lam, y1.fv, y2.fv, y3.fv, y4.fv)) 29 | for k in range(it_min_at_x1): 30 | y_min = min([ y1, y2, y3, y4 ]) 31 | if y_min == y2 or y_min == y1 : 32 | y4 = y3 33 | y3 = y2 34 | y2 = LSEval( y1.lam + phi**2 * (y4.lam - y1.lam) ) 35 | elif y_min == y3 : 36 | y1 = y2 37 | y2 = y3 38 | y3 = LSEval( y1.lam + phi*(y4.lam - y1.lam) ) 39 | elif y_min == y4 : 40 | y2 = y3 41 | y3 = y4 42 | y4 = LSEval( y1.lam + (phi**-1) * (y4.lam - y1.lam) ) 43 | if debugPrintLevel > 0: 44 | printF(' goldenSection search it %i: lam %1.3f %1.3f %1.3f %1.3f f(lam) %1.2e %1.2e %1.2e %1.2e' % ( it,y1.lam,y2.lam,y3.lam,y4.lam,y1.fv,y2.fv,y3.fv,y4.fv)) 45 | if y1.lam > 0 and k+1 >= it: 46 | break 47 | return min([ y1, y2, y3, y4 ]).xv 48 | 49 | def quadraticLineSearch( f, x1, f1, intialStep, it, debugPrintLevel, printF, tol_stag=3, tol_x=10**-6): 50 | if norm(intialStep) == 0: 51 | printF(' quadraticLineSearch: norm search direction is 0, aborting!') 52 | return x1 53 | def LSEval(lam, fv=None): 54 | return LineSearchEvaluation( f, x1, intialStep, lam, fv ) 55 | Y = [ LSEval( 0.0, f1), LSEval( 1 ), LSEval( 2 )] 56 | y_min_prev = min(Y) 57 | count_stagnation = 0 58 | tol_lambda = tol_x / norm(intialStep) 59 | for k in range(it): 60 | Y.sort() 61 | if debugPrintLevel > 0: 62 | printF(' quadratic line search it %i, fmin %1.2e, lam %1.6f %1.6f %1.6f, f(lam) %1.2e %1.2e %1.2e'%( k+1, Y[0].fv, Y[0].lam,Y[1].lam,Y[2].lam,Y[0].fv,Y[1].fv,Y[2].fv )) 63 | #``p[0]*x**(N-1) + p[1]*x**(N-2) + ... + p[N-2]*x + p[N-1]`` 64 | quadraticCoefs, residuals, rank, singular_values, rcond = numpy.polyfit( [y.lam for y in Y], [y.fv for y in Y], 2, full=True) 65 | if quadraticCoefs[0] > 0 and rank == 3: 66 | lam_c = -quadraticCoefs[1] / (2*quadraticCoefs[0]) #diff poly a*x**2 + b*x + c -> grad_poly = 2*a*x + b 67 | lam_c = min( max( [y.lam for y in Y])*4, lam_c) 68 | if lam_c < 0: 69 | if debugPrintLevel > 1: printF(' quadratic line search lam_c < 0') 70 | lam_c = 1.0 / (k + 1) ** 2 71 | else: 72 | if debugPrintLevel > 1: printF(' quadratic fit invalid, using interval halving instead') 73 | lam_c = ( Y[0].lam + Y[1].lam )/2 74 | del Y[2] # Y sorted at start of each iteration 75 | Y.append( LSEval( lam_c )) 76 | y_min = min(Y) 77 | if y_min == y_min_prev: 78 | count_stagnation = count_stagnation + 1 79 | if count_stagnation > tol_stag: 80 | if debugPrintLevel > 0: printF(' terminating quadratic line search as count_stagnation > tol_stag') 81 | break 82 | else: 83 | y_min_prev = y_min 84 | count_stagnation = 0 85 | Lam = [y.lam for y in Y] 86 | if max(Lam) - min(Lam) < tol_lambda: 87 | if debugPrintLevel > 0: printF(' terminating quadratic max(Lam)-min(Lam) < tol_lambda (%e < %e)' % (max(Lam) - min(Lam), tol_lambda)) 88 | break 89 | 90 | return min(Y).xv 91 | 92 | 93 | if __name__ == '__main__': 94 | print('Testing linesearches') 95 | from matplotlib import pyplot 96 | from numpy import sin 97 | 98 | def f1(x): 99 | '(1+sin(x))*(x-0.6)**2' 100 | return float( (1+sin(x))*(x-0.6)**2 ) 101 | def f2(x): 102 | '(1+sin(x))*(x-0.0001)**2' 103 | return float( (1+sin(x))*(x-0.0001)**2 ) 104 | class recordingWrapper: 105 | def __init__(self, f): 106 | self.f = f 107 | self.f_hist = [] 108 | self.x_hist = [] 109 | def __call__(self, x): 110 | self.x_hist.append(x) 111 | self.f_hist.append(self.f(x)) 112 | return self.f_hist[-1] 113 | def printF(text): 114 | print(text) 115 | 116 | lineSearchesToTest = [ goldenSectionSearch, quadraticLineSearch ] 117 | names = ['golden','quadratic'] 118 | 119 | for testFunction in [f1,f2]: 120 | print(testFunction.func_doc) 121 | T =[] 122 | for L,name in zip(lineSearchesToTest,names): 123 | t = recordingWrapper(testFunction) 124 | T.append(t) 125 | xOpt = L(t, numpy.array([0.0]), t( numpy.array([0.0]) ), numpy.array([0.5]), 10, debugPrintLevel=1, printF=printF) 126 | print(' %s line search, xOpt %f, f(xOpt) %e' % (name, xOpt, t(xOpt) ) ) 127 | x_max = max( max(T[0].x_hist), max(T[1].x_hist ) ) 128 | pyplot.figure() 129 | x_plot = numpy.linspace(0, x_max, 100) 130 | y_plot = [ testFunction(x) for x in x_plot ] 131 | pyplot.plot(x_plot, y_plot) 132 | pyplot.title(testFunction.func_doc) 133 | for t,s,label in zip(T, ['r^','go'], names): 134 | pyplot.plot( t.x_hist, t.f_hist, s ,label=label) 135 | pyplot.legend() 136 | 137 | #pyplot.show() 138 | --------------------------------------------------------------------------------