Initial merge of Path module branch

authors: Yorik van Havre, Dan Falck, Brad Collette, Frederik Johansson, Dmitry Platonov,

eb4fd6c Path: moved colors preferences page to Path group
f4e5d70 a little more work on auto tool change
99a6e2f tied tool numbers in ops to last loaded tool
2d6fcda no need to select object for tooltable edit
b53eb15 Path: Fixed path bug #10 - Tooltableeditor
d6e996f Path: Fixed path bug #36 - Finish editing
16fe598 Path: Fixed path bug #37: labels in compound dialog
22dc594 Bugfix in pocket, was producing strange arcs due to a rounding error Path doesnt render values such as:-4.4408920985e-16 produced by python
9aae1d5 Make Gcode editor restore its size and position from last opened
b44e1d6 Move addToProject to PathUtils
71b727a Make all tools show its correct icon in the tree
86b32e0 Further simplify the code and fix issue in PathHop
e63b6b7 Simplify code and make automatic project creation more consistent across tools
261bfa3 Add ability to have external orocos-kdl library instead of the suplied in FreeCAD, used in Robot and Path workbenches
95efa27 Drilling-rapid to first location before moving Z
73537d6 I forgot to parse the SubObjects of the selection
044c23d improvements to drilling selection and properties
347fe62 small improvement to PathKurveUtils sorting
95ffe39 formatting
1003968 split full circles
27b19c2 added basic feedrate to PathKurveUtils
372ef57 Path: removed Path_rc and DemoParts from Cmake
98705ad using toBiarcs for paths based on splines
32663e5 slight startpt/endpt simplification
fd8f258 parametric start and end pts to PathKurve
b5e4761 still working on pathkurvetutils and points
01bf423 worked out clockwise profile paths
2825191 fixed typo
5a87872 still working on start and stop points for kurve
7fe77bc allowing points for start and stop of path
cb57d1b making ptlist consistant
3f2bf6c improvements to PathKurve
e3765a0 added libarea for pofile op-still much work to do
c882df2 small fix for adding arcs to edgelist
2cbac06 forgot Path_rc.py
45bf437 applied fix for windows icons not showing
61dbe07 PathUtils code cleanup
9d49cb8 Fix SortPath if no offset is specified
332eca0 split SortEdges and MakePath in prep for using kurve_funcs from HeeksCNC
799f1f1 tweaking visualization a bit
ba8eee6 small change back to way project holds toolpath for visualization
5ca4984 applied Werner's kdl patch
14c93be added units to tlo,revised centroid post
3c6341d Path: fixed spaces missing in file reading
10c20f9 removed editor button from project and post work
68ff118 removed stock command added small function to find last z value
0b71f92 tidying up profile a bit
fe8b4e9 moved the findtool function to PathUtils
4441695 Path: Exposed Command.transform() to python
09ed1d1 Path: Added a default empty preferences page
6ff0689 forgot to add the PathFromShape.py file
d6e3c4a added simple python proxy for PathShape So that it can be inserted into the Project tree automatically. Later, we can connect the base object to the shape so that it changes if the base shape is changed.
f5ca514 clean up of PathCopy.py
8336b96 moved Path from Shape to New Operation menu
7e73998 made PathCopy a child of the Project/Compound
7674b37 missed a modified file for unit handling
9aa1327 small change to account for pathcopy being InList
c35a109 PathPost doesn't barf now if the user rearranged the project.
09c4a1f working on Machine properties again
4bc32a4 more view property fixes
a453ad2 set defaults from post
3b051cb more property changes
ef5a015 reorganized properties.  Used PropertyLength/Distance in Drilling
311a059 made it so that the viewprovider settings would stick
e427303 fixed post function
3e528dc removed post processor file property from PathProject and added it to PathMachine
58a5339 added Machine object to hold post processor file name and tooltable information. Moved the cornermin,cornermax bounding box of machine to it. Haven't connected post processor file output from it yet, will do that next. Disconnected path output from Project object. It is really just a Group now that holds all the other path objects. Posts will now have to parse the objects inside it to get gcode. I think we are already doing that with the linuxcnc post and I plan on doing that with future posts. Removed a lot of view properties from lots of objects that don't need them. Use mode=2 and vobj.setEditorMode('LineWidth',mode) as an example of that. Removed placement property for things like PathComment because it doesn't make sense to try to alter placement of nongeometric things.
dd8b831 adding properties for using units
f84a2cc Multiple selection of points and circles for drilling.
f3d645a added viewprovider for drill icon in object tree
f564462 changed R plane behaviour for G81 drill cycle
85a056d added a few more drill/tap/bore cycles,changed z to R plane between drill cycles
9312ef9 Path: Added 3D representation of G81, G82 and G83 drill ops
0ed19d5 A stab at pathpocket with some reasonable properties and stepping down.
da9d6b5 forgot a quote mark
1c49c8d changed icon for optional stop
b7eb50e First bit of work on drilling.
4b3bc52 moved where comments and optional stops are located
3e9fbd7 added optional and mandatory stop commands
9e94269 added comment object
c3e44f5 removed a few properties
7cf86d7 automatic populating of tool numbers based on active ToolNumber
b607843 added tool length offset and load tool commands, made PathCompound accept objects that PathProject removed
8a93d54 reworked linuxcnc_post to use commands natively and not toGCode()
85f2cfd PathPost command now handles more cases including generic. Adds a simple dumper post to get raw commands.
aa827fa added G43 tool length offset
b1bf6d3 added new post, modded Profile for comments, fixed ZClearance in Profile
2fd5f2c added post process command and button
3cf6107 forgot about adding a file to cmake again
77f8a7f added new post, rearranged icons,modified PathProject
7e6adf3 added active property to PathPocket
d6b479a fixed stock, added active/inactive to a few ops
9a96b72 added pathplane and made fixture and pathprofile automatically populate or even create a project
d214281 forgot to add the actual script needed for stock
1adf36a added PathStock for material visualization
2ef30ea fixed bug in fixtures and changed icon
7b3e9dd made fixtures go inside a Path Project
6143c9a fixed JSON encoding error
c46cb2b added simple script to use the ToolTableEditor`
fc70a4d Turned Compound & Project into python objects
a1b9127 trying to fix segfualt
1bbf53e repaired fixture offsets
739e387 missing import. revised for new structure.  Creates multiple paths again.
38f2d25 added missing import
1837645 adding parameters to PathCompound
208bfdb Path: New pre/post interface
a252be1 rearranged and added some parameters to PathProject and PathCompound
9fa6278 Path: Added safe height property to project
de6812e made the icon a bit thicker
25beab0 merging Path-Copy with Path-Fixture
5cfffdc added icon for fixture offsets
af18281 Path: Added Path Copy object
89865ef pre support tool change and spindle speed (partially)
e9960f7 changes to opensbp post only.
421c12b Path: Fixed bug in paths with no move
272a11d added linuxcnc_post.py back to cmake
715c72b removed unused files
4ea4e18 added fixture offset script, removed unneeded scripts
855a3e0 fix for toolchange to get Tnumber to show
64f497c Path: Fixed unresponsive import/export dialogs
51c7a9e revert example post to simple form
5e25f3a Renamed pre_ and post_ scripts to _pre and _post
9837f3f first pass cleanup
9a42cee Path: Fixed scripts search path
3467a73 Path: search for pre & post scripts in user dir too - fixes #12
b46e527 Path: fixed precision bug - closes #25
c65fc05 added another post from sliptonic
718f499 Path: Enabled comments in Paths
ea0952f Path: Added preference color for machine extents
7578467 Path: Implemented machine properties  - issue yorikvanhavre/FreeCAD#8
1ae3a7d minor change to comment location
49f742d added Spindle Speed to profile
2a4374c added a second post processor example
5278d46 worked on sorting, reversing, sorting, and more sorting
7bc81bf separating selection function from PathUtils
64dd77c Path: small fix in absolute/relative coords
f435303 face selection works again
fa7074f rolled back and took another try at discretizing curves
ae1ff5b changed discretizing slightly
8e47d16 added discretizing of bezier curves to toolpaths
e85e087 fix for open wires/profiles worked on sorting again
a9a8a29 worked out arc segments over 180 degrees but not full circles yet
073a4a0 refactoring where sorting takes place
6723052 one more time on the sorting
050221c arc direction and path order-still problems but better
d7a79b1 I think I worked out backwards arc problem
a721aae corrected final depth on profile
4f07f01 I forgot to add the file for the previous commit
8eac175 combined ConvGcode and PathSelection into PathUtils
488a593 added some profile utility functions
4c4e0b2 worked on arc moves and reordering
cf04c3a Path: Added Hop tool and added arcs handlingin PathShape
417e9c7 Path: removed Tool number from dressup
4cedb9d Path: Fixed dressup
1b7e79f Path: Added UsePlacements property to compounds
8241513 Path: Added Dressup + fixed cmake + enabled python view provider
9721037 Path: Update bounding boxes when drawing path
9d8192e Path: Split compound into compound and project objects
250cdc7 Path: Added Drilling tool
8939e03 Path: Added translation file
466fe54 Path: Added an initial rapid move to FeatureShapes
7dfab6f Path: Added pocket tool
795f3c0 Path: added a ShowFirstRapid property to the path viewprovider
21e1272 Path: show number of tools in compound's task dialog
8dde2c5 Path: Added Profile tool
32b3fc1 Path: Added GUI import/export
fa1db60 Path: allow to import heeks tooltables and removed c++ workbench
e8c5a3a Path: Fixed tooltable editor
29fd0e4 Path: Allow pre scripts to return a list to create a compound
1eec4bd Path: Tooltable editor (BUGGY)
3ee661d Path: updated Path module dependencies in cmake
7137e04 Path: Added UI to reorder children of a path compound feature
3fb7073 Path: Added Path::FeatureShape feature
f496615 Path: added post editor
069b070 Path: Added structure for post scripts
bd0e669 Path: Added Path.insert() function
fcf1f18 Path: small bugfixes
09124e3 Path: Added spaces in (pseudo) Gcode output
efcb755 Path: Added pre script example
7a6a267 adding macro to find outer profile
274ff82 added a few macros
521d095 Path: Added FeatureCompound object
06eee1a adding python scripts and part files
42119ab added LGPL license
e8f0742 added demo parts and python experiments
c849734 Path: added tooltable property & Path.show() function
1886b4a Path: Added Path::FeaturePython object
b48b9d8 Path: correct placement behaviour and support for G90 and G91
00fe8a6 Path: Added support for G00
896c359 Path: Enabled different color for rapid moves
885a8d9 Path: small fix
a8d310b Path: improvements to the view provider
9fd88c5 Path: now removes comments when creating paths from gcode
dc0a0f1 Path: Fixed Z coordinate
ba6abc8 Path: Fixed color properties
8605629 Path: remember Z coord of last point if not specified
0d66fa9 Path: Misc upgrades to the view provider
f77c399 Path: Now supposes absolute positions instead of relative
a23b4e8 Path: Tooltable now behaves as a dicionary
eef5669 Path: simplified file saving/restoring
f15262f Path: enabled reading & writing of Path features
0a735ee More uniform caps system for enums, and added more materials
ad3f88e added a few more tools
ef62185 added more tools
f489758 removed whitespaces
5f906c6 added a few more tool definitions
84608dc minor changes to names
3ca1b5c Path: added Tool and Tooltable objects
d852a0b Path: extended path py object, enable coin representation in view provider
f7980b3 Path: extended the command object
a6205c4 Added Path Feature
dae6499 Path: Added Path object
693c0df Temporarily removed Gui from cmake
c2519e7 Path module: First commit
b3860d5 made a little progress- added ConvGcode to split and reverse paths
5761235 removed wire.reverse()- I couldn't get the hang of it
54a5482 added selection script along with parameters for profile
cecc1df Path: removed Tool number from dressup
7ca8e79 Path: Fixed dressup
d7d1a9b Path: Added UsePlacements property to compounds
a001d7c Path: Added Dressup + fixed cmake + enabled python view provider
5ff31b3 Path: Update bounding boxes when drawing path
946f26c Path: Split compound into compound and project objects
4c9fedd Path: Added Drilling tool
e88d431 Path: Added translation file
a989530 Path: Added an initial rapid move to FeatureShapes
8faab1e Path: Added pocket tool
e2dd1a4 Path: added a ShowFirstRapid property to the path viewprovider
5daa4ed Path: show number of tools in compound's task dialog
18bd973 Path: Added Profile tool
f613104 Path: Added GUI import/export
7e7bc79 Path: allow to import heeks tooltables and removed c++ workbench
a60325f Path: Fixed tooltable editor
802b413 Path: Allow pre scripts to return a list to create a compound
35fb2a1 Path: Tooltable editor (BUGGY)
3040bc1 Path: updated Path module dependencies in cmake
680d9a8 Path: Added UI to reorder children of a path compound feature
8ed321c Path: Added Path::FeatureShape feature
f81ccdd Path: added post editor
34d2cef Path: Added structure for post scripts
2ea3060 Path: Added Path.insert() function
e8561f4 Path: small bugfixes
ef6fe49 Path: Added spaces in (pseudo) Gcode output
909a944 Path: Added pre script example
58abd71 adding macro to find outer profile
a7415c5 added a few macros
51cd15b Path: Added FeatureCompound object
be6c10a adding python scripts and part files
30bde91 added LGPL license
93babeb added demo parts and python experiments
f69c9a9 Path: added tooltable property & Path.show() function
5796bea Path: Added Path::FeaturePython object
e6052bd Path: correct placement behaviour and support for G90 and G91
9f3977c Path: Added support for G00
1b95678 Path: Enabled different color for rapid moves
90bdc2c Path: small fix
6394ceb Path: improvements to the view provider
aa93f1e Path: now removes comments when creating paths from gcode
35a479b Path: Fixed Z coordinate
1947315 Path: Fixed color properties
987d8e3 Path: remember Z coord of last point if not specified
64da26c Path: Misc upgrades to the view provider
283df2b Path: Now supposes absolute positions instead of relative
b59a46d Path: Tooltable now behaves as a dicionary
b4c2edb Path: simplified file saving/restoring
8b36988 Path: enabled reading & writing of Path features
308bb20 More uniform caps system for enums, and added more materials
f9f129c added a few more tools
eb83a31 added more tools
bb40643 removed whitespaces
13cd406 added a few more tool definitions
6357082 minor changes to names
6a14580 Path: added Tool and Tooltable objects
bc14864 Path: extended path py object, enable coin representation in view provider
a8a19a0 Path: extended the command object
0174d04 Added Path Feature
a829a6c Path: Added Path object
ba7ca94 Temporarily removed Gui from cmake
3c9d77a Path module: First commit
This commit is contained in:
Yorik van Havre
2015-07-04 18:30:10 -03:00
parent 6c32edfb8c
commit 3f4e55a474
120 changed files with 99837 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui::Dialog::DlgSettingsPath</class>
<widget class="QWidget" name="Gui::Dialog::DlgSettingsPath">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>503</width>
<height>526</height>
</rect>
</property>
<property name="windowTitle">
<string>General Path settings</string>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>9</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>General Path settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="Gui::PrefCheckBox" name="pathTestSetting1">
<property name="toolTip">
<string>If this is checked, object names will be prefixed with the IFC ID number</string>
</property>
<property name="text">
<string>Test setting 1</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>pathTestSetting1</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Path</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Test setting 2</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefLineEdit" name="pathTestSetting2">
<property name="toolTip">
<string>A comma-separated list of Ifc entities to exclude from import</string>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string/>
</property>
<property name="prefEntry" stdset="0">
<cstring>pathTestSetting2</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Path</cstring>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Test group</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="Gui::PrefCheckBox" name="pathTestSetting3">
<property name="toolTip">
<string>Some IFC viewers don't like objects exported as extrusions. Use this to force all objects to be exported as BREP geometry.</string>
</property>
<property name="text">
<string>Test setting 3</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>pathTestSetting3</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Path</cstring>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<pixmapfunction>qPixmapFromMimeSource</pixmapfunction>
<customwidgets>
<customwidget>
<class>Gui::PrefCheckBox</class>
<extends>QCheckBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefLineEdit</class>
<extends>QLineEdit</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,39 @@
#***************************************************************************
#* *
#* Copyright (c) 2014 Daniel Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
'''
This macro is used in conjunction with the toolpathparams script to create an object that represents a tool for use in a CNC program. Create a group and then select it- then run the macro.
You will have to edit the parameters inside the Data tab of the tool object.
'''
import PathScripts
import toolpathparams as tp
tl = FreeCAD.ActiveDocument.addObject("App::FeaturePython","Tools")
tp.ToolParams(tl)
tp.ViewProviderToolParams(tl.ViewObject)
sel = FreeCADGui.Selection.getSelection()
g = sel[0]
g.addObject(tl)
App.activeDocument().recompute()

View File

@@ -0,0 +1,65 @@
#***************************************************************************
#* *
#* Copyright (c) 2014 Daniel Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD
from FreeCAD import Base
import Part
import Draft
from PySide import QtGui,QtCore
from math import pi
'''
This macro makes a list of holes for drilling from a solid
1. Select a solid object that has holes in it and run the macro
2. It only collects info on holes that are parallel to the Z axis- I don't have a 4 or 5 axis mill at the moment
3. It pulls the center of the hole bounding box and the XLength for it's diameter
4. It will place a list of the holes on the clipboard
5. Uncomment the line that starts with '#Draft.makeLine' and manipulate it, if you want to see lines down the center of each hole.
6. Manipulate the line that starts with 'holelist.append' to make the list fit your own needs- I've put the ZMax at the ZMax of the solid's bounding box
because I want to make sure that my drill tip doesn't collide with anything on the top of the part. YMMV.
'''
def findholes(obj):
facelist = []
holelist =[]
vz = Base.Vector(0,0,1)
for f in obj.Faces:
if ( round(f.ParameterRange[0], 8)==0.0 ) and ( round(f.ParameterRange[1],8) == round(pi*2, 8) ) : #eliminate flat faces
facelist.append(f)
for h in facelist:
for w in h.Wires:
for c in w.Edges:
if ( isinstance(c.Curve,Part.Line)):
v0=Base.Vector(c.Vertexes[0].X, c.Vertexes[0].Y, c.Vertexes[0].Z); v1=Base.Vector(c.Vertexes[1].X,c.Vertexes[1].Y, c.Vertexes[1].Z)
if (v1.sub(v0).x == 0) and (v1.sub(v0).y == 0):
lsp = Base.Vector(h.BoundBox.Center.x,h.BoundBox.Center.y,h.BoundBox.ZMax)
lep = Base.Vector(h.BoundBox.Center.x,h.BoundBox.Center.y,h.BoundBox.ZMin)
if obj.isInside(lsp, 0,False) or obj.isInside(lep, 0,False):
pass
else:
Draft.makeLine((h.BoundBox.Center.x,h.BoundBox.Center.y,obj.BoundBox.ZMax ),(h.BoundBox.Center.x,h.BoundBox.Center.y,h.BoundBox.ZMin ))
x =h.BoundBox.Center.x;y=h.BoundBox.Center.y;zmax=obj.BoundBox.ZMax;zmin=h.BoundBox.ZMin;diameter=h.BoundBox.XLength
holelist.append((diameter, x,y,zmax,zmin))
clipboard = QtGui.QApplication.clipboard()
clipboard.setText(str(holelist))
sel=Gui.Selection.getSelection()
obj = sel[0].Shape
findholes(obj)

View File

@@ -0,0 +1,45 @@
#***************************************************************************
#* *
#* Copyright (c) 2014 Daniel Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
'''
This macro finds the outside profile of a 3D shape. Right now it just creates wires that represent the shape,but it could be used for finding the outside profile of an object for a toolpath
'''
import FreeCADGui
import DraftGeomUtils
from FreeCAD import Vector
from PathScripts import find_outer_profile as fop
sel = FreeCADGui.Selection.getSelection()[0]
obj = sel
el = fop.edgelist(obj)
hl = fop.horizontal(el)
connected = DraftGeomUtils.findWires(hl)
goodwires = fop.openFilter(connected)
outerwires ,innerwires, same = fop.findOutsideWire(goodwires)
#get distance from outerwires Z to bottom of part
zdiff = obj.Shape.BoundBox.ZMin- outerwires.BoundBox.ZMax
outerwires.Placement.move(Vector(0,0,zdiff))
Part.show(outerwires)
zupperouter = outerwires
zupperouter.Placement.move(Vector(0,0,obj.Shape.BoundBox.ZMax))
Part.show(zupperouter)

View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' Used for CNC machine comments for Path module. Create a comment and place it in the Document tree.'''
import FreeCAD,FreeCADGui,Path,PathGui
from PathScripts import PathProject
from PySide import QtCore,QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class Comment:
def __init__(self,obj):
obj.addProperty("App::PropertyString","Comment","Path",translate("Comment","Comment or note for CNC program"))
obj.Proxy = self
mode = 2
obj.setEditorMode('Placement',mode)
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def onChanged(self,obj,prop):
pass
def execute(self,obj):
output =""
output += '('+ str(obj.Comment)+')\n'
path = Path.Path(output)
obj.Path = path
class _ViewProviderComment:
def __init__(self,vobj): #mandatory
# obj.addProperty("App::PropertyFloat","SomePropertyName","PropertyGroup","Description of this property")
vobj.Proxy = self
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-Comment.svg"
def onChanged(self,vobj,prop): #optional
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def getIcon(self):
return ":/icons/Path-Comment.svg"
class CommandPathComment:
def GetResources(self):
return {'Pixmap' : 'Path-Comment',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathComment","Comment"),
'Accel': "P, C",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathComment","Add a Comment to your CNC program")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathComment","Create a Comment in your CNC program"))
FreeCADGui.addModule("PathScripts.PathComment")
snippet = '''
import Path
import PathScripts
from PathScripts import PathUtils
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Comment")
PathScripts.PathComment.Comment(obj)
PathScripts.PathComment._ViewProviderComment(obj.ViewObject)
PathUtils.addToProject(obj)
'''
FreeCADGui.doCommand(snippet)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Comment',CommandPathComment())
FreeCAD.Console.PrintLog("Loading PathComment... done\n")

View File

@@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD,FreeCADGui,Path,PathGui
from PySide import QtCore,QtGui
"""Path Compound Extended object and FreeCAD command"""
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class ObjectCompoundExtended:
def __init__(self,obj):
obj.addProperty("App::PropertyString","Description", "Path",translate("PathCompoundExtended","An optional description of this compounded operation"))
# obj.addProperty("App::PropertySpeed", "FeedRate", "Path",translate("PathCompoundExtended","The feed rate of the paths in these compounded operations"))
# obj.addProperty("App::PropertyFloat", "SpindleSpeed", "Path",translate("PathCompoundExtended","The spindle speed, in revolutions per minute, of the tool used in these compounded operations"))
obj.addProperty("App::PropertyLength","SafeHeight", "Path",translate("PathCompoundExtended","The safe height for this operation"))
obj.addProperty("App::PropertyLength","RetractHeight","Path",translate("PathCompoundExtended","The retract height, above top surface of part, between compounded operations inside clamping area"))
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def execute(self,obj):
cmds = []
for child in obj.Group:
if child.isDerivedFrom("Path::Feature"):
cmds.extend(child.Path.Commands)
if cmds:
path = Path.Path(cmds)
obj.Path = path
class ViewProviderCompoundExtended:
def __init__(self,vobj):
vobj.Proxy = self
def attach(self,vobj):
self.Object = vobj.Object
return
def getIcon(self):
return ":/icons/Path-Compound.svg"
def __getstate__(self):
return None
def __setstate__(self,state):
return None
class CommandCompoundExtended:
def GetResources(self):
return {'Pixmap' : 'Path-Compound',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathCompoundExtended","Compound"),
'Accel': "P, C",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathCompoundExtended","Creates a Path Compound object")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathCompoundExtended","Create Compound"))
FreeCADGui.addModule("PathScripts.PathCompoundExtended")
snippet = '''
import Path
import PathScripts
from PathScripts import PathUtils
incl = []
prjexists = False
sel = FreeCADGui.Selection.getSelection()
for s in sel:
if s.isDerivedFrom("Path::Feature"):
incl.append(s)
obj = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython","Compound")
PathScripts.PathCompoundExtended.ObjectCompoundExtended(obj)
PathScripts.PathCompoundExtended.ViewProviderCompoundExtended(obj.ViewObject)
project = PathUtils.addToProject(obj)
if incl:
children = []
p = project.Group
g = obj.Group
for child in incl:
p.remove(child)
children.append(FreeCAD.ActiveDocument.getObject(child.Name))
project.Group = p
g.append(children)
obj.Group = children
'''
FreeCADGui.doCommand(snippet)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_CompoundExtended',CommandCompoundExtended())
FreeCAD.Console.PrintLog("Loading PathCompoundExtended... done\n")

View File

@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD,FreeCADGui,Path,PathGui
from PySide import QtCore,QtGui
"""Path Copy object and FreeCAD command"""
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class ObjectPathCopy:
def __init__(self,obj):
obj.addProperty("App::PropertyLink","Base","Path",translate("PathCopy","The path to be copied"))
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def execute(self,obj):
if obj.Base:
if obj.Base.Path:
obj.Path = obj.Base.Path.copy()
class ViewProviderPathCopy:
def __init__(self,vobj):
vobj.Proxy = self
def attach(self,vobj):
self.Object = vobj.Object
return
def getIcon(self):
return ":/icons/Path-Copy.svg"
def __getstate__(self):
return None
def __setstate__(self,state):
return None
class CommandPathCopy:
def GetResources(self):
return {'Pixmap' : 'Path-Copy',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathCopy","Copy"),
'Accel': "P, Y",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathCopy","Creates a linked copy of another path")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathCopy","Create Copy"))
FreeCADGui.addModule("PathScripts.PathCopy")
consolecode = '''
import Path
import PathScripts
from PathScripts import PathCopy
selGood = True
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection()
proj = selection[0].InList[0] #get the group that the selectied object is inside
if len(selection) != 1:
FreeCAD.Console.PrintError(translate("PathCopy","Please select one path object\\n"))
selGood = False
if not selection[0].isDerivedFrom("Path::Feature"):
FreeCAD.Console.PrintError(translate("PathCopy","The selected object is not a path\\n"))
selGood = False
if selGood:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", str(selection[0].Name)+'_Copy')
PathScripts.PathCopy.ObjectPathCopy(obj)
PathScripts.PathCopy.ViewProviderPathCopy(obj.ViewObject)
obj.Base = FreeCAD.ActiveDocument.getObject(selection[0].Name)
g = proj.Group
g.append(obj)
proj.Group = g
FreeCAD.ActiveDocument.recompute()
'''
FreeCADGui.doCommand(consolecode)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Copy',CommandPathCopy())
FreeCAD.Console.PrintLog("Loading PathCopy... done\n")

View File

@@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD,FreeCADGui,Path,PathGui
from PySide import QtCore,QtGui
"""Path Dressup object and FreeCAD command"""
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class ObjectDressup:
def __init__(self,obj):
obj.addProperty("App::PropertyLink","Base","Path",translate("PathDressup","The base path to modify"))
obj.addProperty("App::PropertyInteger","Position","Path",translate("PathDressup","The position of this dressup in the base path"))
obj.addProperty("Path::PropertyPath","Modification","Path",translate("PathDressup","The modification to be added"))
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def execute(self,obj):
if obj.Base:
if obj.Base.isDerivedFrom("Path::Feature"):
before = []
after = []
oldtool = None
if obj.Base.Path:
if obj.Base.Path.Commands:
# split the base path
before = obj.Base.Path.Commands[:obj.Position]
after = obj.Base.Path.Commands[obj.Position:]
# join everything
commands = before + obj.Modification.Commands + after
path = Path.Path(commands)
obj.Path = path
class ViewProviderDressup:
def __init__(self,vobj):
vobj.Proxy = self
def attach(self,vobj):
self.Object = vobj.Object
return
def claimChildren(self):
return [self.Object.Base]
def __getstate__(self):
return None
def __setstate__(self,state):
return None
class CommandPathDressup:
def GetResources(self):
return {'Pixmap' : 'Path-Dressup',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathDressup","Dress-up"),
'Accel': "P, S",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathDressup","Creates a Path Dess-up object from a selected path")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1:
FreeCAD.Console.PrintError(translate("PathDressup","Please select one path object\n"))
return
if not selection[0].isDerivedFrom("Path::Feature"):
FreeCAD.Console.PrintError(translate("PathDressup","The selected object is not a path\n"))
return
if selection[0].isDerivedFrom("Path::FeatureCompoundPython"):
FreeCAD.Console.PrintError(translate("PathDressup", "Please select a Path object"))
return
# everything ok!
FreeCAD.ActiveDocument.openTransaction(translate("PathDressup","Create Dress-up"))
FreeCADGui.addModule("PathScripts.PathDressup")
FreeCADGui.addModule("PathScripts.PathUtils")
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Dressup")')
FreeCADGui.doCommand('PathScripts.PathDressup.ObjectDressup(obj)')
FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name)
FreeCADGui.doCommand('PathScripts.PathDressup.ViewProviderDressup(obj.ViewObject)')
FreeCADGui.doCommand('PathScripts.PathUtils.addToProject(obj)')
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Dressup',CommandPathDressup())
FreeCAD.Console.PrintLog("Loading PathDressup... done\n")

View File

@@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD,FreeCADGui,Path,PathGui
from PySide import QtCore,QtGui
from PathScripts import PathUtils,PathSelection,PathProject
"""Path Drilling object and FreeCAD command"""
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class ObjectDrilling:
def __init__(self,obj):
#obj.addProperty("App::PropertyVector","StartPoint","Path",translate("PathProfile","The start position of the drilling"))
obj.addProperty("App::PropertyLinkSub","Base","Path",translate("Parent Object","The base geometry of this toolpath"))
obj.addProperty("App::PropertyVectorList","locations","Path","The drilling locations")
obj.addProperty("App::PropertyLength", "PeckDepth", "Drilling", translate("PeckDepth","Incremental Drill depth before retracting to clear chips"))
#obj.PeckDepth = (0,0,1000,1)
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Drilling", translate("Clearance Height","The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyDistance", "FinalDepth", "Drilling", translate("Final Depth","Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyDistance", "RetractHeight", "Drilling", translate("Retract Height","The height where feed starts and height during retract tool when path is finished"))
obj.addProperty("App::PropertyLength", "VertFeed", "Feed",translate("Vert Feed","Feed rate for vertical moves in Z"))
#obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed",translate("Horiz Feed","Feed rate for horizontal moves")) #not needed for drilling
obj.addProperty("App::PropertyString","Comment","Path",translate("PathProject","An optional comment for this profile"))
obj.addProperty("App::PropertyBool","Active","Path",translate("Active","Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",translate("PathProfile","The tool number in use"))
obj.ToolNumber = (0,0,1000,1)
obj.setEditorMode('ToolNumber',1) #make this read only
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def execute(self,obj):
output = "G90 G98\n"
# rapid to first hole location, with spindle still retracted:
p0 = obj.locations[0]
output += "G0 X"+str(p0.x) + " Y" + str(p0.y)+ "\n"
# move tool to clearance plane
output += "G0 Z" + str(obj.ClearanceHeight.Value) + "\n"
if obj.PeckDepth.Value > 0:
cmd = "G83"
qword = " Q"+ str(obj.PeckDepth.Value)
else:
cmd = "G81"
qword = ""
for p in obj.locations:
output += cmd + " X" + str(p.x) + " Y" + str(p.y) + " Z" + str(obj.FinalDepth.Value) + qword + " R" + str(obj.RetractHeight.Value) + " F" + str(obj.VertFeed.Value) + "\n"
output += "G80\n"
print output
path = Path.Path(output)
obj.Path = path
# tie the toolnumber to the PathLoadTool object ToolNumber
if len(obj.InList)>0: #check to see if obj is in the Project group yet
project = obj.InList[0]
tl = int(PathUtils.changeTool(obj,project))
obj.ToolNumber= tl
class _ViewProviderDrill:
def __init__(self,obj): #mandatory
# obj.addProperty("App::PropertyFloat","SomePropertyName","PropertyGroup","Description of this property")
obj.Proxy = self
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-Drilling.svg"
# def attach(self): #optional
# # this is executed on object creation and object load from file
# pass
def onChanged(self,obj,prop): #optional
# this is executed when a property of the VIEW PROVIDER changes
pass
def updateData(self,obj,prop): #optional
# this is executed when a property of the APP OBJECT changes
pass
def setEdit(self,vobj,mode): #optional
# this is executed when the object is double-clicked in the tree
pass
def unsetEdit(self,vobj,mode): #optional
# this is executed when the user cancels or terminates edit mode
pass
class CommandPathDrilling:
def GetResources(self):
return {'Pixmap' : 'Path-Drilling',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathDrilling","Drilling"),
'Accel': "P, D",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathDrilling","Creates a Path Drilling object")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
import Path
import Part
from PathScripts import PathUtils,PathDrilling,PathProject
prjexists = False
selection = FreeCADGui.Selection.getSelectionEx()
if not selection:
return
# if everything is ok, execute and register the transaction in the undo/redo stack
FreeCAD.ActiveDocument.openTransaction(translate("PathDrilling","Create Drilling"))
FreeCADGui.addModule("PathScripts.PathDrilling")
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Drilling")
PathDrilling.ObjectDrilling(obj)
myList = obj.locations
for sub in selection:
for point in sub.SubObjects:
if isinstance(point,Part.Vertex):
myList.append(FreeCAD.Vector(point.X, point.Y, point.Z))
if isinstance(point,Part.Edge):
if isinstance(point.Curve,Part.Circle):
center = point.Curve.Center
myList.append(FreeCAD.Vector(center.x,center.y,center.z))
obj.locations = myList
PathDrilling._ViewProviderDrill(obj.ViewObject)
# obj.ViewObject.Proxy = 0
obj.Active = True
project = PathUtils.addToProject(obj)
tl = PathUtils.changeTool(obj,project)
if tl:
obj.ToolNumber = tl
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Drilling',CommandPathDrilling())
FreeCAD.Console.PrintLog("Loading PathDrilling... done\n")

View File

@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' Used to create CNC machine fixture offsets such as G54,G55, etc...'''
import FreeCAD,FreeCADGui,Path,PathGui
from PathScripts import PathProject
from PySide import QtCore,QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class Fixture:
def __init__(self,obj):
obj.addProperty("App::PropertyEnumeration", "Fixture", "Fixture Parameters", translate("Fixture Offset", "Fixture Offset Number"))
obj.Fixture=['G53','G54','G55','G56','G57','G58','G59','G59.1', 'G59.2', 'G59.3', 'G59.4', 'G59.5','G59.6','G59.7', 'G59.8', 'G59.9']
obj.addProperty("App::PropertyBool","Active","Sequence Parameters",translate("Active","Make False, to prevent operation from generating code"))
obj.Proxy = self
def execute(self,obj):
fixlist = ['G53','G54','G55','G56','G57','G58','G59','G59.1', 'G59.2', 'G59.3', 'G59.4', 'G59.5','G59.6','G59.7', 'G59.8', 'G59.9']
fixture=fixlist.index(obj.Fixture)
obj.Path = Path.Path(str(obj.Fixture))
obj.Label = "Fixture"+str(fixture)
if obj.Active:
obj.Path = Path.Path(str(obj.Fixture))
obj.ViewObject.Visibility = True
else:
obj.Path = Path.Path("(inactive operation)")
obj.ViewObject.Visibility = False
class _ViewProviderFixture:
def __init__(self,vobj): #mandatory
# obj.addProperty("App::PropertyFloat","SomePropertyName","PropertyGroup","Description of this property")
vobj.Proxy = self
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-Datums.svg"
# def attach(self): #optional
# # this is executed on object creation and object load from file
# pass
def onChanged(self,vobj,prop): #optional
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def updateData(self,vobj,prop): #optional
# this is executed when a property of the APP OBJECT changes
pass
def setEdit(self,vobj,mode): #optional
# this is executed when the object is double-clicked in the tree
pass
def unsetEdit(self,vobj,mode): #optional
# this is executed when the user cancels or terminates edit mode
pass
class CommandPathFixture:
def GetResources(self):
return {'Pixmap' : 'Path-Datums',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathFixture","Fixture"),
'Accel': "P, F",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathFixture","Creates a Fixture Offset object")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathFixture","Create a Fixture Offset"))
FreeCADGui.addModule("PathScripts.PathFixture")
snippet = '''
import Path
import PathScripts
from PathScripts import PathUtils
prjexists = False
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Fixture")
PathScripts.PathFixture.Fixture(obj)
obj.Active = True
PathScripts.PathFixture._ViewProviderFixture(obj.ViewObject)
PathUtils.addToProject(obj)
'''
FreeCADGui.doCommand(snippet)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Fixture',CommandPathFixture())
FreeCAD.Console.PrintLog("Loading PathFixture... done\n")

View File

@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' Used to make GCode from FreeCAD shapes - Wires and Edges/Curves '''
import FreeCAD,FreeCADGui,Path,PathGui
from PathScripts import PathProject
from PySide import QtCore,QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class FromShape:
def __init__(self,obj):
obj.addProperty("App::PropertyLink","Base","Shape",translate("Shape Object","The base Shape of this toolpath"))
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def execute(self,obj):
pass
class _ViewProviderFromShape:
def __init__(self,vobj): #mandatory
vobj.Proxy = self
def attach(self, vobj):
self.Object = vobj.Object
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-Shape.svg"
class CommandFromShape:
def GetResources(self):
return {'Pixmap' : 'Path-Shape',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathFromShape","Gcode from a Shape"),
'Accel': "P, S",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathFromShape","Creates GCode from a FreeCAD wire/curve")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathFromShape","Create GCode from a wire/curve"))
FreeCADGui.addModule("PathScripts.PathFromShape")
consolecode = '''
import Path
import PathScripts
from PathScripts import PathFromShape
from PathScripts import PathUtils
obj = FreeCAD.activeDocument().addObject('Path::FeatureShape','PathShape')
obj.Shape= FreeCAD.activeDocument().Rectangle.Shape.copy()
PathUtils.addToProject(obj)
PathScripts.PathFromShape._ViewProviderFromShape(obj.ViewObject)
App.ActiveDocument.recompute()
'''
FreeCADGui.doCommand(consolecode)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_FromShape',CommandFromShape())
FreeCAD.Console.PrintLog("Loading PathFromShape... done\n")

View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD,FreeCADGui,Path,PathGui
from PySide import QtCore,QtGui
"""Path Hop object and FreeCAD command"""
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class ObjectHop:
def __init__(self,obj):
obj.addProperty("App::PropertyLink","NextObject","Path",translate("PathHop","The object to be reached by this hop"))
obj.addProperty("App::PropertyDistance","HopHeight","Path",translate("PathHop","The Z height of the hop"))
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def execute(self,obj):
nextpoint = FreeCAD.Vector()
if obj.NextObject:
if obj.NextObject.isDerivedFrom("Path::Feature"):
# look for the first position of the next path
for c in obj.NextObject.Path.Commands:
if c.Name in ["G0","G00","G1","G01","G2","G02","G3","G03"]:
nextpoint = c.Placement.Base
break
# absolute coords, millimeters, cancel offsets
output = "G90\nG21\nG40\n"
# go up to the given height
output += "G0 Z" + str(obj.HopHeight.Value) + "\n"
# go horizontally to the position of nextpoint
output += "G0 X" + str(nextpoint.x) + " Y" + str(nextpoint.y) + "\n"
#print output
path = Path.Path(output)
obj.Path = path
class ViewProviderPathHop:
def __init__(self,vobj):
vobj.Proxy = self
def attach(self,vobj):
self.Object = vobj.Object
return
def getIcon(self):
return ":/icons/Path-Hop.svg"
def __getstate__(self):
return None
def __setstate__(self,state):
return None
class CommandPathHop:
def GetResources(self):
return {'Pixmap' : 'Path-Hop',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathHop","Hop"),
'Accel': "P, H",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathHop","Creates a Path Hop object")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1:
FreeCAD.Console.PrintError(translate("PathHop","Please select one path object\n"))
return
if not selection[0].isDerivedFrom("Path::Feature"):
FreeCAD.Console.PrintError(translate("PathHop","The selected object is not a path\n"))
return
FreeCAD.ActiveDocument.openTransaction(translate("PathHop","Create Hop"))
FreeCADGui.addModule("PathScripts.PathHop")
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Hop")')
FreeCADGui.doCommand('PathScripts.PathHop.ObjectHop(obj)')
FreeCADGui.doCommand('PathScripts.PathHop.ViewProviderPathHop(obj.ViewObject)')
FreeCADGui.doCommand('obj.NextObject = FreeCAD.ActiveDocument.' + selection[0].Name)
FreeCADGui.doCommand('PathScripts.PathUtils.addToProject(obj)')
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Hop',CommandPathHop())
FreeCAD.Console.PrintLog("Loading PathHop... done\n")

View File

@@ -0,0 +1,290 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
'''PathKurve - Path Profile operation using libarea (created by Dan Heeks) for making simple CNC paths.
libarea, originally from HeeksCNC project must be present for this to work.'''
import FreeCAD,FreeCADGui,Path,PathGui
from PathScripts import PathProject,PathUtils,PathKurveUtils,PathSelection
from PySide import QtCore,QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class PathProfile:
def __init__(self,obj):
obj.addProperty("App::PropertyLinkSub","Base","Path",translate("Parent Object","The base geometry of this toolpath"))
obj.addProperty("App::PropertyLinkSub","StartPoint", "Path", translate("Start Point","Linked Start Point of Profile"))
obj.addProperty("App::PropertyLinkSub","EndPoint", "Path", translate("End Point","Linked End Point of Profile"))
obj.addProperty("App::PropertyBool","Active","Path",translate("Active","Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyString","Comment","Path",translate("Comment","An optional comment for this profile"))
obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",translate("PathProfile","The tool number in use"))
obj.ToolNumber = (0,0,1000,1)
obj.setEditorMode('ToolNumber',1) #make this read only
#Depth Properties
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", translate("Clearance Height","The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyLength", "StepDown", "Depth", translate("StepDown","Incremental Step Down of Tool"))
# obj.addProperty("App::PropertyBool","UseStartDepth","Depth",translate("Use Start Depth","make True, if manually specifying a Start Start Depth"))
obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", translate("Start Depth","Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", translate("Final Depth","Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyDistance", "RetractHeight", "Depth", translate("Retract Height","The height desired to retract tool when path is finished"))
#Feed Properties
obj.addProperty("App::PropertyLength", "VertFeed", "Feed",translate("Vert Feed","Feed rate (in units per minute) for vertical moves in Z"))
obj.addProperty("App::PropertyLength", "HorizFeed", "Feed",translate("Horiz Feed","Feed rate (in units per minute) for horizontal moves"))
#Profile Properties
obj.addProperty("App::PropertyEnumeration", "Side", "Profile", translate("Side","Side of edge that tool should cut"))
obj.Side = ['left','right','on'] #side of profile that cutter is on in relation to direction of profile
obj.addProperty("App::PropertyEnumeration", "Direction", "Profile",translate("Direction", "The direction that the toolpath should go around the part ClockWise CW or CounterClockWise CCW"))
obj.Direction = ['CW','CCW'] #this is the direction that the profile runs
obj.addProperty("App::PropertyBool","UseComp","Profile",translate("Use Cutter Comp","make True, if using Cutter Radius Compensation"))
obj.addProperty("App::PropertyIntegerList","Edgelist","Profile",translate("Edge List", "List of edges selected"))
obj.addProperty("App::PropertyDistance", "OffsetExtra", "Profile",translate("OffsetExtra","Extra value to stay away from final profile- good for roughing toolpath"))
# obj.addProperty("App::PropertyLength", "SegLen", "Profile",translate("Seg Len","Tesselation value for tool paths made from beziers, bsplines, and ellipses"))
# #Start Point Properties
obj.addProperty("App::PropertyString","StartPtName","Profile",translate("Start Point","The name of the start point of this path"))
obj.addProperty("App::PropertyBool","UseStartPt","Profile",translate("Use Start Point","Make True, if specifying a Start Point"))
# obj.addProperty("App::PropertyLength", "ExtendAtStart", "Profile", translate("extend at start", "extra length of tool path before start of part edge"))
# obj.addProperty("App::PropertyLength", "LeadInLineLen", "Profile", translate("lead in length","length of straight segment of toolpath that comes in at angle to first part edge"))
# #End Point Properties
obj.addProperty("App::PropertyString","EndPtName","Profile",translate("End Point","The name of the end point of this path"))
obj.addProperty("App::PropertyBool","UseEndPt","Profile",translate("Use End Point","Make True, if specifying an End Point"))
# obj.addProperty("App::PropertyLength", "ExtendAtEnd", "Profile", translate("extend at end","extra length of tool path after end of part edge"))
# obj.addProperty("App::PropertyLength", "LeadOutLineLen", "Profile", translate("lead_out_line_len","length of straight segment of toolpath that comes in at angle to last edge selected"))
# obj.addProperty("App::PropertyDistance", "RollRadius", "Profile", translate("Roll Radius","Radius at start and end"))
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def execute(self,obj):
if obj.Base:
# tie the toolnumber to the PathLoadTool object ToolNumber
if len(obj.InList)>0: #check to see if obj is in the Project group yet
project = obj.InList[0]
tl = int(PathUtils.changeTool(obj,project))
obj.ToolNumber= tl
tool = PathUtils.getTool(obj,obj.ToolNumber)
if tool:
self.radius = tool.Diameter/2
else:
# temporary value,in case we don't have any tools defined already
self.radius = 0.25
# self.radius = 0.25
self.clearance = obj.ClearanceHeight.Value
self.step_down=obj.StepDown.Value
self.start_depth=obj.StartDepth.Value
self.final_depth=obj.FinalDepth.Value
self.rapid_safety_space=obj.RetractHeight.Value
self.side=obj.Side
self.offset_extra=obj.OffsetExtra.Value
self.use_CRC=obj.UseComp
self.vf=obj.VertFeed.Value
self.hf=obj.HorizFeed.Value
edgelist = []
if obj.StartPtName and obj.UseStartPt:
self.startpt = FreeCAD.ActiveDocument.getObject(obj.StartPtName).Shape
else:
self.startpt = None
if obj.EndPtName and obj.UseEndPt:
self.endpt = FreeCAD.ActiveDocument.getObject(obj.EndPtName).Shape
else:
self.endpt = None
for e in obj.Edgelist:
edgelist.append(FreeCAD.ActiveDocument.getObject(obj.Base[0].Name).Shape.Edges[e-1])
output=PathKurveUtils.makePath(edgelist,self.side,self.radius,self.vf,self.hf,self.offset_extra, \
self.rapid_safety_space,self.clearance,self.start_depth,self.step_down, \
self.final_depth,self.use_CRC,obj.Direction,self.startpt,self.endpt)
if obj.Active:
path = Path.Path(output)
obj.Path = path
obj.ViewObject.Visibility = True
else:
path = Path.Path("(inactive operation)")
obj.Path = path
obj.ViewObject.Visibility = False
class _ViewProviderKurve:
def __init__(self,vobj): #mandatory
vobj.Proxy = self
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-Kurve.svg"
class CommandPathKurve:
def GetResources(self):
return {'Pixmap' : 'Path-Kurve',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathProfile","Profile"),
'Accel': "P, P",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathProfile","Creates a Path Profile object from selected edges, using libarea for offset algorithm")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathKurve","Create a Profile operation using libarea"))
FreeCADGui.addModule("PathScripts.PathKurve")
snippet = '''
import Path
from PathScripts import PathSelection,PathProject,PathUtils
import area
def profileop():
selection = PathSelection.multiSelect()
if not selection:
FreeCAD.Console.PrintError('please select some edges\\n')
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Profile")
PathScripts.PathKurve.PathProfile(obj)
obj.Active = True
PathScripts.PathKurve._ViewProviderKurve(obj.ViewObject)
obj.Base = (FreeCAD.ActiveDocument.getObject(selection['objname']))
elist = []
for e in selection['edgenames']:
elist.append(eval(e.lstrip('Edge')))
obj.Edgelist = elist
if selection['pointnames']:
FreeCAD.Console.PrintMessage('There are points selected.\\n')
if len(selection['pointnames'])>1:
obj.StartPtName = selection['pointnames'][0]
obj.StartPoint= FreeCAD.ActiveDocument.getObject(obj.StartPtName)
obj.EndPtName = selection['pointnames'][-1]
obj.EndPoint=FreeCAD.ActiveDocument.getObject(obj.EndPtName)
else:
obj.StartPtName = selection['pointnames'][0]
obj.StartPoint= FreeCAD.ActiveDocument.getObject(obj.StartPtName)
obj.ClearanceHeight = 2.0
obj.StepDown = 1.0
obj.StartDepth=0.0
obj.FinalDepth=-1.0
obj.RetractHeight = 5.0
obj.Side = 'left'
obj.OffsetExtra = 0.0
if selection['clockwise']:
obj.Direction = 'CW'
else:
obj.Direction = 'CCW'
obj.UseComp = False
project = PathUtils.addToProject(obj)
tl = PathUtils.changeTool(obj,project)
if tl:
obj.ToolNumber = tl
from PathScripts import PathProject,PathUtils,PathKurve, PathKurveUtils,PathSelection
try:
import area
except:
FreeCAD.Console.PrintError('libarea needs to be installed for this command to work\\n')
profileop()
'''
FreeCADGui.doCommand(snippet)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Kurve',CommandPathKurve())
FreeCAD.Console.PrintLog("Loading PathKurve... done\n")

View File

@@ -0,0 +1,258 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
'''PathKurveUtils - functions needed for using libarea (created by Dan Heeks) for making simple CNC profile paths '''
import FreeCAD
from FreeCAD import Vector
import FreeCADGui as Gui
import Part
import DraftGeomUtils,DraftVecUtils
from DraftGeomUtils import geomType
import math
import area
import Path
from PathScripts import PathUtils
import PathSelection
def makeAreaVertex(seg):
if seg.ShapeType =='Edge':
if isinstance(seg.Curve,Part.Circle):
segtype = int(seg.Curve.Axis.z) #1=ccw arc,-1=cw arc
vertex = area.Vertex(segtype, area.Point(seg.valueAt(seg.LastParameter)[0],seg.valueAt(seg.LastParameter)[1]), area.Point(seg.Curve.Center.x, seg.Curve.Center.y))
elif isinstance(seg.Curve,Part.Line):
point1 = seg.valueAt(seg.FirstParameter)[0],seg.valueAt(seg.FirstParameter)[1]
point2 = seg.valueAt(seg.LastParameter)[0],seg.valueAt(seg.LastParameter)[1]
segtype = 0 #0=line
vertex = area.Point(seg.valueAt(seg.LastParameter)[0],seg.valueAt(seg.LastParameter)[1])
else:
pass
return vertex
def makeAreaCurve(edges,direction,startpt=None,endpt=None):
curveobj = area.Curve()
cleanededges = PathUtils.cleanedges(edges, 0.01)
#sort the edges
vlist,edgestart,common = PathSelection.Sort2Edges([cleanededges[0],cleanededges[1]])
if cleanededges[0].valueAt(cleanededges[0].FirstParameter)<>edgestart:
firstedge=PathUtils.reverseEdge(cleanededges[0])
else:
firstedge=cleanededges[0]
edgelist=[]
edgelist.append(firstedge)
#get start and end points of each edge aligned
for e in cleanededges[1:]:
if DraftVecUtils.equals(common,e.valueAt(e.FirstParameter)):
edgelist.append(e)
common= e.valueAt(e.LastParameter)
else:
newedge = PathUtils.reverseEdge(e)
common= newedge.valueAt(newedge.LastParameter)
edgelist.append(newedge)
curveobj.append(area.Point(edgestart.x,edgestart.y))
# seglist =[]
# if direction=='CW':
# edgelist.reverse()
# for e in edgelist:
# seglist.append(PathUtils.reverseEdge(e)) #swap end points on every segment
# else:
# for e in edgelist:
# seglist.append(e)
for s in edgelist:
curveobj.append(makeAreaVertex(s))
if startpt:
# future nearest point code yet to be worked out -fixme
# v1 = Vector(startpt.X,startpt.Y,startpt.Z)
# perppoint1 = DraftGeomUtils.findPerpendicular(v1,firstedge)
# perppoint1 = DraftGeomUtils.findDistance(v1,firstedge)
# if perppoint1:
# curveobj.ChangeStart(area.Point(perppoint1[0].x,perppoint1[0].y))
# else:
# curveobj.ChangeStart(area.Point(startpt.X,startpt.Y))
curveobj.ChangeStart(area.Point(startpt.X,startpt.Y))
if endpt:
# future nearest point code yet to be worked out -fixme
# v2 = Vector(endpt.X,endpt.Y,endpt.Z)
# perppoint2 = DraftGeomUtils.findPerpendicular(v2,lastedge)
# if perppoint2:
# curveobj.ChangeEnd(area.Point(perppoint2[0].x,perppoint2[0].y))
# else:
# curveobj.ChangeEnd(area.Point(endpt.X,endpt.Y))
curveobj.ChangeEnd(area.Point(endpt.X,endpt.Y))
if direction == 'CW':
curveobj.Reverse()
return curveobj
# profile command,
# side_of_line should be 'left' or 'right' or 'on'
def profile(curve,side_of_line,radius=1.0,vertfeed=0.0,horizfeed=0.0,offset_extra=0.0, \
rapid_safety_space=None,clearance=None,start_depth=None,stepdown=None, \
final_depth=None,use_CRC=False, \
roll_on=None,roll_off=None,roll_start=False,roll_end=True,roll_radius=None, \
roll_start_pt=None,roll_end_pt=None):
output = ""
offset_curve = area.Curve(curve)
if offset_curve.getNumVertices() <= 1:
raise Exception,"Sketch has no elements!"
if side_of_line == "on":
use_CRC =False
elif (side_of_line == "left") or (side_of_line == "right"):
# get tool radius plus little bit of extra offset, if needed to clean up profile a little more
offset = radius + offset_extra
if side_of_line == 'left':
offset_curve.Offset(offset)
else:
offset_curve.Offset(-offset)
if offset_curve == False:
raise Exception, "couldn't offset kurve " + str(offset_curve)
else:
raise Exception,"Side must be 'left','right', or 'on'"
#===============================================================================
# #roll_on roll_off section
# roll_on_curve = area.Curve()
# if offset_curve.getNumVertices() <= 1: return
# first_span = offset_curve.GetFirstSpan()
# if roll_on == None:
# rollstart = first_span.p
# elif roll_on == 'auto':
# if roll_radius < 0.0000000001:
# rollstart = first_span.p
# v = first_span.GetVector(0.0)
# if direction == 'right':
# off_v = area.Point(v.y, -v.x)
# else:
# off_v = area.Point(-v.y, v.x)
# rollstart = first_span.p + off_v * roll_radius
# else:
# rollstart = roll_on
#
# rvertex = area.Vertex(first_span.p)
#
# if first_span.p == rollstart:
# rvertex.type = 0
# else:
# v = first_span.GetVector(0.0) # get start direction
# rvertex.c, rvertex.type = area.TangentialArc(first_span.p, rollstart, -v)
# rvertex.type = -rvertex.type # because TangentialArc was used in reverse
# # add a start roll on point
# roll_on_curve.append(rollstart)
#
# # add the roll on arc
# roll_on_curve.append(rvertex)
# #end of roll_on roll_off section
#===============================================================================
# do multiple depths
layer_count = int((start_depth - final_depth) / stepdown)
if layer_count * stepdown + 0.00001 < start_depth - final_depth:
layer_count += 1
current_start_depth = start_depth
prev_depth = start_depth
for i in range(1, layer_count+1):
if i == layer_count:
depth = final_depth
else:
depth = start_depth - i * stepdown
mat_depth = prev_depth
start_z = mat_depth
#first move
output += "G0 X"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x))+\
" Y"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y))+\
" Z"+str(PathUtils.fmt(mat_depth + rapid_safety_space))+"\n"
# feed down to depth
mat_depth = depth
if start_z > mat_depth:
mat_depth = start_z
# feed down in Z
output += "G1 X"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x))+\
" Y"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y))+" Z"+str(PathUtils.fmt(depth))+\
" F"+str(PathUtils.fmt(vertfeed))+"\n"
if use_CRC:
if side_of_line == 'left':
output +="G41"+"\n"
else:
output +="G42"+"\n"
# cut the main kurve
current_perim = 0.0
lastx=offset_curve.GetFirstSpan().p.x
lasty=offset_curve.GetFirstSpan().p.y
for span in offset_curve.GetSpans():
current_perim += span.Length()
if span.v.type == 0:#line
#feed(span.v.p.x, span.v.p.y, ez)
output +="G1 X"+str(PathUtils.fmt(span.v.p.x))+" Y"+str(PathUtils.fmt(span.v.p.y))+\
" Z"+str(PathUtils.fmt(depth))+" F"+str(PathUtils.fmt(horizfeed))+"\n"
lastx = span.v.p.x
lasty = span.v.p.y
elif (span.v.type == 1) or (span.v.type == -1):
if span.v.type == 1:# anti-clockwise arc
command = 'G3'
elif span.v.type == -1:#clockwise arc
command = 'G2'
arc_I= span.v.c.x-lastx
arc_J= span.v.c.y-lasty
output +=command +"X"+str(PathUtils.fmt(span.v.p.x))+" Y"+ str(PathUtils.fmt(span.v.p.y))#+" Z"+ str(PathUtils.fmt(depth))
output +=" I"+str(PathUtils.fmt(arc_I))+ " J"+str(PathUtils.fmt(arc_J))+" F"+str(PathUtils.fmt(horizfeed))+'\n'#" K"+str(PathUtils.fmt(depth)) +"\n"
lastx = span.v.p.x
lasty = span.v.p.y
else:
raise Exception, "valid geometry identifier needed"
if use_CRC:
#end_CRC()
output +="G40"+"\n"
# rapid up to the clearance height
output +="G0 Z"+str(PathUtils.fmt(clearance))+"\n"
del offset_curve
return output
def makePath(edges,side,radius,vertfeed,horizfeed,offset_extra,rapid_safety_space,clearance,start_depth,step_down,final_depth,use_CRC,direction,startpt=None,endpt=None):
curve = makeAreaCurve(edges,direction,startpt, endpt)
if direction == 'CW':
curve.Reverse()
path = profile(curve,side,radius,vertfeed,horizfeed,offset_extra,rapid_safety_space,clearance,start_depth,step_down,final_depth,use_CRC)
del curve
return path

View File

@@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' Used for CNC machine to load cutting Tool ie M6T3'''
import FreeCAD,FreeCADGui,Path,PathGui
import PathScripts, PathUtils
from PathScripts import PathProject
from PySide import QtCore,QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class LoadTool:
def __init__(self,obj):
obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber","Tool", translate( "Tool Number", "The active tool"))
obj.ToolNumber = (0,0,10000,1)
obj.addProperty("App::PropertyFloat", "SpindleSpeed", "Tool", translate("Spindle Speed","The speed of the cutting spindle in RPM"))
obj.addProperty("App::PropertyEnumeration", "SpindleDir", "Tool", translate("Spindle Dir","Direction of spindle rotation"))
obj.SpindleDir = ['Forward','Reverse']
obj.Proxy = self
mode = 2
obj.setEditorMode('Placement',mode)
def execute(self,obj):
commands = ""
commands = 'M6T'+str(obj.ToolNumber)+'\n'
if obj.SpindleDir =='Forward':
commands +='M3S'+str(obj.SpindleSpeed)+'\n'
else:
commands +='M4S'+str(obj.SpindleSpeed)+'\n'
obj.Path = Path.Path(commands)
obj.Label = "Tool"+str(obj.ToolNumber)
def onChanged(self,obj,prop):
mode = 2
obj.setEditorMode('Placement',mode)
if prop == "ToolNumber":
proj = PathUtils.findProj()
for g in proj.Group:
if not(isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool)):
g.touch()
class _ViewProviderLoadTool:
def __init__(self,vobj): #mandatory
vobj.Proxy = self
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-LoadTool.svg"
def onChanged(self,vobj,prop): #optional
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def updateData(self,vobj,prop): #optional
# this is executed when a property of the APP OBJECT changes
pass
def setEdit(self,vobj,mode): #optional
# this is executed when the object is double-clicked in the tree
pass
def unsetEdit(self,vobj,mode): #optional
# this is executed when the user cancels or terminates edit mode
pass
class CommandPathLoadTool:
def GetResources(self):
return {'Pixmap' : 'Path-LoadTool',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathLoadTool","Tool Number to Load"),
'Accel': "P, T",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathLoadTool","Tool Number to Load")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("Current Tool","Tool Number to Load"))
FreeCADGui.addModule("PathScripts.PathLoadTool")
snippet = '''
import Path
import PathScripts
from PathScripts import PathProject, PathUtils
prjexists = False
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Tool")
PathScripts.PathLoadTool.LoadTool(obj)
PathScripts.PathLoadTool._ViewProviderLoadTool(obj.ViewObject)
PathUtils.addToProject(obj)
'''
FreeCADGui.doCommand(snippet)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_LoadTool', CommandPathLoadTool())
FreeCAD.Console.PrintLog("Loading PathLoadTool... done\n")

View File

@@ -0,0 +1,235 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' A CNC machine object to define how code is posted '''
import FreeCAD,FreeCADGui,Path,PathGui
import PathScripts
from PathScripts import PathProject, PathUtils
from PySide import QtCore,QtGui
import os, sys
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class Machine:
def __init__(self,obj):
obj.addProperty("App::PropertyString", "MachineName","Base",translate("Machine Name","Name of the Machine that will use the CNC program"))
obj.addProperty("App::PropertyFile", "PostProcessor", "CodeOutput", translate("Post Processor","Select the Post Processor file for this machine"))
obj.addProperty("App::PropertyEnumeration", "MachineUnits","CodeOutput", translate( "Machine Units", "Units that the machine works in, ie Metric or Inch"))
obj.MachineUnits=['Metric', 'Inch']
obj.addProperty("Path::PropertyTooltable","Tooltable", "Base",translate("Tool Table","The tooltable used for this CNC program"))
obj.addProperty("App::PropertyDistance", "X_Max", "Limits", translate("X Maximum Limit","The Maximum distance in X the machine can travel"))
obj.addProperty("App::PropertyDistance", "Y_Max", "Limits", translate("Y Maximum Limit","The Maximum distance in X the machine can travel"))
obj.addProperty("App::PropertyDistance", "Z_Max", "Limits", translate("Y Maximum Limit","The Maximum distance in X the machine can travel"))
obj.addProperty("App::PropertyDistance", "X_Min", "Limits", translate("X Minimum Limit","The Minimum distance in X the machine can travel"))
obj.addProperty("App::PropertyDistance", "Y_Min", "Limits", translate("Y Minimum Limit","The Minimum distance in X the machine can travel"))
obj.addProperty("App::PropertyDistance", "Z_Min", "Limits", translate("Y Minimum Limit","The Minimum distance in X the machine can travel"))
obj.addProperty("App::PropertyDistance", "X", "HomePosition", translate("X Home Position","Home position of machine, in X (mainly for visualization)"))
obj.addProperty("App::PropertyDistance", "Y", "HomePosition", translate("Y Home Position","Home position of machine, in Y (mainly for visualization)"))
obj.addProperty("App::PropertyDistance", "Z", "HomePosition", translate("Z Home Position","Home position of machine, in Z (mainly for visualization)"))
obj.Proxy = self
mode = 2
obj.setEditorMode('Placement',mode)
def execute(self,obj):
obj.Label = "Machine_"+str(obj.MachineName)
gcode = 'G0 X'+str(obj.X.Value)+' Y'+str(obj.Y.Value)+' Z'+str(obj.Z.Value) #need to filter this path out in post- only for visualization
obj.Path = Path.Path(gcode)
def onChanged(self,obj,prop):
mode = 2
obj.setEditorMode('Placement',mode)
if prop == "PostProcessor":
sys.path.append(os.path.split(obj.PostProcessor)[0])
lessextn = os.path.splitext(obj.PostProcessor)[0]
postname = os.path.split(lessextn)[1]
exec "import %s as current_post" % postname
if hasattr (current_post, "UNITS"):
if current_post.UNITS == "G21":
obj.MachineUnits = "Metric"
else:
obj.MachineUnits = "Inch"
if hasattr (current_post, "MACHINE_NAME"): obj.MachineName = current_post.MACHINE_NAME
if hasattr (current_post, "CORNER_MAX"):
obj.X_Max = current_post.CORNER_MAX['x']
obj.Y_Max = current_post.CORNER_MAX['y']
obj.Z_Max = current_post.CORNER_MAX['z']
if hasattr (current_post, "CORNER_MIN"):
obj.X_Min = current_post.CORNER_MIN['x']
obj.Y_Min = current_post.CORNER_MIN['y']
obj.Z_Min = current_post.CORNER_MIN['z']
if prop == "Tooltable":
proj = PathUtils.findProj()
for g in proj.Group:
if not(isinstance(g.Proxy, PathScripts.PathMachine.Machine)):
g.touch()
class _ViewProviderMachine:
def __init__(self,vobj):
vobj.Proxy = self
vobj.addProperty("App::PropertyBool","ShowLimits","Path",translate("ShowMinMaxTravel","Switch the machine max and minimum travel bounding box on/off"))
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',0)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-Machine.svg"
def attach(self,vobj):
from pivy import coin
self.extentsBox = coin.SoSeparator()
vobj.RootNode.addChild(self.extentsBox)
def onChanged(self,vobj,prop):
if prop == "ShowLimits":
self.extentsBox.removeAllChildren()
if vobj.ShowLimits and hasattr(vobj,"Object"):
from pivy import coin
parent = coin.SoType.fromName("SoSkipBoundingGroup").createInstance()
self.extentsBox.addChild(parent)
# set pattern
pattern = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Part").GetInt("GridLinePattern",0x0f0f)
defStyle = coin.SoDrawStyle()
defStyle.lineWidth = 1
defStyle.linePattern = pattern
parent.addChild(defStyle)
# set color
c = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path").GetUnsigned("DefaultExtentsColor",3418866943)
r = float((c>>24)&0xFF)/255.0
g = float((c>>16)&0xFF)/255.0
b = float((c>>8)&0xFF)/255.0
color = coin.SoBaseColor()
parent.addChild(color)
# set boundbox
extents = coin.SoType.fromName("SoFCBoundingBox").createInstance()
extents.coordsOn.setValue(False)
extents.dimensionsOn.setValue(False)
XMax, YMax, ZMax =vobj.Object.X_Max.Value , vobj.Object.Y_Max.Value , vobj.Object.Z_Max.Value
XMin, YMin, ZMin =vobj.Object.X_Min.Value , vobj.Object.Y_Min.Value , vobj.Object.Z_Min.Value
UnitParams = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units")
extents.minBounds.setValue(XMax, YMax, ZMax)
extents.maxBounds.setValue(XMin, YMin, ZMin)
parent.addChild(extents)
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',0)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
def updateData(self,vobj,prop): #optional
# this is executed when a property of the APP OBJECT changes
pass
def setEdit(self,vobj,mode): #optional
# this is executed when the object is double-clicked in the tree
pass
def unsetEdit(self,vobj,mode): #optional
# this is executed when the user cancels or terminates edit mode
pass
class CommandPathMachine:
def GetResources(self):
return {'Pixmap' : 'Path-Machine',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathMachine","Machine Object"),
'Accel': "P, M",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathMachine","Create a Machine object")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathMachine","Create a Machine object"))
CommandPathMachine.Create()
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
@staticmethod
def Create():
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Machine")
Machine(obj)
_ViewProviderMachine(obj.ViewObject)
PathUtils.addToProject(obj)
UnitParams = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units")
if UnitParams.GetInt('UserSchema') == 0:
obj.MachineUnits = 'Metric'
#metric mode
else:
obj.MachineUnits = 'Inch'
obj.ViewObject.ShowFirstRapid = False
return obj
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Machine',CommandPathMachine())
FreeCAD.Console.PrintLog("Loading PathMachine... done\n")

View File

@@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' Used for CNC machine plane selection G17,G18,G19 '''
import FreeCAD,FreeCADGui,Path,PathGui
from PathScripts import PathProject
from PySide import QtCore,QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class Plane:
def __init__(self,obj):
obj.addProperty("App::PropertyEnumeration", "SelectionPlane","Plane", translate( "Selection Plane", "Orientation plane of CNC path"))
obj.SelectionPlane=['XY', 'XZ', 'YZ']
obj.addProperty("App::PropertyBool","Active","Sequence Parameters",translate("Active","Make False, to prevent operation from generating code"))
obj.Proxy = self
def execute(self,obj):
clonelist = ['XY', 'XZ', 'YZ']
cindx = clonelist.index(str(obj.SelectionPlane))
pathlist = ['G17', 'G18', 'G19']
# obj.Path = Path.Path(pathlist[cindx])
labelindx = clonelist.index(obj.SelectionPlane)+1
obj.Label = "Plane"+str(labelindx)
if obj.Active:
obj.Path = Path.Path(pathlist[cindx])
obj.ViewObject.Visibility = True
else:
obj.Path = Path.Path("(inactive operation)")
obj.ViewObject.Visibility = False
class _ViewProviderPlane:
def __init__(self,vobj): #mandatory
vobj.Proxy = self
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-Plane.svg"
def onChanged(self,vobj,prop): #optional
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def updateData(self,vobj,prop): #optional
# this is executed when a property of the APP OBJECT changes
pass
def setEdit(self,vobj,mode): #optional
# this is executed when the object is double-clicked in the tree
pass
def unsetEdit(self,vobj,mode): #optional
# this is executed when the user cancels or terminates edit mode
pass
class CommandPathPlane:
def GetResources(self):
return {'Pixmap' : 'Path-Plane',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathPlane","Selection Plane"),
'Accel': "P, P",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathPlane","Create a Selection Plane object")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathPlane","Create a Selection Plane object"))
FreeCADGui.addModule("PathScripts.PathPlane")
snippet = '''
import Path
import PathScripts
from PathScripts import PathUtils
prjexists = False
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Plane")
PathScripts.PathPlane.Plane(obj)
obj.Active = True
PathScripts.PathPlane._ViewProviderPlane(obj.ViewObject)
PathUtils.addToProject(obj)
'''
FreeCADGui.doCommand(snippet)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Plane',CommandPathPlane())
FreeCAD.Console.PrintLog("Loading PathPlane... done\n")

View File

@@ -0,0 +1,325 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD,FreeCADGui,Path,PathGui
from PySide import QtCore,QtGui
from PathScripts import PathUtils
"""Path Pocket object and FreeCAD command"""
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
def frange(start, stop, step, finish):
x = []
curdepth = start
if step == 0:
return x
# do the base cuts until finishing round
while curdepth >= stop + step + finish:
curdepth = curdepth - step
if curdepth <= stop + finish:
curdepth = stop + finish
x.append(curdepth)
# we might have to do a last pass or else finish round might be too far away
if curdepth - stop > finish:
x.append(stop + finish)
# do the the finishing round
if curdepth >= stop:
curdepth = stop
x.append(curdepth)
# Why this?
# if start >= stop:
# start = stop
# x.append (start)
return x
class ObjectPocket:
def __init__(self,obj):
obj.addProperty("App::PropertyLinkSub","Base","Path",translate("PathProject","The base geometry of this object"))
obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",
translate("PathProfile","The tool number in use"))
obj.ToolNumber = (0, 0, 1000, 0)
obj.addProperty("App::PropertyFloat", "ClearanceHeight", "Pocket", translate("PathProject","The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyFloatConstraint", "StepDown", "Pocket", translate("PathProject","Incremental Step Down of Tool"))
obj.StepDown = (0.0, 0.0, 100.0, 1.0)
obj.addProperty("App::PropertyFloat", "StartDepth", "Pocket", translate("PathProject","Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyBool","UseStartDepth","Pocket",translate("PathProject","make True, if manually specifying a Start Start Depth"))
obj.addProperty("App::PropertyFloat", "FinalDepth", "Pocket", translate("PathProject","Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyFloat", "RetractHeight", "Pocket", translate("PathProject","The height desired to retract tool when path is finished"))
obj.addProperty("App::PropertyEnumeration", "CutMode", "Pocket",translate("PathProject", "The direction that the toolpath should go around the part ClockWise CW or CounterClockWise CCW"))
obj.CutMode = ['Climb','Conventional']
obj.addProperty("App::PropertyFloat", "MaterialAllowance", "Pocket", translate("PathProject","Amount of material to leave"))
obj.addProperty("App::PropertyFloat", "FinishDepth", "Pocket", translate("PathProject","Maximum material removed on final pass."))
obj.addProperty("App::PropertyEnumeration", "StartAt", "Pocket",translate("PathProject", "Start pocketing at center or boundary"))
obj.StartAt = ['Center', 'Edge']
obj.addProperty("App::PropertyFloatConstraint", "VertFeed", "Feed",translate("Vert Feed","Feed rate for vertical moves in Z"))
obj.VertFeed = (0.0, 0.0, 100000.0, 1.0)
obj.addProperty("App::PropertyFloatConstraint", "HorizFeed", "Feed",translate("Horiz Feed","Feed rate for horizontal moves"))
obj.HorizFeed = (0.0, 0.0, 100000.0, 1.0)
obj.addProperty("App::PropertyBool","Active","Path",translate("PathProject","Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyString","Comment","Path",translate("PathProject","An optional comment for this profile"))
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def getStock(self,obj):
"retrieves a stock object from hosting project if any"
for o in obj.InList:
if hasattr(o,"Group"):
for g in o.Group:
if hasattr(g,"Height_Allowance"):
return o
# not found? search one level up
for o in obj.InList:
return self.getStock(o)
return None
def execute(self,obj):
if obj.Base:
tool = PathUtils.getLastTool(obj)
if tool:
radius = tool.Diameter/2
if radius < 0:# safe guard
radius -= radius
else:
# temporary value, to be taken from the properties later on
radius = 1
import Part, DraftGeomUtils
if "Face" in obj.Base[1][0]:
shape = getattr(obj.Base[0].Shape,obj.Base[1][0])
else:
edges = [getattr(obj.Base[0].Shape,sub) for sub in obj.Base[1]]
shape = Part.Wire(edges)
print len(edges)
# absolute coords, millimeters, cancel offsets
output = "G90\nG21\nG40\n"
# save tool
if obj.ToolNumber > 0 and tool.ToolNumber != obj.ToolNumber:
output += "M06 T" + str(tool.ToolNumber) + "\n"
# build offsets
offsets = []
nextradius = radius
result = DraftGeomUtils.pocket2d(shape,nextradius)
while result:
offsets.extend(result)
nextradius += radius
result = DraftGeomUtils.pocket2d(shape,nextradius)
# first move will be rapid, subsequent will be at feed rate
first = True
startPoint = None
fastZPos = max(obj.StartDepth + 2, obj.RetractHeight)
# revert the list so we start with the outer wires
if obj.StartAt != 'Edge':
offsets.reverse()
# print "startDepth: " + str(obj.StartDepth)
# print "finalDepth: " + str(obj.FinalDepth)
# print "stepDown: " + str(obj.StepDown)
# print "finishDepth" + str(obj.FinishDepth)
# print "offsets:", len(offsets)
def prnt(vlu): return str(round(vlu, 4))
for vpos in frange(obj.StartDepth, obj.FinalDepth, obj.StepDown, obj.FinishDepth):
# print "vpos: " + str(vpos)
# loop over successive wires
for currentWire in offsets:
# print "new line (offset)"
last = None
for edge in currentWire.Edges:
# print "new edge"
if not last:
# we set the base GO to our fast move to our starting pos
if first:
startPoint = edge.Vertexes[0].Point
output += "G0 X" + prnt(startPoint.x) + " Y" + prnt(startPoint.y) +\
" Z" + prnt(fastZPos) + "\n"
first = False
#then move slow down to our starting point for our profile
last = edge.Vertexes[0].Point
output += "G1 X" + prnt(last.x) + " Y" + prnt(last.y) + " Z" + prnt(vpos) + "\n"
if isinstance(edge.Curve,Part.Circle):
point = edge.Vertexes[-1].Point
if point == last: # edges can come flipped
point = edge.Vertexes[0].Point
# print "flipped"
center = edge.Curve.Center
relcenter = center.sub(last)
v1 = last.sub(center)
v2 = point.sub(center)
if v1.cross(v2).z < 0:
output += "G2"
else:
output += "G3"
output += " X" + prnt(point.x) + " Y" + prnt(point.y) + " Z" + prnt(vpos)
output += " I" + prnt(relcenter.x) + " J" +prnt(relcenter.y) + " K" + prnt(relcenter.z)
output += "\n"
last = point
else:
point = edge.Vertexes[-1].Point
if point == last: # edges can come flipped
point = edge.Vertexes[0].Point
output += "G1 X" + prnt(point.x) + " Y" + prnt(point.y) + " Z" + prnt(vpos) + "\n"
last = point
#move back up
output += "G1 Z" + prnt(fastZPos) + "\n"
# print output
# path = Path.Path(output)
# obj.Path = path
if obj.Active:
path = Path.Path(output)
obj.Path = path
obj.ViewObject.Visibility = True
else:
path = Path.Path("(inactive operation)")
obj.Path = path
obj.ViewObject.Visibility = False
class ViewProviderPocket:
def __init__(self,vobj):
vobj.Proxy = self
def attach(self,vobj):
self.Object = vobj.Object
return
def getIcon(self):
return ":/icons/Path-Pocket.svg"
def __getstate__(self):
return None
def __setstate__(self,state):
return None
class CommandPathPocket:
def GetResources(self):
return {'Pixmap' : 'Path-Pocket',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathPocket","Pocket"),
'Accel': "P, O",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathPocket","Creates a Path Pocket object from a loop of edges or a face")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelectionEx()
if len(selection) != 1:
FreeCAD.Console.PrintError(translate("PathPocket","Please select an edges loop from one object, or a single face\n"))
return
if len(selection[0].SubObjects) == 0:
FreeCAD.Console.PrintError(translate("PathPocket","Please select an edges loop from one object, or a single face\n"))
return
for s in selection[0].SubObjects:
if s.ShapeType != "Edge":
if (s.ShapeType != "Face") or (len(selection[0].SubObjects) != 1):
FreeCAD.Console.PrintError(translate("PathPocket","Please select only edges or a single face\n"))
return
if selection[0].SubObjects[0].ShapeType == "Edge":
try:
import Part
w = Part.Wire(selection[0].SubObjects)
except:
FreeCAD.Console.PrintError(translate("PathPocket","The selected edges don't form a loop\n"))
return
# if everything is ok, execute and register the transaction in the undo/redo stack
FreeCAD.ActiveDocument.openTransaction(translate("PathPocket","Create Pocket"))
FreeCADGui.addModule("PathScripts.PathPocket")
FreeCADGui.doCommand('prjexists = False')
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Pocket")')
FreeCADGui.doCommand('PathScripts.PathPocket.ObjectPocket(obj)')
FreeCADGui.doCommand('PathScripts.PathPocket.ViewProviderPocket(obj.ViewObject)')
subs = "["
for s in selection[0].SubElementNames:
subs += '"' + s + '",'
subs += "]"
FreeCADGui.doCommand('obj.Base = (FreeCAD.ActiveDocument.' + selection[0].ObjectName + ',' + subs + ')')
FreeCADGui.doCommand('obj.Active = True')
snippet = '''
from PathScripts import PathUtils
PathUtils.addToProject(obj)
ZMax = obj.Base[0].Shape.BoundBox.ZMax
ZMin = obj.Base[0].Shape.BoundBox.ZMin
obj.StepDown = 1.0
obj.StartDepth = ZMax
obj.FinalDepth = ZMin
obj.ClearanceHeight = ZMax + 5.0
'''
FreeCADGui.doCommand(snippet)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Pocket',CommandPathPocket())
FreeCAD.Console.PrintLog("Loading PathPocket... done\n")

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' Post Process command that will make use of the Output File and Post Processor entries in PathProject '''
import FreeCAD, FreeCADGui
import Path, PathScripts,PathGui
from PathScripts import PostUtils
from PathScripts import PathProject
import os,sys
from PySide import QtCore,QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class CommandPathPost:
def GetResources(self):
return {'Pixmap' : 'Path-Post',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathPost","Post Process"),
'Accel': "P, P",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathPost","Post Process the selected Project")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathPost","Post Process the Selected path(s)"))
FreeCADGui.addModule("PathScripts.PathPost")
#select the PathProject that you want to post output from
obj = FreeCADGui.Selection.getSelection()
#default to the dumper post and default .tap file
postname = "dumper_post"
filename = "tmp.tap"
#check if the user has a project and has set the default post and output filename
if hasattr(obj[0],"Group") and hasattr(obj[0],"Path"):
#Check for a machine and use the post processor if it's set
proj = obj[0]
postobj = None
for p in obj[0].Group:
if p.Name == "Machine":
postobj = p
#need to check for existance of these: obj.PostProcessor, obj.OutputFile
if postobj and postobj.PostProcessor:
sys.path.append(os.path.split(postobj.PostProcessor)[0])
lessextn = os.path.splitext(postobj.PostProcessor)[0]
postname = os.path.split(lessextn)[1]
if proj.OutputFile:
filename = proj.OutputFile
exec "import %s as current_post" % postname
current_post.export(obj,filename)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Post',CommandPathPost())
FreeCAD.Console.PrintLog("Loading PathPost... done\n")

View File

@@ -0,0 +1,284 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD,FreeCADGui,Path,PathGui
from FreeCAD import Vector
from PySide import QtCore,QtGui
from PathScripts import PathUtils,PathSelection,PathProject
"""Path Profile object and FreeCAD command"""
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class ObjectProfile:
def __init__(self,obj):
obj.addProperty("App::PropertyLinkSub","Base","Path",translate("Parent Object","The base geometry of this toolpath"))
obj.addProperty("App::PropertyLinkSub","Face1","Path",translate("Face1","First Selected Face to help determine where final depth of tool path is"))
obj.addProperty("App::PropertyLinkSub","Face2","Path",translate("Face2","Second Selected Face to help determine where the upper level of tool path is"))
obj.addProperty("App::PropertyBool","PathClosed","Path",translate("Path Closed","If the toolpath is a closed polyline this is True"))
obj.addProperty("App::PropertyLinkSub","Edge1","Path",translate("Edge 1","First Selected Edge to help determine which geometry to make a toolpath around"))
obj.addProperty("App::PropertyLinkSub","Edge2","Path",translate("Edge 2","Second Selected Edge to help determine which geometry to make a toolpath around"))
obj.addProperty("App::PropertyBool","Active","Path",translate("Active","Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyBool","UsePlacements","Path",translate("Use Placements","make True, if using the profile operation placement properties to transform toolpath in post processor"))
obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",translate("PathProfile","The tool number in use"))
obj.ToolNumber = (0,0,1000,1)
obj.setEditorMode('ToolNumber',1) #make this read only
#Depth Properties
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", translate("Clearance Height","The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyLength", "StepDown", "Depth", translate("StepDown","Incremental Step Down of Tool"))
obj.addProperty("App::PropertyBool","UseStartDepth","Depth",translate("Use Start Depth","make True, if manually specifying a Start Start Depth"))
obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", translate("Start Depth","Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", translate("Final Depth","Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyDistance", "RetractHeight", "Depth", translate("Retract Height","The height desired to retract tool when path is finished"))
obj.addProperty("App::PropertyString","Comment","Path",translate("Comment","An optional comment for this profile"))
#Feed Properties
obj.addProperty("App::PropertySpeed", "VertFeed", "Feed",translate("Vert Feed","Feed rate for vertical moves in Z"))
obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed",translate("Horiz Feed","Feed rate for horizontal moves"))
#Start Point Properties
obj.addProperty("App::PropertyVector","StartPoint","Start Point",translate("Start Point","The start point of this path"))
obj.addProperty("App::PropertyBool","UseStartPoint","Start Point",translate("Use Start Point","make True, if specifying a Start Point"))
obj.addProperty("App::PropertyLength", "ExtendAtStart", "Start Point", translate("extend at start", "extra length of tool path before start of part edge"))
obj.addProperty("App::PropertyLength", "LeadInLineLen", "Start Point", translate("lead in length","length of straight segment of toolpath that comes in at angle to first part edge"))
#End Point Properties
obj.addProperty("App::PropertyBool","UseEndPoint","End Point",translate("Use End Point","make True, if specifying an End Point"))
obj.addProperty("App::PropertyLength", "ExtendAtEnd", "End Point", translate("extend at end","extra length of tool path after end of part edge"))
obj.addProperty("App::PropertyLength", "LeadOutLineLen", "End Point", translate("lead_out_line_len","length of straight segment of toolpath that comes in at angle to last part edge"))
obj.addProperty("App::PropertyVector","EndPoint","End Point",translate("End Point","The end point of this path"))
#Profile Properties
obj.addProperty("App::PropertyEnumeration", "Side", "Profile", translate("Side","Side of edge that tool should cut"))
obj.Side = ['Left','Right','On'] #side of profile that cutter is on in relation to direction of profile
obj.addProperty("App::PropertyEnumeration", "Direction", "Profile",translate("Direction", "The direction that the toolpath should go around the part ClockWise CW or CounterClockWise CCW"))
obj.Direction = ['CW','CCW'] #this is the direction that the profile runs
obj.addProperty("App::PropertyDistance", "RollRadius", "Profile", translate("Roll Radius","Radius at start and end"))
obj.addProperty("App::PropertyDistance", "OffsetExtra", "Profile",translate("OffsetExtra","Extra value to stay away from final profile- good for roughing toolpath"))
obj.addProperty("App::PropertyLength", "SegLen", "Profile",translate("Seg Len","Tesselation value for tool paths made from beziers, bsplines, and ellipses"))
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def execute(self,obj):
if obj.Base:
# tie the toolnumber to the PathLoadTool object ToolNumber
if len(obj.InList)>0: #check to see if obj is in the Project group yet
project = obj.InList[0]
tl = int(PathUtils.changeTool(obj,project))
obj.ToolNumber= tl
tool = PathUtils.getTool(obj,obj.ToolNumber)
if tool:
radius = tool.Diameter/2
else:
# temporary value, to be taken from the properties later on
radius = 0.001
if obj.Base[0].Shape.ShapeType == "Wire": #a pure wire was picked
wire = obj.Base[0].Shape
else: #we are dealing with a face and it's edges or just a face
if obj.Edge1:
e1 = FreeCAD.ActiveDocument.getObject(obj.Base[0].Name).Shape.Edges[eval(obj.Edge1[1][0].lstrip('Edge'))-1]
if e1.BoundBox.ZMax <> e1.BoundBox.ZMin:
FreeCAD.Console.PrintError('vertical edges not valid yet\n')
return
if obj.Base[0].Shape.ShapeType =='Wire':
wire = obj.Base[0].Shape
if obj.Base[0].Shape.ShapeType =='Solid' or obj.Base[0].Shape.ShapeType =='Compound':
shape = obj.Base[0].Shape
for fw in shape.Wires:
if (fw.BoundBox.ZMax == e1.BoundBox.ZMax) and (fw.BoundBox.ZMin == e1.BoundBox.ZMin):
for e in fw.Edges:
if e.isSame(e1):
#FreeCAD.Console.PrintMessage('found the same objects\n')
wire = fw
elif obj.Face1: # we are only dealing with a face or faces
f1 = FreeCAD.ActiveDocument.getObject(obj.Base[0].Name).Shape.Faces[eval(obj.Face1[1][0].lstrip('Face'))-1]
# make the side Left and direction CW for normal cnc milling
obj.Direction = 'CW'
obj.Side = "Left"
# we only consider the outer wire if this is a single Face
wire = f1.OuterWire
if obj.Direction == 'CCW':
clockwise=False
else:
clockwise=True
output =""
output += '('+ str(obj.Comment)+')\n'
FirstEdge= None
if obj.Edge1:
ename = obj.Edge1[1][0]
edgeNumber = int(ename[4:])-1
FirstEdge = obj.Base[0].Shape.Edges[edgeNumber]
ZMax = obj.Base[0].Shape.BoundBox.ZMax
ZCurrent = obj.ClearanceHeight.Value
if obj.UseStartDepth:
output += PathUtils.MakePath(wire,obj.Side,radius,clockwise,obj.ClearanceHeight.Value,obj.StepDown.Value,obj.StartDepth.Value, obj.FinalDepth.Value,FirstEdge,obj.PathClosed,obj.SegLen.Value,obj.VertFeed.Value,obj.HorizFeed.Value)
else:
output += PathUtils.MakePath(wire,obj.Side,radius,clockwise,obj.ClearanceHeight.Value,obj.StepDown.Value,ZMax, obj.FinalDepth.Value,FirstEdge,obj.PathClosed,obj.SegLen.Value,obj.VertFeed.Value,obj.HorizFeed.Value)
if obj.Active:
path = Path.Path(output)
obj.Path = path
obj.ViewObject.Visibility = True
else:
path = Path.Path("(inactive operation)")
obj.Path = path
obj.ViewObject.Visibility = False
class ViewProviderProfile:
def __init__(self,vobj):
vobj.Proxy = self
def attach(self,vobj):
self.Object = vobj.Object
return
def getIcon(self):
return ":/icons/Path-Profile.svg"
def __getstate__(self):
return None
def __setstate__(self,state):
return None
class CommandPathProfile:
def GetResources(self):
return {'Pixmap' : 'Path-Profile',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathProfile","Profile"),
'Accel': "P, P",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathProfile","Creates a Path Profile object from selected faces")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
import Path
from PathScripts import PathUtils,PathProfile,PathProject
prjexists = False
selection = PathSelection.multiSelect()
if not selection:
return
# if everything is ok, execute and register the transaction in the undo/redo stack
FreeCAD.ActiveDocument.openTransaction(translate("PathProfile","Create Profile"))
FreeCADGui.addModule("PathScripts.PathProfile")
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Profile")
PathProfile.ObjectProfile(obj)
PathProfile.ViewProviderProfile(obj.ViewObject)
obj.Base = (FreeCAD.ActiveDocument.getObject(selection['objname']))
if selection['facenames']:
#FreeCAD.Console.PrintMessage('There are edges selected\n')
obj.Face1 = (FreeCAD.ActiveDocument.getObject(selection['objname']),selection['facenames'][0])
if len(selection['facenames'])>1:
obj.Face2 = (FreeCAD.ActiveDocument.getObject(selection['objname']),selection['facenames'][-1])
if selection['edgenames']:
#FreeCAD.Console.PrintMessage('There are edges selected\n')
obj.Edge1 =(FreeCAD.ActiveDocument.getObject(selection['objname']),(selection['edgenames'][0]))
if len(selection['edgenames'])>1:
obj.Edge2 =(FreeCAD.ActiveDocument.getObject(selection['objname']),(selection['edgenames'][-1]))
if selection['pointlist']:
FreeCADGui.doCommand('from FreeCAD import Vector')
stptX, stptY, stptZ = selection['pointlist'][0].X, selection['pointlist'][0].Y, selection['pointlist'][0].Z
obj.StartPoint = Vector((stptX),(stptY),(stptZ))
if len(selection['pointlist'])>1: # we have more than one point so we have an end point
endptX, endptY, endptZ = selection['pointlist'][-1].X, selection['pointlist'][-1].Y, selection['pointlist'][-1].Z
obj.EndPoint = Vector(endptX,endptY,endptZ)
if selection['pathwire'].isClosed():
obj.PathClosed = True
if selection['clockwise']:
obj.Side = "Left"
obj.Direction = "CW"
elif selection['clockwise'] == False:
obj.Side = "Right"
obj.Direction = "CCW"
else:
obj.Side = "On"
obj.Direction = "CCW"
obj.PathClosed = False
ZMax = obj.Base[0].Shape.BoundBox.ZMax
ZMin = obj.Base[0].Shape.BoundBox.ZMin
obj.StepDown.Value = 1.0
obj.StartDepth.Value = ZMax- obj.StepDown.Value
obj.FinalDepth.Value = ZMin-1.0
obj.ClearanceHeight.Value = ZMax + 5.0
obj.SegLen.Value = 0.5
obj.Active = True
obj.ViewObject.ShowFirstRapid = False
project = PathUtils.addToProject(obj)
tl = PathUtils.changeTool(obj,project)
if tl:
obj.ToolNumber = tl
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Profile',CommandPathProfile())
FreeCAD.Console.PrintLog("Loading PathProfile... done\n")

View File

@@ -0,0 +1,154 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD,FreeCADGui,Path,PathGui
from PySide import QtCore,QtGui
"""Path Project object and FreeCAD command"""
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class ObjectPathProject:
def __init__(self,obj):
# obj.addProperty("App::PropertyFile", "PostProcessor", "CodeOutput", translate("PostProcessor","Select the Post Processor file for this project"))
obj.addProperty("App::PropertyFile", "OutputFile", "CodeOutput", translate("OutputFile","The NC output file for this project"))
# obj.addProperty("App::PropertyBool","Editor","CodeOutput",translate("Show Editor","Show G-Code in simple editor after posting code"))
# obj.addProperty("Path::PropertyTooltable","Tooltable", "Path",translate("PathProject","The tooltable of this feature"))
obj.addProperty("App::PropertyString", "Description","Path",translate("PathProject","An optional description for this project"))
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def onChanged(self,obj,prop):
pass
def execute(self,obj):
cmds = []
for child in obj.Group:
if child.isDerivedFrom("Path::Feature"):
cmds.extend(child.Path.Commands)
if cmds:
path = Path.Path(cmds)
obj.Path = path
class ViewProviderProject:
def __init__(self,vobj):
vobj.Proxy = self
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
# vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self):
return ":/icons/Path-Project.svg"
def onChanged(self,vobj,prop):
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
# vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
class CommandProject:
def GetResources(self):
return {'Pixmap' : 'Path-Project',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathProject","Project"),
'Accel': "P, P",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathProject","Creates a Path Project object")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
incl = []
sel = FreeCADGui.Selection.getSelection()
for obj in sel:
if obj.isDerivedFrom("Path::Feature"):
incl.append(obj)
FreeCAD.ActiveDocument.openTransaction(translate("PathProject","Create Project"))
CommandProject.Create(incl)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
@staticmethod
def Create(pathChildren = []):
"""Code to create a project"""
#FreeCADGui.addModule("PathScripts.PathProject")
obj = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython","Project")
ObjectPathProject(obj)
if pathChildren:
for child in pathChildren:
pathChildren.append(FreeCAD.ActiveDocument.getObject(obj.Name))
obj.Group = pathChildren
ViewProviderProject(obj.ViewObject)
#create a machine obj
import PathScripts
PathScripts.PathMachine.CommandPathMachine.Create()
return obj
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Project',CommandProject())
FreeCAD.Console.PrintLog("Loading PathProject... done\n")

View File

@@ -0,0 +1,213 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
'''Path selection function select a face or faces, two edges, etc to get a dictionary with what was selected in order '''
import FreeCAD,FreeCADGui
import Part
from FreeCAD import Vector
def Sort2Edges(edgelist):
'''Sort2Edges(edgelist) simple function to reorder the start and end pts of two edges based on their selection order. Returns the list, the start point, and their common point, => edgelist, vertex, vertex'''
if len(edgelist)>=2:
vlist = []
e0 = edgelist[0]
e1=edgelist[1]
a0 = e0.Vertexes[0]
a1 = e0.Vertexes[1]
b0 = e1.Vertexes[0]
b1 = e1.Vertexes[1]
# comparison routine to order two edges:
if a1.isSame(b0):
vlist.append((a0.Point.x,a0.Point.y))
vlist.append((a1.Point.x,a1.Point.y))
vlist.append((b1.Point.x,b1.Point.y))
elif a0.isSame(b0):
vlist.append((a1.Point.x,a1.Point.y))
vlist.append((a0.Point.x,a0.Point.y))
vlist.append((b1.Point.x,b1.Point.y))
elif a0.isSame(b1):
vlist.append((a1.Point.x,a1.Point.y))
vlist.append((a0.Point.x,a0.Point.y))
vlist.append((b0.Point.x,b0.Point.y))
elif a1.isSame(b1):
vlist.append((a0.Point.x,a0.Point.y))
vlist.append((a1.Point.x,a1.Point.y))
vlist.append((b0.Point.x,b0.Point.y))
edgestart = Vector(vlist[0][0],vlist[0][1],e0.Vertexes[1].Z)
edgecommon = Vector(vlist[1][0],vlist[1][1],e0.Vertexes[1].Z)
return vlist,edgestart,edgecommon
def segments(poly):
''' A sequence of (x,y) numeric coordinates pairs '''
return zip(poly, poly[1:] + [poly[0]])
def check_clockwise(poly):
'''
check_clockwise(poly) a function for returning a boolean if the selected wire is clockwise or counter clockwise
based on point order. poly = [(x1,y1),(x2,y2),(x3,y3)]
'''
clockwise = False
if (sum(x0*y1 - x1*y0 for ((x0, y0), (x1, y1)) in segments(poly))) < 0:
clockwise = not clockwise
return clockwise
def multiSelect():
'''
multiSelect() A function for selecting elements of an object for CNC path operations.
Select just a face, an edge,or two edges to indicate direction, a vertex on the object, a point not on the object,
or some combination. Returns a dictionary.
'''
sel = FreeCADGui.Selection.getSelectionEx()
numobjs = len([selobj.Object for selobj in sel])
if numobjs == 0:
FreeCAD.Console.PrintError('Please select some objects and try again.\n')
return
goodselect = False
for s in sel:
for i in s.SubObjects:
if i.ShapeType == 'Face':
goodselect = True
if i.ShapeType == 'Edge':
goodselect = True
if i.ShapeType == 'Vertex':
goodselect = True
if not goodselect:
FreeCAD.Console.PrintError('Please select a face and/or edges along with points (optional) and try again.\n')
return
selItems = {}
selItems['objname']=None #the parent object name - a 3D solid
selItems['pointlist']=None #start and end points
selItems['pointnames']=None #names of points for document object
selItems['facenames']=None # the selected face name
selItems['facelist']=None #list of faces selected
selItems['edgelist']=None #some edges that could be selected along with points and faces
selItems['edgenames']=None
selItems['pathwire']=None #the whole wire around edges of the face
selItems['clockwise']=None
selItems['circles']=None
facenames = []
edgelist =[]
edgenames=[]
ptlist=[]
ptnames=[]
circlelist=[]
face = False
edges = False
points = False
wireobj = False
circles = False
facelist= []
for s in sel:
if s.Object.Shape.ShapeType in ['Solid','Compound','Wire','Vertex']:
if not (s.Object.Shape.ShapeType =='Vertex'):
objname = s.ObjectName
selItems['objname'] =objname
if s.Object.Shape.ShapeType == 'Wire':
wireobj = True
if s.Object.Shape.ShapeType == 'Vertex':
ptnames.append(s.ObjectName)
# ptlist.append(s.Object)
points = True
for sub in s.SubObjects:
if sub.ShapeType =='Face':
facelist.append(sub)
face = True
if sub.ShapeType =='Edge':
edge = sub
edgelist.append(edge)
edges = True
if isinstance(sub.Curve,Part.Circle):
circlelist.append(edge)
circles = True
if sub.ShapeType =='Vertex':
ptlist.append(sub)
points = True
for sub in s.SubElementNames:
if 'Face' in sub:
facename = sub
facenames.append(facename)
if 'Edge' in sub:
edgenames.append(sub)
# now indicate which wire is going to be processed, based on which edges are selected
if facelist:
selItems['facelist']=facelist
if edges:
if face:
selItems['edgelist'] =edgelist
for fw in facelist[0].Wires:
for e in fw.Edges:
if e.isSame(edge):
pathwire = fw
selItems['pathwire'] =pathwire
elif wireobj:
selItems['pathwire'] =s.Object.Shape
selItems['edgelist'] =edgelist
else:
for w in s.Object.Shape.Wires:
for e in w.Edges:
if e.BoundBox.ZMax == e.BoundBox.ZMin: #if they are on same plane in Z as sel edge
if e.isSame(edge):
pathwire = w
selItems['pathwire'] =pathwire
selItems['edgelist'] =edgelist
if not edges:
if face:
selItems['pathwire'] =facelist[0].OuterWire
if edges and (len(edgelist)>=2):
vlist,edgestart,edgecommon=Sort2Edges(edgelist)
edgepts ={}
edgepts['vlist'] = vlist
edgepts['edgestart']=edgestart # start point of edges selected
edgepts['edgecommon']=edgecommon # point where two edges join- will be last point in in first gcode line
selItems['edgepts']=edgepts
if check_clockwise(vlist):
selItems['clockwise']=True
elif check_clockwise(vlist) == False:
selItems['clockwise']=False
if points:
selItems['pointlist'] = ptlist
selItems['pointnames'] = ptnames
if edges:
selItems['edgenames']=edgenames
if face:
selItems['facenames'] = facenames
if circles:
selItems['circles'] = circlelist
return selItems

View File

@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
'''used to create material stock around a machined part- for visualization '''
import Draft,Part
import FreeCAD, FreeCADGui
from FreeCAD import Vector
from PySide import QtCore, QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class Stock:
def __init__(self, obj):
"Make stock"
obj.addProperty("App::PropertyFloat","Length_Allowance","Stock",translate("Length Allowance","extra allownace from part width")).Length_Allowance = 1.0
obj.addProperty("App::PropertyFloat","Width_Allowance","Stock",translate("Width Allowance","extra allownace from part width")).Width_Allowance = 1.0
obj.addProperty("App::PropertyFloat","Height_Allowance","Stock",translate("Height Allowance","extra allownace from part width")).Height_Allowance = 1.0
obj.addProperty("App::PropertyLink","Base","Base",
"The base object this represents")
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def execute(self, obj):
self.Xmin = obj.Base.Shape.BoundBox.XMin
self.Xmax = obj.Base.Shape.BoundBox.XMax
self.Ymin = obj.Base.Shape.BoundBox.YMin
self.Ymax = obj.Base.Shape.BoundBox.YMax
self.Zmin = obj.Base.Shape.BoundBox.ZMin
self.Zmax = obj.Base.Shape.BoundBox.ZMax
self.length = self.Xmax -self.Xmin+obj.Length_Allowance*2.0
self.width = self.Ymax - self.Ymin+obj.Width_Allowance*2.0
self.height = self.Zmax - self.Zmin+obj.Height_Allowance*2.0
self.pnt = Vector(self.Xmin-obj.Length_Allowance , self.Ymin-obj.Width_Allowance, self.Zmin-obj.Height_Allowance)
obj.Shape = Part.makeBox(self.length,self.width,self.height,self.pnt)
class _ViewProviderStock:
def __init__(self,obj): #mandatory
# obj.addProperty("App::PropertyFloat","SomePropertyName","PropertyGroup","Description of this property")
obj.Proxy = self
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-Stock.svg"
def attach(self, vobj): #optional
self.Object = vobj.Object
class CommandPathStock:
def GetResources(self):
return {'Pixmap' : 'Path-Stock',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathStock","Stock"),
'Accel': "P, S",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathStock","Creates a 3D object to represent raw stock to mill the part out of")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathStock","Creates a 3D object to represent raw stock to mill the part out of"))
FreeCADGui.addModule("PathScripts.PathStock")
snippet = '''
import FreeCADGui
if len(FreeCADGui.Selection.getSelection())>0:
sel=FreeCADGui.Selection.getSelection()
o = sel[0]
if "Shape" in o.PropertiesList:
obj =FreeCAD.ActiveDocument.addObject('Part::FeaturePython',sel[0].Name+('_Stock'))
PathScripts.PathStock.Stock(obj)
PathScripts.PathStock._ViewProviderStock(obj.ViewObject)
PathScripts.PathUtils.addToProject(obj)
baseobj = sel[0]
obj.Base = baseobj
FreeCADGui.ActiveDocument.getObject(sel[0].Name+("_Stock")).ShapeColor = (0.3333,0.6667,1.0000)
FreeCADGui.ActiveDocument.getObject(sel[0].Name+("_Stock")).Transparency = 75
FreeCAD.ActiveDocument.recompute()
else:
FreeCAD.Console.PrintMessage("Select a Solid object and try again.\\n")
else:
FreeCAD.Console.PrintMessage("Select the object you want to show stock for and try again.\\n")
'''
FreeCADGui.doCommand(snippet)
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Stock',CommandPathStock())
FreeCAD.Console.PrintLog("Loading PathStock... done\n")

View File

@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' Used for CNC machine Stops for Path module. Create an Optional or Mandatory Stop.'''
import FreeCAD,FreeCADGui,Path,PathGui
from PathScripts import PathProject
from PySide import QtCore,QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class Stop:
def __init__(self,obj):
obj.addProperty("App::PropertyEnumeration", "Stop", "Path", translate("Program Stop", "Add Optional or Mandatory Stop to the program"))
obj.Stop=['Optional', 'Mandatory']
obj.Proxy = self
mode = 2
obj.setEditorMode('Placement',mode)
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def onChanged(self,obj,prop):
pass
# FreeCAD.ActiveDocument.recompute()
def execute(self,obj):
if obj.Stop == 'Optional':
word = 'M1'
else:
word = 'M0'
output =""
output = word+'\n'
path = Path.Path(output)
obj.Path = path
class _ViewProviderStop:
def __init__(self,vobj): #mandatory
# obj.addProperty("App::PropertyFloat","SomePropertyName","PropertyGroup","Description of this property")
vobj.Proxy = self
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-Stop.svg"
def onChanged(self,vobj,prop): #optional
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
class CommandPathStop:
def GetResources(self):
return {'Pixmap' : 'Path-Stop',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathStop","Stop"),
'Accel': "P, C",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathStop","Add Optional or Mandatory Stop to the program")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathStop","Add Optional or Mandatory Stop to the program"))
FreeCADGui.addModule("PathScripts.PathStop")
snippet = '''
import Path
import PathScripts
from PathScripts import PathUtils
prjexists = False
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Stop")
PathScripts.PathStop.Stop(obj)
PathScripts.PathStop._ViewProviderStop(obj.ViewObject)
PathUtils.addToProject(obj)
'''
FreeCADGui.doCommand(snippet)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Stop',CommandPathStop())
FreeCAD.Console.PrintLog("Loading PathStop... done\n")

View File

@@ -0,0 +1,164 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' Used for CNC machine Tool Length Offsets ie G43H2'''
import FreeCAD,FreeCADGui,Path,PathGui
from PathScripts import PathProject,PathUtils
from PySide import QtCore,QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class ToolLenOffset:
def __init__(self,obj):
obj.addProperty("App::PropertyIntegerConstraint", "HeightNumber","HeightOffset", translate( "Height Offset Number", "The Height offset number of the active tool"))
obj.HeightNumber = (0,0,10000,1)
obj.addProperty("App::PropertyLength", "Height", "HeightOffset", translate("Height","The first height value in Z, to rapid to, before making a feed move in Z"))
obj.addProperty("App::PropertyBool","Active","HeightOffset",translate("Active","Make False, to prevent operation from generating code"))
obj.Proxy = self
mode = 2
obj.setEditorMode('Placement',mode)
def execute(self,obj):
command = 'G43H'+str(obj.HeightNumber)+'G0Z'+str(obj.Height.Value)
obj.Path = Path.Path(command)
obj.Label = "Height"+str(obj.HeightNumber)
if obj.Active:
obj.Path = Path.Path(command)
obj.ViewObject.Visibility = True
else:
obj.Path = Path.Path("(inactive operation)")
obj.ViewObject.Visibility = False
# tie the HeightNumber to the PathLoadTool object ToolNumber
if len(obj.InList)>0: #check to see if obj is in the Project group yet
project = obj.InList[0]
tl = int(PathUtils.changeTool(obj,project))
obj.HeightNumber= tl
def onChanged(self,obj,prop):
if prop == "HeightNumber":
obj.Label = "Height"+str(obj.HeightNumber)
class _ViewProviderTLO:
def __init__(self,vobj): #mandatory
vobj.Proxy = self
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def __getstate__(self): #mandatory
return None
def __setstate__(self,state): #mandatory
return None
def getIcon(self): #optional
return ":/icons/Path-LengthOffset.svg"
def onChanged(self,vobj,prop): #optional
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
def updateData(self,vobj,prop): #optional
# this is executed when a property of the APP OBJECT changes
pass
def setEdit(self,vobj,mode): #optional
# this is executed when the object is double-clicked in the tree
pass
def unsetEdit(self,vobj,mode): #optional
# this is executed when the user cancels or terminates edit mode
pass
class CommandPathToolLenOffset:
def GetResources(self):
return {'Pixmap' : 'Path-LengthOffset',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolLenOffset","Tool Length Offset"),
'Accel': "P, T",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolLenOffset","Create a Tool Length Offset object")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathPlane","Create a Selection Plane object"))
FreeCADGui.addModule("PathScripts.PathToolLenOffset")
snippet = '''
import Path
import PathScripts
from PathScripts import PathProject,PathUtils
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","HeightOffset")
PathScripts.PathToolLenOffset.ToolLenOffset(obj)
obj.Active = True
PathScripts.PathToolLenOffset._ViewProviderTLO(obj.ViewObject)
project = PathUtils.addToProject(obj)
tl = PathUtils.changeTool(obj,project)
if tl:
obj.HeightNumber = tl
obj.ViewObject.ShowFirstRapid = False
FreeCAD.ActiveDocument.recompute()
'''
FreeCADGui.doCommand(snippet)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_ToolLenOffset', CommandPathToolLenOffset())
FreeCAD.Console.PrintLog("Loading PathToolLenOffset... done\n")

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD,FreeCADGui
from PySide import QtCore,QtGui
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class CommandPathToolTableEdit:
def GetResources(self):
return {'Pixmap' : 'Path-ToolTable',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolTableEdit","EditToolTable"),
'Accel': "P, T",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolTableEdit","Edits a Tool Table in a selected Project")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("PathToolTableEdit","Edits a Tool Table in a selected Project"))
FreeCADGui.doCommand("from PathScripts import TooltableEditor")
FreeCADGui.doCommand("from PathScripts import PathUtils")
FreeCADGui.doCommand('machine = PathUtils.findMachine()')
FreeCADGui.doCommand('TooltableEditor.edit(machine.Name)')
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_ToolTableEdit',CommandPathToolTableEdit())
FreeCAD.Console.PrintLog("Loading PathToolTableEdit... done\n")

View File

@@ -0,0 +1,384 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
'''PathUtils -common functions used in PathScripts for filterig, sorting, and generating gcode toolpath data '''
import FreeCAD
import Part
from FreeCAD import Vector
import FreeCADGui
import math
import DraftGeomUtils
from DraftGeomUtils import geomType
import DraftVecUtils
import PathScripts
from PathScripts import PathProject
def cleanedges(splines,precision):
'''cleanedges([splines],precision). Convert BSpline curves, Beziers, to arcs that can be used for cnc paths.
Returns Lines as is. Filters Circle and Arcs for over 180 degrees. Discretizes Ellipses. Ignores other geometry. '''
edges = []
for spline in splines:
if geomType(spline)=="BSplineCurve":
arcs = spline.Curve.toBiArcs(precision)
for i in arcs:
edges.append(Part.Edge(i))
elif geomType(spline)=="BezierCurve":
newspline=spline.Curve.toBSpline()
arcs = newspline.toBiArcs(precision)
for i in arcs:
edges.append(Part.Edge(i))
elif geomType(spline)=="Ellipse":
edges = curvetowire(spline, 1.0) #fixme hardcoded value
elif geomType(spline)=="Circle":
#arcs=filterArcs(spline)
edges.append(spline)
elif geomType(spline)=="Line":
edges.append(spline)
else:
pass
return edges
def curvetowire(obj,steps):
'''adapted from DraftGeomUtils, because the discretize function changed a bit '''
points = obj.copy().discretize(Distance = eval('steps'))
p0 = points[0]
edgelist = []
for p in points[1:]:
edge = Part.makeLine((p0.x,p0.y,p0.z),(p.x,p.y,p.z))
edgelist.append(edge)
p0 = p
return edgelist
def fmt(val): return format(val, '.4f') #fixme set at 4 decimal places for testing
def isSameEdge(e1,e2):
"""isSameEdge(e1,e2): return True if the 2 edges are both lines or arcs/circles and have the same
points - inspired by Yorik's function isSameLine"""
if not (isinstance(e1.Curve,Part.Line) or isinstance(e1.Curve,Part.Circle)):
return False
if not (isinstance(e2.Curve,Part.Line) or isinstance(e2.Curve,Part.Circle)):
return False
if type(e1.Curve) <> type(e2.Curve):
return False
if isinstance(e1.Curve,Part.Line):
if (DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[-1].Point)):
return True
elif (DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[-1].Point)):
return True
if isinstance(e1.Curve,Part.Circle):
center = False; radius= False; endpts=False
if e1.Curve.Center == e2.Curve.Center:
center = True
if e1.Curve.Radius == e2.Curve.Radius:
radius = True
if (DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[-1].Point)):
endpts = True
elif (DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[-1].Point)):
endpts = True
if (center and radius and endpts):
return True
return False
def segments(poly):
''' A sequence of (x,y) numeric coordinates pairs '''
return zip(poly, poly[1:] + [poly[0]])
def check_clockwise(poly):
'''
check_clockwise(poly) a function for returning a boolean if the selected wire is clockwise or counter clockwise
based on point order. poly = [(x1,y1),(x2,y2),(x3,y3)]
'''
clockwise = False
if (sum(x0*y1 - x1*y0 for ((x0, y0), (x1, y1)) in segments(poly))) < 0:
clockwise = not clockwise
return clockwise
def filterArcs(arcEdge):
'''filterArcs(Edge) -used to split arcs that over 180 degrees. Returns list '''
s = arcEdge
if isinstance(s.Curve,Part.Circle):
splitlist =[]
angle = abs(s.LastParameter-s.FirstParameter)
overhalfcircle = False
goodarc = False
if (angle > math.pi):
overhalfcircle = True
else:
goodarc = True
if not goodarc:
arcstpt = s.valueAt(s.FirstParameter)
arcmid = s.valueAt((s.LastParameter-s.FirstParameter)*0.5+s.FirstParameter)
arcquad1 = s.valueAt((s.LastParameter-s.FirstParameter)*0.25+s.FirstParameter)#future midpt for arc1
arcquad2 = s.valueAt((s.LastParameter-s.FirstParameter)*0.75+s.FirstParameter) #future midpt for arc2
arcendpt = s.valueAt(s.LastParameter)
# reconstruct with 2 arcs
arcseg1 = Part.ArcOfCircle(arcstpt,arcquad1,arcmid)
arcseg2 = Part.ArcOfCircle(arcmid,arcquad2,arcendpt)
eseg1 = arcseg1.toShape()
eseg2 = arcseg2.toShape()
splitlist.append(eseg1)
splitlist.append(eseg2)
else:
splitlist.append(s)
elif isinstance(s.Curve,Part.Line):
pass
return splitlist
def reverseEdge(e):
if geomType(e) == "Circle":
arcstpt = e.valueAt(e.FirstParameter)
arcmid = e.valueAt((e.LastParameter-e.FirstParameter)*0.5+e.FirstParameter)
arcendpt = e.valueAt(e.LastParameter)
arcofCirc = Part.ArcOfCircle(arcendpt,arcmid,arcstpt)
newedge = arcofCirc.toShape()
elif geomType(e) == "Line":
stpt = e.valueAt(e.FirstParameter)
endpt = e.valueAt(e.LastParameter)
newedge = Part.makeLine(endpt,stpt)
return newedge
def convert(toolpath,Side,radius,clockwise=False,Z=0.0,firstedge=None,vf=1.0,hf=2.0):
'''convert(toolpath,Side,radius,clockwise=False,Z=0.0,firstedge=None) Converts lines and arcs to G1,G2,G3 moves. Returns a string.'''
last = None
output = ""
# create the path from the offset shape
for edge in toolpath:
if not last:
#set the first point
last = edge.Vertexes[0].Point
#FreeCAD.Console.PrintMessage("last pt= " + str(last)+ "\n")
output += "G1 X"+str(fmt(last.x))+" Y"+str(fmt(last.y))+" Z"+str(fmt(Z))+" F"+str(vf)+"\n"
if isinstance(edge.Curve,Part.Circle):
#FreeCAD.Console.PrintMessage("arc\n")
arcstartpt = edge.valueAt(edge.FirstParameter)
midpt = edge.valueAt((edge.FirstParameter+edge.LastParameter)*0.5)
arcendpt = edge.valueAt(edge.LastParameter)
arcchkpt=edge.valueAt(edge.LastParameter*.99)
if DraftVecUtils.equals(last,arcstartpt):
startpt = arcstartpt
endpt = arcendpt
else:
startpt = arcendpt
endpt = arcstartpt
center = edge.Curve.Center
relcenter = center.sub(last)
#FreeCAD.Console.PrintMessage("arc startpt= " + str(startpt)+ "\n")
#FreeCAD.Console.PrintMessage("arc midpt= " + str(midpt)+ "\n")
#FreeCAD.Console.PrintMessage("arc endpt= " + str(endpt)+ "\n")
arc_cw = check_clockwise([(startpt.x,startpt.y),(midpt.x,midpt.y),(endpt.x,endpt.y)])
#FreeCAD.Console.PrintMessage("arc_cw="+ str(arc_cw)+"\n")
if arc_cw:
output += "G2"
else:
output += "G3"
output += " X"+str(fmt(endpt.x))+" Y"+str(fmt(endpt.y))+" Z"+str(fmt(Z))+" F"+str(hf)
output += " I" + str(fmt(relcenter.x)) + " J" + str(fmt(relcenter.y)) + " K" + str(fmt(relcenter.z))
output += "\n"
last = endpt
#FreeCAD.Console.PrintMessage("last pt arc= " + str(last)+ "\n")
else:
point = edge.Vertexes[-1].Point
if DraftVecUtils.equals(point , last): # edges can come flipped
point = edge.Vertexes[0].Point
output += "G1 X"+str(fmt(point.x))+" Y"+str(fmt(point.y))+" Z"+str(fmt(Z))+" F"+str(hf)+"\n"
last = point
#FreeCAD.Console.PrintMessage("line\n")
#FreeCAD.Console.PrintMessage("last pt line= " + str(last)+ "\n")
return output
def SortPath(wire,Side,radius,clockwise,firstedge=None,SegLen =0.5):
'''SortPath(wire,Side,radius,clockwise,firstedge=None,SegLen =0.5) Sorts the wire and reverses it, if needed. Splits arcs over 180 degrees in two. Returns the reordered offset of the wire. '''
if firstedge:
edgelist = wire.Edges[:]
if wire.isClosed():
elindex = None
n=0
for e in edgelist:
if isSameEdge(e,firstedge):
# FreeCAD.Console.PrintMessage('found first edge\n')
elindex = n
n=n+1
l1 = edgelist[:elindex]
l2 = edgelist[elindex:]
newedgelist = l2+l1
if clockwise:
newedgelist.reverse()
last = newedgelist.pop(-1)
newedgelist.insert(0, last)
preoffset= []
for e in newedgelist:
if clockwise:
r = reverseEdge(e)
preoffset.append(r)
else:
preoffset.append(e)
sortedpreoff = DraftGeomUtils.sortEdgesOld(preoffset)
wire = Part.Wire(sortedpreoff)
else:
sortedpreoff = DraftGeomUtils.sortEdgesOld(edgelist)
wire = Part.Wire(sortedpreoff)
edgelist = []
for e in wire.Edges:
if geomType(e) == "Circle":
arclist = filterArcs(e)
for a in arclist:
edgelist.append(a)
elif geomType(e) == "Line":
edgelist.append(e)
elif geomType(e) == "BSplineCurve" or \
geomType(e) == "BezierCurve" or \
geomType(e) == "Ellipse":
edgelist.append(Part.Wire(curvetowire(e,(SegLen))))
newwire = Part.Wire(edgelist)
if Side == 'Left':
# we use the OCC offset feature
offset = newwire.makeOffset(radius)#tool is outside line
elif Side == 'Right':
offset = newwire.makeOffset(-radius)#tool is inside line
else:
if wire.isClosed():
offset = newwire.makeOffset(0.0)
else:
offset = newwire
return offset
def MakePath(wire,Side,radius,clockwise,ZClearance,StepDown,ZStart,ZFinalDepth,firstedge=None,PathClosed=True,SegLen =0.5,VertFeed=1.0,HorizFeed=2.0):
''' makes the path - just a simple profile for now '''
offset = SortPath(wire,Side,radius,clockwise,firstedge,SegLen=0.5)
toolpath = offset.Edges[:]
paths = ""
first = toolpath[0].Vertexes[0].Point
paths += "G0 X"+str(fmt(first.x))+"Y"+str(fmt(first.y))+"\n"
ZCurrent = ZStart- StepDown
if PathClosed:
while ZCurrent > ZFinalDepth:
paths += convert(toolpath,Side,radius,clockwise,ZCurrent,firstedge,VertFeed,HorizFeed)
ZCurrent = ZCurrent-abs(StepDown)
paths += convert(toolpath,Side,radius,clockwise,ZFinalDepth,firstedge,VertFeed,HorizFeed)
paths += "G0 Z" + str(ZClearance)
else:
while ZCurrent > ZFinalDepth:
paths += convert(toolpath,Side,radius,clockwise,ZCurrent,firstedge,VertFeed,HorizFeed)
paths += "G0 Z" + str(ZClearance)
paths += "G0 X"+str(fmt(first.x))+"Y"+str(fmt(first.y))+"\n"
ZCurrent = ZCurrent-abs(StepDown)
paths += convert(toolpath,Side,radius,clockwise,ZFinalDepth,firstedge,VertFeed,HorizFeed)
paths += "G0 Z" + str(ZClearance)
return paths
# the next two functions are for automatically populating tool numbers/height offset numbers based on previously active toolnumbers
def changeTool(obj,proj):
tlnum = 0
for p in proj.Group:
if not hasattr(p,"Group"):
if isinstance(p.Proxy,PathScripts.PathLoadTool.LoadTool) and p.ToolNumber > 0:
tlnum = p.ToolNumber
if p == obj:
return tlnum
elif hasattr(p,"Group"):
for g in p.Group:
if isinstance(g.Proxy,PathScripts.PathLoadTool.LoadTool):
tlnum = g.ToolNumber
if g == obj:
return tlnum
def getLastTool(obj):
toolNum = obj.ToolNumber
if obj.ToolNumber == 0:
# find tool from previous toolchange
proj = findProj()
toolNum = changeTool(obj, proj)
return getTool(obj, toolNum)
def getTool(obj,number=0):
"retrieves a tool from a hosting object with a tooltable, if any"
for o in obj.InList:
if o.TypeId == "Path::FeatureCompoundPython":
for m in o.Group:
if hasattr(m,"Tooltable"):
return m.Tooltable.getTool(number)
# not found? search one level up
for o in obj.InList:
return getTool(o,number)
return None
def findProj():
for o in FreeCAD.ActiveDocument.Objects:
if "Proxy" in o.PropertiesList:
if isinstance(o.Proxy, PathProject.ObjectPathProject):
return o
def findMachine():
'''find machine object for the tooltable editor '''
for o in FreeCAD.ActiveDocument.Objects:
if "Proxy" in o.PropertiesList:
if isinstance(o.Proxy, PathScripts.PathMachine.Machine):
return o
def addToProject(obj):
"""Adds a path obj to this document, if no PathParoject exists it's created on the fly"""
project = findProj()
if project == None:
project = PathProject.CommandProject.Create()
g = project.Group
g.append(obj)
project.Group = g
return project
def getLastZ(obj):
''' find the last z value in the project '''
lastZ = ""
for g in obj.Group:
for c in g.Path.Commands:
for n in c.Parameters:
if n == 'Z':
lastZ= c.Parameters['Z']
return lastZ

View File

@@ -0,0 +1,162 @@
#***************************************************************************
#* (c) Yorik van Havre (yorik@uncreated.net) 2014 *
#* *
#* This file is part of the FreeCAD CAx development system. *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* FreeCAD is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Lesser General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with FreeCAD; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************/
'''
These are a common functions and classes for creating custom post processors.
'''
from PySide import QtCore, QtGui
import FreeCADGui
import FreeCAD
class OldHighlighter(QtGui.QSyntaxHighlighter):
def highlightBlock(self, text):
myClassFormat = QtGui.QTextCharFormat()
myClassFormat.setFontWeight(QtGui.QFont.Bold)
myClassFormat.setForeground(QtCore.Qt.green)
# the regex pattern to be colored
pattern = "(G.*?|M.*?)\\s"
expression = QtCore.QRegExp(pattern)
index = text.index(expression)
while index >= 0:
length = expression.matchedLength()
setFormat(index, length, myClassFormat)
index = text.index(expression, index + length)
class GCodeHighlighter(QtGui.QSyntaxHighlighter):
def __init__(self, parent=None):
super(GCodeHighlighter, self).__init__(parent)
keywordFormat = QtGui.QTextCharFormat()
keywordFormat.setForeground(QtCore.Qt.cyan)
keywordFormat.setFontWeight(QtGui.QFont.Bold)
keywordPatterns = ["\\bG[0-9]+\\b",
"\\bM[0-9]+\\b"]
self.highlightingRules = [(QtCore.QRegExp(pattern), keywordFormat) for pattern in keywordPatterns]
speedFormat = QtGui.QTextCharFormat()
speedFormat.setFontWeight(QtGui.QFont.Bold)
speedFormat.setForeground(QtCore.Qt.green)
self.highlightingRules.append((QtCore.QRegExp("\\bF[0-9\\.]+\\b"),speedFormat))
def highlightBlock(self, text):
for pattern, format in self.highlightingRules:
expression = QtCore.QRegExp(pattern)
index = expression.indexIn(text)
while index >= 0:
length = expression.matchedLength()
self.setFormat(index, length, format)
index = expression.indexIn(text, index + length)
class GCodeEditorDialog(QtGui.QDialog):
def __init__(self, parent = FreeCADGui.getMainWindow()):
QtGui.QDialog.__init__(self,parent)
layout = QtGui.QVBoxLayout(self)
# nice text editor widget for editing the gcode
self.editor = QtGui.QTextEdit()
font = QtGui.QFont()
font.setFamily("Courier")
font.setFixedPitch(True)
font.setPointSize(10)
self.editor.setFont(font)
self.editor.setText("G01 X55 Y4.5 F300.0")
self.highlighter = GCodeHighlighter(self.editor.document())
layout.addWidget(self.editor)
# OK and Cancel buttons
self.buttons = QtGui.QDialogButtonBox(
QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel,
QtCore.Qt.Horizontal, self)
layout.addWidget(self.buttons)
# restore placement and size
self.paramKey = "User parameter:BaseApp/Values/Mod/Path/GCodeEditor/"
params = FreeCAD.ParamGet(self.paramKey)
posX = params.GetInt("posX")
posY = params.GetInt("posY")
if posX > 0 and posY > 0:
self.move(posX, posY)
width = params.GetInt("width")
height = params.GetInt("height")
if width > 0 and height > 0:
self.resize(width, height)
self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject)
def done(self, *args, **kwargs):
params = FreeCAD.ParamGet(self.paramKey)
params.SetInt("posX", self.x())
params.SetInt("posY", self.y())
params.SetInt("width", self.size().width())
params.SetInt("height", self.size().height())
return QtGui.QDialog.done(self, *args, **kwargs)
def stringsplit(commandline):
returndict = {'command':None, 'X':None, 'Y':None, 'Z':None, 'A':None, 'B':None, 'F':None, 'T':None, 'S':None, 'I':None, 'J':None,'K':None, 'txt': None}
wordlist = [a.strip() for a in commandline.split(" ")]
if wordlist[0][0] == '(':
returndict['command'] = 'message'
returndict['txt'] = wordlist[0]
else:
returndict['command'] = wordlist[0]
for word in wordlist[1:]:
returndict[word[0]] = word[1:]
return returndict
def fmt(num,dec,units):
''' used to format axis moves, feedrate, etc for decimal places and units'''
if units == 'G21': #metric
fnum = '%.*f' % (dec, num)
else: #inch
fnum = '%.*f' % (dec, num/25.4) #since FreeCAD uses metric units internally
return fnum
def editor(gcode):
'''pops up a handy little editor to look at the code output '''
dia = GCodeEditorDialog()
dia.editor.setText(gcode)
result = dia.exec_()
def fcoms(string,commentsym):
''' filter and rebuild comments with user preferred comment symbol'''
if len(commentsym)==1:
s1 = string.replace('(', commentsym)
comment = s1.replace(')', '')
else:
return string
return comment

View File

@@ -0,0 +1,663 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD,Path, xml.sax, os
from PySide import QtCore, QtGui
import DraftGui
# convenience functions
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
# Tooltable XML readers
class FreeCADTooltableHandler( xml.sax.ContentHandler ):
# http://www.tutorialspoint.com/python/python_xml_processing.htm
def __init__(self):
self.tooltable = None
self.tool = None
self.number = None
# Call when an element is found
def startElement(self, tag, attributes):
if tag == "Tooltable":
self.tooltable = Path.Tooltable()
elif tag == "Toolslot":
self.number = int(attributes["number"])
elif tag == "Tool":
self.tool = Path.Tool()
self.tool.Name = str(attributes["name"])
self.tool.ToolType = str(attributes["type"])
self.tool.Material = str(attributes["mat"])
# for some reason without the following line I get an error
print attributes["diameter"]
self.tool.Diameter = float(attributes["diameter"])
self.tool.LengthOffset = float(attributes["length"])
self.tool.FlatRadius = float(attributes["flat"])
self.tool.CornerRadius = float(attributes["corner"])
self.tool.CuttingEdgeAngle = float(attributes["angle"])
self.tool.CuttingEdgeHeight = float(attributes["height"])
# Call when an elements ends
def endElement(self, tag):
if tag == "Toolslot":
if self.tooltable and self.tool and self.number:
self.tooltable.setTool(self.number,self.tool)
self.number = None
self.tool = None
class HeeksTooltableHandler( xml.sax.ContentHandler ):
def __init__(self):
self.tooltable = Path.Tooltable()
self.tool = None
self.number = None
# Call when an element is found
def startElement(self, tag, attributes):
if tag == "Tool":
self.tool = Path.Tool()
self.number = int(attributes["tool_number"])
self.tool.Name = str(attributes["title"])
elif tag == "params":
t = str(attributes["type"])
if t == "drill":
self.tool.ToolType = "Drill"
elif t == "center_drill_bit":
self.tool.ToolType = "CenterDrill"
elif t == "end_mill":
self.tool.ToolType = "EndMill"
elif t == "slot_cutter":
self.tool.ToolType = "SlotCutter"
elif t == "ball_end_mill":
self.tool.ToolType = "BallEndMill"
elif t == "chamfer":
self.tool.ToolType = "Chamfer"
elif t == "engraving_bit":
self.tool.ToolType = "Engraver"
m = str(attributes["material"])
if m == "0":
self.tool.Material = "HighSpeedSteel"
elif m == "1":
self.tool.Material = "Carbide"
# for some reason without the following line I get an error
print attributes["diameter"]
self.tool.Diameter = float(attributes["diameter"])
self.tool.LengthOffset = float(attributes["tool_length_offset"])
self.tool.FlatRadius = float(attributes["flat_radius"])
self.tool.CornerRadius = float(attributes["corner_radius"])
self.tool.CuttingEdgeAngle = float(attributes["cutting_edge_angle"])
self.tool.CuttingEdgeHeight = float(attributes["cutting_edge_height"])
# Call when an elements ends
def endElement(self, tag):
if tag == "Tool":
if self.tooltable and self.tool and self.number:
self.tooltable.setTool(self.number,self.tool)
self.number = None
self.tool = None
# Tooltable Editor
class Editor(QtGui.QDialog):
def __init__(self,obj):
QtGui.QDialog.__init__(self)
self.setObjectName(_fromUtf8("TooltableEditor"))
self.resize(468, 476)
self.verticalLayout = QtGui.QVBoxLayout(self)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.DECIMALS = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt("Decimals",2)
self.FORMAT = DraftGui.makeFormatSpec(self.DECIMALS,'Length')
# left groupbox
self.groupBox = QtGui.QGroupBox(self)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth())
self.groupBox.setSizePolicy(sizePolicy)
self.groupBox.setObjectName(_fromUtf8("groupBox"))
self.verticalLayout_2 = QtGui.QVBoxLayout(self.groupBox)
self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2"))
self.horizontalLayout_9 = QtGui.QHBoxLayout()
self.horizontalLayout_9.setObjectName(_fromUtf8("horizontalLayout_9"))
# import button
self.ButtonImport = QtGui.QPushButton(self.groupBox)
icon = QtGui.QIcon.fromTheme(_fromUtf8("document-import"))
self.ButtonImport.setIcon(icon)
self.ButtonImport.setObjectName(_fromUtf8("ButtonImport"))
self.horizontalLayout_9.addWidget(self.ButtonImport)
# export button
self.ButtonExport = QtGui.QPushButton(self.groupBox)
icon = QtGui.QIcon.fromTheme(_fromUtf8("document-export"))
self.ButtonExport.setIcon(icon)
self.ButtonExport.setObjectName(_fromUtf8("ButtonExport"))
self.horizontalLayout_9.addWidget(self.ButtonExport)
# tools list
self.verticalLayout_2.addLayout(self.horizontalLayout_9)
self.ToolsList = QtGui.QTreeWidget(self.groupBox)
self.ToolsList.setObjectName(_fromUtf8("ToolsList"))
self.ToolsList.header().setDefaultSectionSize(40)
self.verticalLayout_2.addWidget(self.ToolsList)
# add button
self.horizontalLayout_8 = QtGui.QHBoxLayout()
self.horizontalLayout_8.setObjectName(_fromUtf8("horizontalLayout_8"))
self.ButtonAdd = QtGui.QPushButton(self.groupBox)
icon = QtGui.QIcon.fromTheme(_fromUtf8("edit-add"))
self.ButtonAdd.setIcon(icon)
self.ButtonAdd.setObjectName(_fromUtf8("ButtonAdd"))
self.horizontalLayout_8.addWidget(self.ButtonAdd)
# delete button
self.ButtonDelete = QtGui.QPushButton(self.groupBox)
icon = QtGui.QIcon.fromTheme(_fromUtf8("edit-delete"))
self.ButtonDelete.setIcon(icon)
self.ButtonDelete.setObjectName(_fromUtf8("ButtonDelete"))
self.horizontalLayout_8.addWidget(self.ButtonDelete)
# up button
self.ButtonUp = QtGui.QPushButton(self.groupBox)
icon = QtGui.QIcon.fromTheme(_fromUtf8("go-up"))
self.ButtonUp.setIcon(icon)
self.ButtonDelete.setObjectName(_fromUtf8("ButtonUp"))
self.horizontalLayout_8.addWidget(self.ButtonUp)
# down button
self.ButtonDown = QtGui.QPushButton(self.groupBox)
icon = QtGui.QIcon.fromTheme(_fromUtf8("go-down"))
self.ButtonDown.setIcon(icon)
self.ButtonDown.setObjectName(_fromUtf8("ButtonDown"))
self.horizontalLayout_8.addWidget(self.ButtonDown)
# right groupbox
self.verticalLayout_2.addLayout(self.horizontalLayout_8)
self.horizontalLayout.addWidget(self.groupBox)
self.groupBox_2 = QtGui.QGroupBox(self)
self.groupBox_2.setObjectName(_fromUtf8("groupBox_2"))
self.verticalLayout_3 = QtGui.QVBoxLayout(self.groupBox_2)
self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3"))
# name
self.label = QtGui.QLabel(self.groupBox_2)
self.label.setObjectName(_fromUtf8("label"))
self.verticalLayout_3.addWidget(self.label)
self.NameField = QtGui.QLineEdit(self.groupBox_2)
self.NameField.setObjectName(_fromUtf8("NameField"))
self.verticalLayout_3.addWidget(self.NameField)
# type
self.label_2 = QtGui.QLabel(self.groupBox_2)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.verticalLayout_3.addWidget(self.label_2)
self.TypeField = QtGui.QComboBox(self.groupBox_2)
self.TypeField.setObjectName(_fromUtf8("TypeField"))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.TypeField.addItem(_fromUtf8(""))
self.verticalLayout_3.addWidget(self.TypeField)
# material
self.label_3 = QtGui.QLabel(self.groupBox_2)
self.label_3.setObjectName(_fromUtf8("label_3"))
self.verticalLayout_3.addWidget(self.label_3)
self.MaterialField = QtGui.QComboBox(self.groupBox_2)
self.MaterialField.setObjectName(_fromUtf8("MaterialField"))
self.MaterialField.addItem(_fromUtf8(""))
self.MaterialField.addItem(_fromUtf8(""))
self.MaterialField.addItem(_fromUtf8(""))
self.MaterialField.addItem(_fromUtf8(""))
self.MaterialField.addItem(_fromUtf8(""))
self.MaterialField.addItem(_fromUtf8(""))
self.MaterialField.addItem(_fromUtf8(""))
self.MaterialField.addItem(_fromUtf8(""))
self.verticalLayout_3.addWidget(self.MaterialField)
self.label_4 = QtGui.QLabel(self.groupBox_2)
self.label_4.setObjectName(_fromUtf8("label_4"))
self.verticalLayout_3.addWidget(self.label_4)
# diameter
self.horizontalLayout_2 = QtGui.QHBoxLayout()
self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2"))
self.label_5 = QtGui.QLabel(self.groupBox_2)
self.label_5.setObjectName(_fromUtf8("label_5"))
self.horizontalLayout_2.addWidget(self.label_5)
self.DiameterField = QtGui.QDoubleSpinBox(self.groupBox_2)
self.DiameterField.setMaximum(9999)
self.DiameterField.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.DiameterField.setObjectName(_fromUtf8("DiameterField"))
self.horizontalLayout_2.addWidget(self.DiameterField)
self.verticalLayout_3.addLayout(self.horizontalLayout_2)
# length offset
self.horizontalLayout_3 = QtGui.QHBoxLayout()
self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3"))
self.label_6 = QtGui.QLabel(self.groupBox_2)
self.label_6.setObjectName(_fromUtf8("label_6"))
self.horizontalLayout_3.addWidget(self.label_6)
self.LengthOffsetField = QtGui.QDoubleSpinBox(self.groupBox_2)
self.LengthOffsetField.setMaximum(9999)
self.LengthOffsetField.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.LengthOffsetField.setObjectName(_fromUtf8("LengthOffsetField"))
self.horizontalLayout_3.addWidget(self.LengthOffsetField)
self.verticalLayout_3.addLayout(self.horizontalLayout_3)
# flat radius
self.horizontalLayout_4 = QtGui.QHBoxLayout()
self.horizontalLayout_4.setObjectName(_fromUtf8("horizontalLayout_4"))
self.label_7 = QtGui.QLabel(self.groupBox_2)
self.label_7.setObjectName(_fromUtf8("label_7"))
self.horizontalLayout_4.addWidget(self.label_7)
self.FlatRadiusField = QtGui.QDoubleSpinBox(self.groupBox_2)
self.FlatRadiusField.setMaximum(9999)
self.FlatRadiusField.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.FlatRadiusField.setObjectName(_fromUtf8("FlatRadiusField"))
self.horizontalLayout_4.addWidget(self.FlatRadiusField)
self.verticalLayout_3.addLayout(self.horizontalLayout_4)
# corner radius
self.horizontalLayout_5 = QtGui.QHBoxLayout()
self.horizontalLayout_5.setObjectName(_fromUtf8("horizontalLayout_5"))
self.label_8 = QtGui.QLabel(self.groupBox_2)
self.label_8.setObjectName(_fromUtf8("label_8"))
self.horizontalLayout_5.addWidget(self.label_8)
self.CornerRadiusField = QtGui.QDoubleSpinBox(self.groupBox_2)
self.CornerRadiusField.setMaximum(9999)
self.CornerRadiusField.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.CornerRadiusField.setObjectName(_fromUtf8("CornerRadiusField"))
self.horizontalLayout_5.addWidget(self.CornerRadiusField)
self.verticalLayout_3.addLayout(self.horizontalLayout_5)
# cutting edge angle
self.horizontalLayout_6 = QtGui.QHBoxLayout()
self.horizontalLayout_6.setObjectName(_fromUtf8("horizontalLayout_6"))
self.label_9 = QtGui.QLabel(self.groupBox_2)
self.label_9.setObjectName(_fromUtf8("label_9"))
self.horizontalLayout_6.addWidget(self.label_9)
self.CuttingEdgeAngleField = QtGui.QDoubleSpinBox(self.groupBox_2)
self.CuttingEdgeAngleField.setMaximum(360)
self.CuttingEdgeAngleField.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.CuttingEdgeAngleField.setObjectName(_fromUtf8("CuttingEdgeAngleField"))
self.horizontalLayout_6.addWidget(self.CuttingEdgeAngleField)
self.verticalLayout_3.addLayout(self.horizontalLayout_6)
# cutting edge height
self.horizontalLayout_7 = QtGui.QHBoxLayout()
self.horizontalLayout_7.setObjectName(_fromUtf8("horizontalLayout_7"))
self.label_10 = QtGui.QLabel(self.groupBox_2)
self.label_10.setObjectName(_fromUtf8("label_10"))
self.horizontalLayout_7.addWidget(self.label_10)
self.CuttingEdgeHeightField = QtGui.QDoubleSpinBox(self.groupBox_2)
self.CuttingEdgeHeightField.setMaximum(9999)
self.CuttingEdgeHeightField.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.CuttingEdgeHeightField.setObjectName(_fromUtf8("CuttingEdgeHeightField"))
self.horizontalLayout_7.addWidget(self.CuttingEdgeHeightField)
self.verticalLayout_3.addLayout(self.horizontalLayout_7)
self.horizontalLayout.addWidget(self.groupBox_2)
self.verticalLayout.addLayout(self.horizontalLayout)
# ok / cancel box
self.buttonBox = QtGui.QDialogButtonBox(self)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi()
# connect buttons
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), self.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), self.reject)
QtCore.QObject.connect(self.ButtonImport, QtCore.SIGNAL(_fromUtf8("clicked()")), self.read)
QtCore.QObject.connect(self.ButtonExport, QtCore.SIGNAL(_fromUtf8("clicked()")), self.write)
QtCore.QObject.connect(self.ButtonAdd, QtCore.SIGNAL(_fromUtf8("clicked()")), self.addnew)
QtCore.QObject.connect(self.ButtonDelete, QtCore.SIGNAL(_fromUtf8("clicked()")), self.delete)
QtCore.QObject.connect(self.ButtonUp, QtCore.SIGNAL(_fromUtf8("clicked()")), self.moveup)
QtCore.QObject.connect(self.ButtonDown, QtCore.SIGNAL(_fromUtf8("clicked()")), self.movedown)
QtCore.QObject.connect(self.ToolsList, QtCore.SIGNAL(_fromUtf8("currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)")), self.selectTool)
QtCore.QObject.connect(self.NameField, QtCore.SIGNAL(_fromUtf8("textEdited(QString)")), self.changeName)
QtCore.QObject.connect(self.TypeField, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), self.changeType)
QtCore.QObject.connect(self.MaterialField, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), self.changeMaterial)
QtCore.QObject.connect(self.DiameterField, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), self.changeDiameter)
QtCore.QObject.connect(self.LengthOffsetField, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), self.changeLengthOffset)
QtCore.QObject.connect(self.FlatRadiusField, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), self.changeFlatRadius)
QtCore.QObject.connect(self.CornerRadiusField, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), self.changeCornerRadius)
QtCore.QObject.connect(self.CuttingEdgeAngleField, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), self.changeCuttingEdgeAngle)
QtCore.QObject.connect(self.CuttingEdgeHeightField, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), self.changeCuttingEdgeHeight)
QtCore.QMetaObject.connectSlotsByName(self)
self.tooltable = obj.Tooltable.copy()
self.tool = None
self.number = None
self.reset()
def retranslateUi(self):
self.setWindowTitle(_translate("TooltableEditor", "Tooltable editor", None))
self.groupBox.setTitle(_translate("TooltableEditor", "Tools list", None))
self.ButtonImport.setText(_translate("TooltableEditor", "Import...", None))
self.ButtonExport.setText(_translate("TooltableEditor", "Export...", None))
self.ToolsList.headerItem().setText(0, _translate("TooltableEditor", "Slot", None))
self.ToolsList.headerItem().setText(1, _translate("TooltableEditor", "Tool", None))
self.ButtonAdd.setText(_translate("TooltableEditor", "Add new", None))
self.ButtonDelete.setText(_translate("TooltableEditor", "Delete", None))
self.ButtonUp.setText(_translate("TooltableEditor", "Move up", None))
self.ButtonDown.setText(_translate("TooltableEditor", "Move down", None))
self.groupBox_2.setTitle(_translate("TooltableEditor", "Tool properties", None))
self.label.setText(_translate("TooltableEditor", "Name", None))
self.label_2.setText(_translate("TooltableEditor", "Type", None))
self.TypeField.setItemText(0, _translate("TooltableEditor", "Undefined", None))
self.TypeField.setItemText(1, _translate("TooltableEditor", "Drill", None))
self.TypeField.setItemText(2, _translate("TooltableEditor", "Center Drill", None))
self.TypeField.setItemText(3, _translate("TooltableEditor", "Counter Sink", None))
self.TypeField.setItemText(4, _translate("TooltableEditor", "Counter Bore", None))
self.TypeField.setItemText(5, _translate("TooltableEditor", "Reamer", None))
self.TypeField.setItemText(6, _translate("TooltableEditor", "Tap", None))
self.TypeField.setItemText(7, _translate("TooltableEditor", "End Mill", None))
self.TypeField.setItemText(8, _translate("TooltableEditor", "Slot Cutter", None))
self.TypeField.setItemText(9, _translate("TooltableEditor", "Ball End Mill", None))
self.TypeField.setItemText(10, _translate("TooltableEditor", "Chamfer Mill", None))
self.TypeField.setItemText(11, _translate("TooltableEditor", "Corner Round", None))
self.TypeField.setItemText(12, _translate("TooltableEditor", "Engraver", None))
self.label_3.setText(_translate("TooltableEditor", "Material", None))
self.MaterialField.setItemText(0, _translate("TooltableEditor", "Undefined", None))
self.MaterialField.setItemText(1, _translate("TooltableEditor", "High Speed Steel", None))
self.MaterialField.setItemText(2, _translate("TooltableEditor", "High Carbon Tool Steel", None))
self.MaterialField.setItemText(3, _translate("TooltableEditor", "Cast Alloy", None))
self.MaterialField.setItemText(4, _translate("TooltableEditor", "Carbide", None))
self.MaterialField.setItemText(5, _translate("TooltableEditor", "Ceramics", None))
self.MaterialField.setItemText(6, _translate("TooltableEditor", "Diamond", None))
self.MaterialField.setItemText(7, _translate("TooltableEditor", "Sialon", None))
self.label_4.setText(_translate("TooltableEditor", "Properties", None))
self.label_5.setText(_translate("TooltableEditor", "Diameter", None))
# self.DiameterField.setSuffix(_translate("TooltableEditor", "mm", None))
self.label_6.setText(_translate("TooltableEditor", "Length offset", None))
self.LengthOffsetField.setSuffix(_translate("TooltableEditor", "mm", None))
self.label_7.setText(_translate("TooltableEditor", "Flat radius", None))
self.FlatRadiusField.setSuffix(_translate("TooltableEditor", "mm", None))
self.label_8.setText(_translate("TooltableEditor", "Corner radius", None))
self.CornerRadiusField.setSuffix(_translate("TooltableEditor", "mm", None))
self.label_9.setText(_translate("TooltableEditor", "Cutting edge angle", None))
self.CuttingEdgeAngleField.setSuffix(_translate("TooltableEditor", "°", None))
self.label_10.setText(_translate("TooltableEditor", "Cutting edge height", None))
self.CuttingEdgeHeightField.setSuffix(_translate("TooltableEditor", "mm", None))
def reset(self):
"resets the editor with the contents of the current internal tooltable"
self.tool = None
self.number = None
self.ToolsList.clear()
for number,tool in self.tooltable.Tools.iteritems():
item = QtGui.QTreeWidgetItem(self.ToolsList)
item.setText(0,str(number))
item.setText(1,tool.Name)
self.NameField.setText("")
self.TypeField.setCurrentIndex(-1)
self.MaterialField.setCurrentIndex(-1)
self.DiameterField.setValue(0)
self.LengthOffsetField.setValue(0)
self.FlatRadiusField.setValue(0)
self.CornerRadiusField.setValue(0)
self.CuttingEdgeAngleField.setValue(0)
self.CuttingEdgeHeightField.setValue(0)
def selectTool(self,current,previous):
"fills the data of the currently selected tool"
if current:
number = int(current.text(0))
tool = self.tooltable.getTool(number)
if tool:
self.number = number
self.tool = tool
self.NameField.setText(tool.Name)
self.TypeField.setCurrentIndex(self.getType(tool.ToolType))
self.MaterialField.setCurrentIndex(self.getMaterial(tool.Material))
self.DiameterField.setValue(tool.Diameter)
self.LengthOffsetField.setValue(tool.LengthOffset)
self.FlatRadiusField.setValue(tool.FlatRadius)
self.CornerRadiusField.setValue(tool.CornerRadius)
self.CuttingEdgeAngleField.setValue(tool.CuttingEdgeAngle)
self.CuttingEdgeHeightField.setValue(tool.CuttingEdgeHeight)
def getType(self,tooltype):
"gets a combobox index number for a given type or viceversa"
toolslist = ["Drill","CenterDrill","CounterSink","CounterBore",
"Reamer","Tap","EndMill","SlotCutter","BallEndMill",
"ChamferMill","CornerRound","Engraver"]
if isinstance(tooltype,str):
if tooltype in toolslist:
return toolslist.index(tooltype)+1
else:
return 0
else:
if tooltype == 0:
return "Undefined"
else:
return toolslist[tooltype-1]
def getMaterial(self,material):
"gets a combobox index number for a given material or viceversa"
matslist = ["HighSpeedSteel","HighCarbonToolSteel","CastAlloy",
"Carbide","Ceramics","Diamond","Sialon"]
if isinstance(material,str):
if material in matslist:
return matslist.index(material)+1
else:
return 0
else:
if material == 0:
return "Undefined"
else:
return matslist[material-1]
def changeName(self,text):
"called when the corresponding field has changed (needed for nasty pyside bug)"
if self.tool:
self.tool.Name = str(text)
self.changeTool()
if self.number:
l = self.ToolsList.findItems(str(self.number),QtCore.Qt.MatchExactly,0)
if len(l) == 1:
l[0].setText(1,text)
def changeType(self,num):
"called when the corresponding field has changed (needed for nasty pyside bug)"
if self.tool:
self.tool.ToolType = self.getType(num)
self.changeTool()
def changeMaterial(self,num):
"called when the corresponding field has changed (needed for nasty pyside bug)"
if self.tool:
self.tool.Material = self.getMaterial(num)
self.changeTool()
def changeDiameter(self,value):
"called when the corresponding field has changed (needed for nasty pyside bug)"
if self.tool:
self.tool.Diameter = value
self.changeTool()
def changeLengthOffset(self,value):
"called when the corresponding field has changed (needed for nasty pyside bug)"
if self.tool:
self.tool.LengthOffset = value
self.changeTool()
def changeFlatRadius(self,value):
"called when the corresponding field has changed (needed for nasty pyside bug)"
if self.tool:
self.tool.FlatRadius = value
self.changeTool()
def changeCornerRadius(self,value):
"called when the corresponding field has changed (needed for nasty pyside bug)"
if self.tool:
self.tool.CornerRadius = value
self.changeTool()
def changeCuttingEdgeAngle(self,value):
"called when the corresponding field has changed (needed for nasty pyside bug)"
if self.tool:
self.tool.CuttingEdgeAngle = value
self.changeTool()
def changeCuttingEdgeHeight(self,value):
"called when the corresponding field has changed (needed for nasty pyside bug)"
if self.tool:
self.tool.CuttingEdgeHeight = value
self.changeTool()
def changeTool(self):
"changes a given tool"
if self.number and self.tool:
self.tooltable.setTool(self.number,self.tool)
def delete(self):
"deletes the current tool"
if self.number:
self.tooltable.deleteTool(self.number)
self.reset()
def addnew(self):
"adds a new tool at the end of the table"
tool = Path.Tool()
print self.NameField
if self.NameField.text():
tool.Name = str(self.NameField.text())
tool.ToolType = self.getType(self.TypeField.currentIndex())
tool.Material = self.getMaterial(self.MaterialField.currentIndex())
tool.Diameter = self.DiameterField.value()
tool.LengthOffset = self.LengthOffsetField.value()
tool.FlatRadius = self.FlatRadiusField.value()
tool.CornerRadius = self.CornerRadiusField.value()
tool.CuttingEdgeAngle = self.CuttingEdgeAngleField.value()
tool.CuttingEdgeHeight = self.CuttingEdgeHeightField.value()
self.tooltable.addTools(tool)
self.reset()
def read(self):
"imports a tooltable from a file"
filename = QtGui.QFileDialog.getOpenFileName(self, _translate("TooltableEditor","Open tooltable",None),None, _translate("TooltableEditor","Tooltable XML (*.xml);;HeeksCAD tooltable (*.tooltable)",None))
if filename:
parser = xml.sax.make_parser()
parser.setFeature(xml.sax.handler.feature_namespaces, 0)
if os.path.splitext(filename[0])[1].lower() == ".tooltable":
Handler = HeeksTooltableHandler()
else:
Handler = FreeCADTooltableHandler()
parser.setContentHandler( Handler )
parser.parse(str(filename[0]))
if Handler.tooltable:
self.tooltable = Handler.tooltable
self.reset()
def write(self):
"exports the tooltable to a file"
if self.tooltable:
filename = QtGui.QFileDialog.getSaveFileName(self, _translate("TooltableEditor","Save tooltable",None),None, _translate("TooltableEditor","Tooltable XML (*.xml)",None))
if filename:
fil = open(str(filename[0]),"wb")
fil.write('<?xml version="1.0" encoding="UTF-8"?>\n')
fil.write(self.tooltable.Content)
fil.close()
print "Written ",filename[0]
def moveup(self):
"moves a tool to a lower number, if possible"
if self.number:
if self.number < 2:
return
target = self.number - 1
t1 = self.tooltable.getTool(self.number).copy()
self.tooltable.deleteTool(self.number)
if target in self.tooltable.Tools.keys():
t2 = self.tooltable.getTool(target).copy()
self.tooltable.deleteTool(target)
self.tooltable.setTool(self.number,t2)
self.tooltable.setTool(target,t1)
self.reset()
def movedown(self):
"moves a tool to a higher number, if possible"
if self.number:
target = self.number + 1
t1 = self.tooltable.getTool(self.number).copy()
self.tooltable.deleteTool(self.number)
if target in self.tooltable.Tools.keys():
t2 = self.tooltable.getTool(target).copy()
self.tooltable.deleteTool(target)
self.tooltable.setTool(self.number,t2)
self.tooltable.setTool(target,t1)
self.reset()
def edit(objectname):
"""edit(objectname): this is the main function of this module.
opens an editor dialog to edit the Tooltable of the given object"""
obj = FreeCAD.ActiveDocument.getObject(objectname)
if not obj:
raise Exception(_translate("TooltableEditor","Object not found",None))
if not hasattr(obj,"Tooltable"):
raise Exception(_translate("TooltableEditor","Object doesn't have a tooltable property",None))
dialog = Editor(obj)
r = dialog.exec_()
if r:
tooltable = dialog.tooltable
FreeCAD.ActiveDocument.openTransaction("Edit Tooltable")
obj.Tooltable = tooltable
FreeCAD.ActiveDocument.commitTransaction()
obj.ViewObject.finishEditing()

View File

View File

@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' example post for Centroid CNC mill'''
import FreeCAD
import datetime
now = datetime.datetime.now()
originfile = FreeCAD.ActiveDocument.FileName
import Path, PathScripts
from PathScripts import PostUtils
#***************************************************************************
# user editable stuff here
UNITS = "G20" #old style inch units for this shop
MACHINE_NAME = "BigMill"
CORNER_MIN = {'x':-609.6, 'y':-152.4, 'z':0 } #use metric for internal units
CORNER_MAX = {'x':609.6, 'y':152.4, 'z':304.8 } #use metric for internal units
SHOW_EDITOR = True
MODAL = True
COMMENT= ';' #centroid control comment symbol
HEADER = ""
HEADER += ";Exported by FreeCAD\n"
HEADER += ";Post Processor: " + __name__ +"\n"
HEADER += ";CAM file: "+originfile+"\n"
HEADER += ";Output Time:"+str(now)+"\n"
TOOLRETURN = '''M5 M25
G49 H0\n''' #spindle off,height offset canceled,spindle retracted (M25 is a centroid command to retract spindle)
ZAXISRETURN = '''G91 G28 X0 Z0
G90\n'''
SAFETYBLOCK = 'G90 G80 G40 G49\n'
AXIS_DECIMALS = 4
FEED_DECIMALS = 1
SPINDLE_DECIMALS = 0
FOOTER = 'M99'+'\n'
# don't edit with the stuff below the next line unless you know what you're doing :)
#***************************************************************************
if open.__module__ == '__builtin__':
pythonopen = open
def export(selection,filename):
params = ['X','Y','Z','A','B','I','J','F','H','S','T','Q','R','L'] #Using XY plane most of the time so skipping K
for obj in selection:
if not hasattr(obj,"Path"):
print "the object " + obj.Name + " is not a path. Please select only path and Compounds."
return
myMachine = None
for pathobj in selection:
if hasattr(pathobj,"Group"): #We have a compound or selection.
for p in pathobj.Group:
if p.Name == "Machine":
myMachine = p
if myMachine is None:
print "No machine found in this selection"
else:
if myMachine.MachineUnits == "Metric":
UNITS = "G21"
else:
UNITS = "G20"
gcode =''
gcode+= HEADER
gcode+= SAFETYBLOCK
gcode+= UNITS+'\n'
lastcommand = None
gcode+= COMMENT+ selection[0].Description +'\n'
gobjects = []
for g in selection[0].Group:
if g.Name <>'Machine': #filtering out gcode home position from Machine object
gobjects.append(g)
for obj in gobjects:
for c in obj.Path.Commands:
outstring = []
command = c.Name
if command[0]=='(':
command = PostUtils.fcoms(command, COMMENT)
outstring.append(command)
if MODAL == True:
if command == lastcommand:
outstring.pop(0)
if c.Parameters >= 1:
for param in params:
if param in c.Parameters:
if param == 'F':
outstring.append(param + PostUtils.fmt(c.Parameters['F'], FEED_DECIMALS,UNITS))
elif param == 'H':
outstring.append(param + str(int(c.Parameters['H'])))
elif param == 'S':
outstring.append(param + PostUtils.fmt(c.Parameters['S'], SPINDLE_DECIMALS,'G21')) #rpm is unitless-therefore I had to 'fake it out' by using metric units which don't get converted from entered value
elif param == 'T':
outstring.append(param + str(int(c.Parameters['T'])))
else:
outstring.append(param + PostUtils.fmt(c.Parameters[param],AXIS_DECIMALS,UNITS))
outstr = str(outstring)
outstr =outstr.replace('[','')
outstr =outstr.replace(']','')
outstr =outstr.replace("'",'')
outstr =outstr.replace(",",'')
gcode+= outstr + '\n'
lastcommand = c.Name
gcode+= TOOLRETURN
gcode+= SAFETYBLOCK
gcode+= FOOTER
if SHOW_EDITOR:
PostUtils.editor(gcode)
gfile = pythonopen(filename,"wb")
gfile.write(gcode)
gfile.close()

View File

@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
''' Example Post, using Path.Commands instead of Path.toGCode strings for Path gcode output. '''
import FreeCAD
import Path, PathScripts
from PathScripts import PostUtils
def fmt(num):
fnum = ""
fnum += '%.3f' % (num)
return fnum
def ffmt(num):
fnum = ""
fnum += '%.1f' % (num)
return fnum
class saveVals(object):
''' save command info for modal output'''
def __init__(self, command):
self.com = command.Name
self.params = command.Parameters
def retVals(self):
return self.com, self.params
def lineout(command, oldvals, modal):
line = ""
if modal and (oldvals.com == command.Name):
line +=""
else:
line += str(command.Name)
if command.Name == 'M6':
line+= 'T'+str(int(command.Parameters['T']))
if command.Name == 'M3':
line+= 'S'+str(ffmt(command.Parameters['S']))
if command.Name == 'M4':
line+= 'S'+str(ffmt(command.Parameters['S']))
if 'X' in command.Parameters:
line += "X"+str(fmt(command.Parameters['X']))
if 'Y' in command.Parameters:
line += "Y"+str(fmt(command.Parameters['Y']))
if 'Z' in command.Parameters:
line += "Z"+str(fmt(command.Parameters['Z']))
if 'I' in command.Parameters:
line += "I"+str(fmt(command.Parameters['I']))
if 'J' in command.Parameters:
line += "J"+str(fmt(command.Parameters['J']))
if 'F' in command.Parameters:
line += "F"+str(ffmt(command.Parameters['F']))
return line
def export(obj,filename):
modal=True
commands = obj[0]
gcode = ''
safetyblock1 = 'G90G40G49\n'
gcode+=safetyblock1
units = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units")
if units.GetInt('UserSchema') == 0:
firstcommand = Path.Command('G21') #metric mode
else:
firstcommand = Path.Command('G20') #inch mode
oldvals = saveVals(firstcommand) #save first command for modal use
fp = obj[0]
gcode+= firstcommand.Name
if hasattr(fp,"Path"):
for c in fp.Path.Commands:
gcode+= lineout(c, oldvals, modal)+'\n'
oldvals = saveVals(c)
gcode+='M2\n'
gfile = open(filename,"wb")
gfile.write(gcode)
gfile.close()
else:
FreeCAD.Console.PrintError('Select a path object and try again\n')
if obj[0].Editor:
FreeCAD.Console.PrintMessage('Editor Activated\n')
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(gcode)
dia.exec_()

View File

@@ -0,0 +1,93 @@
#***************************************************************************
#* (c) sliptonic (shopinthewoods@gmail.com) 2014 *
#* *
#* This file is part of the FreeCAD CAx development system. *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* FreeCAD is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Lesser General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with FreeCAD; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************/
'''
Dumper is an extremely simple postprocessor file for the Path workbench. It is used
to dump the command list from one or more Path objects for simple inspection. This post
doesn't do any manipulation of the path and doesn't write anything to disk. It just
shows the dialog so you can see it. Useful for debugging, but not much else.
'''
import datetime
now = datetime.datetime.now()
from PathScripts import PostUtils
SHOW_EDITOR = True
# to distinguish python built-in open function from the one declared below
if open.__module__ == '__builtin__':
pythonopen = open
def export(objectslist,filename):
output = '''(This ouput produced with the dump post processor)
(Dump is useful for inspecting the raw commands in your paths)
(but is not useful for driving machines.)
(Consider setting a default postprocessor in your project or )
(exporting your paths using a specific post that matches your machine)
'''
"called when freecad exports a list of objects"
for obj in objectslist:
if not hasattr(obj,"Path"):
print "the object " + obj.Name + " is not a path. Please select only path and Compounds."
return
print "postprocessing..."
output += parse(obj)
if SHOW_EDITOR:
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(output)
result = dia.exec_()
if result:
final = dia.editor.toPlainText()
else:
final = output
else:
final = output
print "done postprocessing."
def parse(pathobj):
out = ""
if hasattr(pathobj,"Group"): #We have a compound or project.
out += "(compound: " + pathobj.Label + ")\n"
for p in pathobj.Group:
out += parse(p)
return out
else: #parsing simple path
if not hasattr(pathobj,"Path"): #groups might contain non-path things like stock.
return out
out += "(Path: " + pathobj.Label + ")\n"
for c in pathobj.Path.Commands:
out += str(c) + "\n"
return out
print __name__ + " gcode postprocessor loaded."

View File

@@ -0,0 +1,102 @@
#***************************************************************************
#* (c) Yorik van Havre (yorik@uncreated.net) 2014 *
#* *
#* This file is part of the FreeCAD CAx development system. *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* FreeCAD is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Lesser General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with FreeCAD; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************/
'''
This is an example postprocessor file for the Path workbench. It is used
to save a list of FreeCAD Path objects to a file.
Read the Path Workbench documentation to know how to convert Path objects
to GCode.
'''
import datetime
now = datetime.datetime.now()
# to distinguish python built-in open function from the one declared below
if open.__module__ == '__builtin__':
pythonopen = open
def export(objectslist,filename):
"called when freecad exports a list of objects"
if len(objectslist) > 1:
print "This script is unable to write more than one Path object"
return
obj = objectslist[0]
if not hasattr(obj,"Path"):
print "the given object is not a path"
gcode = obj.Path.toGCode()
gcode = parse(gcode)
gfile = pythonopen(filename,"wb")
gfile.write(gcode)
gfile.close()
def parse(inputstring):
"parse(inputstring): returns a parsed output string"
print "postprocessing..."
output = ""
# write some stuff first
output += "N10 ;time:"+str(now)+"\n"
output += "N20 G17 G20 G80 G40 G90\n"
output += "N30 (Exported by FreeCAD)\n"
linenr = 100
lastcommand = None
# treat the input line by line
lines = inputstring.split("\n")
for line in lines:
# split the G/M command from the arguments
if " " in line:
command,args = line.split(" ",1)
else:
# no space found, which means there are no arguments
command = line
args = ""
# add a line number
output += "N" + str(linenr) + " "
# only print the command if it is not the same as the last one
if command != lastcommand:
output += command + " "
output += args + "\n"
# increment the line number
linenr += 10
# store the latest command
lastcommand = command
# write some more stuff at the end
output += "N" + str(linenr) + " M05\n"
output += "N" + str(linenr + 10) + " M25\n"
output += "N" + str(linenr + 20) + " G00 X-1.0 Y1.0\n"
output += "N" + str(linenr + 30) + " G17 G80 G40 G90\n"
output += "N" + str(linenr + 40) + " M99\n"
print "done postprocessing."
return output
print __name__ + " gcode postprocessor loaded."

View File

@@ -0,0 +1,101 @@
#***************************************************************************
#* (c) Yorik van Havre (yorik@uncreated.net) 2014 *
#* *
#* This file is part of the FreeCAD CAx development system. *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* FreeCAD is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Lesser General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with FreeCAD; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************/
'''
This is an example preprocessor file for the Path workbench. Its aim is to
open a gcode file, parse its contents, and create the appropriate objects
in FreeCAD.
Read the Path Workbench documentation to know how to create Path objects
from GCode.
'''
import os, Path
import FreeCAD
# to distinguish python built-in open function from the one declared below
if open.__module__ == '__builtin__':
pythonopen = open
def open(filename):
"called when freecad opens a file."
docname = os.path.splitext(os.path.basename(filename))[0]
doc = FreeCAD.newDocument(docname)
insert(filename,doc.Name)
def insert(filename,docname):
"called when freecad imports a file"
gfile = pythonopen(filename)
gcode = gfile.read()
gfile.close()
gcode = parse(gcode)
doc = FreeCAD.getDocument(docname)
obj = doc.addObject("Path::Feature","Path")
path = Path.Path(gcode)
obj.Path = path
def parse(inputstring):
"parse(inputstring): returns a parsed output string"
print "preprocessing..."
# split the input by line
lines = inputstring.split("\n")
output = ""
lastcommand = None
for l in lines:
# remove any leftover trailing and preceding spaces
l = l.strip()
if not l:
# discard empty lines
continue
if l[0].upper() in ["N"]:
# remove line numbers
l = l.split(" ",1)[1]
if l[0] in ["(","%","#"]:
# discard comment and other non strictly gcode lines
continue
if l[0].upper() in ["G","M"]:
# found a G or M command: we store it
output += l + "\n"
last = l[0].upper()
for c in l[1:]:
if not c.isdigit():
break
else:
last += c
lastcommand = last
elif lastcommand:
# no G or M command: we repeat the last one
output += lastcommand + " " + l + "\n"
print "done preprocessing."
return output
print __name__ + " gcode preprocessor loaded."

View File

@@ -0,0 +1,234 @@
#***************************************************************************
#* (c) sliptonic (shopinthewoods@gmail.com) 2014 *
#* *
#* This file is part of the FreeCAD CAx development system. *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* FreeCAD is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Lesser General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with FreeCAD; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************/
'''
This is a postprocessor file for the Path workbench. It is used to
take a pseudo-gcode fragment outputted by a Path object, and output
real GCode suitable for a linuxcnc 3 axis mill. This postprocessor, once placed
in the appropriate PathScripts folder, can be used directly from inside FreeCAD,
via the GUI importer or via python scripts with:
import linuxcnc_post
linuxcnc_post.export(object,"/path/to/file.ncc")
'''
import datetime
now = datetime.datetime.now()
from PathScripts import PostUtils
#These globals set common customization preferences
OUTPUT_COMMENTS = True
OUTPUT_HEADER = True
OUTPUT_LINE_NUMBERS = False
SHOW_EDITOR = True
MODAL = False #if true commands are suppressed if the same as previous line.
COMMAND_SPACE = " "
LINENR = 100 #line number starting value
#These globals will be reflected in the Machine configuration of the project
UNITS = "G21" #G21 for metric, G20 for us standard
MACHINE_NAME = "Millstone"
CORNER_MIN = {'x':0, 'y':0, 'z':0 }
CORNER_MAX = {'x':500, 'y':300, 'z':300 }
#Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''G17 G90
'''
#Postamble text will appear following the last operation.
POSTAMBLE = '''M05
G00 X-1.0 Y1.0
G17 G90
M2
'''
#Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
#Post operation text will be inserted after every operation
POST_OPERATION = ''''''
#Tool Change commands will be inserted before a tool change
TOOL_CHANGE = ''''''
# to distinguish python built-in open function from the one declared below
if open.__module__ == '__builtin__':
pythonopen = open
def export(objectslist,filename):
global UNITS
for obj in objectslist:
if not hasattr(obj,"Path"):
print "the object " + obj.Name + " is not a path. Please select only path and Compounds."
return
print "postprocessing..."
gcode = ""
#Find the machine.
#The user my have overriden post processor defaults in the GUI. Make sure we're using the current values in the Machine Def.
myMachine = None
for pathobj in objectslist:
if hasattr(pathobj,"Group"): #We have a compound or project.
for p in pathobj.Group:
if p.Name == "Machine":
myMachine = p
if myMachine is None:
print "No machine found in this project"
else:
if myMachine.MachineUnits == "Metric":
UNITS = "G21"
else:
UNITS = "G20"
# write header
if OUTPUT_HEADER:
gcode += linenumber() + "(Exported by FreeCAD)\n"
gcode += linenumber() + "(Post Processor: " + __name__ +")\n"
gcode += linenumber() + "(Output Time:"+str(now)+")\n"
#Write the preamble
if OUTPUT_COMMENTS: gcode += linenumber() + "(begin preamble)\n"
for line in PREAMBLE.splitlines(True):
gcode += linenumber() + line
gcode += linenumber() + UNITS + "\n"
for obj in objectslist:
#do the pre_op
if OUTPUT_COMMENTS: gcode += linenumber() + "(begin operation: " + obj.Label + ")\n"
for line in PRE_OPERATION.splitlines(True):
gcode += linenumber() + line
gcode += parse(obj)
#do the post_op
if OUTPUT_COMMENTS: gcode += linenumber() + "(finish operation: " + obj.Label + ")\n"
for line in POST_OPERATION.splitlines(True):
gcode += linenumber() + line
#do the post_amble
if OUTPUT_COMMENTS: gcode += "(begin postamble)\n"
for line in POSTAMBLE.splitlines(True):
gcode += linenumber() + line
if SHOW_EDITOR:
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(gcode)
result = dia.exec_()
if result:
final = dia.editor.toPlainText()
else:
final = gcode
else:
final = gcode
print "done postprocessing."
gfile = pythonopen(filename,"wb")
gfile.write(gcode)
gfile.close()
def linenumber():
global LINENR
if OUTPUT_LINE_NUMBERS == True:
LINENR += 10
return "N" + str(LINENR) + " "
return ""
def parse(pathobj):
out = ""
lastcommand = None
#params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control the order of parameters
params = ['X','Y','Z','A','B','I','J','F','S','T','Q','R','L'] #linuxcnc doesn't want K properties on XY plane Arcs need work.
if hasattr(pathobj,"Group"): #We have a compound or project.
if OUTPUT_COMMENTS: out += linenumber() + "(compound: " + pathobj.Label + ")\n"
for p in pathobj.Group:
out += parse(p)
return out
else: #parsing simple path
if not hasattr(pathobj,"Path"): #groups might contain non-path things like stock.
return out
if OUTPUT_COMMENTS: out += linenumber() + "(Path: " + pathobj.Label + ")\n"
for c in pathobj.Path.Commands:
outstring = []
command = c.Name
outstring.append(command)
# if modal: only print the command if it is not the same as the last one
if MODAL == True:
if command == lastcommand:
outstring.pop(0)
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
if param == 'F':
outstring.append(param + format(c.Parameters['F'], '.2f'))
elif param == 'T':
outstring.append(param + str(c.Parameters['T']))
else:
outstring.append(param + format(c.Parameters[param], '.4f'))
# store the latest command
lastcommand = command
# Check for Tool Change:
if command == 'M6':
if OUTPUT_COMMENTS: out += linenumber() + "(begin toolchange)\n"
for line in TOOL_CHANGE.splitlines(True):
out += linenumber() + line
if command == "message":
if OUTPUT_COMMENTS == False:
out = []
else:
outstring.pop(0) #remove the command
#prepend a line number and append a newline
if len(outstring) >= 1:
if OUTPUT_LINE_NUMBERS:
outstring.insert(0,(linenumber()))
#append the line to the final output
for w in outstring:
out += w + COMMAND_SPACE
out = out.strip() + "\n"
return out
print __name__ + " gcode postprocessor loaded."

View File

@@ -0,0 +1,254 @@
#***************************************************************************
#* (c) sliptonic (shopinthewoods@gmail.com) 2014 *
#* *
#* This file is part of the FreeCAD CAx development system. *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* FreeCAD is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Lesser General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with FreeCAD; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************/
'''
This is an postprocessor file for the Path workbench. It will output path data in a format suitable for OpenSBP controllers like shopbot. This postprocessor, once placed in the appropriate PathScripts folder, can be used directly from inside FreeCAD,
via the GUI importer or via python scripts with:
import Path
Path.write(object,"/path/to/file.ncc","post_opensbp")
'''
import datetime
now = datetime.datetime.now()
from PathScripts import PostUtils
OUTPUT_COMMENTS = False
OUTPUT_HEADER = True
SHOW_EDITOR = True
COMMAND_SPACE = ","
#Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''
'''
#Postamble text will appear following the last operation.
POSTAMBLE = '''
'''
#Pre operation text will be inserted before every operation
PRE_OPERATION = '''
'''
#Post operation text will be inserted after every operation
POST_OPERATION = '''
'''
#Tool Change commands will be inserted before a tool change
TOOL_CHANGE = ''''A tool change is about to happen
'''
def move(commandline):
print "processing a move"
txt = ""
if commandline['F'] != None : #Feed Rate has changed
print "command contains an F"
txt += feedrate(commandline)
if commandline['command'] == 'G0':
txt += "J3"
else:
txt += "M3"
for p in ['X','Y','Z']:
if commandline[p] == None:
txt += "," + format(CurrentState[p], '.4f')
else:
txt += "," + format(eval(commandline[p]), '.4f')
CurrentState[p] = eval(commandline[p])
txt += "\n"
return txt
def feedrate(commandline):
#figure out what kind of feed rate we're talking about jog or move
NOCHANGE = False
txt = ""
setspeed = eval(commandline['F'])
if commandline['command'] == 'G1': #move
movetype = "MS"
else: #jog
movetype = "JS"
print "movetype: " + movetype
if commandline['X'] == None:
newX = CurrentState['X']
else:
newX = eval(commandline['X'])
if commandline['Y'] == None:
newY = CurrentState['Y']
else:
newY = eval(commandline['Y'])
if commandline['Z'] == None:
newZ = CurrentState['Z']
else:
newZ = eval(commandline['Z'])
if newX == CurrentState['X'] and newY == CurrentState['Y']:
# ZMove only
AXISMOVE = "Z"
if CurrentState[movetype+'Z'] == setspeed:
NOCHANGE = True
else:
AXISMOVE = "XY"
if CurrentState[movetype+'XY'] == setspeed:
NOCHANGE = True
if AXISMOVE == "XY" and newZ != CurrentState['Z']:
AXISMOVE = "XYZ"
if CurrentState[movetype+'XY'] == setspeed and CurrentState[movetype+'Z'] == setspeed:
NOCHANGE = True
print "axismove: " + AXISMOVE
#figure out if it has actually changed.
if NOCHANGE == True:
txt = ""
else: #something changed
if AXISMOVE == "XY":
txt += movetype + "," + format(setspeed, '.4f')
CurrentState[movetype+'XY'] = setspeed
elif AXISMOVE == "Z":
txt += movetype + ",," + format(setspeed, '.4f')
CurrentState[movetype+'Z'] = setspeed
else: #XYZMOVE
txt += movetype + "," + format(setspeed, '.4f') + "," + format(setspeed, '.4f')
print txt
CurrentState[movetype+'XY'] = setspeed
CurrentState[movetype+'Z'] = setspeed
txt += "\n"
return txt
def arc(commandline):
if commandline['command'] == 'G2': #CW
dirstring = "1"
else: #G3 means CCW
dirstring = "-1"
txt = "CG,,"
txt += format(eval(commandline['X']), '.4f') + ","
txt += format(eval(commandline['Y']), '.4f') + ","
txt += format(eval(commandline['I']), '.4f') + ","
txt += format(eval(commandline['J']), '.4f') + ","
txt += "T" + ","
txt += dirstring
txt += "\n"
return txt
def tool_change(commandline):
print "tool change"
txt = ""
if OUTPUT_COMMENTS: txt += "'a tool change happens now\n"
for line in TOOL_CHANGE.splitlines(True):
txt += line
txt += "&ToolName = " + commandline['T']
txt += "\n"
txt += "&Tool=" + commandline['T']
txt += "\n"
return txt
def comment(commandline):
print "a comment"
def spindle(commandline):
txt =""
if commandline['command'] == "M3": #CW
pass
else:
pass
txt += "TR," + commandline['S']
return txt
#Supported Commands
scommands = {"G0": move,
"G1": move,
"G2": arc,
"G3": arc,
"M6": tool_change,
"M3": spindle,
"message": comment
}
CurrentState = {'X':0, 'Y':0, 'Z':0, 'F':0, 'S':0, 'JSXY':0, 'JSZ':0, 'MSXY':0, 'MSZ':0}
def parse(inputstring):
"parse(inputstring): returns a parsed output string"
print "postprocessing..."
output = ""
params = ['X','Y','Z','A','B','I','J','K','F','S','T'] #This list control the order of parameters
# write some stuff first
if OUTPUT_HEADER:
print "outputting header"
output += "'Exported by FreeCAD\n"
output += "'Post Processor: " + __name__ +"\n"
output += "'Output Time:"+str(now)+"\n"
#Write the preamble
if OUTPUT_COMMENTS: output += "'begin preamble\n"
for line in PREAMBLE.splitlines(True):
output += line
# treat the input line by line
lines = inputstring.splitlines(True)
for line in lines:
commandline = PostUtils.stringsplit(line)
command = commandline['command']
try:
print commandline
print "command: " + command
print command in scommands
output += scommands[command](commandline)
except:
print "I don't know what the hell the command: " + command + " means. Maybe I should support it."
print "finished"
# write some more stuff at the end
if OUTPUT_COMMENTS: output += "'begin postamble\n"
for line in POSTAMBLE.splitlines(True):
output += line
if SHOW_EDITOR:
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(output)
result = dia.exec_()
if result:
final = dia.editor.toPlainText()
else:
final = output
else:
final = output
print "done postprocessing."
return final
print __name__ + " gcode postprocessor loaded."

View File

@@ -0,0 +1,202 @@
#***************************************************************************
#* (c) sliptonic (shopinthewoods<at>gmail.com) 2014 *
#* *
#* This file is part of the FreeCAD CAx development system. *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* FreeCAD is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Lesser General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with FreeCAD; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************/
'''
This is a preprocessor file for the Path workbench. Its aim is to
parse the contents of a given OpenSBP file, and transform it to make it
suitable for use in a Path object. This preprocessor, once placed in the
appropriate PathScripts folder, can be used directly from inside FreeCAD,
via the GUI importer or via python scripts with:
import opensbp_pre
opensbp_pre.insert("/path/to/myfile.ngc","DocumentName")
DONE
Correctly imports single axis and multi axis moves.
Stores Jog and Feed speeds
Appends Multiaxis Feed speed to G1 moves
Jog rates don't append to G0 moves
Make single axis feed rates work
Imports CG (non-diameter) arcs.
Handles CW and CCW spindle speeds
if operations are preceded by a comment ('New Path ...) They are split into multiple paths
TODO
Many other OpenSBP commands not handled
'''
AXIS = 'X','Y','Z','A','B' #OpenSBP always puts multiaxis move parameters in this order
SPEEDS = 'XY','Z','A','B'
import FreeCAD
import os, Path
# to distinguish python built-in open function from the one declared below
if open.__module__ == '__builtin__':
pythonopen = open
def open(filename):
"called when freecad opens a file."
docname = os.path.splitext(os.path.basename(filename))[0]
doc = FreeCAD.newDocument(docname)
insert(filename,doc.Name)
def insert(filename,docname):
"called when freecad imports a file"
"This insert expects parse to return a list of strings"
"each string will become a separate path"
gfile = pythonopen(filename)
gcode = gfile.read()
gfile.close()
gcode = parse(gcode)
doc = FreeCAD.getDocument(docname)
for subpath in gcode:
obj = doc.addObject("Path::Feature","Path")
path = Path.Path(subpath)
obj.Path = path
def parse(inputstring):
"parse(inputstring): returns a list of parsed output string"
print "preprocessing..."
# split the input by line
lines = inputstring.split("\n")
return_output = []
output = ""
last = {'X':None,'Y':None,'Z':None,'A':None,'B':None}
lastrapidspeed = {'XY':"50", 'Z':"50", 'A':"50", 'B':"50" } #set default rapid speeds
lastfeedspeed = {'XY':"50", 'Z':"50", 'A':"50", 'B':"50" } #set default feed speed
movecommand = ['G1', 'G0', 'G02', 'G03']
for l in lines:
# remove any leftover trailing and preceding spaces
l = l.strip()
if not l:
# discard empty lines
continue
if l[0] in ["'","&"]:
# discard comment and other non strictly gcode lines
if l[0:9] == "'New Path":
# starting new path
if any (x in output for x in movecommand): #make sure the path has at least one move command.
return_output.append(output)
output = ""
continue
words = [a.strip() for a in l.split(",")]
words[0] = words[0].upper()
if words[0] in ["J2","J3","J4","J5","M2","M3","M4","M5"]: #multi-axis jogs and moves
if words[0][0] == 'J': #jog move
s = "G0 "
else: #feed move
s = "G1 "
speed = lastfeedspeed["XY"]
for i in range (1, len(words)):
if words [i] == '':
if last[AXIS[i-1]] == None:
continue
else:
s += AXIS[i-1] + last[AXIS[i-1]]
else:
s += AXIS[i-1] + words[i]
last[AXIS[i-1]] = words[i]
output += s +" F" + speed + '\n'
if words[0] in ["JA","JB","JX","JY","JZ","MA","MB","MX","MY","MZ"]: #single axis jogs and moves
if words[0][0] == 'J': #jog move
s = "G0 "
if words[0][1] in ['X','Y']:
speed = lastrapidspeed["XY"]
else:
speed = lastrapidspeed[words[0][1]]
else: #feed move
s = "G1 "
if words[0][1] in ['X','Y']:
speed = lastfeedspeed["XY"]
else:
speed = lastfeedspeed[words[0][1]]
last[words[0][1]] = words[1]
output += s + words[0][1] + str(words[1]) + " F" + speed + "\n"
if words[0] in ["JS"]: #set jog speed
for i in range (1, len(words)):
if words [i] == '':
continue
else:
lastrapidspeed[SPEEDS[i-1]] = words[i]
if words[0] in ["MD"]: #move distance with distance and angle.
#unsupported at this time
continue
if words[0] in ["MH"]: #move home
#unsupported at this time
continue
if words[0] in ["MS"]: #set move speed
for i in range (1, len(words)):
if words [i] == '':
continue
else:
lastfeedspeed[SPEEDS[i-1]] = words[i]
if words[0] in ["MO"]: #motors off
#unsupported at this time
continue
if words[0] in ["TR"]: #Setting spindle speed
if int(words[1]) < 0:
s = "M4 S"
else:
s = "M3 S"
s += str(abs(int(words[1])))
output += s + '\n'
if words[0] in ["CG"]: #Gcode circle/arc
if words[1] != "": # diameter mode
print "diameter mode not supported"
continue
else:
if words[7] == "1": #CW
s = "G2"
else: #CCW
s = "G3"
s += " X" + words[2] + " Y" + words[3] + " I" + words[4] + " J" + words[5] + " F" + str(lastfeedspeed["XY"])
output += s + '\n'
#Make sure all appended paths have at least one move command.
if any (x in output for x in movecommand):
return_output.append(output)
print "done preprocessing."
return return_output
print __name__ + " gcode preprocessor loaded."