├── .gitignore ├── COPYING.TXT ├── Changelog ├── INSTALL.TXT ├── LICENSE.TXT ├── MANIFEST.in ├── Makefile ├── README.TXT ├── Tests ├── CylindricalCutterTest.py ├── DropCutterTest.py ├── PolygonExtractorTest.py ├── PushCutterTest.py ├── STLImporterTest.py ├── SimulationTest.py ├── SphericalCutterTest.py ├── ToroidalCutterTest.py ├── TriangleKdtreeTest.py ├── TriangleTest.py └── VisualizationTest.py ├── debian ├── changelog ├── compat ├── control ├── copyright ├── dirs ├── docs ├── manpages ├── patches │ └── series ├── pycam.mime ├── pycam.sharedmimeinfo ├── pycompat ├── pyversions ├── rules ├── source │ └── format └── watch ├── doc └── ParameterGroup.dia ├── man ├── Makefile └── pycam.1.inc ├── pycam ├── Cutters │ ├── BaseCutter.py │ ├── CylindricalCutter.py │ ├── SphericalCutter.py │ ├── ToroidalCutter.py │ └── __init__.py ├── Exporters │ ├── EMCToolExporter.py │ ├── GCodeExporter.py │ ├── STLExporter.py │ ├── SVGExporter.py │ └── __init__.py ├── Geometry │ ├── Letters.py │ ├── Line.py │ ├── Matrix.py │ ├── Model.py │ ├── Path.py │ ├── Plane.py │ ├── Point.py │ ├── PointKdtree.py │ ├── Polygon.py │ ├── PolygonExtractor.py │ ├── Triangle.py │ ├── TriangleKdtree.py │ ├── __init__.py │ ├── intersection.py │ ├── kdtree.py │ └── utils.py ├── Gui │ ├── Console.py │ ├── ControlsGTK.py │ ├── OpenGLTools.py │ ├── Project.py │ ├── Settings.py │ ├── Visualization.py │ ├── __init__.py │ └── common.py ├── Importers │ ├── CXFImporter.py │ ├── DXFImporter.py │ ├── PSImporter.py │ ├── STLImporter.py │ ├── SVGImporter.py │ ├── TestModel.py │ ├── ToolpathSettingsParser.py │ └── __init__.py ├── PathGenerators │ ├── ContourFollow.py │ ├── DropCutter.py │ ├── EngraveCutter.py │ ├── PushCutter.py │ └── __init__.py ├── PathProcessors │ ├── ContourCutter.py │ ├── PathAccumulator.py │ ├── PolygonCutter.py │ ├── SimpleCutter.py │ ├── ZigZagCutter.py │ └── __init__.py ├── Physics │ ├── __init__.py │ └── ode_physics.py ├── Plugins │ ├── Bounds.py │ ├── Clipboard.py │ ├── EMCToolExport.py │ ├── FilenameDialog.py │ ├── Fonts.py │ ├── GCodeParameters.py │ ├── GCodeTouchOff.py │ ├── GtkConsole.py │ ├── Log.py │ ├── MemoryAnalyzer.py │ ├── ModelExport.py │ ├── ModelExtrusion.py │ ├── ModelPlaneMirror.py │ ├── ModelPolygons.py │ ├── ModelPosition.py │ ├── ModelProjection.py │ ├── ModelRotation.py │ ├── ModelScaling.py │ ├── ModelSupport.py │ ├── ModelSupportDistributed.py │ ├── ModelSupportGrid.py │ ├── ModelSwapAxes.py │ ├── Models.py │ ├── OpenGLViewAxes.py │ ├── OpenGLViewBounds.py │ ├── OpenGLViewDimension.py │ ├── OpenGLViewGrid.py │ ├── OpenGLViewModel.py │ ├── OpenGLViewSupportModelPreview.py │ ├── OpenGLViewToolpath.py │ ├── OpenGLWindow.py │ ├── ParallelProcessing.py │ ├── ParameterGroupManager.py │ ├── PathParameters.py │ ├── PathPatterns.py │ ├── PluginSelector.py │ ├── PostprocessorEMC2.py │ ├── ProcessStrategies.py │ ├── Processes.py │ ├── ProgressBar.py │ ├── TaskParameters.py │ ├── TaskTypes.py │ ├── Tasks.py │ ├── ToolParameters.py │ ├── ToolTypes.py │ ├── ToolpathCrop.py │ ├── ToolpathExport.py │ ├── ToolpathGrid.py │ ├── ToolpathSimulation.py │ ├── Toolpaths.py │ ├── Tools.py │ ├── Units.py │ └── __init__.py ├── Simulation │ ├── ODEBlocks.py │ ├── ZBuffer.py │ └── __init__.py ├── Toolpath │ ├── Generator.py │ ├── MotionGrid.py │ ├── SupportGrid.py │ └── __init__.py ├── Utils │ ├── FontCache.py │ ├── __init__.py │ ├── iterators.py │ ├── locations.py │ ├── log.py │ ├── polynomials.py │ ├── rootsolver.py │ ├── threading.py │ └── xml_handling.py └── __init__.py ├── pyinstaller ├── hooks │ └── hook-pycam.py ├── pycam.spec ├── pyinstaller_fix_module_exception.patch └── pyinstaller_info.txt ├── pylint.sh ├── release_info.txt ├── samples ├── Box0+1.stl ├── Box0.stl ├── Box1.stl ├── Box2.stl ├── Box3.stl ├── SampleScene.stl ├── SampleScene2.scad ├── SampleScene2.stl ├── SampleScene3.scad ├── SampleScene3.stl ├── Sphere0.stl ├── Sphere_cut.scad ├── Sphere_cut.stl ├── TestModel.stl ├── multilayer_engrave.svg ├── polygon2.svg ├── polygon3.svg ├── polygon4.svg ├── polygons.svg ├── problem.conf ├── problem_1_triangle.stl ├── pycam-text.dxf ├── pycam-textbox.scad ├── pycam-textbox.stl ├── pycam.stl ├── pycam_text_2d.svg ├── rectangle.svg └── sled.stl ├── scripts ├── profile_pycam.py ├── pycam └── pycam_win32_postinstall.py ├── setup.cfg ├── setup.py ├── share ├── desktop │ └── pycam.desktop ├── fonts │ ├── README │ ├── courier.cxf │ ├── cursive.cxf │ ├── cyrillic_ii.cxf │ ├── gothgbt.cxf │ ├── gothgrt.cxf │ ├── gothitt.cxf │ ├── greek_ol.cxf │ ├── greekc.cxf │ ├── greekcs.cxf │ ├── greekp.cxf │ ├── greeks.cxf │ ├── hershey.readme │ ├── iso8859-11.cxf │ ├── italicc.cxf │ ├── italiccs.cxf │ ├── italict.cxf │ ├── kochigothic.cxf │ ├── kochimincho.cxf │ ├── normallatin1.cxf │ ├── normallatin1.readme │ ├── normallatin2.cxf │ ├── romanc.cxf │ ├── romancs.cxf │ ├── romand.cxf │ ├── romanp.cxf │ ├── romans.cxf │ ├── romans2.cxf │ ├── romant.cxf │ ├── scriptc.cxf │ ├── scripts.cxf │ ├── standard.cxf │ ├── symbol.cxf │ ├── symbol_astro.cxf │ ├── symbol_misc1.cxf │ ├── symbol_misc2.cxf │ └── unicode.cxf ├── mime │ ├── application-sla.svg │ ├── icons │ │ ├── 128x128 │ │ │ └── application-sla.png │ │ ├── 32x32 │ │ │ └── application-sla.png │ │ └── 64x64 │ │ │ └── application-sla.png │ ├── pycam.mime │ └── pycam.xml ├── misc │ └── DXF.gpl ├── pycam.ico └── ui │ ├── bounds.ui │ ├── clipboard.ui │ ├── emc_tool_export.ui │ ├── extrusion_chamfer.png │ ├── extrusion_radius_down.png │ ├── extrusion_radius_up.png │ ├── extrusion_sigmoidal.png │ ├── extrusion_sine.png │ ├── fonts.ui │ ├── gcode_preferences.ui │ ├── gcode_touch_off.ui │ ├── gtk_console.ui │ ├── gtkrc_windows │ ├── log.ui │ ├── logo_128px.png │ ├── logo_16px.png │ ├── logo_32px.png │ ├── logo_48px.png │ ├── logo_64px.png │ ├── logo_gui.bmp │ ├── logo_gui.png │ ├── logo_gui_vertical.bmp │ ├── logo_scalable.svg │ ├── memory_analyzer.ui │ ├── menubar.xml │ ├── model_export.ui │ ├── model_extrusion.ui │ ├── model_plane_mirror.ui │ ├── model_polygons.ui │ ├── model_position.ui │ ├── model_projection.ui │ ├── model_rotation.ui │ ├── model_scaling.ui │ ├── model_support.ui │ ├── model_support_distributed.ui │ ├── model_support_grid.ui │ ├── model_swap_axes.ui │ ├── models.ui │ ├── opengl.ui │ ├── opengl_view_dimension.ui │ ├── opengl_view_grid.ui │ ├── parallel_processing.ui │ ├── plugin_selector.ui │ ├── processes.ui │ ├── progress_bar.ui │ ├── pycam-project.ui │ ├── tasks.ui │ ├── toolpath_crop.ui │ ├── toolpath_export.ui │ ├── toolpath_grid.ui │ ├── toolpath_simulation.ui │ ├── toolpaths.ui │ ├── tools.ui │ ├── units.ui │ ├── visible.svg │ └── visible_off.svg └── technical_details.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.py[cod] 4 | 5 | dist 6 | build 7 | eggs 8 | *.egg-info 9 | .svn 10 | 11 | .DS_Store* 12 | ehthumbs.db 13 | Icon? 14 | Thumbs.db 15 | 16 | *~ 17 | .*.swp 18 | 19 | -------------------------------------------------------------------------------- /INSTALL.TXT: -------------------------------------------------------------------------------- 1 | Dependencies of the graphical interface: 2 | ======================================== 3 | 4 | Windows: 5 | -------- 6 | 7 | Please note that there are two ways of using PyCAM on Windows: 8 | 1) use the standalone executable (experimental - but it does not require further dependencies) 9 | http://sourceforge.net/projects/pycam/files/ 10 | 11 | 2) the installer package - it requires the following dependencies: 12 | 13 | BEWARE: this currently (as of 2010/10/20) does not work due to a problem with 14 | the gtk-installer. I guess, it will be fixed in the next weeks. Until then you 15 | should use the standalone executable for Windows (see above). 16 | 17 | Python 2.6: 18 | http://www.python.org/download/releases/2.6.6/ 19 | Please note that some of the GTK packages below require 20 | specifically v2.6 (thus v2.7 will not help). 21 | 22 | GTK v2.16 and GtkGlExt and PyGTK: 23 | http://www.bonifazi.eu/appunti/ (Blog) 24 | http://www.bonifazi.eu/appunti/pygtk_windows_installer.exe 25 | 26 | PyODE (optional): 27 | http://sourceforge.net/projects/pyode/files/pyode/snapshot-2010-03-22/PyODE-snapshot-2010-03-22.win32-py2.6.exe/download 28 | 29 | 30 | Notes: 31 | - you will probably need to add some directories manually to your search PATH setting: 32 | C:\Programs\Common Files\GTK\bin;C:\Programs\Python25;C:\Programs\GtkGLExt\1.0\bin 33 | (this is just an example - adjust it to your specific installation paths) 34 | - run a python console and check if the following commands work: 35 | import gtk 36 | import gtk.gtkgl 37 | import OpenGL 38 | import ode # this is optional 39 | 40 | 41 | Unix: 42 | ----- 43 | 44 | Install the following packages with your package manager: 45 | python 46 | python-gtk2 47 | python-opengl 48 | python-gtkglext1 49 | python-pyode (optional) 50 | python-setproctitle (optional) 51 | python-psyco (optional) 52 | 53 | On a debian or ubuntu system you would just type the following in a root console: 54 | apt-get install python-gtkglext1 python-opengl python-gtk2 python-pyode python-setproctitle python-psyco 55 | Please note that you need to enable the "universe" repository in Ubuntu. 56 | 57 | BEWARE: Debian "Lenny" and Ubuntu "Jaunty" (maybe also Dapper/Hardy/Intrepid) 58 | contain older "python-opengl" and "python-pyode" packages, that expose problems 59 | with PyCAM. 60 | You need to add "Squeeze" (for Debian) or "Karmic/Lucid" (for Ubuntu) to your 61 | package repository list and run the following: 62 | apt-get update 63 | apt-get install python-pyode python-opengl 64 | Afterwards you can remove the new package repository again. 65 | 66 | Users of Python 2.5 with multiple CPU cores may want to install the package 67 | "python-processing". This enables multi-threading for Python before v2.6. 68 | 69 | 70 | MacOS 71 | ----- 72 | 73 | Please read http://sourceforge.net/projects/pycam/forums/forum/860183/topic/3800091 74 | for the issues involved with installing PyCAM's dependencies via MacPorts. 75 | (thanks to lilalinux for this description) 76 | 77 | 78 | Minimal requirements for non-GUI mode 79 | ===================================== 80 | If you plan to use PyCAM only in batch mode (without a graphical user 81 | interface), then you just need to install Python. 82 | See the manpage (man pycam) or the output of "pycam --help" for further defails. 83 | 84 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | pycam - CNC Toolpath Generation in python 2 | ========================================= 3 | 4 | Copyright (C) 2008 Lode Leroy 5 | ----------------------------- 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20 | 21 | See COPYING 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.TXT 2 | include technical_details.txt 3 | include INSTALL.TXT 4 | include COPYING.TXT 5 | include README.TXT 6 | include Changelog 7 | include release_info.txt 8 | include pycam 9 | recursive-include desktop * 10 | recursive-include man * 11 | recursive-include src *.py 12 | recursive-include share * 13 | recursive-include samples * 14 | recursive-include Tests * 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # export SVN_REPO_BASE=. if you want to use the local version instead of trunk 2 | # from the subversion repository. 3 | 4 | # use something like "VERSION=0.2 make" to override the VERSION on the command line 5 | VERSION ?= $(shell sed -n "s/^.*[\t ]*VERSION[\t ]*=[\t ]*[\"']\([^\"']*\)[\"'].*/\1/gp" pycam/__init__.py) 6 | REPO_TAGS ?= https://pycam.svn.sourceforge.net/svnroot/pycam/tags 7 | RELEASE_PREFIX ?= pycam- 8 | ARCHIVE_DIR_RELATIVE ?= release-archives 9 | EXPORT_DIR = $(RELEASE_PREFIX)$(VERSION) 10 | EXPORT_FILE_PREFIX = $(EXPORT_DIR) 11 | EXPORT_ZIP = $(EXPORT_FILE_PREFIX).zip 12 | EXPORT_TGZ = $(EXPORT_FILE_PREFIX).tar.gz 13 | EXPORT_WIN32 = $(EXPORT_FILE_PREFIX).win32.exe 14 | PYTHON_EXE ?= python 15 | # check if the local version of python's distutils support "--plat-name" 16 | # (introduced in python 2.6) 17 | DISTUTILS_PLAT_NAME = $(shell $(PYTHON_EXE) setup.py --help build_ext | grep -q -- "--plat-name" && echo "--plat-name win32") 18 | 19 | # turn the destination directory into an absolute path 20 | ARCHIVE_DIR := $(shell pwd)/$(ARCHIVE_DIR_RELATIVE) 21 | 22 | .PHONY: zip tgz win32 clean dist git_export upload create_archive_dir man 23 | 24 | dist: zip tgz win32 25 | @# remove the tmp directory when everything is done 26 | @rm -rf "$(EXPORT_DIR)" 27 | 28 | clean: 29 | @rm -rf "$(EXPORT_DIR)" 30 | 31 | man: git_export 32 | @make -C "$(EXPORT_DIR)/man" 33 | 34 | git_export: clean 35 | @if git status 2>/dev/null >&2;\ 36 | then git clone . "$(EXPORT_DIR)";\ 37 | else echo "No git repo found."; exit 1;\ 38 | fi 39 | # Windows needs a different name for the startup script - due to process creation (no fork/exec) 40 | @cp "$(EXPORT_DIR)/scripts/pycam" "$(EXPORT_DIR)/scripts/pycam-loader.py" 41 | 42 | create_archive_dir: 43 | @mkdir -p "$(ARCHIVE_DIR)" 44 | 45 | zip: create_archive_dir man git_export 46 | cd "$(EXPORT_DIR)"; $(PYTHON_EXE) setup.py sdist --format zip --dist-dir "$(ARCHIVE_DIR)" 47 | 48 | tgz: create_archive_dir man git_export 49 | cd "$(EXPORT_DIR)"; $(PYTHON_EXE) setup.py sdist --format gztar --dist-dir "$(ARCHIVE_DIR)" 50 | 51 | win32: create_archive_dir man git_export 52 | # this is a binary release 53 | cd "$(EXPORT_DIR)"; $(PYTHON_EXE) setup.py bdist_wininst --user-access-control force --dist-dir "$(ARCHIVE_DIR)" $(DISTUTILS_PLAT_NAME) 54 | 55 | upload: 56 | svn cp "$(SVN_REPO_BASE)" "$(REPO_TAGS)/release-$(VERSION)" -m "tag release $(VERSION)" 57 | svn import "$(ARCHIVE_DIR)/$(EXPORT_ZIP)" "$(REPO_TAGS)/archives/$(EXPORT_ZIP)" -m "added released zip file for version $(VERSION)" 58 | svn import "$(ARCHIVE_DIR)/$(EXPORT_TGZ)" "$(REPO_TAGS)/archives/$(EXPORT_TGZ)" -m "added released tgz file for version $(VERSION)" 59 | svn import "$(ARCHIVE_DIR)/$(EXPORT_WIN32)" "$(REPO_TAGS)/archives/$(EXPORT_WIN32)" -m "added released win32 installer for version $(VERSION)" 60 | 61 | -------------------------------------------------------------------------------- /README.TXT: -------------------------------------------------------------------------------- 1 | pycam : Python CAM 2 | ------------------ 3 | 4 | Toolpath Generation for 3-Axis CNC machining 5 | 6 | Read the wiki for details: 7 | http://sourceforge.net/apps/mediawiki/pycam/index.php?title=User_Manual 8 | 9 | 10 | RUNNING: 11 | 12 | extract the archive and run "python pycam" 13 | 14 | 15 | 16 | USAGE: 17 | 18 | as a practical approach, you would probably: 19 | 20 | 1) for "rough" cutting, 21 | use the Cylindrical cutter 22 | with the PushCutter Pathgenerator 23 | and the Polygon PostProcessor in "x" or "y" mode 24 | 25 | 26 | 2) for "semifinish" cutting, 27 | use the Cylindrical/Toroidal cutter 28 | with the PushCutter Pathgenerator 29 | and the Contour PostProcessor in "xy" mode 30 | 31 | 3) "finish" cutting 32 | use the Spherical cutter 33 | with the DropCutter Pathgenerator 34 | and the ZigZag PostProcessor in "x" or "y" mode 35 | 36 | 37 | 38 | 39 | KNOWN BUGS: 40 | 41 | http://sourceforge.net/tracker/?group_id=237831&atid=1104176 42 | 43 | 44 | CONTRIBUTORS: 45 | 46 | * Lode Leroy: initiated the project; developed the toolpath generation, 47 | collision detection, geometry, Tk interface, ... 48 | 49 | * Lars Kruse: GTK interface and many features 50 | 51 | * Paul: GCode stepping precision 52 | 53 | * Arthur Magill: distutils packaging 54 | 55 | * Sebastian Kuzminsky: debian packaging 56 | 57 | -------------------------------------------------------------------------------- /Tests/DropCutterTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | $Id$ 5 | 6 | Copyright 2009 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import sys 25 | sys.path.insert(0,'.') 26 | 27 | from pycam.Geometry import * 28 | from pycam.Cutters.SphericalCutter import * 29 | from pycam.Cutters.CylindricalCutter import * 30 | from pycam.Cutters.ToroidalCutter import * 31 | 32 | from pycam.Gui.Visualization import ShowTestScene 33 | 34 | from pycam.Importers.TestModel import TestModel 35 | 36 | 37 | from pycam.PathGenerators.DropCutter import DropCutter 38 | from pycam.Exporters.SimpleGCodeExporter import SimpleGCodeExporter 39 | 40 | if __name__ == "__main__": 41 | 42 | #c = SphericalCutter(1, Point(0,0,7)) 43 | #c = CylindricalCutter(1, Point(0,0,7)) 44 | c = ToroidalCutter(1, 0.1, Point(0,0,7)) 45 | print "c=", c 46 | 47 | #model = TestModel() 48 | model = Model() 49 | model.append(Triangle(Point(-3,-4,1),Point(-3,4,1),Point(3,0,1))) 50 | 51 | 52 | if True: 53 | samples = 50 54 | lines = 50 55 | x0 = -7.0 56 | x1 = +7.0 57 | y0 = -7.0 58 | y1 = +7.0 59 | z0 = 0 60 | z1 = 4 61 | dx = (x1-x0)/samples 62 | dy = (y1-y0)/lines 63 | pg = DropCutter(c, model) 64 | 65 | pathlist = pg.GenerateToolPath(x0, x1, y0, y1, z0, z1, dx, dy, 0) 66 | 67 | ShowTestScene(model, c, pathlist) 68 | 69 | -------------------------------------------------------------------------------- /Tests/PushCutterTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | $Id$ 5 | 6 | Copyright 2008 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import sys 25 | sys.path.insert(0,'.') 26 | 27 | from pycam.Geometry import * 28 | from pycam.Cutters.SphericalCutter import * 29 | from pycam.Cutters.CylindricalCutter import * 30 | from pycam.Cutters.ToroidalCutter import * 31 | 32 | from pycam.Gui.Visualization import ShowTestScene 33 | 34 | from pycam.Importers import STLImporter 35 | 36 | from pycam.PathGenerators.PushCutter import PushCutter 37 | from pycam.PathProcessors import * 38 | from pycam.Exporters.SimpleGCodeExporter import SimpleGCodeExporter 39 | 40 | if __name__ == "__main__": 41 | 42 | c = SphericalCutter(0.1, Point(0,0,7)) 43 | #c = CylindricalCutter(1, Point(0,0,7)) 44 | #c = ToroidalCutter(1, 0.25, Point(0,0,7)) 45 | print "c=", c 46 | 47 | #model = TestModel() 48 | #model = STLImporter.ImportModel("Samples/STL/Box0.stl") 49 | #model = STLImporter.ImportModel("Samples/STL/Box1.stl") 50 | model = STLImporter.ImportModel("Samples/STL/Box0+1.stl") 51 | #model = Model() 52 | #model.append(Triangle(Point(0,0,0),Point(0,5,4),Point(0,-5,4))) 53 | #model.append(Triangle(Point(2,0,0),Point(2,-5,4),Point(2,5,4))) 54 | 55 | if True: 56 | lines = 20 57 | layers = 4 58 | x0 = -7.0 59 | x1 = +7.0 60 | y0 = -7.0 61 | y1 = +7.0 62 | z0 = 2.0 63 | z1 = 4.0 64 | pc = PushCutter(c, model, SimpleCutter()) 65 | #pc = PushCutter(c, model, ZigZagCutter()) 66 | #pc = PushCutter(c, model, PolygonCutter()) 67 | 68 | dx = 0 69 | if lines>1: 70 | dy = float(y1-y0)/(lines-1) 71 | else: 72 | dy = INFINITE 73 | if layers>1: 74 | dz = float(z1-z0)/(layers-1) 75 | else: 76 | dz = INFINITE 77 | 78 | pathlist = pc.GenerateToolPath(x0,x1,y0,y1,z0,z1,dx,dy,dz) 79 | c.moveto(Point(x0,y0,z0)) 80 | ShowTestScene(model, c, pathlist) 81 | -------------------------------------------------------------------------------- /Tests/STLImporterTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | $Id$ 5 | 6 | Copyright 2010 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import sys, time 25 | sys.path.insert(0,'.') 26 | 27 | 28 | from pycam.Importers import STLImporter 29 | from pycam.Gui.Visualization import ShowTestScene 30 | 31 | 32 | if len(sys.argv)>1: 33 | filename = sys.argv[1] 34 | else: 35 | filename = "Samples/STL/TestModel.stl" 36 | 37 | start = time.clock() 38 | model = STLImporter.ImportModel(filename) 39 | end = time.clock() 40 | 41 | print "time=", (end-start) 42 | 43 | #ShowTestScene(model) 44 | 45 | -------------------------------------------------------------------------------- /Tests/SimulationTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | $Id$ 5 | 6 | Copyright 2009 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import sys 25 | sys.path.insert(0,'.') 26 | 27 | from pycam.Gui.Visualization import Visualization 28 | from pycam.Simulation.ZBuffer import ZBuffer 29 | from pycam.Importers.TestModel import TestModel 30 | from pycam.Geometry.Triangle import Triangle 31 | from pycam.Geometry.Point import Point 32 | from pycam.Cutters.SphericalCutter import SphericalCutter 33 | 34 | from OpenGL.GL import * 35 | 36 | model = TestModel() 37 | 38 | zbuffer = ZBuffer(-5,+5,150, -5,+5,150, 1,5) 39 | 40 | #zbuffer.add_wave() 41 | 42 | #zbuffer.add_triangle(Triangle(Point(-4,0,0),Point(3,5,2),Point(4,-3,4))) 43 | 44 | c = SphericalCutter(0.25) 45 | 46 | p = Point(-5,-5,2) 47 | c.moveto(p) 48 | 49 | zbuffer.add_triangles(model.triangles()) 50 | 51 | #zbuffer.add_cutter(c) 52 | 53 | 54 | def DrawScene(): 55 | size=1 56 | # axes 57 | glBegin(GL_LINES) 58 | glColor3f(1,0,0) 59 | glVertex3f(0,0,0) 60 | glVertex3f(size,0,0) 61 | glEnd() 62 | glBegin(GL_LINES) 63 | glColor3f(0,1,0) 64 | glVertex3f(0,0,0) 65 | glVertex3f(0,size,0) 66 | glEnd() 67 | glBegin(GL_LINES) 68 | glColor3f(0,0,1) 69 | glVertex3f(0,0,0) 70 | glVertex3f(0,0,size) 71 | glEnd() 72 | 73 | glColor3f(1,1,1) 74 | c.to_OpenGL() 75 | 76 | 77 | glColor3f(0.9,0.8,0.7) 78 | # glMaterial(GL_FRONT_AND_BACK, GL_AMBIENT, (0.9, 0.8, 0.7, 0.2)) 79 | # glMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE, (0.8, 0.8, 0.8, 0.2)) 80 | glMaterial(GL_FRONT_AND_BACK, GL_SPECULAR, (1.0, 1.0, 1.0, 1.0)) 81 | glMaterial(GL_FRONT_AND_BACK, GL_SHININESS, (0.5)) 82 | zbuffer.to_OpenGL() 83 | 84 | dy = 0.1 85 | dx = 0.23 86 | dz = -0.01 87 | 88 | def HandleKey(key, x, y): 89 | global dx,dy,dz 90 | p.x += dx 91 | if p.x>5 or p.x<-5: 92 | dx = -dx 93 | p.x += dx * 2 94 | p.y += dy 95 | if p.y>5 or p.y<-5: 96 | dy = -dy 97 | p.y += dy * 2 98 | p.z += dz 99 | 100 | c.moveto(p) 101 | zbuffer.add_cutter(c) 102 | 103 | Visualization("VisualizationTest", DrawScene, handleKey = HandleKey) 104 | 105 | -------------------------------------------------------------------------------- /Tests/TriangleKdtreeTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | $Id$ 5 | 6 | Copyright 2009 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import sys 25 | sys.path.insert(0,'.') 26 | 27 | from pycam.Geometry.TriangleKdtree import * 28 | from pycam.Geometry.Model import Model 29 | from pycam.Importers.TestModel import TestModel 30 | 31 | print "# get model" 32 | testmodel = TestModel() 33 | print "# subdivide" 34 | model = testmodel.subdivide(5) 35 | print "# build kdtree" 36 | kdtree = BuildKdtree2d(model.triangles(), 2, 0.1) 37 | #print "#kdtree=",kdtree 38 | 39 | x = 2 40 | y = 2 41 | r = 0.1 42 | 43 | minx = x-r 44 | miny = y-r 45 | maxx = x+r 46 | maxy = y+r 47 | 48 | 49 | print "# query kdtree" 50 | ResetKdtree2dStats(False) 51 | tests = SearchKdtree2d(kdtree, minx, maxx, miny, maxy) 52 | print "# query kdtree" 53 | ResetKdtree2dStats(True) 54 | hits = SearchKdtree2d(kdtree, minx, maxx, miny, maxy) 55 | #print "# hits=%d / tests=%d" % GetKdtree2dStats(), "/ triangles=%d" % len(model.triangles()) 56 | print "# hits=%d " % len(hits), "/ tests=%d" % len(tests), "/ triangles=%d" % len(model.triangles()) 57 | 58 | -------------------------------------------------------------------------------- /Tests/TriangleTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | $Id$ 5 | 6 | Copyright 2010 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import sys 25 | sys.path.insert(0,'.') 26 | 27 | import math 28 | 29 | from pycam.Geometry import * 30 | from pycam.Gui.Visualization import Visualization 31 | 32 | 33 | p1 = Point(1,0,0) 34 | p2 = Point(0,1,0) 35 | p3 = Point(0,0,1) 36 | t = Triangle(p1,p2,p3) 37 | t.id=1 38 | t.calc_circumcircle() 39 | 40 | def DrawScene(): 41 | t.to_OpenGL() 42 | 43 | if __name__ == "__main__": 44 | 45 | print "p1=" + str(p1); 46 | print "p2=" + str(p2); 47 | print "p3=" + str(p3); 48 | 49 | print "p2-p1=" + str(p2.sub(p1)) 50 | print "p3-p2=" + str(p3.sub(p2)) 51 | print "p1-p3=" + str(p1.sub(p3)) 52 | 53 | print "p2.p1=" + str(p2.dot(p1)) 54 | print "p3.p2=" + str(p3.dot(p2)) 55 | print "p1.p3=" + str(p1.dot(p3)) 56 | 57 | print "p1xp2=" + str(p1.cross(p2)) 58 | print "p2xp3=" + str(p2.cross(p3)) 59 | print "p3xp1=" + str(p3.cross(p1)) 60 | 61 | print t 62 | 63 | print "circ(t) = %s@%s" % (t.radius(),t.center()) 64 | 65 | 66 | Visualization("VisualizationTest", DrawScene) 67 | -------------------------------------------------------------------------------- /Tests/VisualizationTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | $Id$ 5 | 6 | Copyright 2008 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import sys 25 | sys.path.insert(0,'.') 26 | 27 | from pycam.Gui.Visualization import Visualization 28 | from pycam.Importers.TestModel import TestModel 29 | 30 | model = TestModel() 31 | 32 | def DrawScene(): 33 | model.to_OpenGL() 34 | 35 | Visualization("VisualizationTest", DrawScene) 36 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | pycam (0.5.1-1) unstable; urgency=low 2 | 3 | * initial debian package 4 | * Closes: #600779 5 | 6 | -- Lars Kruse Mon, 13 Jun 2011 02:04:59 +0200 7 | 8 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: pycam 2 | Section: python 3 | Priority: extra 4 | Maintainer: Lars Kruse 5 | Build-Depends: python, debhelper (>= 7), cdbs, patchutils 6 | Build-Depends-Indep: python-support, help2man 7 | Standards-Version: 3.9.2 8 | Homepage: http://sourceforge.net/projects/pycam/ 9 | 10 | Package: pycam 11 | Architecture: all 12 | Depends: python-gtk2, python-opengl (>>3.0.0~b6-3), python-gtkglext1, 13 | python-rsvg, ${misc:Depends}, ${python:Depends} 14 | Recommends: python-pyode (>>1.2.0-3), python-psyco, python-setproctitle, 15 | python-guppy, inkscape, pstoedit 16 | Suggests: qcad-data | librecad-data 17 | Description: CAM program & Python library for generating toolpaths 18 | PyCAM is a toolpath generator for 3 axis machines. The generated 19 | GCode can be used with EMC2 and other machine controllers. 20 | The included Python library can be used independently from the GUI. 21 | . 22 | Features: 23 | * read and write STL model files (3D) 24 | * support for 2D models (DXF/SVG/PS) 25 | * generate toolpaths (GCode) for various strategies and drill 26 | definitions 27 | * manage and store processing templates 28 | * scale, move, rotate, flip and transform the model 29 | * interactive 3D model view based on OpenGL 30 | * non-interactive generation of GCode via commandline 31 | * render single-line fonts (provided by QCAD) 32 | 33 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 2 | Name: PyCAM 3 | Maintainer: Lars Kruse 4 | Source: http://pycam.sourceforge.net 5 | 6 | Files: * 7 | Copyright: 2010-2011, Lars Kruse 8 | 2006-2010, Lode Leroy 9 | License: GPL-3+ 10 | 11 | Files: debian/* 12 | Copyright: 2010-2011, Lars Kruse 13 | 2010, Sebastian Kuzminsky 14 | License: GPL-3+ 15 | 16 | License: GPL-3+ 17 | This program is free software: you can redistribute it and/or modify 18 | it under the terms of the GNU General Public License as published by 19 | the Free Software Foundation, either version 3 of the License, or 20 | (at your option) any later version. 21 | . 22 | This package is distributed in the hope that it will be useful, 23 | but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 | GNU General Public License for more details. 26 | . 27 | You should have received a copy of the GNU General Public License 28 | along with this program. If not, see . 29 | . 30 | On Debian systems, the full text of the GNU General Public License 31 | version 3 can be found in the file `/usr/share/common-licenses/GPL-3'. 32 | 33 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | usr/share/applications 2 | usr/share/icons/hicolor/scalable/apps 3 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | technical_details.txt 2 | README.TXT 3 | 4 | -------------------------------------------------------------------------------- /debian/manpages: -------------------------------------------------------------------------------- 1 | man/pycam.1 2 | -------------------------------------------------------------------------------- /debian/patches/series: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/debian/patches/series -------------------------------------------------------------------------------- /debian/pycam.mime: -------------------------------------------------------------------------------- 1 | ../share/mime/pycam.mime -------------------------------------------------------------------------------- /debian/pycam.sharedmimeinfo: -------------------------------------------------------------------------------- 1 | ../share/mime/pycam.xml -------------------------------------------------------------------------------- /debian/pycompat: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /debian/pyversions: -------------------------------------------------------------------------------- 1 | 2.5- 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # This file was originally written by Joey Hess and Craig Small. 5 | # As a special exception, when this file is copied by dh-make into a 6 | # dh-make output file, you may use that output file without restriction. 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. 8 | 9 | # Uncomment this to turn on verbose mode. 10 | #export DH_VERBOSE=1 11 | 12 | 13 | export DEB_PYTHON_SYSTEM=pysupport 14 | 15 | # Debhelper must be included before python-distutils to use 16 | # dh_python / dh_pycentral / dh_pysupport 17 | include /usr/share/cdbs/1/rules/debhelper.mk 18 | include /usr/share/cdbs/1/class/python-distutils.mk 19 | 20 | # clean the manpage 21 | clean:: 22 | make -C man clean 23 | 24 | # build the manpage 25 | build/pycam:: 26 | make -C man 27 | 28 | # install the .desktop file 29 | install/pycam:: 30 | # "desktop" file 31 | cp -v share/desktop/pycam.desktop `pwd`/debian/pycam/usr/share/applications/ 32 | # application logo for the menu entry 33 | cp -v share/ui/logo_scalable.svg `pwd`/debian/pycam/usr/share/icons/hicolor/scalable/apps/pycam.svg 34 | # remove "doc" directory from /usr/share/pycam/ 35 | rm -rf `pwd`/debian/pycam/usr/share/pycam/doc 36 | # the CXF fonts are distributed by QCAD - use them instead of the embedded ones 37 | rm -rf `pwd`/debian/pycam/usr/share/pycam/fonts 38 | # the gtkrc file for Windows is useless 39 | rm `pwd`/debian/pycam/usr/share/pycam/ui/gtkrc_windows 40 | 41 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=3 2 | http://sf.net/pycam/pycam-(.+)\.tar\.gz 3 | -------------------------------------------------------------------------------- /man/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | 3 | pycam.1: ../pycam pycam.1.inc 4 | help2man --no-info --name="Toolpath Generation for 3-Axis CNC machining" \ 5 | --section=1 --manual="PyCAM manual" --include=pycam.1.inc --output=pycam.1 ../scripts/pycam 6 | 7 | clean: 8 | @rm -f pycam.1 9 | 10 | -------------------------------------------------------------------------------- /man/pycam.1.inc: -------------------------------------------------------------------------------- 1 | [EXAMPLES] 2 | .nf 3 | .B pycam \-\-export\-gcode=output.ngc \-\-bounds\-type=relative\-margin \-\-bounds-lower=0.1,0.05,-0.1 foo.stl 4 | 5 | .fi 6 | Use the default settings to process the model \fBfoo.stl\fR with an adjusted 7 | lower margin (minx, miny, minz) of 10% (for x), 5% (for y) and \-10% (for z). 8 | 9 | [ENVIRONMENT] 10 | .IP PYCAM_DATA_DIR 11 | Override the default data directory of PyCAM. This allows 12 | you to provide customized logos, menu files or non-default sample files. 13 | .IP PYCAM_FONT_DIR 14 | Override the default location of engrave fonts. 15 | .IP PYTHONPATH 16 | You may want to define this variable in case that you installed the 17 | \fBPyCAM\fR python package in a non-default location. 18 | 19 | [REPORTING BUGS] 20 | See http://sourceforge.net/tracker/?group_id=237831&atid=1104176 21 | 22 | [SEE ALSO] 23 | Take a look at the output of \fBpycam \-\-help\fR to get a slightly better 24 | formatted list of options. The manual that you are reading right now is 25 | derived from this output. 26 | 27 | Take a look at the wiki for more information about PyCAM: 28 | http://sourceforge.net/apps/mediawiki/pycam/ 29 | 30 | The website of the PyCAM project: http://pycam.sourceforge.net 31 | 32 | -------------------------------------------------------------------------------- /pycam/Cutters/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | __all__ = [ "SphericalCutter", "CylindricalCutter", "ToroidalCutter", 24 | "BaseCutter" ] 25 | 26 | from pycam.Cutters.BaseCutter import BaseCutter 27 | from pycam.Cutters.SphericalCutter import SphericalCutter 28 | from pycam.Cutters.CylindricalCutter import CylindricalCutter 29 | from pycam.Cutters.ToroidalCutter import ToroidalCutter 30 | 31 | 32 | def get_tool_from_settings(tool_settings, height=None): 33 | """ get the tool specified by the relevant settings 34 | 35 | The settings must include: 36 | - "shape": one of "SphericalCutter", "CylindricalCutter" and 37 | "ToroidalCutter" 38 | - "radius": the tool radius 39 | The following settings are optional or shape specific: 40 | - "torus_radius": necessary for ToroidalCutter 41 | 42 | @type tool_settings: dict 43 | @value tool_settings: contains the attributes of the tool 44 | @type height: float 45 | @value height: the height of the tool 46 | @rtype: BaseCutter | basestring 47 | @return: a tool object or an error string 48 | """ 49 | cuttername = tool_settings["shape"] 50 | radius = tool_settings["tool_radius"] 51 | if cuttername == "SphericalCutter": 52 | return SphericalCutter(radius, height=height) 53 | elif cuttername == "CylindricalCutter": 54 | return CylindricalCutter(radius, height=height) 55 | elif cuttername == "ToroidalCutter": 56 | toroid = tool_settings["torus_radius"] 57 | return ToroidalCutter(radius, toroid, height=height) 58 | else: 59 | return "Invalid cutter shape: '%s' is not known" % str(cuttername) 60 | 61 | -------------------------------------------------------------------------------- /pycam/Exporters/EMCToolExporter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | import os 24 | 25 | class EMCToolExporter(object): 26 | 27 | def __init__(self, tools): 28 | """ tools are expected to be dictionaries containing the following keys: 29 | - id 30 | - radius 31 | - name 32 | """ 33 | self.tools = tools 34 | 35 | def get_tool_definition_string(self): 36 | result = [] 37 | tools = list(self.tools) 38 | tools.sort(key=lambda item: item["id"]) 39 | #result.append(self.HEADER_ROW) 40 | for tool in tools: 41 | # use an arbitrary length 42 | tool_length = tool["radius"] * 10 43 | line = "T%d P%d D%f Z-%f ;%s" % (tool["id"], tool["id"], 44 | 2 * tool["radius"], tool_length, tool["name"]) 45 | result.append(line) 46 | # add the dummy line for the "last" tool 47 | result.append("T99999 P99999 Z+0.100000 ;dummy tool") 48 | return os.linesep.join(result) 49 | 50 | -------------------------------------------------------------------------------- /pycam/Exporters/STLExporter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | $Id$ 5 | 6 | Copyright 2010 Lars Kruse 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | from pycam import VERSION 25 | import datetime 26 | import os 27 | 28 | class STLExporter(object): 29 | 30 | def __init__(self, model, name="model", created_by="pycam", linesep=None, 31 | **kwargs): 32 | self.model = model 33 | self.name = name 34 | self.created_by = created_by 35 | if linesep is None: 36 | self.linesep = os.linesep 37 | else: 38 | self.linesep = linesep 39 | 40 | def __str__(self): 41 | return self.linesep.join(self.get_output_lines) 42 | 43 | def write(self, stream): 44 | for line in self.get_output_lines(): 45 | stream.write(line) 46 | stream.write(self.linesep) 47 | 48 | def get_output_lines(self): 49 | date = datetime.date.today().isoformat() 50 | yield """solid "%s"; Produced by %s (v%s), %s""" \ 51 | % (self.name, self.created_by, VERSION, date) 52 | for triangle in self.model.triangles(): 53 | norm = triangle.normal.normalized() 54 | yield "facet normal %f %f %f" % (norm.x, norm.y, norm.z) 55 | yield " outer loop" 56 | # Triangle vertices are stored in clockwise order - thus we need 57 | # to reverse the order (STL expects counter-clockwise orientation). 58 | for point in (triangle.p1, triangle.p3, triangle.p2): 59 | yield " vertex %f %f %f" % (point.x, point.y, point.z) 60 | yield " endloop" 61 | yield "endfacet" 62 | yield "endsolid" 63 | 64 | -------------------------------------------------------------------------------- /pycam/Exporters/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | Copyright 2008-2009 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | __all__ = [ "GCodeExporter", "SVGExporter", "STLExporter", "EMCToolExporter"] 25 | 26 | -------------------------------------------------------------------------------- /pycam/Geometry/Path.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | Copyright 2008 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | """ the points of a path are only used for describing coordinates. Thus we 25 | don't really need complete "Point" instances that consume a lot of memory. 26 | Since python 2.6 the "namedtuple" factory is available. 27 | This reduces the memory consumption of a toolpath down to 1/3. 28 | """ 29 | 30 | try: 31 | # this works for python 2.6 or above (saves memory) 32 | # TODO: disabled for now - check if we could enable it later ... 33 | import INVALID_IMPORT 34 | from collections import namedtuple 35 | tuple_point = namedtuple("TuplePoint", "x y z") 36 | get_point_object = lambda point: tuple_point(point.x, point.y, point.z) 37 | except ImportError: 38 | # dummy for python < v2.6 (consumes more memory) 39 | get_point_object = lambda point: point 40 | 41 | from pycam.Geometry import IDGenerator 42 | 43 | 44 | class Path(IDGenerator): 45 | 46 | def __init__(self): 47 | super(Path, self).__init__() 48 | self.top_join = None 49 | self.bot_join = None 50 | self.winding = 0 51 | self.points = [] 52 | 53 | def __repr__(self): 54 | text = "" 55 | text += "path %d: " % self.id 56 | first = True 57 | for point in self.points: 58 | if first: 59 | first = False 60 | else: 61 | text += "-" 62 | text += "%d(%g,%g,%g)" % (point.id, point.x, point.y, point.z) 63 | return text 64 | 65 | def insert(self, index, point): 66 | self.points.insert(index, get_point_object(point)) 67 | 68 | def append(self, point): 69 | self.points.append(get_point_object(point)) 70 | 71 | def reverse(self): 72 | self.points.reverse() 73 | -------------------------------------------------------------------------------- /pycam/Geometry/PointKdtree.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2009 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | from pycam.Geometry.utils import epsilon 24 | from pycam.Geometry.Point import Point 25 | from pycam.Geometry.kdtree import Node, kdtree 26 | 27 | 28 | class PointKdtree(kdtree): 29 | 30 | __slots__ = ["_n", "tolerance"] 31 | 32 | def __init__(self, points=None, cutoff=5, cutoff_distance=0.5, 33 | tolerance=epsilon): 34 | if points is None: 35 | points = [] 36 | self._n = None 37 | self.tolerance = tolerance 38 | nodes = [] 39 | for p in points: 40 | n = Node(p, (p.x, p.y, p.z)) 41 | nodes.append(n) 42 | kdtree.__init__(self, nodes, cutoff, cutoff_distance) 43 | 44 | def dist(self, n1, n2): 45 | dx = n1.bound[0]-n2.bound[0] 46 | dy = n1.bound[1]-n2.bound[1] 47 | dz = n1.bound[2]-n2.bound[2] 48 | return dx*dx+dy*dy+dz*dz 49 | 50 | def Point(self, x, y, z): 51 | #return Point(x,y,z) 52 | if self._n: 53 | n = self._n 54 | n.bound = (x, y, z) 55 | else: 56 | n = Node(None, (x, y, z)) 57 | (nn, dist) = self.nearest_neighbor(n, self.dist) 58 | if nn and (dist < self.tolerance): 59 | self._n = n 60 | return nn.obj 61 | else: 62 | n.obj = Point(x, y, z) 63 | self._n = None 64 | self.insert(n) 65 | return n.obj 66 | 67 | -------------------------------------------------------------------------------- /pycam/Geometry/TriangleKdtree.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008-2009 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | from pycam.Geometry.kdtree import kdtree, Node 24 | 25 | overlaptest = True 26 | 27 | def SearchKdtree2d(tree, minx, maxx, miny, maxy): 28 | if tree.bucket: 29 | triangles = [] 30 | for n in tree.nodes: 31 | if not overlaptest: 32 | triangles.append(n.obj) 33 | else: 34 | if not ((n.bound[0] > maxx) or 35 | (n.bound[1] < minx) or 36 | (n.bound[2] > maxy) or 37 | (n.bound[3] < miny)): 38 | triangles.append(n.obj) 39 | return triangles 40 | else: 41 | if tree.cutdim == 0: 42 | if maxx < tree.minval: 43 | return [] 44 | elif maxx < tree.cutval: 45 | return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy) 46 | else: 47 | return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy) \ 48 | + SearchKdtree2d(tree.hi, minx, maxx, miny, maxy) 49 | elif tree.cutdim == 1: 50 | if minx > tree.maxval: 51 | return [] 52 | elif minx > tree.cutval: 53 | return SearchKdtree2d(tree.hi, minx, maxx, miny, maxy) 54 | else: 55 | return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy) \ 56 | + SearchKdtree2d(tree.hi, minx, maxx, miny, maxy) 57 | elif tree.cutdim == 2: 58 | if maxy < tree.minval: 59 | return [] 60 | elif maxy < tree.cutval: 61 | return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy) 62 | else: 63 | return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy) \ 64 | + SearchKdtree2d(tree.hi, minx, maxx, miny, maxy) 65 | elif tree.cutdim == 3: 66 | if miny > tree.maxval: 67 | return [] 68 | elif miny > tree.cutval: 69 | return SearchKdtree2d(tree.hi, minx, maxx, miny, maxy) 70 | else: 71 | return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy) \ 72 | + SearchKdtree2d(tree.hi, minx, maxx, miny, maxy) 73 | 74 | 75 | class TriangleKdtree(kdtree): 76 | 77 | __slots__ = [] 78 | 79 | def __init__(self, triangles, cutoff=3, cutoff_distance=1.0): 80 | nodes = [] 81 | for t in triangles: 82 | n = Node(t, (min(t.p1.x, t.p2.x, t.p3.x), 83 | max(t.p1.x, t.p2.x, t.p3.x), 84 | min(t.p1.y, t.p2.y, t.p3.y), 85 | max(t.p1.y, t.p2.y, t.p3.y))) 86 | nodes.append(n) 87 | super(TriangleKdtree, self).__init__(nodes, cutoff, cutoff_distance) 88 | 89 | def Search(self, minx, maxx, miny, maxy): 90 | return SearchKdtree2d(self, minx, maxx, miny, maxy) 91 | 92 | -------------------------------------------------------------------------------- /pycam/Geometry/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import decimal 25 | import math 26 | 27 | INFINITE = 100000 28 | epsilon = 0.00001 29 | 30 | # use the "decimal" module for fixed precision numbers (only for debugging) 31 | _use_precision = False 32 | 33 | 34 | # the lambda functions below are more efficient than function definitions 35 | 36 | if _use_precision: 37 | ceil = lambda value: int((value + number(1).next_minus()) // 1) 38 | else: 39 | ceil = lambda value: int(math.ceil(value)) 40 | 41 | # return "0" for "-epsilon < value < 0" (to work around floating inaccuracies) 42 | # otherwise: return the sqrt function of the current type (could even raise 43 | # exceptions) 44 | if _use_precision: 45 | sqrt = lambda value: (((value < -epsilon) or (value > 0)) and \ 46 | value.sqrt()) or 0 47 | else: 48 | sqrt = lambda value: (((value < -epsilon) or (value > 0)) and \ 49 | math.sqrt(value)) or 0 50 | 51 | if _use_precision: 52 | number = lambda value: decimal.Decimal(str(value)) 53 | else: 54 | number = float 55 | 56 | 57 | -------------------------------------------------------------------------------- /pycam/Gui/Console.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | __all__ = ["ConsoleProgressBar"] 24 | 25 | import os 26 | 27 | class ConsoleProgressBar(object): 28 | 29 | STYLE_NONE = 0 30 | STYLE_TEXT = 1 31 | STYLE_BAR = 2 32 | STYLE_DOT = 3 33 | PROGRESS_BAR_LENGTH = 70 34 | 35 | def __init__(self, output, style=None): 36 | if style is None: 37 | style = ConsoleProgressBar.STYLE_TEXT 38 | self.output = output 39 | self.style = style 40 | self.last_length = 0 41 | self.text = "" 42 | self.percent = 0 43 | 44 | def _output_current_state(self, progress_happened=True): 45 | if self.style == ConsoleProgressBar.STYLE_TEXT: 46 | text = "%d%% %s" % (self.percent, self.text) 47 | self.last_length = len(text) 48 | elif self.style == ConsoleProgressBar.STYLE_BAR: 49 | bar_length = ConsoleProgressBar.PROGRESS_BAR_LENGTH 50 | hashes = int(bar_length * self.percent / 100.0) 51 | empty = bar_length - hashes 52 | text = "[%s%s]" % ("#" * hashes, "." * empty) 53 | # include a text like " 10% " in the middle 54 | percent_text = " %d%% " % self.percent 55 | start_text = text[:(len(text) - len(percent_text)) / 2] 56 | end_text = text[-(len(text) - len(start_text) - len(percent_text)):] 57 | text = start_text + percent_text + end_text 58 | self.last_length = len(text) 59 | elif self.style == ConsoleProgressBar.STYLE_DOT: 60 | if progress_happened: 61 | text = "." 62 | else: 63 | text = "" 64 | # don't remove any previous characters 65 | self.last_length = 0 66 | else: 67 | raise ValueError("ConsoleProgressBar: invalid style (%d)" \ 68 | % self.style) 69 | self.output.write(text) 70 | self.output.flush() 71 | 72 | def update(self, text=None, percent=None, **kwargs): 73 | if self.style == ConsoleProgressBar.STYLE_NONE: 74 | return 75 | if not text is None: 76 | self.text = text 77 | if not percent is None: 78 | self.percent = int(percent) 79 | if self.last_length > 0: 80 | # delete the previous line 81 | self.output.write("\x08" * self.last_length) 82 | self._output_current_state(progress_happened = (not percent is None)) 83 | 84 | def finish(self): 85 | if self.style == ConsoleProgressBar.STYLE_NONE: 86 | return 87 | # show that we are finished 88 | self.update(percent=100) 89 | # finish the line 90 | self.output.write(os.linesep) 91 | 92 | -------------------------------------------------------------------------------- /pycam/Gui/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | __all__ = ["common", "Console", "OpenGLTools", "Project", "Settings", 24 | "Vizualisation"] 25 | 26 | -------------------------------------------------------------------------------- /pycam/Importers/PSImporter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | from pycam.Importers.SVGImporter import convert_eps2dxf 24 | import pycam.Importers.DXFImporter 25 | import pycam.Utils 26 | import tempfile 27 | import os 28 | 29 | log = pycam.Utils.log.get_logger() 30 | 31 | 32 | def import_model(filename, program_locations=None, unit="mm", callback=None, 33 | **kwargs): 34 | local_file = False 35 | if hasattr(filename, "read"): 36 | infile = filename 37 | ps_file_handle, ps_file_name = tempfile.mkstemp(suffix=".ps") 38 | try: 39 | temp_file = os.fdopen(ps_file_handle, "w") 40 | temp_file.write(infile.read()) 41 | temp_file.close() 42 | except IOError, err_msg: 43 | log.error("PSImporter: Failed to create temporary local file " + \ 44 | "(%s): %s" % (ps_file_name, err_msg)) 45 | return 46 | filename = ps_file_name 47 | else: 48 | uri = pycam.Utils.URIHandler(filename) 49 | if not uri.exists(): 50 | log.error("PSImporter: file (%s) does not exist" % filename) 51 | return None 52 | if not uri.is_local(): 53 | # non-local file - write it to a temporary file first 54 | ps_file_handle, ps_file_name = tempfile.mkstemp(suffix=".ps") 55 | os.close(ps_file_handle) 56 | log.debug("Retrieving PS file for local access: %s -> %s" % \ 57 | (uri, ps_file_name)) 58 | if not uri.retrieve_remote_file(ps_file_name, callback=callback): 59 | log.error("PSImporter: Failed to retrieve the PS model file: " + \ 60 | "%s -> %s" % (uri, ps_file_name)) 61 | return 62 | filename = ps_file_name 63 | else: 64 | filename = uri.get_local_path() 65 | local_file = True 66 | 67 | if program_locations and "pstoedit" in program_locations: 68 | pstoedit_path = program_locations["pstoedit"] 69 | else: 70 | pstoedit_path = None 71 | 72 | def remove_temp_file(filename): 73 | if os.path.isfile(filename): 74 | try: 75 | os.remove(filename) 76 | except OSError, err_msg: 77 | log.warn("PSImporter: failed to remove temporary file " \ 78 | + "(%s): %s" % (filename, err_msg)) 79 | 80 | # convert eps to dxf via pstoedit 81 | dxf_file_handle, dxf_file_name = tempfile.mkstemp(suffix=".dxf") 82 | os.close(dxf_file_handle) 83 | success = convert_eps2dxf(filename, dxf_file_name, unit=unit, 84 | location=pstoedit_path) 85 | if not local_file: 86 | remove_temp_file(ps_file_name) 87 | if not success: 88 | result = None 89 | elif callback and callback(): 90 | log.warn("PSImporter: load model operation cancelled") 91 | result = None 92 | else: 93 | log.info("Successfully converted PS file to DXF file") 94 | # pstoedit uses "inch" -> force a scale operation 95 | result = pycam.Importers.DXFImporter.import_model(dxf_file_name, 96 | unit=unit, callback=callback) 97 | # always remove the dxf file 98 | remove_temp_file(dxf_file_name) 99 | return result 100 | 101 | -------------------------------------------------------------------------------- /pycam/Importers/TestModel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008-2009 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | from pycam.Geometry.Triangle import Triangle 24 | from pycam.Geometry.Line import Line 25 | from pycam.Geometry.Point import Point 26 | from pycam.Geometry.Model import Model 27 | 28 | 29 | def get_test_model(): 30 | points = [] 31 | points.append(Point(-2, 1, 4)) 32 | points.append(Point(2, 1, 4)) 33 | points.append(Point(0, -2, 4)) 34 | points.append(Point(-5, 2, 2)) 35 | points.append(Point(-1, 3, 2)) 36 | points.append(Point(5, 2, 2)) 37 | points.append(Point(4, -1, 2)) 38 | points.append(Point(2, -4, 2)) 39 | points.append(Point(-2, -4, 2)) 40 | points.append(Point(-3, -2, 2)) 41 | 42 | lines = [] 43 | lines.append(Line(points[0], points[1])) 44 | lines.append(Line(points[1], points[2])) 45 | lines.append(Line(points[2], points[0])) 46 | lines.append(Line(points[0], points[3])) 47 | lines.append(Line(points[3], points[4])) 48 | lines.append(Line(points[4], points[0])) 49 | lines.append(Line(points[4], points[1])) 50 | lines.append(Line(points[4], points[5])) 51 | lines.append(Line(points[5], points[1])) 52 | lines.append(Line(points[5], points[6])) 53 | lines.append(Line(points[6], points[1])) 54 | lines.append(Line(points[6], points[2])) 55 | lines.append(Line(points[6], points[7])) 56 | lines.append(Line(points[7], points[2])) 57 | lines.append(Line(points[7], points[8])) 58 | lines.append(Line(points[8], points[2])) 59 | lines.append(Line(points[8], points[9])) 60 | lines.append(Line(points[9], points[2])) 61 | lines.append(Line(points[9], points[0])) 62 | lines.append(Line(points[9], points[3])) 63 | 64 | model = Model() 65 | for p1, p2, p3, l1, l2, l3 in ( 66 | (0, 1, 2, 0, 1, 2), 67 | (0, 3, 4, 3, 4, 5), 68 | (0, 4, 1, 5, 6, 0), 69 | (1, 4, 5, 6, 7, 8), 70 | (1, 5, 6, 8, 9, 10), 71 | (1, 6, 2, 10, 11, 1), 72 | (2, 6, 7, 11, 12, 13), 73 | (2, 7, 8, 13, 14, 15), 74 | (2, 8, 9, 15, 16, 17), 75 | (2, 9, 0, 17, 18, 2), 76 | (0, 9, 3, 18, 19, 3)): 77 | model.append(Triangle(points[p1], points[p2], points[p3])) 78 | return model 79 | 80 | -------------------------------------------------------------------------------- /pycam/Importers/ToolpathSettingsParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | import pycam.Gui.Settings 24 | import pycam.Gui.Project 25 | import pycam.Utils.log 26 | import pycam.Utils 27 | import re 28 | import os 29 | import sys 30 | 31 | COMMENT_CHARACTERS = r";#" 32 | REGEX_META_KEYWORDS = r"[%s]?%s (.*): (.*)$" % (COMMENT_CHARACTERS, 33 | pycam.Gui.Project.ProjectGui.META_DATA_PREFIX) 34 | REGEX_SETTINGS_START = r"[%s]?%s$" % (COMMENT_CHARACTERS, 35 | pycam.Gui.Settings.ToolpathSettings.META_MARKER_START) 36 | REGEX_SETTINGS_END = r"[%s]?%s$" % (COMMENT_CHARACTERS, 37 | pycam.Gui.Settings.ToolpathSettings.META_MARKER_END) 38 | 39 | log = pycam.Utils.log.get_logger() 40 | 41 | 42 | def parse_toolpath_settings(filename): 43 | """ parse potential PyCAM settings from a given file 44 | 45 | This is mainly useful to retrieve task settings from a GCode file. 46 | @value filename: the name of the file to be read 47 | @type filename: str 48 | @returns: a dictionary (of all setting names and values) and the content 49 | of the 'comment' section (as a single string) 50 | @rtype: tuple(dict, str) 51 | """ 52 | keywords = {} 53 | in_meta_zone = False 54 | meta_content = [] 55 | if filename == "-": 56 | # read from stdin, if the input filename is "-" 57 | infile = sys.stdin 58 | close_file = False 59 | else: 60 | # open the file 61 | try: 62 | infile = pycam.Utils.URIHandler(filename).open() 63 | except IOError, err_msg: 64 | log.warn("ToolpathSettingsParser: Failed to read file (%s): %s" % \ 65 | (filename, err_msg)) 66 | return None 67 | close_file = True 68 | for line in infile.readlines(): 69 | match = re.match(REGEX_META_KEYWORDS, line) 70 | if match: 71 | keywords[match.groups()[0]] = match.groups()[1].strip() 72 | if in_meta_zone: 73 | if re.match(REGEX_SETTINGS_END, line): 74 | in_meta_zone = False 75 | else: 76 | if line and line[0] in COMMENT_CHARACTERS: 77 | meta_content[-1].append(line[1:].strip()) 78 | else: 79 | if re.match(REGEX_SETTINGS_START, line): 80 | in_meta_zone = True 81 | meta_content.append([]) 82 | if close_file: 83 | # only close the file if it was opened before (e.g. not stdin) 84 | infile.close() 85 | return keywords, [os.linesep.join(one_block) for one_block in meta_content] 86 | 87 | if __name__ == "__main__": 88 | # for testing: output the parsed content of the given file (first argument) 89 | print "\n#################\n".join(parse_toolpath_settings(sys.argv[1])[1]) 90 | 91 | -------------------------------------------------------------------------------- /pycam/Importers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008 Lode Leroy 6 | Copyright 2010 Lars Kruse 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | __all__ = ["STLImporter", "DXFImporter", "SVGImporter", "TestModel"] 25 | 26 | import pycam.Utils.log 27 | import pycam.Utils 28 | import pycam.Importers.DXFImporter 29 | import pycam.Importers.STLImporter 30 | import pycam.Importers.SVGImporter 31 | import pycam.Importers.PSImporter 32 | 33 | import os 34 | 35 | log = pycam.Utils.log.get_logger() 36 | 37 | 38 | def detect_file_type(filename, quiet=False): 39 | # also accept URI input 40 | uri = pycam.Utils.URIHandler(filename) 41 | filename = uri.get_path() 42 | failure = (None, None) 43 | # check all listed importers 44 | # TODO: this should be done by evaluating the header of the file 45 | if filename.lower().endswith(".stl"): 46 | return ("stl", pycam.Importers.STLImporter.ImportModel) 47 | elif filename.lower().endswith(".dxf"): 48 | return ("dxf", pycam.Importers.DXFImporter.import_model) 49 | elif filename.lower().endswith(".svg"): 50 | return ("svg", pycam.Importers.SVGImporter.import_model) 51 | elif filename.lower().endswith(".eps") \ 52 | or filename.lower().endswith(".ps"): 53 | return ("ps", pycam.Importers.PSImporter.import_model) 54 | else: 55 | if not quiet: 56 | log.error(("Importers: Failed to detect the model type of '%s'. " \ 57 | + "Is the file extension (stl/dxf/svg/eps/ps) correct?") \ 58 | % filename) 59 | return failure 60 | 61 | -------------------------------------------------------------------------------- /pycam/PathGenerators/DropCutter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010-2011 Lars Kruse 6 | Copyright 2008-2009 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | from pycam.PathGenerators import get_max_height_dynamic 25 | from pycam.Utils import ProgressCounter 26 | from pycam.Utils.threading import run_in_parallel 27 | import pycam.Geometry.Model 28 | import pycam.Utils.log 29 | 30 | log = pycam.Utils.log.get_logger() 31 | 32 | 33 | # We need to use a global function here - otherwise it does not work with 34 | # the multiprocessing Pool. 35 | def _process_one_grid_line((positions, minz, maxz, model, cutter, physics)): 36 | """ This function assumes, that the positions are next to each other. 37 | Otherwise the dynamic over-sampling (in get_max_height_dynamic) is 38 | pointless. 39 | """ 40 | return get_max_height_dynamic(model, cutter, positions, minz, maxz, physics) 41 | 42 | 43 | class DropCutter(object): 44 | 45 | def __init__(self, path_processor, physics=None): 46 | self.pa = path_processor 47 | self.physics = physics 48 | 49 | def GenerateToolPath(self, cutter, models, motion_grid, minz=None, maxz=None, draw_callback=None): 50 | quit_requested = False 51 | model = pycam.Geometry.Model.get_combined_model(models) 52 | 53 | # Transfer the grid (a generator) into a list of lists and count the 54 | # items. 55 | lines = [] 56 | # usually there is only one layer - but an xy-grid consists of two 57 | for layer in motion_grid: 58 | for line in layer: 59 | lines.append(line) 60 | 61 | num_of_lines = len(lines) 62 | progress_counter = ProgressCounter(len(lines), draw_callback) 63 | current_line = 0 64 | 65 | self.pa.new_direction(0) 66 | 67 | args = [] 68 | for one_grid_line in lines: 69 | # simplify the data (useful for remote processing) 70 | xy_coords = [(pos.x, pos.y) for pos in one_grid_line] 71 | args.append((xy_coords, minz, maxz, model, cutter, 72 | self.physics)) 73 | for points in run_in_parallel(_process_one_grid_line, args, 74 | callback=progress_counter.update): 75 | self.pa.new_scanline() 76 | if draw_callback and draw_callback(text="DropCutter: processing " \ 77 | + "line %d/%d" % (current_line + 1, num_of_lines)): 78 | # cancel requested 79 | quit_requested = True 80 | break 81 | for point in points: 82 | if point is None: 83 | # exceeded maxz - the cutter has to skip this point 84 | self.pa.end_scanline() 85 | self.pa.new_scanline() 86 | continue 87 | self.pa.append(point) 88 | # "draw_callback" returns true, if the user requested to quit 89 | # via the GUI. 90 | # The progress counter may return True, if cancel was requested. 91 | if draw_callback and draw_callback(tool_position=point, 92 | toolpath=self.pa.paths): 93 | quit_requested = True 94 | break 95 | progress_counter.increment() 96 | self.pa.end_scanline() 97 | # update progress 98 | current_line += 1 99 | if quit_requested: 100 | break 101 | self.pa.end_direction() 102 | self.pa.finish() 103 | return self.pa.paths 104 | 105 | -------------------------------------------------------------------------------- /pycam/PathGenerators/EngraveCutter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | Copyright 2008-2009 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import pycam.PathProcessors.PathAccumulator 25 | from pycam.Geometry.Point import Point, Vector 26 | from pycam.Geometry.Line import Line 27 | from pycam.Geometry.Plane import Plane 28 | from pycam.Geometry.utils import ceil 29 | from pycam.PathGenerators import get_max_height_dynamic, get_free_paths_ode, \ 30 | get_free_paths_triangles 31 | from pycam.Utils import ProgressCounter 32 | import pycam.Utils.log 33 | 34 | log = pycam.Utils.log.get_logger() 35 | 36 | 37 | class EngraveCutter(object): 38 | 39 | def __init__(self, path_processor, physics=None): 40 | self.pa_push = path_processor 41 | # We use a separated path processor for the last "drop" layer. 42 | # This path processor does not need to be configurable. 43 | self.pa_drop = pycam.PathProcessors.PathAccumulator.PathAccumulator( 44 | reverse=self.pa_push.reverse) 45 | self.physics = physics 46 | 47 | def GenerateToolPath(self, cutter, models, motion_grid, minz=None, 48 | maxz=None, draw_callback=None): 49 | quit_requested = False 50 | 51 | model = pycam.Geometry.Model.get_combined_model(models) 52 | 53 | if draw_callback: 54 | draw_callback(text="Engrave: optimizing polygon order") 55 | 56 | # resolve the generator 57 | motion_grid = list(motion_grid) 58 | num_of_layers = len(motion_grid) 59 | 60 | push_layers = motion_grid[:-1] 61 | push_generator = pycam.PathGenerators.PushCutter.PushCutter( 62 | self.pa_push, physics=self.physics) 63 | current_layer = 0 64 | for push_layer in push_layers: 65 | # update the progress bar and check, if we should cancel the process 66 | if draw_callback and draw_callback(text="Engrave: processing " \ 67 | + "layer %d/%d" % (current_layer + 1, num_of_layers)): 68 | # cancel immediately 69 | quit_requested = True 70 | break 71 | # no callback: otherwise the status text gets lost 72 | push_generator.GenerateToolPath(cutter, [model], [push_layer]) 73 | if draw_callback and draw_callback(): 74 | # cancel requested 75 | quit_requested = True 76 | break 77 | current_layer += 1 78 | 79 | if quit_requested: 80 | return self.pa_push.paths 81 | 82 | drop_generator = pycam.PathGenerators.DropCutter.DropCutter(self.pa_drop, 83 | physics=self.physics) 84 | drop_layers = motion_grid[-1:] 85 | if draw_callback: 86 | draw_callback(text="Engrave: processing layer " + \ 87 | "%d/%d" % (current_layer + 1, num_of_layers)) 88 | drop_generator.GenerateToolPath(cutter, [model], drop_layers, 89 | minz=minz, maxz=maxz, draw_callback=draw_callback) 90 | return self.pa_push.paths + self.pa_drop.paths 91 | 92 | -------------------------------------------------------------------------------- /pycam/PathProcessors/ContourCutter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008-2010 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | import pycam.PathProcessors 24 | from pycam.Geometry.PolygonExtractor import PolygonExtractor 25 | from pycam.Geometry.Point import Point 26 | from pycam.Toolpath import simplify_toolpath 27 | 28 | class ContourCutter(pycam.PathProcessors.BasePathProcessor): 29 | def __init__(self, reverse=False): 30 | super(ContourCutter, self).__init__() 31 | self.curr_path = None 32 | self.scanline = None 33 | self.polygon_extractor = None 34 | self.points = [] 35 | self.reverse = reverse 36 | self.__forward = Point(1, 1, 0) 37 | 38 | def append(self, point): 39 | # Sort the points in positive x/y direction - otherwise the 40 | # PolygonExtractor breaks. 41 | if self.points and (point.sub(self.points[0]).dot(self.__forward) < 0): 42 | self.points.insert(0, point) 43 | else: 44 | self.points.append(point) 45 | 46 | def new_direction(self, direction): 47 | if self.polygon_extractor == None: 48 | self.polygon_extractor = PolygonExtractor(PolygonExtractor.CONTOUR) 49 | 50 | self.polygon_extractor.new_direction(direction) 51 | 52 | def end_direction(self): 53 | self.polygon_extractor.end_direction() 54 | 55 | def new_scanline(self): 56 | self.polygon_extractor.new_scanline() 57 | self.points = [] 58 | 59 | def end_scanline(self): 60 | for i in range(1, len(self.points) - 1): 61 | self.polygon_extractor.append(self.points[i]) 62 | self.polygon_extractor.end_scanline() 63 | 64 | def finish(self): 65 | self.polygon_extractor.finish() 66 | if self.polygon_extractor.merge_path_list: 67 | paths = self.polygon_extractor.merge_path_list 68 | elif self.polygon_extractor.hor_path_list: 69 | paths = self.polygon_extractor.hor_path_list 70 | else: 71 | paths = self.polygon_extractor.ver_path_list 72 | if paths: 73 | for path in paths: 74 | path.append(path.points[0]) 75 | simplify_toolpath(path) 76 | if paths: 77 | if self.reverse: 78 | paths.reverse() 79 | self.paths.extend(paths) 80 | self.sort_layered() 81 | self.polygon_extractor = None 82 | 83 | -------------------------------------------------------------------------------- /pycam/PathProcessors/PathAccumulator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | Copyright 2008 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import pycam.PathProcessors 25 | from pycam.Toolpath import simplify_toolpath 26 | from pycam.Geometry.Path import Path 27 | 28 | 29 | class PathAccumulator(pycam.PathProcessors.BasePathProcessor): 30 | def __init__(self, zigzag=False, reverse=False): 31 | super(PathAccumulator, self).__init__() 32 | self.curr_path = None 33 | self.zigzag = zigzag 34 | self.scanline = None 35 | self.reverse = reverse 36 | 37 | def append(self, point): 38 | if self.curr_path == None: 39 | self.curr_path = Path() 40 | if self.reverse: 41 | self.curr_path.insert(0, point) 42 | else: 43 | self.curr_path.append(point) 44 | 45 | def new_direction(self, direction): 46 | self.scanline = 0 47 | 48 | def new_scanline(self): 49 | self.scanline += 1 50 | if self.curr_path: 51 | print "ERROR: curr_path expected to be empty" 52 | self.curr_path = None 53 | 54 | def end_scanline(self): 55 | if self.curr_path: 56 | if self.zigzag and (self.scanline % 2 == 0): 57 | self.curr_path.reverse() 58 | simplify_toolpath(self.curr_path) 59 | if self.reverse: 60 | self.paths.insert(0, self.curr_path) 61 | else: 62 | self.paths.append(self.curr_path) 63 | self.curr_path = None 64 | 65 | -------------------------------------------------------------------------------- /pycam/PathProcessors/PolygonCutter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | Copyright 2008 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import pycam.PathProcessors 25 | from pycam.Geometry.Path import Path 26 | from pycam.Geometry.PolygonExtractor import PolygonExtractor 27 | from pycam.Toolpath import simplify_toolpath 28 | 29 | 30 | class PolygonCutter(pycam.PathProcessors.BasePathProcessor): 31 | def __init__(self, reverse=False): 32 | super(PolygonCutter, self).__init__() 33 | self.curr_path = None 34 | self.scanline = None 35 | self.polygon_extractor = PolygonExtractor(PolygonExtractor.MONOTONE) 36 | self.reverse = reverse 37 | 38 | def append(self, point): 39 | self.polygon_extractor.append(point) 40 | 41 | def new_direction(self, direction): 42 | self.polygon_extractor.new_direction(direction) 43 | 44 | def end_direction(self): 45 | self.polygon_extractor.end_direction() 46 | 47 | def new_scanline(self): 48 | self.polygon_extractor.new_scanline() 49 | 50 | def end_scanline(self): 51 | self.polygon_extractor.end_scanline() 52 | 53 | def finish(self): 54 | self.polygon_extractor.finish() 55 | paths = [] 56 | source_paths = [] 57 | if self.polygon_extractor.hor_path_list: 58 | source_paths.extend(self.polygon_extractor.hor_path_list) 59 | if self.polygon_extractor.ver_path_list: 60 | source_paths.extend(self.polygon_extractor.ver_path_list) 61 | for path in source_paths: 62 | points = path.points 63 | for i in range(0, (len(points)+1)/2): 64 | new_path = Path() 65 | if i % 2 == 0: 66 | new_path.append(points[i]) 67 | new_path.append(points[-i-1]) 68 | else: 69 | new_path.append(points[-i-1]) 70 | new_path.append(points[i]) 71 | paths.append(new_path) 72 | if paths: 73 | for path in paths: 74 | simplify_toolpath(path) 75 | if self.reverse: 76 | path.reverse() 77 | self.paths.extend(paths) 78 | self.sort_layered() 79 | 80 | -------------------------------------------------------------------------------- /pycam/PathProcessors/SimpleCutter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | import pycam.PathProcessors 24 | from pycam.Geometry.Path import Path 25 | from pycam.Toolpath import simplify_toolpath 26 | 27 | class SimpleCutter(pycam.PathProcessors.BasePathProcessor): 28 | def __init__(self, reverse=False): 29 | super(SimpleCutter, self).__init__() 30 | self.curr_path = None 31 | self.reverse = reverse 32 | 33 | def append(self, point): 34 | curr_path = None 35 | if self.curr_path == None: 36 | curr_path = Path() 37 | self.curr_path = curr_path 38 | else: 39 | curr_path = self.curr_path 40 | self.curr_path = None 41 | curr_path.append(point) 42 | if self.curr_path == None: 43 | simplify_toolpath(curr_path) 44 | if self.reverse: 45 | curr_path.reverse() 46 | self.paths.insert(0, curr_path) 47 | else: 48 | self.paths.append(curr_path) 49 | 50 | def new_scanline(self): 51 | if self.curr_path: 52 | print "ERROR: curr_path expected to be empty" 53 | self.curr_path = None 54 | 55 | def end_scanline(self): 56 | if self.curr_path: 57 | print "ERROR: curr_path expected to be empty" 58 | self.curr_path = None 59 | 60 | def finish(self): 61 | self.sort_layered() 62 | 63 | -------------------------------------------------------------------------------- /pycam/PathProcessors/ZigZagCutter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | import pycam.PathProcessors 24 | from pycam.Geometry.Path import Path 25 | from pycam.Toolpath import simplify_toolpath 26 | 27 | class ZigZagCutter(pycam.PathProcessors.BasePathProcessor): 28 | def __init__(self, reverse=False): 29 | super(ZigZagCutter, self).__init__() 30 | self.curr_path = None 31 | self.scanline = None 32 | self.curr_scanline = None 33 | self.reverse = reverse 34 | 35 | def append(self, point): 36 | curr_path = None 37 | if self.curr_path == None: 38 | curr_path = Path() 39 | self.curr_path = curr_path 40 | else: 41 | curr_path = self.curr_path 42 | self.curr_path = None 43 | 44 | curr_path.append(point) 45 | 46 | if self.curr_path == None: 47 | if (self.scanline % 2) == 0: 48 | self.curr_scanline.append(curr_path) 49 | else: 50 | curr_path.reverse() 51 | self.curr_scanline.insert(0, curr_path) 52 | 53 | def new_direction(self, direction): 54 | self.scanline = 0 55 | 56 | def new_scanline(self): 57 | self.scanline += 1 58 | self.curr_scanline = [] 59 | 60 | def end_scanline(self): 61 | for path in self.curr_scanline: 62 | simplify_toolpath(path) 63 | if self.reverse: 64 | path.reverse() 65 | self.paths.append(path) 66 | self.curr_scanline = None 67 | 68 | -------------------------------------------------------------------------------- /pycam/PathProcessors/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | __all__ = ["PathAccumulator", "SimpleCutter", "ZigZagCutter", "PolygonCutter", 24 | "ContourCutter", "BasePathProcessor"] 25 | 26 | 27 | class BasePathProcessor(object): 28 | 29 | def __init__(self): 30 | self.paths = [] 31 | 32 | def new_direction(self, direction): 33 | pass 34 | 35 | def end_direction(self): 36 | pass 37 | 38 | def finish(self): 39 | pass 40 | 41 | def sort_layered(self, upper_first=True): 42 | if upper_first: 43 | compare_height = lambda path1, path2: \ 44 | path1.points[0].z < path2.points[0].z 45 | else: 46 | compare_height = lambda path1, path2: \ 47 | path1.points[0].z > path2.points[0].z 48 | finished = False 49 | while not finished: 50 | index = 0 51 | finished = True 52 | while index < len(self.paths) - 1: 53 | current_path = self.paths[index] 54 | next_path = self.paths[index + 1] 55 | if compare_height(current_path, next_path): 56 | del self.paths[index] 57 | self.paths.insert(index + 1, current_path) 58 | finished = False 59 | index += 1 60 | 61 | -------------------------------------------------------------------------------- /pycam/Physics/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | __all__ = ["ode"] 24 | 25 | -------------------------------------------------------------------------------- /pycam/Plugins/EMCToolExport.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | import pycam.Exporters.EMCToolExporter 26 | 27 | FILTER_EMC_TOOL = (("EMC tool files", "*.tbl"),) 28 | 29 | class EMCToolExport(pycam.Plugins.PluginBase): 30 | 31 | UI_FILE = "emc_tool_export.ui" 32 | DEPENDS = ["Tools", "FilenameDialog"] 33 | CATEGORIES = ["Export"] 34 | 35 | def setup(self): 36 | self._last_emc_tool_file = None 37 | if self.gui: 38 | self.export_action = self.gui.get_object("ExportEMCToolDefinition") 39 | self.register_gtk_accelerator("export", self.export_action, None, 40 | "ExportEMCToolDefinition") 41 | self._gtk_handlers = ((self.export_action, "activate", 42 | self.export_emc_tools), ) 43 | self.core.register_ui("export_menu", "ExportEMCToolDefinition", 44 | self.export_action, 80) 45 | self._event_handlers = (("tool-selection-changed", 46 | self._update_emc_tool_button), ) 47 | self.register_gtk_handlers(self._gtk_handlers) 48 | self.register_event_handlers(self._event_handlers) 49 | self._update_emc_tool_button() 50 | return True 51 | 52 | def teardown(self): 53 | if self.gui: 54 | self.core.unregister_ui("export_menu", self.export_action) 55 | self.unregister_gtk_accelerator("export", self.export_action) 56 | self.unregister_gtk_handlers(self._gtk_handlers) 57 | self.unregister_event_handlers(self._event_handlers) 58 | 59 | def _update_emc_tool_button(self, widget=None): 60 | exportable = len(self.core.get("tools")) > 0 61 | self.export_action.set_sensitive(exportable) 62 | 63 | def export_emc_tools(self, widget=None, filename=None): 64 | if callable(filename): 65 | filename = filename() 66 | if not filename: 67 | # TODO: separate this away from Gui/Project.py 68 | # TODO: implement "last_model_filename" in core 69 | filename = self.core.get("get_filename_func")("Save toolpath to ...", 70 | mode_load=False, type_filter=FILTER_EMC_TOOL, 71 | filename_templates=(self._last_emc_tool_file, 72 | self.core.get("last_model_filename"))) 73 | if filename: 74 | self._last_emc_tool_file = filename 75 | tools_dict = [] 76 | tools = self.core.get("tools") 77 | for tool in tools: 78 | tools_dict.append({"name": tool["name"], "id": tool["id"], 79 | "radius": tool["parameters"].get("radius", 1)}) 80 | export = pycam.Exporters.EMCToolExporter.EMCToolExporter(tools_dict) 81 | text = export.get_tool_definition_string() 82 | try: 83 | out = file(filename, "w") 84 | out.write(text) 85 | out.close() 86 | self.log.info("EMC tool file written: %s" % filename) 87 | except IOError, err_msg: 88 | self.log.error("Failed to save EMC tool file: %s" % err_msg) 89 | else: 90 | self.core.emit_event("notify-file-saved", filename) 91 | 92 | -------------------------------------------------------------------------------- /pycam/Plugins/GCodeTouchOff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | 26 | 27 | class GCodeTouchOff(pycam.Plugins.PluginBase): 28 | 29 | DEPENDS = ["GCodePreferences"] 30 | CATEGORIES = ["GCode"] 31 | UI_FILE = "gcode_touch_off.ui" 32 | 33 | def setup(self): 34 | if self.gui: 35 | box = self.gui.get_object("TouchOffBox") 36 | box.unparent() 37 | self.core.register_ui("gcode_preferences", "Touch Off", 38 | box, weight=70) 39 | self._gtk_handlers = [] 40 | for objname, setting in ( 41 | ("GCodeTouchOffOnStartup", "touch_off_on_startup"), 42 | ("GCodeTouchOffOnToolChange", "touch_off_on_tool_change")): 43 | obj = self.gui.get_object(objname) 44 | self._gtk_handlers.append((obj, "toggled", self.update_widgets)) 45 | self.core.add_item(setting, obj.get_active, obj.set_active) 46 | selector = self.gui.get_object("TouchOffLocationSelector") 47 | self._gtk_handlers.append((selector, "changed", self.update_widgets)) 48 | selector.set_active(0) 49 | self.register_gtk_handlers(self._gtk_handlers) 50 | self.update_widgets() 51 | return True 52 | 53 | def teardown(self): 54 | if self.gui: 55 | self.core.unregister_ui("gcode_preferences", 56 | self.gui.get_object("TouchOffBox")) 57 | self.unregister_gtk_handlers(self._gtk_handlers) 58 | for setting in ("touch_off_on_startup", "touch_off_on_tool_change"): 59 | del self.core[setting] 60 | 61 | def update_widgets(self, widget=None): 62 | # tool change controls 63 | pos_control = self.gui.get_object("TouchOffLocationSelector") 64 | tool_change_pos_model = pos_control.get_model() 65 | active_pos_index = pos_control.get_active() 66 | if active_pos_index < 0: 67 | pos_key = None 68 | else: 69 | pos_key = tool_change_pos_model[active_pos_index][0] 70 | # disable/enable the touch off position controls 71 | position_controls_table = self.gui.get_object("TouchOffLocationTable") 72 | touch_off_enabled = any([self.gui.get_object(objname).get_active() 73 | for objname in ("GCodeTouchOffOnStartup", 74 | "GCodeTouchOffOnToolChange")]) 75 | position_controls_table.set_sensitive(touch_off_enabled) 76 | # show or hide the vbox containing the absolute tool change location 77 | absolute_pos_box = self.gui.get_object("AbsoluteToolChangePositionBox") 78 | if (pos_key == "absolute") and touch_off_enabled: 79 | absolute_pos_box.show() 80 | else: 81 | absolute_pos_box.hide() 82 | # disable/enable touch probe height 83 | if self.gui.get_object("GCodeTouchOffOnStartup").get_active(): 84 | update_func = "show" 85 | else: 86 | update_func = "hide" 87 | for objname in ("TouchOffHeight", "TouchOffHeightLabel", 88 | "LengthUnitTouchOffHeight"): 89 | getattr(self.gui.get_object(objname), update_func)() 90 | 91 | -------------------------------------------------------------------------------- /pycam/Plugins/ModelPlaneMirror.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | 26 | 27 | class ModelPlaneMirror(pycam.Plugins.PluginBase): 28 | 29 | UI_FILE = "model_plane_mirror.ui" 30 | DEPENDS = ["Models"] 31 | CATEGORIES = ["Model"] 32 | 33 | def setup(self): 34 | if self.gui: 35 | mirror_box = self.gui.get_object("ModelMirrorBox") 36 | mirror_box.unparent() 37 | self.core.register_ui("model_handling", "Mirror", mirror_box, 0) 38 | self._gtk_handlers = ((self.gui.get_object("PlaneMirrorButton"), 39 | "clicked", self._plane_mirror), ) 40 | self._event_handlers = (("model-selection-changed", 41 | self._update_plane_widgets), ) 42 | self.register_gtk_handlers(self._gtk_handlers) 43 | self.register_event_handlers(self._event_handlers) 44 | self._update_plane_widgets() 45 | return True 46 | 47 | def teardown(self): 48 | if self.gui: 49 | self.unregister_gtk_handlers(self._gtk_handlers) 50 | self.unregister_event_handlers(self._event_handlers) 51 | 52 | def _update_plane_widgets(self): 53 | plane_widget = self.gui.get_object("ModelMirrorBox") 54 | if self.core.get("models").get_selected(): 55 | plane_widget.show() 56 | else: 57 | plane_widget.hide() 58 | 59 | def _plane_mirror(self, widget=None): 60 | models = self.core.get("models").get_selected() 61 | if not models: 62 | return 63 | self.core.emit_event("model-change-before") 64 | progress = self.core.get("progress") 65 | progress.update(text="Mirroring model") 66 | progress.disable_cancel() 67 | progress.set_multiple(len(models), "Model") 68 | for plane in ("XY", "XZ", "YZ"): 69 | if self.gui.get_object("MirrorPlane%s" % plane).get_active(): 70 | break 71 | for model in models: 72 | model.model.transform_by_template("%s_mirror" % plane.lower(), 73 | callback=progress.update) 74 | progress.update_multiple() 75 | progress.finish() 76 | self.core.emit_event("model-change-after") 77 | 78 | -------------------------------------------------------------------------------- /pycam/Plugins/ModelPolygons.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | 26 | 27 | class ModelPolygons(pycam.Plugins.PluginBase): 28 | 29 | UI_FILE = "model_polygons.ui" 30 | DEPENDS = ["Models"] 31 | CATEGORIES = ["Model"] 32 | 33 | def setup(self): 34 | if self.gui: 35 | polygon_frame = self.gui.get_object("ModelPolygonFrame") 36 | polygon_frame.unparent() 37 | self.core.register_ui("model_handling", "Polygons", polygon_frame, 0) 38 | self._gtk_handlers = ( 39 | (self.gui.get_object("ToggleModelDirectionButton"), "clicked", 40 | self._toggle_direction), 41 | (self.gui.get_object("DirectionsGuessButton"), "clicked", 42 | self._revise_directions)) 43 | self._event_handlers = ( 44 | ("model-change-after", self._update_polygon_controls), 45 | ("model-selection-changed", self._update_polygon_controls)) 46 | self.register_gtk_handlers(self._gtk_handlers) 47 | self.register_event_handlers(self._event_handlers) 48 | self._update_polygon_controls() 49 | return True 50 | 51 | def teardown(self): 52 | if self.gui: 53 | self.core.unregister_ui("model_handling", 54 | self.gui.get_object("ModelPolygonFrame")) 55 | self.unregister_gtk_handlers(self._gtk_handlers) 56 | self.unregister_event_handlers(self._event_handlers) 57 | 58 | def _get_polygon_models(self): 59 | models = [] 60 | for model in self.core.get("models").get_selected(): 61 | if model and hasattr(model.model, "reverse_directions"): 62 | models.append(model) 63 | return models 64 | 65 | def _update_polygon_controls(self): 66 | models = self._get_polygon_models() 67 | frame = self.gui.get_object("ModelPolygonFrame") 68 | if models: 69 | frame.show() 70 | else: 71 | frame.hide() 72 | 73 | def _toggle_direction(self, widget=None): 74 | models = self._get_polygon_models() 75 | if not models: 76 | return 77 | self.core.emit_event("model-change-before") 78 | progress = self.core.get("progress") 79 | progress.update(text="Reversing directions of contour model") 80 | progress.set_multiple(len(models), "Model") 81 | for model in models: 82 | model.model.reverse_directions(callback=progress.update) 83 | progress.update_multiple() 84 | progress.finish() 85 | self.core.emit_event("model-change-after") 86 | 87 | def _revise_directions(self, widget=None): 88 | models = self._get_polygon_models() 89 | if not models: 90 | return 91 | self.core.emit_event("model-change-before") 92 | progress = self.core.get("progress") 93 | progress.update(text="Analyzing directions of contour model") 94 | progress.set_multiple(len(models), "Model") 95 | for model in models: 96 | model.model.revise_directions(callback=progress.update) 97 | progress.update_multiple() 98 | progress.finish() 99 | self.core.emit_event("model-change-after") 100 | 101 | -------------------------------------------------------------------------------- /pycam/Plugins/ModelProjection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | from pycam.Geometry.Plane import Plane 26 | from pycam.Geometry.Point import Point, Vector 27 | 28 | 29 | class ModelProjection(pycam.Plugins.PluginBase): 30 | 31 | UI_FILE = "model_projection.ui" 32 | DEPENDS = ["Models"] 33 | CATEGORIES = ["Model"] 34 | 35 | def setup(self): 36 | if self.gui: 37 | projection_frame = self.gui.get_object("ModelProjectionFrame") 38 | projection_frame.unparent() 39 | self.core.register_ui("model_handling", "Projection", 40 | projection_frame, 10) 41 | self._gtk_handlers = ((self.gui.get_object("ProjectionButton"), 42 | "clicked", self._projection), ) 43 | self._event_handlers = ( 44 | ("model-change-after", self._update_controls), 45 | ("model-selection-changed", self._update_controls)) 46 | self.register_gtk_handlers(self._gtk_handlers) 47 | self.register_event_handlers(self._event_handlers) 48 | self._update_controls() 49 | return True 50 | 51 | def teardown(self): 52 | if self.gui: 53 | self.core.unregister_ui("model_handling", 54 | self.gui.get_object("ModelProjectionFrame")) 55 | self.unregister_gtk_handlers(self._gtk_handlers) 56 | self.unregister_event_handlers(self._event_handlers) 57 | 58 | def _get_projectable_models(self): 59 | models = self.core.get("models").get_selected() 60 | projectables = [] 61 | for model in models: 62 | if (not model is None) and \ 63 | hasattr(model.model, "get_waterline_contour"): 64 | projectables.append(model) 65 | return projectables 66 | 67 | def _update_controls(self): 68 | models = self._get_projectable_models() 69 | control = self.gui.get_object("ModelProjectionFrame") 70 | if models: 71 | control.show() 72 | else: 73 | control.hide() 74 | 75 | def _projection(self, widget=None): 76 | models = self._get_projectable_models() 77 | if not models: 78 | return 79 | progress = self.core.get("progress") 80 | progress.update(text="Calculating 2D projection") 81 | progress.set_multiple(len(models), "Model") 82 | for model_dict in models: 83 | model = model_dict.model 84 | for objname, z_level in (("ProjectionModelTop", model.maxz), 85 | ("ProjectionModelMiddle", (model.minz + model.maxz) / 2.0), 86 | ("ProjectionModelBottom", model.minz), 87 | ("ProjectionModelCustom", 88 | self.gui.get_object("ProjectionZLevel").get_value())): 89 | if self.gui.get_object(objname).get_active(): 90 | plane = Plane(Point(0, 0, z_level), Vector(0, 0, 1)) 91 | self.log.info("Projecting 3D model at level z=%g" % plane.p.z) 92 | new_model = model.get_waterline_contour(plane, 93 | callback=progress.update) 94 | if new_model: 95 | self.core.get("models").add_model(new_model, 96 | name_template="Projected model #%d") 97 | else: 98 | self.log.warn("The 2D projection at z=%g is empty. Aborted." % \ 99 | plane.p.z) 100 | break 101 | progress.update_multiple() 102 | progress.finish() 103 | 104 | -------------------------------------------------------------------------------- /pycam/Plugins/ModelRotation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | import pycam.Geometry.Matrix 26 | 27 | 28 | class ModelRotation(pycam.Plugins.PluginBase): 29 | 30 | UI_FILE = "model_rotation.ui" 31 | DEPENDS = ["Models"] 32 | CATEGORIES = ["Model"] 33 | 34 | def setup(self): 35 | if self.gui: 36 | rotation_box = self.gui.get_object("ModelRotationBox") 37 | rotation_box.unparent() 38 | self.core.register_ui("model_handling", "Rotation", rotation_box, 39 | -10) 40 | self._gtk_handlers = ((self.gui.get_object("RotateModelButton"), 41 | "clicked", self._rotate_model), ) 42 | self._event_handlers = (("model-selection-changed", 43 | self._update_controls), ) 44 | self.register_gtk_handlers(self._gtk_handlers) 45 | self.register_event_handlers(self._event_handlers) 46 | self._update_controls() 47 | return True 48 | 49 | def teardown(self): 50 | if self.gui: 51 | self.core.unregister_ui("model_handling", 52 | self.gui.get_object("ModelRotationBox")) 53 | self.unregister_gtk_handlers(self._gtk_handlers) 54 | self.unregister_event_handlers(self._event_handlers) 55 | 56 | def _update_controls(self): 57 | widget = self.gui.get_object("ModelRotationBox") 58 | if self.core.get("models").get_selected(): 59 | widget.show() 60 | else: 61 | widget.hide() 62 | 63 | def _rotate_model(self, widget=None): 64 | models = self.core.get("models").get_selected() 65 | if not models: 66 | return 67 | self.core.emit_event("model-change-before") 68 | for axis in "XYZ": 69 | if self.gui.get_object("RotationAxis%s" % axis).get_active(): 70 | break 71 | axis_vector = {"X": (1, 0, 0), "Y": (0, 1, 0), "Z": (0, 0, 1)}[axis] 72 | for control, angle in (("RotationAngle90CCKW", -90), 73 | ("RotationAngle90CKW", 90), 74 | ("RotationAngle180", 180), 75 | ("RotationAngleCustomCKW", 76 | self.gui.get_object("RotationAngle").get_value())): 77 | if self.gui.get_object(control).get_active(): 78 | break 79 | matrix = pycam.Geometry.Matrix.get_rotation_matrix_axis_angle( 80 | axis_vector, angle, use_radians=False) 81 | progress = self.core.get("progress") 82 | progress.update(text="Rotating model") 83 | progress.disable_cancel() 84 | progress.set_multiple(len(models), "Model") 85 | for model in models: 86 | model.model.transform_by_matrix(matrix, callback=progress.update) 87 | progress.update_multiple() 88 | self.core.emit_event("model-change-after") 89 | progress.finish() 90 | 91 | -------------------------------------------------------------------------------- /pycam/Plugins/ModelSwapAxes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | 26 | 27 | class ModelSwapAxes(pycam.Plugins.PluginBase): 28 | 29 | UI_FILE = "model_swap_axes.ui" 30 | DEPENDS = ["Models"] 31 | CATEGORIES = ["Model"] 32 | 33 | def setup(self): 34 | if self.gui: 35 | swap_box = self.gui.get_object("ModelSwapBox") 36 | swap_box.unparent() 37 | self.core.register_ui("model_handling", "Swap axes", swap_box, 0) 38 | self._gtk_handlers = ((self.gui.get_object("SwapAxesButton"), 39 | "clicked", self._swap_axes), ) 40 | self._event_handlers = (("model-selection-changed", 41 | self._update_controls), ) 42 | self.register_gtk_handlers(self._gtk_handlers) 43 | self.register_event_handlers(self._event_handlers) 44 | self._update_controls() 45 | return True 46 | 47 | def teardown(self): 48 | if self.gui: 49 | self.core.unregister_ui("model_handling", 50 | self.gui.get_object("ModelSwapBox")) 51 | self.unregister_gtk_handlers(self._gtk_handlers) 52 | self.unregister_event_handlers(self._event_handlers) 53 | 54 | def _update_controls(self): 55 | box = self.gui.get_object("ModelSwapBox") 56 | if self.core.get("models").get_selected(): 57 | box.show() 58 | else: 59 | box.hide() 60 | 61 | def _swap_axes(self, widget=None): 62 | models = self.core.get("models").get_selected() 63 | if not models: 64 | return 65 | self.core.emit_event("model-change-before") 66 | for axes, template in (("XY", "x_swap_y"), ("XZ", "x_swap_z"), 67 | ("YZ", "y_swap_z")): 68 | if self.gui.get_object("SwapAxes%s" % axes).get_active(): 69 | break 70 | progress = self.core.get("progress") 71 | progress.update(text="Swap axes of model") 72 | progress.disable_cancel() 73 | progress.set_multiple(len(models), "Model") 74 | for model in models: 75 | model.model.transform_by_template(template, 76 | callback=progress.update) 77 | progress.update_multiple() 78 | progress.finish() 79 | self.core.emit_event("model-change-after") 80 | 81 | -------------------------------------------------------------------------------- /pycam/Plugins/OpenGLViewAxes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | import pycam.Plugins 24 | from pycam.Gui.OpenGLTools import draw_direction_cone 25 | 26 | 27 | class OpenGLViewAxes(pycam.Plugins.PluginBase): 28 | 29 | DEPENDS = ["OpenGLWindow"] 30 | CATEGORIES = ["Visualization", "OpenGL"] 31 | 32 | def setup(self): 33 | import OpenGL.GL 34 | self._GL = OpenGL.GL 35 | self.core.register_event("visualize-items", self.draw_axes) 36 | self.core.get("register_display_item")("show_axes", 37 | "Show Coordinate System", 50) 38 | self.core.emit_event("visual-item-updated") 39 | return True 40 | 41 | def teardown(self): 42 | self.core.unregister_event("visualize-items", self.draw_axes) 43 | self.core.get("unregister_display_item")("show_axes") 44 | self.core.emit_event("visual-item-updated") 45 | 46 | def draw_axes(self): 47 | if not self.core.get("show_axes"): 48 | return 49 | GL = self._GL 50 | GL.glMatrixMode(GL.GL_MODELVIEW) 51 | GL.glLoadIdentity() 52 | low, high = [None, None, None], [None, None, None] 53 | self.core.call_chain("get_draw_dimension", low, high) 54 | if None in low or None in high: 55 | low, high = (0, 0, 0), (10, 10, 10) 56 | length = 1.2 * max(max(high), abs(min(low))) 57 | origin = (0, 0, 0) 58 | cone_length = 0.05 59 | old_line_width = GL.glGetFloatv(GL.GL_LINE_WIDTH) 60 | if self.core.get("view_light"): 61 | GL.glDisable(GL.GL_LIGHTING) 62 | GL.glLineWidth(1.5) 63 | # draw a colored line ending in a cone for each axis 64 | for index in range(3): 65 | end = [0, 0, 0] 66 | end[index] = length 67 | color = [0.0, 0.0, 0.0] 68 | # reduced brightness (not 1.0) 69 | color[index] = 0.8 70 | GL.glColor3f(*color) 71 | # we need to wait until the color change is active 72 | GL.glFinish() 73 | GL.glBegin(GL.GL_LINES) 74 | GL.glVertex3f(*origin) 75 | GL.glVertex3f(*end) 76 | GL.glEnd() 77 | # Position the cone slightly behind the end of the line - otherwise 78 | # the end of the line (width=2) is visible at the top of the cone. 79 | draw_direction_cone(origin, end, position=1.0 + cone_length, 80 | precision=32, size=cone_length) 81 | GL.glLineWidth(old_line_width) 82 | if self.core.get("view_light"): 83 | GL.glEnable(GL.GL_LIGHTING) 84 | 85 | -------------------------------------------------------------------------------- /pycam/Plugins/OpenGLViewBounds.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | import pycam.Plugins 24 | 25 | 26 | class OpenGLViewBounds(pycam.Plugins.PluginBase): 27 | 28 | DEPENDS = ["OpenGLWindow", "Bounds"] 29 | CATEGORIES = ["Bounds", "Visualization", "OpenGL"] 30 | 31 | def setup(self): 32 | import OpenGL.GL 33 | self._GL = OpenGL.GL 34 | self.core.get("register_color")("color_bounding_box", "Bounding box", 35 | 40) 36 | self.core.get("register_display_item")("show_bounding_box", 37 | "Show Bounding Box", 40) 38 | self.core.register_chain("get_draw_dimension", self.get_draw_dimension) 39 | self.core.register_event("visualize-items", self.draw_bounds) 40 | self.core.emit_event("visual-item-updated") 41 | return True 42 | 43 | def teardown(self): 44 | self.core.unregister_event("visualize-items", self.draw_bounds) 45 | self.core.unregister_chain("get_draw_dimension", 46 | self.get_draw_dimension) 47 | self.core.get("unregister_color")("color_bounding_box") 48 | self.core.get("unregister_display_item")("show_bounding_box") 49 | self.core.emit_event("visual-item-updated") 50 | 51 | def get_draw_dimension(self, low, high): 52 | if not self.core.get("show_bounding_box"): 53 | return 54 | mlow, mhigh = self._get_bounds() 55 | if None in mlow or None in mhigh: 56 | return 57 | for index in range(3): 58 | if (low[index] is None) or (mlow[index] < low[index]): 59 | low[index] = mlow[index] 60 | if (high[index] is None) or (mhigh[index] > high[index]): 61 | high[index] = mhigh[index] 62 | 63 | def _get_bounds(self): 64 | bounds = self.core.get("bounds").get_selected() 65 | if not bounds: 66 | return ([None, None, None], [None, None, None]) 67 | low, high = bounds.get_absolute_limits() 68 | return low, high 69 | 70 | def draw_bounds(self): 71 | GL = self._GL 72 | if not self.core.get("show_bounding_box"): 73 | return 74 | low, high = self._get_bounds() 75 | if None in low or None in high: 76 | return 77 | minx, miny, minz = low[0], low[1], low[2] 78 | maxx, maxy, maxz = high[0], high[1], high[2] 79 | p1 = [minx, miny, minz] 80 | p2 = [minx, maxy, minz] 81 | p3 = [maxx, maxy, minz] 82 | p4 = [maxx, miny, minz] 83 | p5 = [minx, miny, maxz] 84 | p6 = [minx, maxy, maxz] 85 | p7 = [maxx, maxy, maxz] 86 | p8 = [maxx, miny, maxz] 87 | if self.core.get("view_light"): 88 | GL.glDisable(GL.GL_LIGHTING) 89 | # lower rectangle 90 | color = self.core.get("color_bounding_box") 91 | GL.glColor4f(color["red"], color["green"], color["blue"], color["alpha"]) 92 | GL.glFinish() 93 | GL.glBegin(GL.GL_LINES) 94 | # all combinations of neighbouring corners 95 | for corner_pair in [(p1, p2), (p1, p5), (p1, p4), (p2, p3), 96 | (p2, p6), (p3, p4), (p3, p7), (p4, p8), (p5, p6), 97 | (p6, p7), (p7, p8), (p8, p5)]: 98 | GL.glVertex3f(*(corner_pair[0])) 99 | GL.glVertex3f(*(corner_pair[1])) 100 | GL.glEnd() 101 | if self.core.get("view_light"): 102 | GL.glEnable(GL.GL_LIGHTING) 103 | 104 | -------------------------------------------------------------------------------- /pycam/Plugins/OpenGLViewDimension.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | 26 | 27 | class OpenGLViewDimension(pycam.Plugins.PluginBase): 28 | 29 | UI_FILE = "opengl_view_dimension.ui" 30 | DEPENDS = ["Models", "OpenGLWindow"] 31 | CATEGORIES = ["Model", "Visualization", "OpenGL"] 32 | 33 | def setup(self): 34 | if self.gui: 35 | import pango 36 | self.core.register_ui("opengl_window", "Dimension", 37 | self.gui.get_object("DimensionTable"), weight=20) 38 | self.core.get("register_display_item")("show_dimensions", 39 | "Show Dimensions", 60), 40 | # Color the dimension value according to the axes. 41 | # For "y" axis: 100% green is too bright on light background - we 42 | # reduce it a bit. 43 | for color, names in ( 44 | (pango.AttrForeground(65535, 0, 0, 0, 100), 45 | ("model_dim_x_label", "model_dim_x", "ModelCornerXMax", 46 | "ModelCornerXMin", "ModelCornerXSpaces")), 47 | (pango.AttrForeground(0, 50000, 0, 0, 100), 48 | ("model_dim_y_label", "model_dim_y", "ModelCornerYMax", 49 | "ModelCornerYMin", "ModelCornerYSpaces")), 50 | (pango.AttrForeground(0, 0, 65535, 0, 100), 51 | ("model_dim_z_label", "model_dim_z", "ModelCornerZMax", 52 | "ModelCornerZMin", "ModelCornerZSpaces"))): 53 | attributes = pango.AttrList() 54 | attributes.insert(color) 55 | for name in names: 56 | self.gui.get_object(name).set_attributes(attributes) 57 | self._event_handlers = ( 58 | ("model-change-after", self.update_model_dimensions), 59 | ("visual-item-updated", self.update_model_dimensions), 60 | ("model-list-chaned", self.update_model_dimensions)) 61 | self.register_event_handlers(self._event_handlers) 62 | return True 63 | 64 | def teardown(self): 65 | if self.gui: 66 | self.unregister_event_handlers(self._event_handlers) 67 | self.core.unregister_ui("opengl_window", 68 | self.gui.get_object("DimensionTable")) 69 | self.core.get("unregister_display_item")("show_dimensions") 70 | 71 | def update_model_dimensions(self, widget=None): 72 | dimension_bar = self.gui.get_object("DimensionTable") 73 | models = [m.model for m in self.core.get("models").get_visible()] 74 | low, high = pycam.Geometry.Model.get_combined_bounds(models) 75 | if None in low or None in high: 76 | low, high = (0, 0, 0), (0, 0, 0) 77 | if self.core.get("show_dimensions"): 78 | for value, label_suffix in ((low[0], "XMin"), (low[1], "YMin"), 79 | (low[2], "ZMin"), (high[0], "XMax"), (high[1], "YMax"), 80 | (high[2], "ZMax")): 81 | label_name = "ModelCorner%s" % label_suffix 82 | value = "%.3f" % value 83 | self.gui.get_object(label_name).set_label(value) 84 | for name, size in ( 85 | ("model_dim_x", high[0] - low[0]), 86 | ("model_dim_y", high[1] - low[1]), 87 | ("model_dim_z", high[2] - low[2])): 88 | self.gui.get_object(name).set_text("%.3f %s" \ 89 | % (size, self.core.get("unit_string"))) 90 | 91 | dimension_bar.show() 92 | else: 93 | dimension_bar.hide() 94 | -------------------------------------------------------------------------------- /pycam/Plugins/OpenGLViewSupportModelPreview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | import pycam.Plugins 24 | 25 | 26 | class OpenGLViewSupportModelPreview(pycam.Plugins.PluginBase): 27 | 28 | DEPENDS = ["OpenGLWindow", "OpenGLViewModel"] 29 | CATEGORIES = ["Visualization", "OpenGL", "Support bridges"] 30 | 31 | def setup(self): 32 | import OpenGL.GL 33 | import OpenGL.GLUT 34 | self._GL = OpenGL.GL 35 | self.core.register_event("visualize-items", self.draw_support_preview) 36 | self.core.get("register_display_item")("show_support_preview", "Show Support Model Preview", 30) 37 | self.core.get("register_color")("color_support_preview", "Support model", 30) 38 | self.core.emit_event("visual-item-updated") 39 | return True 40 | 41 | def teardown(self): 42 | self.core.unregister_event("visualize-items", self.draw_support_preview) 43 | self.core.get("unregister_display_item")("show_support_preview") 44 | self.core.get("unregister_color")("color_support_preview") 45 | self.core.emit_event("visual-item-updated") 46 | 47 | def draw_support_preview(self): 48 | if not self.core.get("show_support_preview"): 49 | return 50 | models = self.core.get("current_support_models")[:] 51 | if not models: 52 | return 53 | GL = self._GL 54 | # disable lighting 55 | if self.core.get("view_light"): 56 | GL.glDisable(GL.GL_LIGHTING) 57 | # show a wireframe 58 | if self.core.get("view_polygon"): 59 | GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE) 60 | # change the color 61 | col = self.core.get("color_support_preview") 62 | color = (col["red"], col["green"], col["blue"], col["alpha"]) 63 | GL.glColor4f(*color) 64 | # we need to wait until the color change is active 65 | GL.glFinish() 66 | # draw the models 67 | self.core.call_chain("draw_models", models) 68 | # enable lighting again 69 | if self.core.get("view_light"): 70 | GL.glEnable(GL.GL_LIGHTING) 71 | # enable polygon fill mode again 72 | if self.core.get("view_polygon"): 73 | GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL) 74 | 75 | -------------------------------------------------------------------------------- /pycam/Plugins/PathPatterns.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | import pycam.Toolpath.MotionGrid 26 | 27 | 28 | class PathPatternSpiral(pycam.Plugins.PluginBase): 29 | 30 | DEPENDS = ["ParameterGroupManager", "PathParamPattern", 31 | "PathParamMillingStyle", "PathParamSpiralDirection", 32 | "PathParamRoundedSpiralCorners"] 33 | CATEGORIES = ["Process", "Path pattern"] 34 | 35 | def setup(self): 36 | parameters = { 37 | "milling_style": pycam.Toolpath.MotionGrid.MILLING_STYLE_IGNORE, 38 | "spiral_direction": None, 39 | "rounded_corners": False, 40 | } 41 | self.core.get("register_parameter_set")("path_pattern", "spiral", 42 | "Spiral", self.get_grid_generator, parameters=parameters, 43 | weight=30) 44 | return True 45 | 46 | def teardown(self): 47 | self.core.get("unregister_parameter_set")("path_pattern", "spiral") 48 | 49 | def get_grid_generator(self, pattern): 50 | kwargs = pattern["parameters"] 51 | func = pycam.Toolpath.MotionGrid.get_spiral 52 | return func, kwargs 53 | 54 | 55 | class PathPatternGrid(pycam.Plugins.PluginBase): 56 | 57 | DEPENDS = ["ParameterGroupManager", "PathParamPattern", 58 | "PathParamMillingStyle", "PathParamGridDirection"] 59 | CATEGORIES = ["Process", "Path pattern"] 60 | 61 | def setup(self): 62 | parameters = { 63 | "milling_style": pycam.Toolpath.MotionGrid.MILLING_STYLE_IGNORE, 64 | "grid_direction": pycam.Toolpath.MotionGrid.GRID_DIRECTION_X, 65 | } 66 | self.core.get("register_parameter_set")("path_pattern", "grid", 67 | "Grid", self.get_grid_generator, parameters=parameters, 68 | weight=10) 69 | return True 70 | 71 | def teardown(self): 72 | self.core.get("unregister_parameter_set")("path_pattern", "grid") 73 | 74 | def get_grid_generator(self, pattern): 75 | kwargs = pattern["parameters"] 76 | func = pycam.Toolpath.MotionGrid.get_fixed_grid 77 | return func, kwargs 78 | 79 | -------------------------------------------------------------------------------- /pycam/Plugins/PostprocessorEMC2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | from pycam.Exporters.GCodeExporter import GCodeGenerator 26 | 27 | class PostprocessorEMC2(pycam.Plugins.PluginBase): 28 | 29 | DEPENDS = ["ToolpathExport"] 30 | CATEGORIES = ["Postprocessor"] 31 | 32 | def setup(self): 33 | self.core.get("register_postprocessor")("emc2", "EMC2", GCodeGenerator) 34 | return True 35 | 36 | def teardown(self): 37 | self.core.get("unregister_postprocessor")("emc2") 38 | 39 | -------------------------------------------------------------------------------- /pycam/Plugins/TaskTypes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | 26 | 27 | class TaskTypeMilling(pycam.Plugins.PluginBase): 28 | 29 | DEPENDS = ["Tasks", "TaskParamCollisionModels", "TaskParamTool", 30 | "TaskParamProcess", "TaskParamBounds"] 31 | CATEGORIES = ["Task"] 32 | 33 | def setup(self): 34 | parameters = {"collision_models": [], 35 | "tool": None, 36 | "process": None, 37 | "bounds": None, 38 | } 39 | self.core.get("register_parameter_set")("task", "milling", 40 | "Milling", self.run_task, parameters=parameters, 41 | weight=10) 42 | return True 43 | 44 | def teardown(self): 45 | self.core.get("unregister_parameter_set")("task", "milling") 46 | 47 | def run_task(self, task, callback=None): 48 | environment = {} 49 | for key in task["parameters"]: 50 | environment[key] = task["parameters"][key] 51 | if environment["tool"] is None: 52 | self.log.error("You need to assign a tool to this task.") 53 | return 54 | if environment["process"] is None: 55 | self.log.error("You need to assign a process to this task.") 56 | return 57 | if environment["bounds"] is None: 58 | self.log.error("You need to assign bounds to this task.") 59 | return 60 | funcs = {} 61 | for key, set_name in (("tool", "shape"), ("process", "strategy")): 62 | funcs[key] = self.core.get("get_parameter_sets")( 63 | key)[environment[key][set_name]]["func"] 64 | tool = funcs["tool"](tool=environment["tool"], environment=environment) 65 | path_generator, motion_grid, (low, high) = funcs["process"]( 66 | environment["process"], environment=environment) 67 | if path_generator is None: 68 | # we assume that an error message was given already 69 | return 70 | models = [m.model for m in task["parameters"]["collision_models"]] 71 | if not models: 72 | # issue a warning - and go ahead ... 73 | self.log.warn("No collision model was selected. This can be " + \ 74 | "intentional, but maybe you simply forgot it.") 75 | moves = path_generator.GenerateToolPath(tool, models, motion_grid, 76 | minz=low[2], maxz=high[2], draw_callback=callback) 77 | if not moves: 78 | self.log.info("No valid moves found") 79 | return None 80 | data = {} 81 | for item_name in ("tool", "process", "bounds"): 82 | self.core.call_chain("get_toolpath_information", 83 | environment[item_name], data) 84 | tp = pycam.Toolpath.Toolpath(moves, parameters=data) 85 | return tp 86 | 87 | -------------------------------------------------------------------------------- /pycam/Plugins/ToolTypes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2011 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | 24 | import pycam.Plugins 25 | import pycam.Cutters.SphericalCutter 26 | import pycam.Cutters.ToroidalCutter 27 | import pycam.Cutters.CylindricalCutter 28 | 29 | 30 | class ToolTypeBallNose(pycam.Plugins.PluginBase): 31 | 32 | DEPENDS = ["Tools", "ToolParamRadius", "ToolParamFeedrate"] 33 | CATEGORIES = ["Tool", "Parameter"] 34 | 35 | def setup(self): 36 | parameters = {"radius": 1.0, 37 | "feedrate": 300, 38 | "spindle_speed": 1000, 39 | } 40 | self.core.get("register_parameter_set")("tool", "ballnose", 41 | "Ball nose", self.get_tool, parameters=parameters, 42 | weight=20) 43 | return True 44 | 45 | def teardown(self): 46 | self.core.get("unregister_parameter_set")("tool", "ballnose") 47 | 48 | def get_tool(self, tool, environment=None): 49 | return pycam.Cutters.SphericalCutter(tool["parameters"]["radius"]) 50 | 51 | 52 | class ToolTypeBullNose(pycam.Plugins.PluginBase): 53 | 54 | DEPENDS = ["Tools", "ToolParamRadius", "ToolParamTorusRadius", 55 | "ToolParamFeedrate"] 56 | CATEGORIES = ["Tool", "Parameter"] 57 | 58 | def setup(self): 59 | parameters = {"radius": 1.0, 60 | "torus_radius": 0.25, 61 | "feedrate": 300, 62 | "spindle_speed": 1000, 63 | } 64 | self.core.get("register_parameter_set")("tool", "bullnose", 65 | "Bull nose", self.get_tool, parameters=parameters, 66 | weight=30) 67 | return True 68 | 69 | def teardown(self): 70 | self.core.get("unregister_parameter_set")("tool", "bullnose") 71 | 72 | def get_tool(self, tool, environment=None): 73 | return pycam.Cutters.ToroidalCutter( 74 | tool["parameters"]["radius"], 75 | tool["parameters"]["torus_radius"]) 76 | 77 | 78 | class ToolTypeFlat(pycam.Plugins.PluginBase): 79 | 80 | DEPENDS = ["Tools", "ToolParamRadius", "ToolParamFeedrate"] 81 | CATEGORIES = ["Tool", "Parameter"] 82 | 83 | def setup(self): 84 | parameters = {"radius": 1.0, 85 | "feedrate": 300, 86 | "spindle_speed": 1000, 87 | } 88 | self.core.get("register_parameter_set")("tool", "flat", "Flat bottom", 89 | self.get_tool, parameters=parameters, weight=10) 90 | return True 91 | 92 | def teardown(self): 93 | self.core.get("unregister_parameter_set")("tool", "flat") 94 | 95 | def get_tool(self, tool, environment=None): 96 | return pycam.Cutters.CylindricalCutter(tool["parameters"]["radius"]) 97 | 98 | -------------------------------------------------------------------------------- /pycam/Simulation/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | Copyright 2009 Lode Leroy 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | __all__ = ['ZBuffer', 'ODEBlocks'] 25 | 26 | -------------------------------------------------------------------------------- /pycam/Utils/iterators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | class Iterator(object): 24 | def __init__(self, seq, start=0): 25 | self.seq = seq 26 | self.ind = start 27 | 28 | def next(self): 29 | if self.ind >= len(self.seq): 30 | return None 31 | else: 32 | item = self.seq[self.ind] 33 | self.ind += 1 34 | return item 35 | 36 | def insertBefore(self, item): 37 | self.seq.insert(self.ind - 1, item) 38 | self.ind += 1 39 | 40 | def insert(self, item): 41 | self.seq.insert(self.ind, item) 42 | self.ind += 1 43 | 44 | def replace(self, item_old, item_new): 45 | for i in range(len(self.seq)): 46 | if self.seq[i] == item_old: 47 | self.seq[i] = item_new 48 | 49 | def remove(self, item): 50 | for i in range(len(self.seq)): 51 | if self.seq[i] == item: 52 | del self.seq[i] 53 | if i < self.ind: 54 | self.ind -= 1 55 | return 56 | 57 | def takeNext(self): 58 | if self.ind >= len(self.seq): 59 | return None 60 | else: 61 | return self.seq.pop(self.ind) 62 | 63 | def copy(self): 64 | return Iterator(self.seq, self.ind) 65 | 66 | def peek(self, i=0): 67 | if self.ind + i >= len(self.seq): 68 | return None 69 | else: 70 | return self.seq[self.ind + i] 71 | 72 | def remains(self): 73 | return len(self.seq) - self.ind 74 | 75 | 76 | class CyclicIterator(object): 77 | def __init__(self, seq, start=0): 78 | self.seq = seq 79 | self.ind = start 80 | self.count = len(seq) 81 | 82 | def next(self): 83 | item = self.seq[self.ind] 84 | self.ind += 1 85 | if self.ind == len(self.seq): 86 | self.ind = 0 87 | return item 88 | 89 | def copy(self): 90 | return CyclicIterator(self.seq, self.ind) 91 | 92 | def peek(self, i=0): 93 | idx = self.ind + i 94 | while idx >= len(self.seq): 95 | idx -= len(self.seq) 96 | return self.seq[idx] 97 | 98 | 99 | if __name__ == "__main__": 100 | l = [1, 2, 4, 6] 101 | print "l=", l 102 | i = Iterator(l) 103 | print i.peek() 104 | while True: 105 | val = i.next() 106 | if val == None: 107 | break 108 | if val == 4: 109 | i.insertBefore(3) 110 | i.insert(5) 111 | 112 | print "l=", l 113 | i = Iterator(l) 114 | print "peek(0)=", i.peek(0) 115 | print "peek(1)=", i.peek(1) 116 | print "i.next()=", i.next() 117 | print "peek(0)=", i.peek(0) 118 | print "peek(1)=", i.peek(1) 119 | 120 | print "remains=", i.remains() 121 | 122 | print "l=", l 123 | sum_value = 0 124 | i = CyclicIterator(l) 125 | print "cycle :", 126 | while sum_value < 30: 127 | val = i.next() 128 | print val, 129 | sum_value += val 130 | print "=", sum_value 131 | 132 | i = Iterator(l) 133 | print "l=", l 134 | i.next() 135 | i.next() 136 | print "next,next : ", i.peek() 137 | i.remove(2) 138 | print "remove(2) : ", i.peek() 139 | i.remove(4) 140 | print "remove(4) : ", i.peek() 141 | print "l=", l 142 | 143 | -------------------------------------------------------------------------------- /pycam/Utils/rootsolver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2008 Lode Leroy 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | def find_root_subdivide(f, x0, x1, tolerance, scale): 24 | ymin = 0 25 | xmin = 0 26 | while x1 - x0 > tolerance: 27 | for i in range(scale): 28 | x = x1 + (i / scale) * (x1 - x0) 29 | y = f(x) 30 | abs_y = abs(y) 31 | if i == 0: 32 | ymin = abs_y 33 | xmin = x 34 | else: 35 | if abs_y < ymin: 36 | ymin = abs_y 37 | xmin = x 38 | x0 = xmin - 1 / scale 39 | x1 = xmin + 1 / scale 40 | scale /= 10 41 | return xmin 42 | 43 | def find_root_newton_raphson(f, df, x0, tolerance, maxiter): 44 | x = x0 45 | iter_count = 0 46 | while iter_count < maxiter: 47 | y = f(x) 48 | if y == 0: 49 | return x 50 | dy = df(x) 51 | if dy == 0: 52 | return None 53 | dx = y / dy 54 | x = x - dx 55 | if dx < tolerance: 56 | break 57 | iter_count += 1 58 | return x 59 | 60 | def find_root(f, df=None, x0=0, x1=1, tolerance=0.001): 61 | return find_root_subdivide(f=f, x0=x0, x1=x1, tolerance=tolerance, 62 | scale=10.0) 63 | 64 | -------------------------------------------------------------------------------- /pycam/Utils/xml_handling.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | $Id$ 5 | 6 | Copyright 2010 Lars Kruse 7 | 8 | This file is part of PyCAM. 9 | 10 | PyCAM is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | PyCAM is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with PyCAM. If not, see . 22 | """ 23 | 24 | import xml.etree.ElementTree as ET 25 | 26 | 27 | def get_xml(item, name=None): 28 | if name is None: 29 | if hasattr(item, "node_key"): 30 | name = item.node_key 31 | else: 32 | name = "value" 33 | if isinstance(item, (list, tuple, set)): 34 | leaf = ET.Element(name) 35 | for single in item: 36 | leaf.append(get_xml(single)) 37 | return leaf 38 | elif isinstance(item, dict): 39 | leaf = ET.Element(name) 40 | for key, value in item.iteritems(): 41 | leaf.append(get_xml(value, name=key)) 42 | return leaf 43 | else: 44 | leaf = ET.Element(name) 45 | leaf.text = str(item) 46 | return leaf 47 | 48 | def parse_xml_dict(item): 49 | pass 50 | 51 | def get_xml_lines(item): 52 | lines = [] 53 | content = ET.tostring(item) 54 | content = content.replace("><", ">\n<") 55 | indent = 0 56 | for line in content.split("\n"): 57 | indented = False 58 | if line.startswith(""): 65 | pass 66 | elif line.startswith(". 21 | """ 22 | 23 | __all__ = ["Cutters", "Exporters", "Geometry", "Gui", "Importers", 24 | "PathGenerators", "PathProcessors", "Utils"] 25 | 26 | VERSION = "0.6-svn" 27 | 28 | -------------------------------------------------------------------------------- /pyinstaller/hooks/hook-pycam.py: -------------------------------------------------------------------------------- 1 | # keysyms does not seem to be recognized by pyinstaller 2 | # There will be exceptions after any keypress without this line. 3 | # rsvg is required for SVG icons (dynamically loaded on demand) 4 | hiddenimports = ["gtk.keysyms", "rsvg"] 5 | 6 | -------------------------------------------------------------------------------- /pyinstaller/pyinstaller_fix_module_exception.patch: -------------------------------------------------------------------------------- 1 | diff -ruN pyinstaller-1.4/iu.py pyinstaller-1.4.fixed//iu.py 2 | --- pyinstaller-1.4/iu.py 2010-02-11 01:23:39.000000000 +0100 3 | +++ pyinstaller-1.4.fixed//iu.py 2010-08-23 19:07:50.000000000 +0200 4 | # see http://www.pyinstaller.org/ticket/205 5 | # Exceptions are thrown for imported modules are not available in Windows. 6 | @@ -451,7 +451,8 @@ 7 | if ctx and hasattr(sys.modules[ctx], nmparts[i]): 8 | debug("importHook done with %s %s %s (case 1)" % (name, __globals_name, fromlist)) 9 | return sys.modules[nmparts[0]] 10 | - del sys.modules[fqname] 11 | + if fqname in sys.modules: 12 | + del sys.modules[fqname] 13 | raise ImportError, "No module named %s" % fqname 14 | if fromlist is None: 15 | debug("importHook done with %s %s %s (case 2)" % (name, __globals_name, fromlist)) 16 | -------------------------------------------------------------------------------- /pyinstaller/pyinstaller_info.txt: -------------------------------------------------------------------------------- 1 | PyInstaller (http://pyinstaller.org) can be used to create standalone binaries for Windows. 2 | 3 | How to build a standalone exe file (on Windows only): 4 | 1) install the PyCAM dependency installer: 5 | https://pycam.svn.sourceforge.net/svnroot/pycam/dependency_installer 6 | * add "C:\GtkGLext\1.0\bin" to your PATH environment variable 7 | 2) install UPX (compression) 8 | * Debian/Ubuntu: apt-get install upx-ucl 9 | * Windows: http://upx.sourceforge.net 10 | * extract the archive to your program directory 11 | * add this directory to your PATH environment variable 12 | 3) download pyinstaller (svn co http://svn.pyinstaller.org/tags/1.5 pyinstaller) 13 | 4) run "cmd.exe" (or open a terminal) 14 | 5) "cd PATH_TO_PYCAM" 15 | 6) "python PYINSTALLER_PATH/Configure.py" 16 | 7) "python PYINSTALLER_PATH/Build.py pyinstaller/pycam.spec" 17 | 8) test and upload the binary file "pycam-VERSION_standalone.?" 18 | 19 | Known issues: 20 | * multiprocessing on Windows: no server/client capabilities 21 | * python-setproctitle v1.0.1 causes a segfault - remove it from the build system 22 | * Linux: pre-built executable don't seem to work across different versions of libc 23 | * Linux/MacOS?: you need to remove the reference to "windll" at the top of the "pycam" script (line 32-36) 24 | 25 | Debugging: 26 | * enable the "debug" parameter in the "EXE" call at the end of the spec file 27 | 28 | -------------------------------------------------------------------------------- /pylint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BASE_PATH="$(dirname "$0")" 4 | 5 | set -eu 6 | 7 | IGNORE_LIST="C0111,C0103,W0613,R0914,R0911,R0912,W0511,W0603,,R0902,W0612,R0903,R0201,R0915,R0913,W0602" 8 | 9 | PYTHONPATH="$BASE_PATH/src" pylint -i y -d "$IGNORE_LIST" "$1" 10 | 11 | -------------------------------------------------------------------------------- /release_info.txt: -------------------------------------------------------------------------------- 1 | 1) update the version and the changelog 2 | - in "Changelog" (version, release date, changes) 3 | - in "src/pycam/__init__.py" (version) 4 | - commit the changes 5 | 6 | 2) create the archives 7 | - "make dist" 8 | - carefully check the resulting content of the archives 9 | 10 | 3a) create an svn tag for the release (includes uploading the archive files) 11 | - "make upload" 12 | 13 | 3b) create the Windows standalone binary 14 | - see pyinstaller/pyinstall_info.txt for details 15 | 16 | 4) upload files to sourceforge 17 | - https://sourceforge.net/project/admin/explorer.php?group_id=237831 18 | - create a directory for the new release 19 | - click at the icon to the left of the new directory and upload the new archives 20 | - create a file called "release-notes-0.x" and upload it to the same directory 21 | - first line: "PyCAM v0.x release notes:" 22 | - second line: empty 23 | - further lines: summary of changes (complete sentences) 24 | - mark the release notes files as "Release notes" (see "Properties") 25 | - set the release notes and the target operating systems for the archives 26 | - zip: others 27 | - exe: Windows 28 | - tar.gz: Linux, Mac, BSD, Solaris 29 | - standalone binary (Windows): no specific architecture 30 | 31 | 5) announcements 32 | - run "python setup.py register" (for the PyPI package index) 33 | - create a project news items at sourceforge 34 | - create a new release at http://freshmeat.net 35 | - post the new release at http://www.cnczone.com/forums/showthread.php?t=63716 36 | - create a blog post at: http://fab.senselab.org 37 | 38 | 6) other stuff 39 | - create a new release tag for the bug tracker: 40 | https://sourceforge.net/tracker/admin/index.php?group_id=237831&atid=1104176&add_group=1 41 | 42 | -------------------------------------------------------------------------------- /samples/Box0.stl: -------------------------------------------------------------------------------- 1 | solid box0 2 | facet normal -0.000000 0.000000 1.000000 3 | outer loop 4 | vertex -4.961088 -4.824490 4.188777 5 | vertex -0.772311 -0.635713 4.188777 6 | vertex -4.961088 -0.635713 4.188777 7 | endloop 8 | endfacet 9 | facet normal -0.000000 0.000000 1.000000 10 | outer loop 11 | vertex -4.961088 -4.824490 4.188777 12 | vertex -0.772311 -4.824490 4.188777 13 | vertex -0.772311 -0.635713 4.188777 14 | endloop 15 | endfacet 16 | facet normal 0.000000 0.000000 -1.000000 17 | outer loop 18 | vertex -0.772311 -4.824490 0.000000 19 | vertex -4.961088 -0.635713 0.000000 20 | vertex -0.772311 -0.635713 0.000000 21 | endloop 22 | endfacet 23 | facet normal 0.000000 0.000000 -1.000000 24 | outer loop 25 | vertex -0.772311 -4.824490 0.000000 26 | vertex -4.961088 -4.824490 0.000000 27 | vertex -4.961088 -0.635713 0.000000 28 | endloop 29 | endfacet 30 | facet normal 1.000000 -0.000000 0.000000 31 | outer loop 32 | vertex -0.772311 -4.824490 4.188777 33 | vertex -0.772311 -0.635713 0.000000 34 | vertex -0.772311 -0.635713 4.188777 35 | endloop 36 | endfacet 37 | facet normal 1.000000 -0.000000 0.000000 38 | outer loop 39 | vertex -0.772311 -4.824490 4.188777 40 | vertex -0.772311 -4.824490 0.000000 41 | vertex -0.772311 -0.635713 0.000000 42 | endloop 43 | endfacet 44 | facet normal -1.000000 0.000000 0.000000 45 | outer loop 46 | vertex -4.961088 -4.824490 0.000000 47 | vertex -4.961088 -0.635713 4.188777 48 | vertex -4.961088 -0.635713 0.000000 49 | endloop 50 | endfacet 51 | facet normal -1.000000 0.000000 0.000000 52 | outer loop 53 | vertex -4.961088 -4.824490 0.000000 54 | vertex -4.961088 -4.824490 4.188777 55 | vertex -4.961088 -0.635713 4.188777 56 | endloop 57 | endfacet 58 | facet normal 0.000000 1.000000 0.000000 59 | outer loop 60 | vertex -4.961088 -0.635713 4.188777 61 | vertex -0.772311 -0.635713 0.000000 62 | vertex -4.961088 -0.635713 0.000000 63 | endloop 64 | endfacet 65 | facet normal 0.000000 1.000000 0.000000 66 | outer loop 67 | vertex -4.961088 -0.635713 4.188777 68 | vertex -0.772311 -0.635713 4.188777 69 | vertex -0.772311 -0.635713 0.000000 70 | endloop 71 | endfacet 72 | facet normal 0.000000 -1.000000 0.000000 73 | outer loop 74 | vertex -4.961088 -4.824490 0.000000 75 | vertex -0.772311 -4.824490 4.188777 76 | vertex -4.961088 -4.824490 4.188777 77 | endloop 78 | endfacet 79 | facet normal 0.000000 -1.000000 0.000000 80 | outer loop 81 | vertex -4.961088 -4.824490 0.000000 82 | vertex -0.772311 -4.824490 0.000000 83 | vertex -0.772311 -4.824490 4.188777 84 | endloop 85 | endfacet 86 | endsolid box0 87 | -------------------------------------------------------------------------------- /samples/Box1.stl: -------------------------------------------------------------------------------- 1 | solid box1 2 | facet normal 0.707143 -0.408041 0.577452 3 | outer loop 4 | vertex 2.563112 -5.599983 2.267282 5 | vertex 5.339321 -3.997340 0.000022 6 | vertex 2.562797 -2.393577 4.533387 7 | endloop 8 | endfacet 9 | facet normal 0.707143 -0.408041 0.577452 10 | outer loop 11 | vertex 5.339321 -3.997340 0.000022 12 | vertex 5.339006 -0.790934 2.266127 13 | vertex 2.562797 -2.393577 4.533387 14 | endloop 15 | endfacet 16 | facet normal -0.707143 0.408041 -0.577452 17 | outer loop 18 | vertex -0.213384 -3.997869 0.000000 19 | vertex -0.213699 -0.791464 2.266105 20 | vertex 2.562824 -2.395226 -2.267260 21 | endloop 22 | endfacet 23 | facet normal -0.707143 0.408041 -0.577452 24 | outer loop 25 | vertex -0.213699 -0.791464 2.266105 26 | vertex 2.562510 0.811179 -0.001155 27 | vertex 2.562824 -2.395226 -2.267260 28 | endloop 29 | endfacet 30 | facet normal 0.707070 0.408176 -0.577446 31 | outer loop 32 | vertex 5.339006 -0.790934 2.266127 33 | vertex 2.562824 -2.395226 -2.267260 34 | vertex 2.562510 0.811179 -0.001155 35 | endloop 36 | endfacet 37 | facet normal 0.707070 0.408176 -0.577446 38 | outer loop 39 | vertex 5.339006 -0.790934 2.266127 40 | vertex 5.339321 -3.997340 0.000022 41 | vertex 2.562824 -2.395226 -2.267260 42 | endloop 43 | endfacet 44 | facet normal -0.707070 -0.408176 0.577446 45 | outer loop 46 | vertex -0.213384 -3.997869 0.000000 47 | vertex 2.562797 -2.393577 4.533387 48 | vertex -0.213699 -0.791464 2.266105 49 | endloop 50 | endfacet 51 | facet normal -0.707070 -0.408176 0.577446 52 | outer loop 53 | vertex -0.213384 -3.997869 0.000000 54 | vertex 2.563112 -5.599983 2.267282 55 | vertex 2.562797 -2.393577 4.533387 56 | endloop 57 | endfacet 58 | facet normal -0.000080 0.816637 0.577152 59 | outer loop 60 | vertex -0.213699 -0.791464 2.266105 61 | vertex 5.339006 -0.790934 2.266127 62 | vertex 2.562510 0.811179 -0.001155 63 | endloop 64 | endfacet 65 | facet normal -0.000080 0.816637 0.577152 66 | outer loop 67 | vertex -0.213699 -0.791464 2.266105 68 | vertex 2.562797 -2.393577 4.533387 69 | vertex 5.339006 -0.790934 2.266127 70 | endloop 71 | endfacet 72 | facet normal 0.000080 -0.816637 -0.577152 73 | outer loop 74 | vertex -0.213384 -3.997869 0.000000 75 | vertex 5.339321 -3.997340 0.000022 76 | vertex 2.563112 -5.599983 2.267282 77 | endloop 78 | endfacet 79 | facet normal 0.000080 -0.816637 -0.577152 80 | outer loop 81 | vertex -0.213384 -3.997869 0.000000 82 | vertex 2.562824 -2.395226 -2.267260 83 | vertex 5.339321 -3.997340 0.000022 84 | endloop 85 | endfacet 86 | endsolid box1 87 | -------------------------------------------------------------------------------- /samples/Box2.stl: -------------------------------------------------------------------------------- 1 | solid "model"; Produced by pycam (v0.3), 2010-10-03 2 | facet normal 0.707143 -0.408041 0.577452 3 | outer loop 4 | vertex 5.339321 -3.997340 0.000022 5 | vertex 2.562797 -2.393577 4.533387 6 | vertex 2.563112 -5.599983 2.267282 7 | endloop 8 | endfacet 9 | facet normal 0.707143 -0.408041 0.577452 10 | outer loop 11 | vertex 5.339006 -0.790934 2.266127 12 | vertex 2.562797 -2.393577 4.533387 13 | vertex 5.339321 -3.997340 0.000022 14 | endloop 15 | endfacet 16 | facet normal -0.707143 0.408041 -0.577452 17 | outer loop 18 | vertex -0.213699 -0.791464 2.266105 19 | vertex 2.562824 -2.395226 -2.267260 20 | vertex -0.213384 -3.997869 0.000000 21 | endloop 22 | endfacet 23 | facet normal -0.707143 0.408041 -0.577452 24 | outer loop 25 | vertex 2.562510 0.811179 -0.001155 26 | vertex 2.562824 -2.395226 -2.267260 27 | vertex -0.213699 -0.791464 2.266105 28 | endloop 29 | endfacet 30 | facet normal 0.707070 0.408176 -0.577446 31 | outer loop 32 | vertex 2.562824 -2.395226 -2.267260 33 | vertex 2.562510 0.811179 -0.001155 34 | vertex 5.339006 -0.790934 2.266127 35 | endloop 36 | endfacet 37 | facet normal 0.707070 0.408176 -0.577446 38 | outer loop 39 | vertex 5.339321 -3.997340 0.000022 40 | vertex 2.562824 -2.395226 -2.267260 41 | vertex 5.339006 -0.790934 2.266127 42 | endloop 43 | endfacet 44 | facet normal -0.707070 -0.408176 0.577446 45 | outer loop 46 | vertex 2.562797 -2.393577 4.533387 47 | vertex -0.213699 -0.791464 2.266105 48 | vertex -0.213384 -3.997869 0.000000 49 | endloop 50 | endfacet 51 | facet normal -0.707070 -0.408176 0.577446 52 | outer loop 53 | vertex 2.563112 -5.599983 2.267282 54 | vertex 2.562797 -2.393577 4.533387 55 | vertex -0.213384 -3.997869 0.000000 56 | endloop 57 | endfacet 58 | facet normal -0.000080 0.816637 0.577152 59 | outer loop 60 | vertex 5.339006 -0.790934 2.266127 61 | vertex 2.562510 0.811179 -0.001155 62 | vertex -0.213699 -0.791464 2.266105 63 | endloop 64 | endfacet 65 | facet normal -0.000080 0.816637 0.577152 66 | outer loop 67 | vertex 2.562797 -2.393577 4.533387 68 | vertex 5.339006 -0.790934 2.266127 69 | vertex -0.213699 -0.791464 2.266105 70 | endloop 71 | endfacet 72 | facet normal 0.000080 -0.816637 -0.577152 73 | outer loop 74 | vertex 5.339321 -3.997340 0.000022 75 | vertex 2.563112 -5.599983 2.267282 76 | vertex -0.213384 -3.997869 0.000000 77 | endloop 78 | endfacet 79 | facet normal 0.000080 -0.816637 -0.577152 80 | outer loop 81 | vertex 2.562824 -2.395226 -2.267260 82 | vertex 5.339321 -3.997340 0.000022 83 | vertex -0.213384 -3.997869 0.000000 84 | endloop 85 | endfacet 86 | endsolid 87 | -------------------------------------------------------------------------------- /samples/SampleScene2.scad: -------------------------------------------------------------------------------- 1 | // example scene for pycam 2 | 3 | module scene() { 4 | sphere(r=15, center=true); 5 | translate(v=[0,80,5]) cylinder(h = 10, r1 = 20, r2 = 10, center = true); 6 | translate(v=[0,40,15]) { 7 | intersection() { 8 | translate([0,0,-15]) rotate([60,0,90]) cube([30, 20, 20], center=true); 9 | cylinder(h = 30, r = 10, center = true); 10 | } 11 | } 12 | translate([0,130,-5]) { 13 | intersection() { 14 | sphere(20, center=true); 15 | rotate([30,60,0]) cube(30, center=true); 16 | } 17 | } 18 | } 19 | 20 | // remove the parts of the objects below the zero plane 21 | difference() { 22 | scale([0.5, 0.5, 0.3]) scene(); 23 | translate([0,0,-20]) cube([200,200,40], center=true); 24 | } -------------------------------------------------------------------------------- /samples/SampleScene3.scad: -------------------------------------------------------------------------------- 1 | difference() { 2 | sphere(r=30); 3 | translate([-50, -50, -100]) cube([100, 100, 100]); 4 | } 5 | 6 | translate([-30, 50, 0]) union() { 7 | cube([140, 30, 10]); 8 | translate([40, 15, 10]) rotate([0, 90, 0]) cylinder(r=10, h=60); 9 | } 10 | 11 | translate([80, 0, 0]) scale(1.2) union() { 12 | difference() { 13 | cylinder(r1=20, r2=5, h=20); 14 | translate([-25, 30, -10]) rotate([60, 0, 0]) cube([50, 50, 50]); 15 | } 16 | difference() { 17 | translate([0, 35, -21]) rotate([70, 0, 0]) cylinder(r=16, h=50); 18 | translate([-50, -50, -100]) cube([100, 100, 100]); 19 | } 20 | } -------------------------------------------------------------------------------- /samples/Sphere_cut.scad: -------------------------------------------------------------------------------- 1 | intersection() { 2 | sphere(r=5, center=true, $fn=6); 3 | cube([20,20,5], center=true); 4 | } -------------------------------------------------------------------------------- /samples/multilayer_engrave.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 28 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 63 | 72 | frame: redtext: green 86 | 87 | 88 | -------------------------------------------------------------------------------- /samples/polygon2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 28 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 62 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /samples/polygon3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 28 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 62 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /samples/polygon4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 24 | 31 | 32 | 50 | 52 | 53 | 55 | image/svg+xml 56 | 58 | 59 | 60 | 61 | 62 | 66 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /samples/problem.conf: -------------------------------------------------------------------------------- 1 | [ProcessDefault] 2 | engrave_offset: 0.0 3 | pocketing_type: none 4 | 5 | [Process0] 6 | name: Remove material y 7 | path_strategy: PushRemoveStrategy 8 | path_direction: y 9 | milling_style: ignore 10 | material_allowance: 0.01 11 | overlap_percent: 25 12 | step_down: 0.12 13 | 14 | [Process1] 15 | name: Carve contour 16 | path_strategy: ContourPolygonStrategy 17 | path_direction: x 18 | milling_style: ignore 19 | material_allowance: 0.01 20 | overlap_percent: 40 21 | step_down: 1.0 22 | 23 | [Process2] 24 | name: Cleanup x 25 | path_strategy: SurfaceStrategy 26 | path_direction: x 27 | milling_style: ignore 28 | material_allowance: 0.0 29 | overlap_percent: 75 30 | step_down: 0.11811023622 31 | 32 | [Process3] 33 | name: cleanup y 34 | path_strategy: SurfaceStrategy 35 | path_direction: y 36 | milling_style: ignore 37 | material_allowance: 0.0 38 | overlap_percent: 75 39 | step_down: 0.11811023622 40 | 41 | [Process4] 42 | name: Gravure 43 | path_strategy: EngraveStrategy 44 | path_direction: x 45 | milling_style: conventional 46 | material_allowance: 0.0 47 | overlap_percent: 50 48 | step_down: 0.0393700787402 49 | 50 | [Tool0] 51 | name: Cylindrical 52 | shape: CylindricalCutter 53 | tool_radius: 0.0625 54 | torus_radius: 0.25 55 | feedrate: 60.0 56 | speed: 10000.0 57 | 58 | [Tool1] 59 | name: Toroidal 60 | shape: ToroidalCutter 61 | tool_radius: 0.062 62 | torus_radius: 0.02 63 | feedrate: 30.0 64 | speed: 1000.0 65 | 66 | [Tool2] 67 | name: Spherical 68 | shape: SphericalCutter 69 | tool_radius: 0.0625 70 | torus_radius: 0.00984251968504 71 | feedrate: 30.0 72 | speed: 1000.0 73 | 74 | [TaskDefault] 75 | tool: 0 76 | 77 | [Task0] 78 | name: Rough top 79 | process: 0 80 | bounds: 0 81 | enabled: 0 82 | 83 | [Task1] 84 | name: rough2 bottom 85 | process: 0 86 | bounds: 0 87 | enabled: 0 88 | 89 | [Task2] 90 | name: finish x top 91 | process: 2 92 | bounds: 0 93 | enabled: 0 94 | 95 | [Task3] 96 | name: finish x middle 97 | process: 2 98 | bounds: 0 99 | enabled: 0 100 | 101 | [Task4] 102 | name: finish x bottom 103 | process: 2 104 | bounds: 2 105 | enabled: 0 106 | 107 | [Task5] 108 | name: finish y top 109 | process: 3 110 | bounds: 0 111 | enabled: 0 112 | 113 | [Task6] 114 | name: finish y middle 115 | process: 3 116 | bounds: 0 117 | enabled: 0 118 | 119 | [Task7] 120 | name: finish y bottom 121 | process: 3 122 | bounds: 2 123 | enabled: 1 124 | 125 | [BoundsDefault] 126 | type: relative_margin 127 | z_high: 0.0 128 | 129 | [Bounds0] 130 | name: Minimum 131 | x_low: 0.0 132 | x_high: 0.0 133 | y_low: 0.0 134 | y_high: 0.0 135 | z_low: 0.0 136 | 137 | [Bounds1] 138 | name: 10% margin 139 | x_low: 0.1 140 | x_high: 0.1 141 | y_low: 0.1 142 | y_high: 0.1 143 | z_low: 0.05 144 | 145 | [Bounds2] 146 | name: problem 147 | x_low: -0.13 148 | x_high: -0.83 149 | y_low: -0.28 150 | y_high: -0.68 151 | z_low: 0.05 152 | -------------------------------------------------------------------------------- /samples/problem_1_triangle.stl: -------------------------------------------------------------------------------- 1 | solid vcg 2 | facet normal -2.847750e-02 1.220272e-01 -9.921181e-01 3 | outer loop 4 | vertex 8.845570e-01 1.323334e+00 8.942510e-01 5 | vertex 8.742290e-01 1.593305e+00 9.277530e-01 6 | vertex 1.152810e+00 1.435092e+00 9.002970e-01 7 | endloop 8 | endfacet 9 | endsolid vcg 10 | -------------------------------------------------------------------------------- /samples/pycam-textbox.scad: -------------------------------------------------------------------------------- 1 | // Combine the PyCAM logo with a slightly depressed rounded-cornered box. 2 | 3 | module block(a, b, height, radius) { 4 | translate([radius, radius, 0]) union() { 5 | translate([-radius, 0, 0]) cube([a, b - 2 * radius, height]); 6 | translate([0, -radius, 0]) cube([a - 2 * radius, b, height]); 7 | cylinder(r=radius, h=height); 8 | translate([a - 2 * radius, 0, 0]) cylinder(r=radius, h=height); 9 | translate([a - 2 * radius, b - 2 * radius, 0]) cylinder(r=radius, h=height); 10 | translate([0, b - 2 * radius, 0]) cylinder(r=radius, h=height); 11 | } 12 | } 13 | 14 | translate([0, 0, -10]) difference() { 15 | block(130, 50, 10, 10); 16 | translate([5, 5, 5]) block(120, 40, 6, 10); 17 | } 18 | translate([15, 17, -5.05]) linear_extrude(file="pycam-text.dxf", height=3); 19 | 20 | -------------------------------------------------------------------------------- /samples/rectangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /scripts/profile_pycam.py: -------------------------------------------------------------------------------- 1 | import cProfile 2 | from os.path import join 3 | import pstats 4 | import sys 5 | from time import time 6 | 7 | from pycam.Cutters.CylindricalCutter import CylindricalCutter 8 | from pycam.Gui.Console import ConsoleProgressBar 9 | from pycam.Importers.STLImporter import ImportModel 10 | from pycam.PathGenerators.DropCutter import DropCutter 11 | from pycam.PathProcessors.PathAccumulator import PathAccumulator 12 | from pycam.Toolpath import Bounds 13 | from pycam.Toolpath.MotionGrid import get_fixed_grid 14 | from pycam.Utils.locations import get_data_file_location 15 | 16 | # Disable multi processing 17 | from pycam.Utils import threading 18 | threading.__multiprocessing = False 19 | 20 | """ Profile PyCAM doing several operations, print out the top 10 21 | (sorted by actual local runtime) methods. 22 | """ 23 | 24 | model = ImportModel(get_data_file_location(join('samples', 'pycam-textbox.stl'))) 25 | print model.minx, model.miny, model.maxx, model.maxy 26 | 27 | 28 | def run_dropcutter(): 29 | """ Run DropCutter on standard PyCAM sample plaque """ 30 | progress_bar = ConsoleProgressBar(sys.stdout) 31 | 32 | overlap = .6 33 | layer_distance = 1 34 | tool = CylindricalCutter(10) 35 | path_generator = DropCutter(PathAccumulator()) 36 | bounds = Bounds(Bounds.TYPE_CUSTOM, 37 | (model.minx-5, model.miny-5, model.minz), 38 | (model.maxx+5, model.maxy+5, model.maxz)) 39 | 40 | 41 | low, high = bounds.get_absolute_limits() 42 | line_distance = 2 * tool.radius * (1.0 - overlap) 43 | 44 | motion_grid = get_fixed_grid((low, high), layer_distance, 45 | line_distance, tool.radius / 4.0) 46 | moves = path_generator.GenerateToolPath(tool, [model], motion_grid, 47 | minz=low[2], maxz=high[2], 48 | draw_callback=progress_bar.update) 49 | 50 | 51 | 52 | if __name__ == '__main__': 53 | start_time = time() 54 | cProfile.run('run_dropcutter()', 'dropcutter.pyprof') 55 | run_time = time() - start_time 56 | print '\nDropcutter took %f seconds' % run_time 57 | p = pstats.Stats('dropcutter.pyprof') 58 | print 'Top ten time-consuming functions:' 59 | p.sort_stats('time').print_stats(10) 60 | -------------------------------------------------------------------------------- /scripts/pycam_win32_postinstall.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | $Id$ 4 | 5 | Copyright 2010 Lars Kruse 6 | 7 | This file is part of PyCAM. 8 | 9 | PyCAM is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | PyCAM is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with PyCAM. If not, see . 21 | """ 22 | 23 | import distutils.sysconfig 24 | import os 25 | import sys 26 | 27 | try: 28 | logfile = os.path.join(distutils.sysconfig.PREFIX, "pycam-wininst-postinstall.log", "a") 29 | except OSError: 30 | logfile = None 31 | if logfile: 32 | sys.stdout = logfile 33 | sys.stderr = logfile 34 | 35 | LINK_EXTENSION = ".lnk" 36 | 37 | try: 38 | START_MENU_BASEDIR = get_special_folder_path("CSIDL_COMMON_PROGRAMS") 39 | except OSError: 40 | START_MENU_BASEDIR = get_special_folder_path("CSIDL_PROGRAMS") 41 | except NameError: 42 | START_MENU_BASEDIR = "C:\\" 43 | START_MENU_SUBDIR = os.path.join(START_MENU_BASEDIR, "PyCAM") 44 | 45 | # create a start menu item for pycam 46 | PYTHON_EXE = os.path.join(distutils.sysconfig.EXEC_PREFIX, "pythonw.exe") 47 | # surround the start script with quotes to avoid space-issues 48 | START_SCRIPT = '"%s"' % os.path.join(distutils.sysconfig.EXEC_PREFIX, "Scripts", "pycam-loader.py") 49 | 50 | SHARE_DIR = os.path.join(distutils.sysconfig.PREFIX, "share", "pycam") 51 | 52 | PYTHON_DOC_DIR = os.path.join(SHARE_DIR, "doc") 53 | 54 | ICON_FILE = os.path.join(SHARE_DIR, "pycam.ico") 55 | 56 | # add some more doc files 57 | DOC_FILES = [("LICENSE.TXT", "License"),] 58 | WEB_LINKS = [ 59 | (r"http://pycam.sourceforge.net/", "Project's Website"), 60 | (r"http://sourceforge.net/tracker/?group_id=237831&atid=1104176", "Report a Bug"), 61 | (r"http://sourceforge.net/projects/pycam/forums", "Forum Discussions"), 62 | (r"http://sourceforge.net/apps/mediawiki/pycam/index.php?title=User_Manual", "User Manual")] 63 | 64 | MENU_ITEMS = map(lambda v: (os.path.join(PYTHON_DOC_DIR, v[0]), v[1]), DOC_FILES) 65 | MENU_ITEMS.extend(WEB_LINKS) 66 | 67 | action = sys.argv[1] 68 | 69 | if action == "-install": 70 | if not os.path.exists(START_MENU_SUBDIR): 71 | os.mkdir(START_MENU_SUBDIR) 72 | directory_created(START_MENU_SUBDIR) 73 | for menu_item in MENU_ITEMS: 74 | target, description = menu_item 75 | filename = os.path.join(START_MENU_SUBDIR, description) + LINK_EXTENSION 76 | create_shortcut(target, description, filename) 77 | file_created(filename) 78 | filename = os.path.join(START_MENU_SUBDIR, "Run PyCAM") + LINK_EXTENSION 79 | create_shortcut(PYTHON_EXE, "Run PyCAM", filename, START_SCRIPT, "", ICON_FILE) 80 | file_created(filename) 81 | elif action == "-remove": 82 | pass 83 | else: 84 | pass 85 | 86 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wininst] 2 | install_script = pycam_win32_postinstall.py 3 | bitmap = share/ui/logo_gui_vertical.bmp 4 | 5 | [bdist_msi] 6 | install_script = pycam_win32_postinstall.py 7 | 8 | [bdist_rpm] 9 | packager = Lars Kruse 10 | doc_files = Changelog 11 | README.TXT 12 | INSTALL.TXT 13 | LICENSE.TXT 14 | COPYING.TXT 15 | doc/ 16 | examples/ 17 | 18 | -------------------------------------------------------------------------------- /share/desktop/pycam.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Name=PyCAM 4 | GenericName=Toolpath Generator 5 | Comment=generate GCode for 3-Axis CNC machining 6 | Exec=pycam %u 7 | TryExec=pycam 8 | Terminal=false 9 | Type=Application 10 | Categories=Development;Engineering;Robotics;Education;Science;2DGraphics;VectorGraphics;3DGraphics; 11 | MimeType=application/sla;image/svg+xml;application/postscript;image/vnd.dxf; 12 | Icon=pycam 13 | 14 | -------------------------------------------------------------------------------- /share/fonts/README: -------------------------------------------------------------------------------- 1 | The fonts delivered with PyCAM are taken from QCAD: 2 | http://www.qcad.org/ 3 | 4 | The fonts are licensed under the GPL v2.0 or later. 5 | 6 | Thanks to the QCAD developers for this great piece of work! 7 | 8 | -------------------------------------------------------------------------------- /share/fonts/courier.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/courier.cxf -------------------------------------------------------------------------------- /share/fonts/cursive.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/cursive.cxf -------------------------------------------------------------------------------- /share/fonts/iso8859-11.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/iso8859-11.cxf -------------------------------------------------------------------------------- /share/fonts/italicc.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/italicc.cxf -------------------------------------------------------------------------------- /share/fonts/italiccs.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/italiccs.cxf -------------------------------------------------------------------------------- /share/fonts/italict.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/italict.cxf -------------------------------------------------------------------------------- /share/fonts/normallatin1.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/normallatin1.cxf -------------------------------------------------------------------------------- /share/fonts/normallatin1.readme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/normallatin1.readme -------------------------------------------------------------------------------- /share/fonts/normallatin2.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/normallatin2.cxf -------------------------------------------------------------------------------- /share/fonts/romanc.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/romanc.cxf -------------------------------------------------------------------------------- /share/fonts/romancs.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/romancs.cxf -------------------------------------------------------------------------------- /share/fonts/romand.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/romand.cxf -------------------------------------------------------------------------------- /share/fonts/romanp.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/romanp.cxf -------------------------------------------------------------------------------- /share/fonts/romans.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/romans.cxf -------------------------------------------------------------------------------- /share/fonts/romans2.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/romans2.cxf -------------------------------------------------------------------------------- /share/fonts/romant.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/romant.cxf -------------------------------------------------------------------------------- /share/fonts/scriptc.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/scriptc.cxf -------------------------------------------------------------------------------- /share/fonts/scripts.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/scripts.cxf -------------------------------------------------------------------------------- /share/fonts/standard.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/standard.cxf -------------------------------------------------------------------------------- /share/fonts/symbol.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/symbol.cxf -------------------------------------------------------------------------------- /share/fonts/unicode.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/fonts/unicode.cxf -------------------------------------------------------------------------------- /share/mime/icons/128x128/application-sla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/mime/icons/128x128/application-sla.png -------------------------------------------------------------------------------- /share/mime/icons/32x32/application-sla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/mime/icons/32x32/application-sla.png -------------------------------------------------------------------------------- /share/mime/icons/64x64/application-sla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/mime/icons/64x64/application-sla.png -------------------------------------------------------------------------------- /share/mime/pycam.mime: -------------------------------------------------------------------------------- 1 | application/sla; pycam '%s'; description="Stereo Lithographic File"; test=test -n "$DISPLAY"; nametemplate=%s.stl 2 | image/vnd.dxf; pycam '%s'; description="Drawing Interchange File"; test=test -n "$DISPLAY"; nametemplate=%s.dxf 3 | image/svg+xml; pycam '%s'; description="Vector Graphics"; test=test -n "$DISPLAY" && which inkscape && which pstoedit; nametemplate=%s.svg 4 | application/postscript; pycam '%s'; description="Postscript"; test=test -n "$DISPLAY" && which pstoedit 5 | -------------------------------------------------------------------------------- /share/mime/pycam.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stereo Lithographic Files (solid 3D models) 5 | 6 | 7 | 8 | 9 | 10 | 11 | Drawing Interchange Files (2D or 3D models) 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /share/pycam.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/pycam.ico -------------------------------------------------------------------------------- /share/ui/clipboard.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | _Paste 7 | _Paste 8 | Paste a model from clipboard 9 | gtk-paste 10 | 11 | 12 | _Copy 13 | _Copy 14 | Copy model to clipboard 15 | gtk-copy 16 | 17 | 18 | -------------------------------------------------------------------------------- /share/ui/emc_tool_export.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | _Export tools for EMC2 ... 7 | _Export tools for EMC2 ... 8 | 9 | 10 | -------------------------------------------------------------------------------- /share/ui/extrusion_chamfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/extrusion_chamfer.png -------------------------------------------------------------------------------- /share/ui/extrusion_radius_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/extrusion_radius_down.png -------------------------------------------------------------------------------- /share/ui/extrusion_radius_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/extrusion_radius_up.png -------------------------------------------------------------------------------- /share/ui/extrusion_sigmoidal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/extrusion_sigmoidal.png -------------------------------------------------------------------------------- /share/ui/extrusion_sine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/extrusion_sine.png -------------------------------------------------------------------------------- /share/ui/gcode_preferences.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 5 7 | GCode preferences 8 | normal 9 | 10 | 11 | True 12 | vertical 13 | 2 14 | 15 | 16 | True 17 | True 18 | left 19 | 20 | 21 | 1 22 | 23 | 24 | 25 | 26 | True 27 | end 28 | 29 | 30 | gtk-close 31 | True 32 | True 33 | True 34 | True 35 | 36 | 37 | False 38 | False 39 | 0 40 | 41 | 42 | 43 | 44 | False 45 | end 46 | 0 47 | 48 | 49 | 50 | 51 | 52 | CloseButton 53 | 54 | 55 | 56 | True 57 | 58 | 59 | gtk-preferences 60 | True 61 | True 62 | True 63 | True 64 | 65 | 66 | False 67 | False 68 | 0 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /share/ui/gtkrc_windows: -------------------------------------------------------------------------------- 1 | gtk-theme-name = "MS-Windows" 2 | 3 | gtk-icon-sizes = "gtk-menu=13,13:gtk-small-toolbar=16,16:gtk-large-toolbar=24,24:gtk-dnd=32,32" 4 | gtk-toolbar-icon-size = small-toolbar 5 | 6 | # disable images in buttons. i've only seen ugly delphi apps use this feature. 7 | gtk-button-images = 0 8 | 9 | # enable/disable images in menus. most "stock" microsoft apps don't use these, except sparingly. 10 | # the office apps use them heavily, though. 11 | gtk-menu-images = 1 12 | 13 | # use the win32 button ordering instead of the GNOME HIG one, where applicable 14 | gtk-alternative-button-order = 1 15 | 16 | # use the win32 sort indicators direction, as in Explorer 17 | gtk-alternative-sort-arrows = 1 18 | 19 | # Windows users don't expect the PC Speaker beeping at them when they backspace in an empty textview and stuff like that 20 | gtk-error-bell = 0 21 | 22 | style "msw-default" 23 | { 24 | GtkWidget::interior-focus = 1 25 | GtkOptionMenu::indicator-size = { 9, 5 } 26 | GtkOptionMenu::indicator-spacing = { 7, 5, 2, 2 } 27 | GtkSpinButton::shadow-type = in 28 | 29 | # Owen and I disagree that these should be themable 30 | #GtkUIManager::add-tearoffs = 0 31 | #GtkComboBox::add-tearoffs = 0 32 | 33 | GtkComboBox::appears-as-list = 1 34 | GtkComboBox::focus-on-click = 0 35 | 36 | GOComboBox::add_tearoffs = 0 37 | 38 | GtkTreeView::allow-rules = 0 39 | GtkTreeView::expander-size = 12 40 | 41 | GtkExpander::expander-size = 12 42 | 43 | GtkScrolledWindow::scrollbar_spacing = 1 44 | 45 | GtkSeparatorMenuItem::horizontal-padding = 2 46 | 47 | engine "wimp" 48 | { 49 | } 50 | } 51 | class "*" style "msw-default" 52 | 53 | binding "ms-windows-tree-view" 54 | { 55 | bind "Right" { "expand-collapse-cursor-row" (1,1,0) } 56 | bind "Left" { "expand-collapse-cursor-row" (1,0,0) } 57 | } 58 | 59 | class "GtkTreeView" binding "ms-windows-tree-view" 60 | 61 | style "msw-combobox-thickness" = "msw-default" 62 | { 63 | xthickness = 0 64 | ythickness = 0 65 | } 66 | 67 | widget_class "*TreeView*ComboBox*" style "msw-combobox-thickness" 68 | widget_class "*ComboBox*GtkFrame*" style "msw-combobox-thickness" 69 | -------------------------------------------------------------------------------- /share/ui/logo_128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/logo_128px.png -------------------------------------------------------------------------------- /share/ui/logo_16px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/logo_16px.png -------------------------------------------------------------------------------- /share/ui/logo_32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/logo_32px.png -------------------------------------------------------------------------------- /share/ui/logo_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/logo_48px.png -------------------------------------------------------------------------------- /share/ui/logo_64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/logo_64px.png -------------------------------------------------------------------------------- /share/ui/logo_gui.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/logo_gui.bmp -------------------------------------------------------------------------------- /share/ui/logo_gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/logo_gui.png -------------------------------------------------------------------------------- /share/ui/logo_gui_vertical.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pib/pycam/df4e35e19cfafdef35853d42815cb6c17ed28964/share/ui/logo_gui_vertical.bmp -------------------------------------------------------------------------------- /share/ui/menubar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /share/ui/model_export.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Save Model _as ... 7 | Save Model as ... 8 | Save the current model to a new STL file. 9 | gtk-save-as 10 | 11 | 12 | _Save Model 13 | _Save Model 14 | Save the current model to the STL file. 15 | 16 | 17 | -------------------------------------------------------------------------------- /share/ui/model_polygons.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | True 9 | 0 10 | none 11 | 12 | 13 | True 14 | 12 15 | 16 | 17 | True 18 | vertical 19 | 20 | 21 | True 22 | 3 23 | start 24 | 25 | 26 | Toggle direction 27 | True 28 | True 29 | True 30 | 31 | 32 | False 33 | False 34 | 2 35 | 0 36 | 37 | 38 | 39 | 40 | Revise directions 41 | True 42 | True 43 | True 44 | 45 | 46 | False 47 | False 48 | 1 49 | 50 | 51 | 52 | 53 | False 54 | 0 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | True 64 | <b>Polygon winding</b> 65 | True 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /share/ui/opengl_view_grid.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | 3 8 | 9 | 10 | True 11 | Major grid: 12 | 13 | 14 | False 15 | 0 16 | 17 | 18 | 19 | 20 | True 21 | 100mm 22 | 23 | 24 | False 25 | 1 26 | 27 | 28 | 29 | 30 | True 31 | vertical 32 | 33 | 34 | False 35 | 3 36 | 2 37 | 38 | 39 | 40 | 41 | True 42 | Minor grid: 43 | 44 | 45 | False 46 | 3 47 | 48 | 49 | 50 | 51 | True 52 | 20mm 53 | 54 | 55 | False 56 | 4 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /share/ui/progress_bar.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | True 9 | vertical 10 | 11 | 12 | end 13 | 14 | 15 | False 16 | 0 17 | 18 | 19 | 20 | 21 | 3 22 | 23 | 24 | True 25 | end 26 | 27 | 28 | 0 29 | 30 | 31 | 32 | 33 | True 34 | vertical 35 | 36 | 37 | Show Progress 38 | True 39 | True 40 | True 41 | 42 | 43 | False 44 | 0 45 | 46 | 47 | 48 | 49 | Cancel 50 | True 51 | True 52 | True 53 | 54 | 55 | False 56 | 1 57 | 58 | 59 | 60 | 61 | False 62 | 1 63 | 64 | 65 | 66 | 67 | False 68 | 1 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /technical_details.txt: -------------------------------------------------------------------------------- 1 | 2 | The DropCutter goes as folows: 3 | 1) make lines along the X-axis (Lines) 4 | 2) for each line, take a number of samples (Samples) 5 | 3) at each sample, drop the cutter until it hits the model, this is a cutter location point 6 | 4) connect the cutter location points along the line 7 | 8 | 9 | The PushCutter goes as follows: 10 | 1) make slices along the Z-axis (Levels) 11 | 2) make lines along the X-axis (Lines) 12 | 3) push the cutter along the line until it hits the model, this is a cutter location 13 | if we do not hit anything, we're finished 14 | 4) skip the parts where the model is above the current z-level 15 | 5) go back to 3 16 | 17 | 18 | The PathAccumulator connects the cutter locations by cutting in between, 19 | it's usefull mainly for debugging 20 | 21 | The SimpleCutter connects the cutter locations in an on-off (cut/don't cut) pattern, 22 | it's usefull only for debugging 23 | 24 | The ZigZagCutter connects the cutter locations by cutting in between points, 25 | then come back along the next line in reverse direction. 26 | it's usefull mainly in combination with the DropCutter 27 | 28 | The PolygonCutter extracts polygonial sections from the cutter scanlines, 29 | then cuts those in a zig-zag pattern 30 | it's usefull mainly in combination with the PushCutter 31 | 32 | The ContourCutter extracts contours from the cutter scanlines, 33 | it's usefull only in combination with the PushCutter 34 | 35 | --------------------------------------------------------------------------------