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:
142
src/Mod/Path/PathScripts/DlgSettingsPath.ui
Normal file
142
src/Mod/Path/PathScripts/DlgSettingsPath.ui
Normal 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>
|
||||
39
src/Mod/Path/PathScripts/Macros/create_tool.py
Normal file
39
src/Mod/Path/PathScripts/Macros/create_tool.py
Normal 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()
|
||||
65
src/Mod/Path/PathScripts/Macros/holefinder.py
Normal file
65
src/Mod/Path/PathScripts/Macros/holefinder.py
Normal 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)
|
||||
45
src/Mod/Path/PathScripts/Macros/outer_profile.py
Normal file
45
src/Mod/Path/PathScripts/Macros/outer_profile.py
Normal 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)
|
||||
137
src/Mod/Path/PathScripts/PathComment.py
Normal file
137
src/Mod/Path/PathScripts/PathComment.py
Normal 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")
|
||||
139
src/Mod/Path/PathScripts/PathCompoundExtended.py
Normal file
139
src/Mod/Path/PathScripts/PathCompoundExtended.py
Normal 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")
|
||||
135
src/Mod/Path/PathScripts/PathCopy.py
Normal file
135
src/Mod/Path/PathScripts/PathCopy.py
Normal 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")
|
||||
136
src/Mod/Path/PathScripts/PathDressup.py
Normal file
136
src/Mod/Path/PathScripts/PathDressup.py
Normal 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")
|
||||
199
src/Mod/Path/PathScripts/PathDrilling.py
Normal file
199
src/Mod/Path/PathScripts/PathDrilling.py
Normal 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")
|
||||
151
src/Mod/Path/PathScripts/PathFixture.py
Normal file
151
src/Mod/Path/PathScripts/PathFixture.py
Normal 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")
|
||||
108
src/Mod/Path/PathScripts/PathFromShape.py
Normal file
108
src/Mod/Path/PathScripts/PathFromShape.py
Normal 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")
|
||||
137
src/Mod/Path/PathScripts/PathHop.py
Normal file
137
src/Mod/Path/PathScripts/PathHop.py
Normal 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")
|
||||
290
src/Mod/Path/PathScripts/PathKurve.py
Normal file
290
src/Mod/Path/PathScripts/PathKurve.py
Normal 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")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
258
src/Mod/Path/PathScripts/PathKurveUtils.py
Normal file
258
src/Mod/Path/PathScripts/PathKurveUtils.py
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
163
src/Mod/Path/PathScripts/PathLoadTool.py
Normal file
163
src/Mod/Path/PathScripts/PathLoadTool.py
Normal 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")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
235
src/Mod/Path/PathScripts/PathMachine.py
Normal file
235
src/Mod/Path/PathScripts/PathMachine.py
Normal 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")
|
||||
|
||||
|
||||
148
src/Mod/Path/PathScripts/PathPlane.py
Normal file
148
src/Mod/Path/PathScripts/PathPlane.py
Normal 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")
|
||||
|
||||
|
||||
325
src/Mod/Path/PathScripts/PathPocket.py
Normal file
325
src/Mod/Path/PathScripts/PathPocket.py
Normal 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")
|
||||
90
src/Mod/Path/PathScripts/PathPost.py
Normal file
90
src/Mod/Path/PathScripts/PathPost.py
Normal 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")
|
||||
284
src/Mod/Path/PathScripts/PathProfile.py
Normal file
284
src/Mod/Path/PathScripts/PathProfile.py
Normal 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")
|
||||
154
src/Mod/Path/PathScripts/PathProject.py
Normal file
154
src/Mod/Path/PathScripts/PathProject.py
Normal 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")
|
||||
213
src/Mod/Path/PathScripts/PathSelection.py
Normal file
213
src/Mod/Path/PathScripts/PathSelection.py
Normal 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
|
||||
|
||||
133
src/Mod/Path/PathScripts/PathStock.py
Normal file
133
src/Mod/Path/PathScripts/PathStock.py
Normal 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")
|
||||
|
||||
143
src/Mod/Path/PathScripts/PathStop.py
Normal file
143
src/Mod/Path/PathScripts/PathStop.py
Normal 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")
|
||||
164
src/Mod/Path/PathScripts/PathToolLenOffset.py
Normal file
164
src/Mod/Path/PathScripts/PathToolLenOffset.py
Normal 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")
|
||||
|
||||
|
||||
|
||||
|
||||
62
src/Mod/Path/PathScripts/PathToolTableEdit.py
Normal file
62
src/Mod/Path/PathScripts/PathToolTableEdit.py
Normal 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")
|
||||
|
||||
384
src/Mod/Path/PathScripts/PathUtils.py
Normal file
384
src/Mod/Path/PathScripts/PathUtils.py
Normal 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
|
||||
|
||||
162
src/Mod/Path/PathScripts/PostUtils.py
Normal file
162
src/Mod/Path/PathScripts/PostUtils.py
Normal 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
|
||||
|
||||
|
||||
|
||||
663
src/Mod/Path/PathScripts/TooltableEditor.py
Normal file
663
src/Mod/Path/PathScripts/TooltableEditor.py
Normal 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()
|
||||
|
||||
0
src/Mod/Path/PathScripts/__init__.py
Normal file
0
src/Mod/Path/PathScripts/__init__.py
Normal file
145
src/Mod/Path/PathScripts/centroid_post.py
Normal file
145
src/Mod/Path/PathScripts/centroid_post.py
Normal 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()
|
||||
|
||||
106
src/Mod/Path/PathScripts/comparams_post.py
Normal file
106
src/Mod/Path/PathScripts/comparams_post.py
Normal 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_()
|
||||
|
||||
|
||||
93
src/Mod/Path/PathScripts/dumper_post.py
Normal file
93
src/Mod/Path/PathScripts/dumper_post.py
Normal 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."
|
||||
|
||||
102
src/Mod/Path/PathScripts/example_post.py
Normal file
102
src/Mod/Path/PathScripts/example_post.py
Normal 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."
|
||||
|
||||
101
src/Mod/Path/PathScripts/example_pre.py
Normal file
101
src/Mod/Path/PathScripts/example_pre.py
Normal 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."
|
||||
|
||||
234
src/Mod/Path/PathScripts/linuxcnc_post.py
Normal file
234
src/Mod/Path/PathScripts/linuxcnc_post.py
Normal 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."
|
||||
|
||||
254
src/Mod/Path/PathScripts/opensbp_post.py
Normal file
254
src/Mod/Path/PathScripts/opensbp_post.py
Normal 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."
|
||||
|
||||
202
src/Mod/Path/PathScripts/opensbp_pre.py
Normal file
202
src/Mod/Path/PathScripts/opensbp_pre.py
Normal 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."
|
||||
|
||||
Reference in New Issue
Block a user