├── pycam ├── Flow │ └── __init__.py ├── Cutters │ └── __init__.py ├── Exporters │ ├── __init__.py │ ├── LinuxCNCToolExporter.py │ └── STLExporter.py ├── Physics │ └── __init__.py ├── Simulation │ └── __init__.py ├── .gitignore ├── Test │ ├── assets │ │ ├── cube_binary.stl │ │ └── cube_ascii.stl │ ├── test_dxf_importer.py │ ├── test_cxf_fonts.py │ ├── test_stl_loader.py │ ├── test_svg_loader.py │ └── __init__.py ├── errors.py ├── Utils │ ├── progress.py │ └── rootsolver.py ├── Plugins │ ├── TaskTypes.py │ ├── OpenGLViewTool.py │ ├── PathPatterns.py │ ├── ModelPlaneMirror.py │ ├── ModelSwapAxes.py │ ├── Units.py │ ├── ModelPolygons.py │ ├── OpenGLViewSupportModelPreview.py │ ├── ModelRotation.py │ └── OpenGLViewAxes.py ├── PathProcessors │ ├── __init__.py │ ├── PolygonCutter.py │ └── ContourCutter.py ├── Toolpath │ └── Steps.py ├── Geometry │ ├── PointKdtree.py │ └── Path.py ├── Importers │ ├── __init__.py │ └── TestModel.py ├── PathGenerators │ └── EngraveCutter.py ├── run_cli.py └── Gui │ ├── OpenGLTools.py │ └── Console.py ├── debian ├── manpages ├── source │ └── format ├── pycam.mime ├── pycam.sharedmimeinfo ├── docs ├── pycam.install ├── copyright ├── rules └── control ├── requirements.txt ├── share ├── pycam.ico ├── fonts │ ├── romanc.cxf │ ├── romand.cxf │ ├── romanp.cxf │ ├── romans.cxf │ ├── romant.cxf │ ├── symbol.cxf │ ├── courier.cxf │ ├── cursive.cxf │ ├── italicc.cxf │ ├── italiccs.cxf │ ├── italict.cxf │ ├── romancs.cxf │ ├── romans2.cxf │ ├── scriptc.cxf │ ├── scripts.cxf │ ├── standard.cxf │ ├── unicode.cxf │ ├── iso8859-11.cxf │ ├── normallatin1.cxf │ ├── normallatin2.cxf │ ├── normallatin1.readme │ └── README ├── ui │ ├── logo_16px.png │ ├── logo_32px.png │ ├── logo_48px.png │ ├── logo_64px.png │ ├── logo_gui.bmp │ ├── logo_gui.png │ ├── logo_128px.png │ ├── extrusion_sine.png │ ├── extrusion_chamfer.png │ ├── extrusion_radius_up.png │ ├── extrusion_sigmoidal.png │ ├── logo_gui_vertical.bmp │ ├── extrusion_radius_down.png │ ├── emc_tool_export.ui │ ├── model_export.ui │ ├── clipboard.ui │ ├── menubar.xml │ ├── gtkrc_windows │ ├── opengl_view_grid.ui │ └── progress_bar.ui ├── mime │ ├── icons │ │ ├── 32x32 │ │ │ └── application-sla.png │ │ ├── 64x64 │ │ │ └── application-sla.png │ │ └── 128x128 │ │ │ └── application-sla.png │ └── pycam.xml └── desktop │ └── pycam.desktop ├── docs ├── img │ ├── 3d-view.png │ ├── favicon.ico │ ├── menu-edit.png │ ├── menu-file.png │ ├── menu-help.png │ ├── touch-off.png │ ├── menu-extras.png │ ├── server-mode.png │ ├── menu-settings.png │ ├── menu-windows.png │ ├── pycam-logo-160.png │ ├── pycam-logo-600.png │ ├── engrave-font-normal.png │ ├── engrave-font-symbol.png │ ├── process-statistics.png │ ├── screenshot-settings.png │ ├── screenshot-startup.png │ ├── touch-off-settings.png │ ├── 2d-multilayer-engrave.png │ ├── 3d-view-context-menu.png │ ├── 3d-view-visible-items.png │ ├── engrave-font-courier.png │ ├── engrave-font-cursive.png │ ├── engrave-font-greek-ol.png │ ├── engrave-font-japanese.png │ ├── engrave-font-standard.png │ ├── engrave-font-unicode.png │ ├── model-transformations.png │ ├── process-milling-climb.png │ ├── engrave-font-cyrillic2.png │ ├── engrave-font-greek-plain.png │ ├── engrave-font-iso8859-11.png │ ├── engrave-font-roman-plain.png │ ├── process-strategy-surface.png │ ├── screenshot-toolpath-view.png │ ├── engrave-font-gothic-german.png │ ├── engrave-font-greek-complex.png │ ├── engrave-font-greek-ol-plus.png │ ├── engrave-font-greek-simplex.png │ ├── engrave-font-kochi-gothic.png │ ├── engrave-font-kochi-mincho.png │ ├── engrave-font-normal-latin1.png │ ├── engrave-font-normal-latin2.png │ ├── engrave-font-roman-complex.png │ ├── engrave-font-roman-duplex.png │ ├── engrave-font-roman-simplex.png │ ├── engrave-font-roman-triplex.png │ ├── engrave-font-symbol-astro.png │ ├── engrave-font-symbol-misc-1.png │ ├── engrave-font-symbol-misc-2.png │ ├── process-strategy-engraving.png │ ├── screenshot-contour-cutter.png │ ├── screenshot-polygon-cutter.png │ ├── 2d-multilayer-engrave-model.png │ ├── engrave-font-gothic-british.png │ ├── engrave-font-gothic-italian.png │ ├── engrave-font-italian-complex.png │ ├── engrave-font-italian-triplex.png │ ├── engrave-font-script_complex.png │ ├── process-milling-conventional.png │ ├── screenshot-linuxcnc-example.png │ ├── screenshot-model-operations.png │ ├── server-mode-gui-preferences.png │ ├── 2d-modeling-polygon_directions.png │ ├── 2d-multilayer-engrave-toolpath.png │ ├── process-strategy-contour-follow.png │ ├── process-strategy-slice-removal.png │ ├── screenshot-finishing-operation.png │ ├── screenshot-processing-settings.png │ ├── engrave-font-greek-complex-small.png │ ├── engrave-font-italian-complex-small.png │ ├── engrave-font-roman-complex-small.png │ ├── process-strategy-contour-polygon.png │ └── 2d-multilayer-engrave.svg ├── tool-types.md ├── task-settings.md ├── parallel-processing.md ├── bounding-box.md ├── cli-examples.md ├── screenshots.md ├── installation.md ├── supported-formats.md ├── modeling-openscad-dxf.md ├── articles.md ├── index.md ├── other-programs.md ├── video-translations.md ├── keyboard-shortcuts.md ├── model-transformations.md ├── requirements.md ├── menu-items.md ├── main-page.md └── installation-macos.md ├── .codespell.exclude ├── samples ├── Sphere_cut.scad ├── problem_1_triangle.stl ├── rectangle.svg ├── SampleScene3.scad ├── SampleScene2.scad ├── pycam-textbox.scad ├── polygon2.svg ├── polygon3.svg ├── polygon5.svg ├── Box2.stl ├── Box1.stl ├── Box0.stl ├── polygon4.svg └── multilayer_engrave.svg ├── pyinstaller ├── hooks │ └── hook-pycam.py ├── pyinstaller_fix_module_exception.patch └── pyinstaller_info.txt ├── Dockerfile ├── scripts ├── pylint.sh ├── list_events.sh ├── run_flake8 └── profile_pycam.py ├── MANIFEST.in ├── .coveragerc ├── .gitignore ├── man ├── Makefile └── pycam.1.inc ├── setup.cfg ├── LICENSE.TXT ├── .github └── workflows │ └── ci.yml ├── technical_details.txt ├── README.md ├── mkdocs.yml ├── INSTALL.md ├── CODE_OF_CONDUCT.md └── setup.py /pycam/Flow/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pycam/Cutters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pycam/Exporters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pycam/Physics/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pycam/Simulation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/manpages: -------------------------------------------------------------------------------- 1 | man/pycam.1 2 | -------------------------------------------------------------------------------- /pycam/.gitignore: -------------------------------------------------------------------------------- 1 | Version.py 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/pycam.mime: -------------------------------------------------------------------------------- 1 | ../share/mime/pycam.mime -------------------------------------------------------------------------------- /debian/pycam.sharedmimeinfo: -------------------------------------------------------------------------------- 1 | ../share/mime/pycam.xml -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyOpenGL 2 | PyYAML 3 | svg.path 4 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | Changelog 2 | README.md 3 | technical_details.txt 4 | -------------------------------------------------------------------------------- /debian/pycam.install: -------------------------------------------------------------------------------- 1 | share/desktop/pycam.desktop /usr/share/applications 2 | -------------------------------------------------------------------------------- /share/pycam.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/pycam.ico -------------------------------------------------------------------------------- /docs/img/3d-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/3d-view.png -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/img/menu-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/menu-edit.png -------------------------------------------------------------------------------- /docs/img/menu-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/menu-file.png -------------------------------------------------------------------------------- /docs/img/menu-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/menu-help.png -------------------------------------------------------------------------------- /docs/img/touch-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/touch-off.png -------------------------------------------------------------------------------- /share/fonts/romanc.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/romanc.cxf -------------------------------------------------------------------------------- /share/fonts/romand.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/romand.cxf -------------------------------------------------------------------------------- /share/fonts/romanp.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/romanp.cxf -------------------------------------------------------------------------------- /share/fonts/romans.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/romans.cxf -------------------------------------------------------------------------------- /share/fonts/romant.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/romant.cxf -------------------------------------------------------------------------------- /share/fonts/symbol.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/symbol.cxf -------------------------------------------------------------------------------- /share/ui/logo_16px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/logo_16px.png -------------------------------------------------------------------------------- /share/ui/logo_32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/logo_32px.png -------------------------------------------------------------------------------- /share/ui/logo_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/logo_48px.png -------------------------------------------------------------------------------- /share/ui/logo_64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/logo_64px.png -------------------------------------------------------------------------------- /share/ui/logo_gui.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/logo_gui.bmp -------------------------------------------------------------------------------- /share/ui/logo_gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/logo_gui.png -------------------------------------------------------------------------------- /.codespell.exclude: -------------------------------------------------------------------------------- 1 | # see http://www.daa.com.au/pipermail/pygtk/2009-May/017052.html 2 | -------------------------------------------------------------------------------- /docs/img/menu-extras.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/menu-extras.png -------------------------------------------------------------------------------- /docs/img/server-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/server-mode.png -------------------------------------------------------------------------------- /share/fonts/courier.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/courier.cxf -------------------------------------------------------------------------------- /share/fonts/cursive.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/cursive.cxf -------------------------------------------------------------------------------- /share/fonts/italicc.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/italicc.cxf -------------------------------------------------------------------------------- /share/fonts/italiccs.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/italiccs.cxf -------------------------------------------------------------------------------- /share/fonts/italict.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/italict.cxf -------------------------------------------------------------------------------- /share/fonts/romancs.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/romancs.cxf -------------------------------------------------------------------------------- /share/fonts/romans2.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/romans2.cxf -------------------------------------------------------------------------------- /share/fonts/scriptc.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/scriptc.cxf -------------------------------------------------------------------------------- /share/fonts/scripts.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/scripts.cxf -------------------------------------------------------------------------------- /share/fonts/standard.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/standard.cxf -------------------------------------------------------------------------------- /share/fonts/unicode.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/unicode.cxf -------------------------------------------------------------------------------- /share/ui/logo_128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/logo_128px.png -------------------------------------------------------------------------------- /docs/img/menu-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/menu-settings.png -------------------------------------------------------------------------------- /docs/img/menu-windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/menu-windows.png -------------------------------------------------------------------------------- /docs/img/pycam-logo-160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/pycam-logo-160.png -------------------------------------------------------------------------------- /docs/img/pycam-logo-600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/pycam-logo-600.png -------------------------------------------------------------------------------- /share/fonts/iso8859-11.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/iso8859-11.cxf -------------------------------------------------------------------------------- /share/ui/extrusion_sine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/extrusion_sine.png -------------------------------------------------------------------------------- /share/fonts/normallatin1.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/normallatin1.cxf -------------------------------------------------------------------------------- /share/fonts/normallatin2.cxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/normallatin2.cxf -------------------------------------------------------------------------------- /docs/img/engrave-font-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-normal.png -------------------------------------------------------------------------------- /docs/img/engrave-font-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-symbol.png -------------------------------------------------------------------------------- /docs/img/process-statistics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/process-statistics.png -------------------------------------------------------------------------------- /docs/img/screenshot-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/screenshot-settings.png -------------------------------------------------------------------------------- /docs/img/screenshot-startup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/screenshot-startup.png -------------------------------------------------------------------------------- /docs/img/touch-off-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/touch-off-settings.png -------------------------------------------------------------------------------- /samples/Sphere_cut.scad: -------------------------------------------------------------------------------- 1 | intersection() { 2 | sphere(r=5, center=true, $fn=6); 3 | cube([20,20,5], center=true); 4 | } -------------------------------------------------------------------------------- /share/fonts/normallatin1.readme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/fonts/normallatin1.readme -------------------------------------------------------------------------------- /share/ui/extrusion_chamfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/extrusion_chamfer.png -------------------------------------------------------------------------------- /share/ui/extrusion_radius_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/extrusion_radius_up.png -------------------------------------------------------------------------------- /share/ui/extrusion_sigmoidal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/extrusion_sigmoidal.png -------------------------------------------------------------------------------- /share/ui/logo_gui_vertical.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/logo_gui_vertical.bmp -------------------------------------------------------------------------------- /docs/img/2d-multilayer-engrave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/2d-multilayer-engrave.png -------------------------------------------------------------------------------- /docs/img/3d-view-context-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/3d-view-context-menu.png -------------------------------------------------------------------------------- /docs/img/3d-view-visible-items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/3d-view-visible-items.png -------------------------------------------------------------------------------- /docs/img/engrave-font-courier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-courier.png -------------------------------------------------------------------------------- /docs/img/engrave-font-cursive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-cursive.png -------------------------------------------------------------------------------- /docs/img/engrave-font-greek-ol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-greek-ol.png -------------------------------------------------------------------------------- /docs/img/engrave-font-japanese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-japanese.png -------------------------------------------------------------------------------- /docs/img/engrave-font-standard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-standard.png -------------------------------------------------------------------------------- /docs/img/engrave-font-unicode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-unicode.png -------------------------------------------------------------------------------- /docs/img/model-transformations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/model-transformations.png -------------------------------------------------------------------------------- /docs/img/process-milling-climb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/process-milling-climb.png -------------------------------------------------------------------------------- /pycam/Test/assets/cube_binary.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/pycam/Test/assets/cube_binary.stl -------------------------------------------------------------------------------- /share/ui/extrusion_radius_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/ui/extrusion_radius_down.png -------------------------------------------------------------------------------- /docs/img/engrave-font-cyrillic2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-cyrillic2.png -------------------------------------------------------------------------------- /docs/img/engrave-font-greek-plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-greek-plain.png -------------------------------------------------------------------------------- /docs/img/engrave-font-iso8859-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-iso8859-11.png -------------------------------------------------------------------------------- /docs/img/engrave-font-roman-plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-roman-plain.png -------------------------------------------------------------------------------- /docs/img/process-strategy-surface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/process-strategy-surface.png -------------------------------------------------------------------------------- /docs/img/screenshot-toolpath-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/screenshot-toolpath-view.png -------------------------------------------------------------------------------- /docs/img/engrave-font-gothic-german.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-gothic-german.png -------------------------------------------------------------------------------- /docs/img/engrave-font-greek-complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-greek-complex.png -------------------------------------------------------------------------------- /docs/img/engrave-font-greek-ol-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-greek-ol-plus.png -------------------------------------------------------------------------------- /docs/img/engrave-font-greek-simplex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-greek-simplex.png -------------------------------------------------------------------------------- /docs/img/engrave-font-kochi-gothic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-kochi-gothic.png -------------------------------------------------------------------------------- /docs/img/engrave-font-kochi-mincho.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-kochi-mincho.png -------------------------------------------------------------------------------- /docs/img/engrave-font-normal-latin1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-normal-latin1.png -------------------------------------------------------------------------------- /docs/img/engrave-font-normal-latin2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-normal-latin2.png -------------------------------------------------------------------------------- /docs/img/engrave-font-roman-complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-roman-complex.png -------------------------------------------------------------------------------- /docs/img/engrave-font-roman-duplex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-roman-duplex.png -------------------------------------------------------------------------------- /docs/img/engrave-font-roman-simplex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-roman-simplex.png -------------------------------------------------------------------------------- /docs/img/engrave-font-roman-triplex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-roman-triplex.png -------------------------------------------------------------------------------- /docs/img/engrave-font-symbol-astro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-symbol-astro.png -------------------------------------------------------------------------------- /docs/img/engrave-font-symbol-misc-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-symbol-misc-1.png -------------------------------------------------------------------------------- /docs/img/engrave-font-symbol-misc-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-symbol-misc-2.png -------------------------------------------------------------------------------- /docs/img/process-strategy-engraving.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/process-strategy-engraving.png -------------------------------------------------------------------------------- /docs/img/screenshot-contour-cutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/screenshot-contour-cutter.png -------------------------------------------------------------------------------- /docs/img/screenshot-polygon-cutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/screenshot-polygon-cutter.png -------------------------------------------------------------------------------- /docs/img/2d-multilayer-engrave-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/2d-multilayer-engrave-model.png -------------------------------------------------------------------------------- /docs/img/engrave-font-gothic-british.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-gothic-british.png -------------------------------------------------------------------------------- /docs/img/engrave-font-gothic-italian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-gothic-italian.png -------------------------------------------------------------------------------- /docs/img/engrave-font-italian-complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-italian-complex.png -------------------------------------------------------------------------------- /docs/img/engrave-font-italian-triplex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-italian-triplex.png -------------------------------------------------------------------------------- /docs/img/engrave-font-script_complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-script_complex.png -------------------------------------------------------------------------------- /docs/img/process-milling-conventional.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/process-milling-conventional.png -------------------------------------------------------------------------------- /docs/img/screenshot-linuxcnc-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/screenshot-linuxcnc-example.png -------------------------------------------------------------------------------- /docs/img/screenshot-model-operations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/screenshot-model-operations.png -------------------------------------------------------------------------------- /docs/img/server-mode-gui-preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/server-mode-gui-preferences.png -------------------------------------------------------------------------------- /share/mime/icons/32x32/application-sla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/mime/icons/32x32/application-sla.png -------------------------------------------------------------------------------- /share/mime/icons/64x64/application-sla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/mime/icons/64x64/application-sla.png -------------------------------------------------------------------------------- /docs/img/2d-modeling-polygon_directions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/2d-modeling-polygon_directions.png -------------------------------------------------------------------------------- /docs/img/2d-multilayer-engrave-toolpath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/2d-multilayer-engrave-toolpath.png -------------------------------------------------------------------------------- /docs/img/process-strategy-contour-follow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/process-strategy-contour-follow.png -------------------------------------------------------------------------------- /docs/img/process-strategy-slice-removal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/process-strategy-slice-removal.png -------------------------------------------------------------------------------- /docs/img/screenshot-finishing-operation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/screenshot-finishing-operation.png -------------------------------------------------------------------------------- /docs/img/screenshot-processing-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/screenshot-processing-settings.png -------------------------------------------------------------------------------- /share/mime/icons/128x128/application-sla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/share/mime/icons/128x128/application-sla.png -------------------------------------------------------------------------------- /docs/img/engrave-font-greek-complex-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-greek-complex-small.png -------------------------------------------------------------------------------- /docs/img/engrave-font-italian-complex-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-italian-complex-small.png -------------------------------------------------------------------------------- /docs/img/engrave-font-roman-complex-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/engrave-font-roman-complex-small.png -------------------------------------------------------------------------------- /docs/img/process-strategy-contour-polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebKuzminsky/pycam/HEAD/docs/img/process-strategy-contour-polygon.png -------------------------------------------------------------------------------- /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 | hiddenimports = ["gtk.keysyms"] 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/tool-types.md: -------------------------------------------------------------------------------- 1 | Tool types 2 | ========== 3 | Pycam support three tool shapes: Flat bottom (cylindrical), ball nose (spherical), and bull nose (toroidal). 4 | In addition to the tool shape the feedrate and spindle speed are also configured per tool. 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:9 2 | 3 | WORKDIR /root/pycam 4 | 5 | COPY . . 6 | 7 | RUN apt-get update && apt-get install -y python3 \ 8 | python3-gi \ 9 | python3-opengl \ 10 | python3-yaml \ 11 | gir1.2-gtk-3.0 12 | 13 | CMD [ "pycam/run_gui.py" ] 14 | -------------------------------------------------------------------------------- /docs/task-settings.md: -------------------------------------------------------------------------------- 1 | Task settings 2 | ============= 3 | Toolpaths are generated in tasks, which are formed by a combination of a tool, a process, bounds, and zero or more collision models. This way it is possible to make different combinations for different steps in the milling process. 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /scripts/pylint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | BASE_PATH="$(cd "$(dirname "$0")"; pwd)" 6 | 7 | IGNORE_LIST="C0103,C0111,C0301" 8 | IGNORE_LIST="$IGNORE_LIST,W0511,W0602,W0603,W0612,W0613" 9 | IGNORE_LIST="$IGNORE_LIST,R0201,R0902,R0903,R0911,R0912,R0913,R0914,R0915" 10 | 11 | PYTHONPATH="$BASE_PATH/pycam" pylint -i y -d "$IGNORE_LIST" "$1" 12 | -------------------------------------------------------------------------------- /scripts/list_events.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | BASE_DIR=$(cd "$(dirname "$0")"; pwd) 6 | 7 | grep -rh _event "$BASE_DIR/../pycam" | \ 8 | grep -v " def " | \ 9 | grep -v configure_event | \ 10 | grep -v expose_event | \ 11 | sed 's/.*_event//g' | \ 12 | grep '"' | \ 13 | cut -f 2 -d '"' | \ 14 | grep -E "^[0-9A-Za-z_-]+$" | \ 15 | sort | \ 16 | uniq -c 17 | 18 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Changelog 2 | include COPYING.TXT 3 | include INSTALL.TXT 4 | include LICENSE.TXT 5 | include mkdocs.yml 6 | include README.md 7 | include release_info.txt 8 | include requirements.txt 9 | include technical_details.txt 10 | include pycam 11 | recursive-include docs * 12 | recursive-include man * 13 | recursive-include share * 14 | recursive-include samples * 15 | recursive-include Tests * 16 | -------------------------------------------------------------------------------- /samples/rectangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /share/ui/emc_tool_export.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | _Export tools for LinuxCNC ... 7 | _Export tools for LinuxCNC ... 8 | 9 | 10 | -------------------------------------------------------------------------------- /share/desktop/pycam.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Name=PyCAM 4 | GenericName=CNC 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;X-CNC; 11 | MimeType=application/x-yaml;application/sla;image/svg+xml;application/postscript;image/vnd.dxf; 12 | Icon=pycam 13 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = pycam 4 | 5 | [report] 6 | exclude_lines = 7 | # Have to re-enable the standard pragma 8 | pragma: no cover 9 | # Don't complain about missing debug-only code: 10 | def __repr__ 11 | if self\.debug 12 | # Don't complain if tests don't hit defensive assertion code: 13 | raise AssertionError 14 | raise NotImplementedError 15 | # Don't complain if non-runnable code isn't run: 16 | if __name__ == .__main__.: 17 | 18 | [html] 19 | directory = build/coverage-report 20 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.o 3 | *.so 4 | *.py[cod] 5 | 6 | test.ngc 7 | 8 | man/pycam.1 9 | man/pycam.1.html 10 | docs/release-notes.md 11 | 12 | dist 13 | build 14 | eggs 15 | *.egg-info 16 | .svn 17 | .coverage 18 | .idea 19 | .ropeproject/ 20 | 21 | .DS_Store* 22 | ehthumbs.db 23 | Icon? 24 | Thumbs.db 25 | 26 | *~ 27 | .*.sw? 28 | 29 | man/pycam.1 30 | 31 | # Generated mkdocs files go into the site directory 32 | site/ 33 | 34 | # debian packaging 35 | debian/debhelper-build-stamp 36 | debian/files 37 | debian/pycam/ 38 | debian/pycam.debhelper.log 39 | debian/pycam.*.debhelper 40 | debian/pycam.substvars 41 | -------------------------------------------------------------------------------- /man/Makefile: -------------------------------------------------------------------------------- 1 | RUN_SCRIPT = ../pycam/run_gui.py 2 | PYCAM_MAN_INCLUDE_FILE = pycam.1.inc 3 | MAN_FILES = pycam.1 4 | HTML_FILES = $(patsubst %,%.html,$(MAN_FILES)) 5 | RM = rm -f 6 | 7 | 8 | .PHONY: build clean html man 9 | 10 | 11 | man: $(MAN_FILES) 12 | 13 | html: $(HTML_FILES) 14 | 15 | pycam.1: $(RUN_SCRIPT) $(PYCAM_MAN_INCLUDE_FILE) 16 | help2man --no-info --name="Toolpath Generation for 3-Axis CNC machining" \ 17 | --section=1 --manual="PyCAM manual" --include="$(PYCAM_MAN_INCLUDE_FILE)" \ 18 | --output="$@" "$(RUN_SCRIPT)" 19 | 20 | %.html: % 21 | man2html $* >"$@" 22 | 23 | clean: 24 | $(RM) $(MAN_FILES) $(HTML_FILES) 25 | -------------------------------------------------------------------------------- /docs/parallel-processing.md: -------------------------------------------------------------------------------- 1 | Parallel processing is fully supported on Linux. Available features on 2 | other platforms are limited. 3 | 4 | | Feature | Linux | MacOS | Windows (.exe) | Windows (installer) | 5 | --------------------------- | ------ | ------ | -------------- | ------------------- | 6 | | Multiple local processes | Yes | Yes | No | Yes | 7 | | Run server locally | Yes | ? | No | Yes | 8 | | Connect to a remote server | Yes | ? | No | Yes | 9 | | Mixed local and remote | Yes | ? | No | No | 10 | -------------------------------------------------------------------------------- /docs/bounding-box.md: -------------------------------------------------------------------------------- 1 | A bounding box is a 3D area where you want the mill to do its work or 2 | actually stay away. 3 | 4 | This is useful for: 5 | 6 | - Doing work in areas that require specific movements e.g. for more 7 | detailed finish ins small areas or higher speed of material removal 8 | for roughing in larger ones. 9 | - Staying away from areas, f.i. to avoid clamps 10 | 11 | You can set up to work inside the bounding box and outside the bounding 12 | box (plus another option editor has to look up, also not mentioned here 13 | where to find these settings in preferences). 14 | 15 | The bounding box is shown in the model by a set of lines describing the 16 | box. 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.md 12 | INSTALL.TXT 13 | LICENSE.TXT 14 | COPYING.TXT 15 | docs/ 16 | examples/ 17 | 18 | [flake8] 19 | max-line-length = 99 20 | # flake8 defaults: E121,E123,E126,E226,E24,E704,W503,W504 21 | # custom additions: 22 | # E731: sometimes we like lambda 23 | # E741: tolerate short names 24 | # N806: TODO: fix these non-standard naming exceptions 25 | ignore = E121,E123,E126,E226,E24,E704,W503,W504,E731,E741,N806 26 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/clipboard.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | _Copy 7 | _Copy 8 | Copy model to clipboard 9 | gtk-copy 10 | 11 | 12 | _Paste 13 | _Paste 14 | Paste a model from clipboard 15 | gtk-paste 16 | 17 | 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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, see . 19 | 20 | See COPYING 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/cli-examples.md: -------------------------------------------------------------------------------- 1 | Using PyCAM via the command-line 2 | ================================ 3 | 4 | **WARNING: this page is outdated. Please refer to the output of 5 | “pycam --help” or [browse it online](/manpages/pycam.1.html).** 6 | 7 | The following examples show some command use-cases for the 8 | non-interactive generation of GCode with PyCAM: 9 | 10 | - load a specific settings file for the GUI: 11 | 12 | 13 | 14 | pycam --config SOME_CONFIG_FILE 15 | 16 | - open a model: 17 | 18 | 19 | 20 | pycam SOME_MODEL_FILE 21 | 22 | - generate a GCode file using all default tasks (taken from the 23 | default settings): 24 | 25 | 26 | 27 | pycam SOME_MODEL_FILE DESTINATION_GCODE_FILE 28 | 29 | - generate a GCode file using a custom settings file and picking just 30 | one specific task: 31 | 32 | 33 | 34 | pycam --config YOUR_SETTINGS_FILE --task 2 SOME_MODEL_FILE DESTINATION_GCODE_FILE 35 | -------------------------------------------------------------------------------- /scripts/run_flake8: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This script is just a wrapper around different versions of flake8. 4 | It can be removed as soon as "python -m flake8" works across all common distributions. 5 | """ 6 | 7 | import sys 8 | 9 | 10 | def get_flake8_runner(): 11 | def import_via_main_or_maincli(): 12 | """ try to import "main" from flake8.main or flake8.main.cli 13 | 14 | The "flake8" module in Debian Jessie and Stretch, as well as Ubuntu Trusty/Xenial use 15 | different names. 16 | """ 17 | try: 18 | # Debian Stretch 19 | from flake8.main.cli import main 20 | except ImportError: 21 | # Debian Jessie and Ubuntu Trusty 22 | from flake8.main import main 23 | return main 24 | try: 25 | return import_via_main_or_maincli() 26 | except ImportError: 27 | # probably a virtualenv on travis is hiding the system packages from us 28 | sys.path.insert(0, "/usr/lib/python3/dist-packages") 29 | return import_via_main_or_maincli() 30 | 31 | 32 | if __name__ == "__main__": 33 | runner = get_flake8_runner() 34 | runner() 35 | -------------------------------------------------------------------------------- /pycam/errors.py: -------------------------------------------------------------------------------- 1 | class PycamBaseException(Exception): 2 | pass 3 | 4 | 5 | class AbortOperationException(PycamBaseException): 6 | pass 7 | 8 | 9 | class CommunicationError(PycamBaseException): 10 | pass 11 | 12 | 13 | class InitializationError(PycamBaseException): 14 | pass 15 | 16 | 17 | class InvalidDataError(PycamBaseException): 18 | pass 19 | 20 | 21 | class MissingAttributeError(InvalidDataError): 22 | pass 23 | 24 | 25 | class AmbiguousDataError(InvalidDataError): 26 | pass 27 | 28 | 29 | class UnexpectedAttributeError(InvalidDataError): 30 | pass 31 | 32 | 33 | class InvalidKeyError(InvalidDataError): 34 | 35 | def __init__(self, invalid_key, choice_enum): 36 | # retrieve the pretty name of the enum 37 | enum_name = str(choice_enum).split("'")[1] 38 | super().__init__("Unknown {}: {} (should be one of: {})".format( 39 | enum_name, invalid_key, ", ".join([item.value for item in choice_enum]))) 40 | 41 | 42 | class LoadFileError(PycamBaseException): 43 | pass 44 | 45 | 46 | class MissingDependencyError(PycamBaseException): 47 | """ a dependency (e.g. an external python module) is missing """ 48 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: [push, pull_request] 3 | jobs: 4 | Run-tests: 5 | runs-on: ubuntu-20.04 6 | container: debian:bullseye 7 | steps: 8 | - name: Check out repository code 9 | uses: actions/checkout@v2 10 | with: 11 | fetch-depth: 0 12 | 13 | # Install build dependencies 14 | - run: apt-get update 15 | - run: apt-get -V --yes --no-install-recommends --no-install-suggests install dpkg-dev devscripts equivs 16 | 17 | - run: dpkg-checkbuilddeps || true 18 | - run: mk-build-deps -i -r -t 'apt-get -V --yes --no-install-recommends --no-install-suggests' 19 | - run: rm pycam-build-deps_* 20 | 21 | - run: pycam/run_gui.py --help 22 | - run: pycam/run_gui.py --version 23 | - run: pycam/run_cli.py --help 24 | - run: pycam/run_cli.py --version 25 | 26 | - run: dpkg-buildpackage -us -uc 27 | 28 | - run: apt-get -V --yes install ../pycam_*.deb 29 | 30 | - run: cd .. ; pycam --help 31 | - run: cd .. ; pycam --version 32 | - run: cd .. ; pycam-cli --help 33 | - run: cd .. ; pycam-cli --version 34 | 35 | - run: ls -al 36 | - run: ls -al ../ 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/screenshots.md: -------------------------------------------------------------------------------- 1 | Screenshots 2 | =========== 3 | 4 | Startup Screen 5 | -------------- 6 | 7 | ![Screenshot of PyCAM at startup](img/screenshot-startup.png) 8 | 9 | Model operations 10 | ---------------- 11 | 12 | ![Screenshot of the Model Operations](img/screenshot-model-operations.png) 13 | 14 | Processing settings 15 | ------------------- 16 | 17 | ![Screenshot of Processing Settings](img/screenshot-processing-settings.png) 18 | 19 | Roughing Operation 20 | ------------------ 21 | 22 | ![Screenshot of Polygon Cutter 3D View](img/screenshot-polygon-cutter.png) 23 | 24 | Semi-finishing Operation 25 | ------------------------ 26 | 27 | ![Screenshot of Contour Cutter 3D View](img/screenshot-contour-cutter.png) 28 | 29 | Finishing Operation 30 | ------------------- 31 | 32 | ![Screenshot of Finishing Operation 3D View](img/screenshot-finishing-operation.png) 33 | 34 | Toolpath management 35 | ------------------- 36 | 37 | ![Screenshot of list of Toolpaths](img/screenshot-toolpath-view.png) 38 | 39 | Settings 40 | -------- 41 | 42 | ![Screenshot of list of PyCAM setting](img/screenshot-settings.png) 43 | 44 | Importing the GCode in LinuxCNC 45 | --------------------------- 46 | 47 | ![Screenshot of G-Code imported into LinuxCNC](img/screenshot-linuxcnc-example.png) 48 | 49 | -------------------------------------------------------------------------------- /pycam/Utils/progress.py: -------------------------------------------------------------------------------- 1 | from pycam.Utils.events import get_event_handler, get_mainloop 2 | 3 | 4 | class ProgressContext: 5 | 6 | def __init__(self, title): 7 | self._title = title 8 | self._progress = get_event_handler().get("progress") 9 | 10 | def __enter__(self): 11 | if self._progress: 12 | self._progress.update(text=self._title, percent=0) 13 | # start an indefinite pulse (until we receive more details) 14 | self._progress.update() 15 | else: 16 | self._progress = None 17 | return self 18 | 19 | def __exit__(self, exc_type, exc_value, traceback): 20 | if self._progress: 21 | self._progress.finish() 22 | 23 | def update(self, *args, **kwargs): 24 | mainloop = get_mainloop() 25 | if mainloop is None: 26 | return False 27 | mainloop.update() 28 | if self._progress: 29 | return self._progress.update(*args, **kwargs) 30 | else: 31 | return False 32 | 33 | def set_multiple(self, count, base_text=None): 34 | if self._progress: 35 | self._progress.set_multiple(count, base_text=base_text) 36 | 37 | def update_multiple(self): 38 | if self._progress: 39 | self._progress.update_multiple() 40 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: PyCAM 3 | Upstream-Contact: Lars Kruse 4 | Source: https://github.com/SebKuzminsky/pycam 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 | -------------------------------------------------------------------------------- /pycam/Test/test_dxf_importer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2018 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import os 21 | 22 | from pycam.Importers.DXFImporter import import_model 23 | import pycam.Test 24 | 25 | 26 | ASSETS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") 27 | 28 | DXF_TEST_FILES = {"bezier_lines.dxf"} 29 | 30 | 31 | class TestDXFImporter(pycam.Test.PycamTestCase): 32 | """ Checks ability to open some sample .dxf files correctly """ 33 | 34 | def test_load_dxf_files(self): 35 | for test_filename in DXF_TEST_FILES: 36 | full_filename = os.path.join(ASSETS_PATH, test_filename) 37 | model = import_model(full_filename) 38 | assert model 39 | -------------------------------------------------------------------------------- /pycam/Test/test_cxf_fonts.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2018 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import os 21 | 22 | import pycam.Test 23 | import pycam.Utils.FontCache 24 | 25 | 26 | FONT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 27 | os.path.pardir, os.path.pardir, "share", "fonts") 28 | 29 | 30 | class TestCXFImporter(pycam.Test.PycamTestCase): 31 | """ 32 | Checks ability to open all included .cxf font files correctly 33 | """ 34 | 35 | def test_load_ascii_file(self): 36 | cache = pycam.Utils.FontCache.FontCache(FONT_PATH) 37 | # the number of fonts is lower by one, but "Standard" and "Normal" are aliases 38 | assert len(cache) == 35 39 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # vim: noexpandtab 3 | # See debhelper(7) (uncomment to enable) 4 | # output every command that modifies files on the build system. 5 | DH_VERBOSE = 1 6 | 7 | # see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/* 8 | DPKG_EXPORT_BUILDFLAGS = 1 9 | include /usr/share/dpkg/default.mk 10 | 11 | # see FEATURE AREAS in dpkg-buildflags(1) 12 | #export DEB_BUILD_MAINT_OPTIONS = hardening=+all 13 | 14 | # see ENVIRONMENT in dpkg-buildflags(1) 15 | # package maintainers to append CFLAGS 16 | #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 17 | # package maintainers to append LDFLAGS 18 | #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 19 | 20 | 21 | # main packaging script based on dh7 syntax 22 | %: 23 | dh $@ --with python3 24 | 25 | # This target updates pycam/Version.py and debian/changelog with current 26 | # information from git. It's intended to be run before generating the 27 | # debian source package. 28 | prep-source: 29 | $(MAKE) update-version 30 | $(MAKE) update-deb-changelog 31 | rm -f pycam/*.pyc 32 | 33 | override_dh_auto_clean: 34 | $(MAKE) clean 35 | debian/rules prep-source 36 | 37 | override_dh_auto_build: 38 | $(MAKE) -C man 39 | python3 setup.py build 40 | 41 | override_dh_auto_install: 42 | python3 setup.py install --root=$(CURDIR)/debian/pycam --prefix=/usr 43 | # remove redundant copy of docs 44 | rm -rf $(CURDIR)/debian/pycam/usr/share/pycam/doc 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /technical_details.txt: -------------------------------------------------------------------------------- 1 | 2 | The DropCutter goes as follows: 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 is useful mainly for debugging 20 | 21 | The SimpleCutter connects the cutter locations in an on-off (cut/don't cut) pattern, 22 | it is useful 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 is useful 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 is useful mainly in combination with the PushCutter 31 | 32 | The ContourCutter extracts contours from the cutter scanlines, 33 | it is useful only in combination with the PushCutter 34 | -------------------------------------------------------------------------------- /pycam/Plugins/TaskTypes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2011 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | 21 | import pycam.Plugins 22 | import pycam.Toolpath 23 | 24 | 25 | class TaskTypeMilling(pycam.Plugins.PluginBase): 26 | 27 | DEPENDS = ["Tasks", "TaskParamCollisionModels", "TaskParamTool", "TaskParamProcess", 28 | "TaskParamBounds"] 29 | CATEGORIES = ["Task"] 30 | 31 | def setup(self): 32 | parameters = {"collision_models": [], "tool": None, "process": None, "bounds": None} 33 | self.core.get("register_parameter_set")("task", "milling", "Milling", None, 34 | parameters=parameters, weight=10) 35 | return True 36 | 37 | def teardown(self): 38 | self.core.get("unregister_parameter_set")("task", "milling") 39 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | Installing the latest release 2 | ----------------------------- 3 | 4 | ### Linux / \*BSD 5 | 6 | 1. [download](http://sourceforge.net/projects/pycam/files/) the archive 7 | of your choice 8 | 2. install the [requirements](requirements.md) for your system 9 | 3. run `scripts/pycam` in the PyCAM directory 10 | 11 | ### Mac OS X 12 | 13 | See [Installation MacOS](installation-macos.md) for details. 14 | 15 | ### Windows 16 | 17 | Use the [standalone executable for Windows](https://sourceforge.net/projects/pycam/files/pycam/0.5.1/pycam-0.5.1.1_standalone.exe/download). 18 | There are no further requirements. 19 | 20 | If you want to use [multiple CPU cores or distributed processing](parallel-processing), then you will need to use the 21 | [dependency installer](https://sourceforge.net/projects/pycam/files/pycam/0.5.1/python2.5-gtk-opengl.exe/download) 22 | and the [PyCAM installer package](https://sourceforge.net/projects/pycam/files/pycam/0.5.1/pycam-0.5.1.win32.exe/download). 23 | 24 | Installing the development version 25 | ---------------------------------- 26 | 27 | 1. install the [git](http://git-scm.com/) client (via your package 28 | manager or by [downloading](http://git-scm.com/downloads) it) 29 | 2. checkout the PyCAM repository: 30 | `git clone `[`git@github.com:SebKuzminsky/pycam.git`](git@github.com:SebKuzminsky/pycam.git) 31 | 3. install the [requirements](requirements.md) for your system 32 | 4. run `pycam/run_gui.py` in the PyCAM directory 33 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: pycam 2 | Section: science 3 | Priority: optional 4 | Maintainer: Lars Kruse 5 | Build-Depends: 6 | debhelper-compat (= 13), 7 | codespell, 8 | dh-python, 9 | help2man, 10 | python3, 11 | python3-flake8, 12 | python3-numpy, 13 | python3-pytest, 14 | python3-setuptools, 15 | python3-svg.path, 16 | python3-yaml, 17 | Standards-Version: 3.9.8 18 | Homepage: https://github.com/SebKuzminsky/pycam 19 | 20 | Package: pycam 21 | Architecture: all 22 | Depends: 23 | librecad-data, 24 | python3-gi, 25 | gir1.2-gtk-3.0, 26 | python3-svg.path, 27 | python3-yaml, 28 | ${misc:Depends}, 29 | ${python3:Depends}, 30 | Recommends: 31 | inkscape, 32 | pstoedit, 33 | python3-numpy, 34 | python3-opengl, 35 | python3-setproctitle, 36 | Description: CAM program & Python library for generating toolpaths 37 | PyCAM is a toolpath generator for 3 axis machines. The generated 38 | GCode can be used with LinuxCNC and other machine controllers. 39 | The included Python library can be used independently from the GUI. 40 | . 41 | Features: 42 | * read and write STL model files (3D) 43 | * support for 2D models (DXF/SVG/PS) 44 | * generate toolpaths (GCode) for various strategies and drill 45 | definitions 46 | * manage and store processing templates 47 | * scale, move, rotate, flip and transform the model 48 | * interactive 3D model view based on OpenGL 49 | * non-interactive generation of GCode via commandline 50 | * render single-line fonts (provided by QCAD) 51 | -------------------------------------------------------------------------------- /docs/supported-formats.md: -------------------------------------------------------------------------------- 1 | Supported formats of PyCAM 2 | ========================== 3 | 4 | PyCAM can import and export data from and to different formats. 5 | 6 | Read the following pages for hints about creating usable models: 7 | 8 | - [2D modeling with Inkscape (SVG)](modeling-inkscape-svg.md) 9 | - [2D modeling with OpenSCAD (DXF)](modeling-openscad-dxf.md) 10 | 11 | STL 12 | --- 13 | 14 | [STL files](http://en.wikipedia.org/wiki/STL_(file_format)) describe the surface 15 | of 3D models as a mesh of triangles. The STL format definition describes 16 | an ascii and a binary storage format. Both are supported by PyCAM. 17 | 18 | PyCAM can transform 3D models and save the result as an ascii STL file. 19 | 20 | DXF 21 | --- 22 | 23 | [DXF files](http://en.wikipedia.org/wiki/DXF_(file_format)) can describe 3D or 24 | 2D models. PyCAM can import both types. The following DXF primitives are 25 | supported: 26 | 27 | - LINE / POLYLINE / LWPOLYLINE 28 | - ARC / CIRCLE 29 | - TEXT / MTEXT 30 | - 3DFACE 31 | 32 | SVG 33 | --- 34 | 35 | [Scalable vector files](http://en.wikipedia.org/wiki/Scalable_Vector_Graphics) can describe 2D 36 | models. They are supposed to be used as contour models for engravings. 37 | 38 | Before PyCAM v0.7 you needed to install *Inkscape* and *pstoedit* if you want 39 | to import SVG files. Please take a look at the 40 | [requirements](requirements#Optional_external_programs) for more details. 41 | 42 | Additionally you should read the [hints for Inkscape](modeling-inkscape-svg.md) to avoid 43 | common pitfalls. 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/SebKuzminsky/pycam.svg?branch=master)](https://travis-ci.org/SebKuzminsky/pycam) 2 | 3 | # PyCAM: a toolpath generator 4 | 5 | PyCAM generates toolpaths (GCode) based on 2D or 3D models for 3-axis CNC machining. 6 | 7 | 8 | ## Running 9 | 10 | Extract the archive or clone the repository. 11 | 12 | Graphical Interface: `pycam/run_gui.py` 13 | 14 | Scripted Toolpath Processing: `pycam/run_cli.py FLOW_SPECIFICATION_FILE` 15 | 16 | 17 | ## Resources 18 | 19 | See the [documentation](http://pycam.sourceforge.net/introduction/) for a short introduction. 20 | 21 | * [Website / Documentation](http://pycam.sf.net/) 22 | * [Getting started](http://pycam.sf.net/getting-started.md) 23 | * [FAQ](http://pycam.sf.net/faq.md) 24 | * [Video tutorials](http://vimeo.com/channels/pycam) 25 | * [Screenshots](http://pycam.sourceforge.net/screenshots/) 26 | * [Mailing lists](https://sourceforge.net/p/pycam/mailman/) 27 | 28 | 29 | ## Development 30 | 31 | * [Code Repository](https://github.com/SebKuzminsky/pycam) 32 | * [Issue Tracker](https://github.com/SebKuzminsky/pycam/issues) 33 | 34 | 35 | ## Contributors 36 | 37 | * Lode Leroy: initiated the project; developed the toolpath generation, 38 | collision detection, geometry, Tk interface, ... 39 | * Lars Kruse: GTK interface and many features 40 | * Paul: GCode stepping precision 41 | * Arthur Magill: distutils packaging 42 | * Sebastian Kuzminsky: debian packaging 43 | * Nicholas Humfrey: documentation, recovery of old sourceforge-wiki 44 | * Piers Titus van der Torren: documentation 45 | * Reuben Rissler: gtk3 migration 46 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: PyCAM 2 | site_description: Toolpath generator for 3-axis CNC machining 3 | site_url: http://pycam.sourceforge.net/ 4 | 5 | repo_url: https://github.com/SebKuzminsky/pycam 6 | repo_name: GitHub 7 | 8 | theme: readthedocs 9 | 10 | pages: 11 | - Home: index.md 12 | - Installation: 13 | - Installing: installation.md 14 | - Requirements: requirements.md 15 | - MacOS: installation-macos.md 16 | - Release Notes: release-notes.md 17 | - Introduction: 18 | - Introduction: introduction.md 19 | - Features: features.md 20 | - Screenshots: screenshots.md 21 | - Supported Formats: supported-formats.md 22 | - Getting Started: getting-started.md 23 | - Modeling with Inkscape SVG: modeling-inkscape-svg.md 24 | - Modeling with OpenSCAD DXF: modeling-openscad-dxf.md 25 | - FAQ: faq.md 26 | - Using the GUI: 27 | - Menu Items: menu-items.md 28 | - 3D View: 3d-view.md 29 | - Model Transformations: model-transformations.md 30 | - Engrave Fonts: engrave-fonts.md 31 | - Tool Types: tool-types.md 32 | - Process Settings: process-settings.md 33 | - Bounding Box: bounding-box.md 34 | - Task Settings: task-settings.md 35 | - Advanced Usage: 36 | - Keyboard Shortcuts: keyboard-shortcuts.md 37 | - Command-line Examples: cli-examples.md 38 | - Server Mode: server-mode.md 39 | - Parallel Processing: parallel-processing.md 40 | - Touch Off: touch-off.md 41 | - OpenGL Troubles: opengl-troubles.md 42 | - Developers Guide: 43 | - Developers Guide: developers-guide.md 44 | - Video Translations: video-translations.md 45 | - External: 46 | - Other Programs: other-programs.md 47 | - Articles mentioning PyCAM: articles.md 48 | -------------------------------------------------------------------------------- /docs/modeling-openscad-dxf.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | [OpenSCAD](http://openscad.org) is a parametric (non-interactive) 2D/3D 5 | modeller. The following hints refer to 2D modeling. 6 | 7 | 3D to 2D projection / Sectional drawing 8 | --------------------------------------- 9 | 10 | The following example code creates a sectional drawing of a sphere at a 11 | specific z-level: 12 | 13 | projection(cut=true) translate([0, 0, -3]) sphere(r=10); 14 | 15 | See the [OpenSCAD User 16 | Manual](http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/3D_to_2D_Projection) 17 | for details. 18 | 19 | 2D modeling 20 | ------------ 21 | 22 | OpenSCAD supports a variety of 2D primitives. See the [OpenSCAD User 23 | Manual](http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/2D_Primitives) 24 | for details. 25 | 26 | DXF Export 27 | ---------- 28 | 29 | OpenSCAD can export 2D models to DXF. 30 | 31 | Sadly there are currently several limitations: 32 | 33 | - The latest release (2010.05) creates single lines (instead of 34 | connected line segments). This makes it hard to manipulate the DXF 35 | file with [Inkscape](http://inkscape.org) or other vector graphic 36 | editors. The latest revision (development repository) fixed this 37 | issue. 38 | - Lines defining outlines and inner holes are currently drawn in the 39 | same direction (clockwise). Thus PyCAM can't distinguish between 40 | inner and outer lines for engraving offsets. You can reverse the 41 | inner lines with a vector graphics editor (e.g. Inkscape). 42 | 43 | Both of the above issues can be easily fixed with PyCAM's [Revise 44 | directions](model-transformations#Miscellaneous) operation. 45 | -------------------------------------------------------------------------------- /pycam/Test/test_stl_loader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2018 Ruslan Panasiuk 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import os 21 | 22 | import pycam.Test 23 | from pycam.Importers.STLImporter import import_model 24 | 25 | cwd = os.path.dirname(os.path.abspath(__file__)) 26 | 27 | ASSETS_DIR = 'assets' 28 | 29 | 30 | def path_to_asset(asset_name): 31 | """ 32 | Returns abs path for given `asset_name` 33 | :param asset_name: file name of the asset from 'Tests/assets' 34 | :returns: str - abs path to asset 35 | """ 36 | return os.path.join(cwd, ASSETS_DIR, asset_name) 37 | 38 | 39 | class TestSTLLoader(pycam.Test.PycamTestCase): 40 | """ 41 | Checks ability to load binary .stl files correctly 42 | """ 43 | 44 | def test_load_ascii_file(self): 45 | model = import_model(path_to_asset('cube_ascii.stl')) 46 | self.assertEqual(len(model), 12) 47 | 48 | def test_load_binary_file(self): 49 | model = import_model(path_to_asset('cube_binary.stl')) 50 | self.assertEqual(len(model), 12) 51 | -------------------------------------------------------------------------------- /docs/articles.md: -------------------------------------------------------------------------------- 1 | - 2 | -- this video shows how to turn a picture via Gimp, Inkscape and 3 | PyCAM into GCode 4 | - -- 5 | how to set up a FabLab in seven days 6 | - -- 7 | turn a puzzle-like shape into GCode 8 | - -- PyCAM installed by Ubuntu 9 | users (registered via popcon) 10 | - 11 | -- modeling chain description 12 | - (as 13 | [HTML](http://cr4.globalspec.com/blogentry/16801/OpenSource-CNC-From-CAD-to-FAB)) 14 | -- using PyCAM or Blender for toolpath generation 15 | - (nl) -- using 16 | PyCAM with a Mantis machine 17 | - (german) 18 | -- generating a gear wheel toolpath 19 | - -- using 20 | a Walther 2520 21 | - -- 22 | short review of some CAD/CAM packages 23 | - -- list of GCode generators at 24 | 25 | - - 26 | very concise howto 27 | - - very 28 | short article 29 | - -- presentation about 30 | CAM software and format converters (2009) 31 | -------------------------------------------------------------------------------- /pycam/Exporters/LinuxCNCToolExporter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2010 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import os 21 | 22 | 23 | class LinuxCNCToolExporter: 24 | 25 | def __init__(self, tools): 26 | """ tools are expected to be dictionaries containing the following keys: 27 | - id 28 | - radius 29 | - name 30 | """ 31 | self.tools = tools 32 | 33 | def get_tool_definition_string(self): 34 | result = [] 35 | tools = list(self.tools) 36 | tools.sort(key=lambda item: item["id"]) 37 | # result.append(self.HEADER_ROW) 38 | for tool in tools: 39 | # use an arbitrary length 40 | tool_length = tool["radius"] * 10 41 | line = "T%d P%d D%f Z-%f ;%s" % (tool["id"], tool["id"], 2 * tool["radius"], 42 | tool_length, tool["name"]) 43 | result.append(line) 44 | # add the dummy line for the "last" tool 45 | result.append("T99999 P99999 Z+0.100000 ;dummy tool") 46 | return os.linesep.join(result) 47 | -------------------------------------------------------------------------------- /pycam/Test/assets/cube_ascii.stl: -------------------------------------------------------------------------------- 1 | solid "Solid" 2 | facet normal 0 1 0 3 | outer loop 4 | vertex 0 10 10 5 | vertex 10 10 10 6 | vertex 0 10 0 7 | endloop 8 | endfacet 9 | facet normal 0 1 0 10 | outer loop 11 | vertex 0 10 0 12 | vertex 10 10 10 13 | vertex 10 10 0 14 | endloop 15 | endfacet 16 | facet normal 1 0 0 17 | outer loop 18 | vertex 10 10 10 19 | vertex 10 0 10 20 | vertex 10 10 0 21 | endloop 22 | endfacet 23 | facet normal 1 0 0 24 | outer loop 25 | vertex 10 10 0 26 | vertex 10 0 10 27 | vertex 10 0 0 28 | endloop 29 | endfacet 30 | facet normal -1 0 0 31 | outer loop 32 | vertex 0 0 10 33 | vertex 0 10 10 34 | vertex 0 0 0 35 | endloop 36 | endfacet 37 | facet normal -1 0 0 38 | outer loop 39 | vertex 0 0 0 40 | vertex 0 10 10 41 | vertex 0 10 0 42 | endloop 43 | endfacet 44 | facet normal 0 0 1 45 | outer loop 46 | vertex 10 10 10 47 | vertex 0 10 10 48 | vertex 10 0 10 49 | endloop 50 | endfacet 51 | facet normal 0 0 1 52 | outer loop 53 | vertex 10 0 10 54 | vertex 0 10 10 55 | vertex 0 0 10 56 | endloop 57 | endfacet 58 | facet normal 0 -1 0 59 | outer loop 60 | vertex 10 0 10 61 | vertex 0 0 10 62 | vertex 10 0 0 63 | endloop 64 | endfacet 65 | facet normal 0 -1 0 66 | outer loop 67 | vertex 10 0 0 68 | vertex 0 0 10 69 | vertex 0 0 0 70 | endloop 71 | endfacet 72 | facet normal 0 0 -1 73 | outer loop 74 | vertex 0 10 0 75 | vertex 10 10 0 76 | vertex 0 0 0 77 | endloop 78 | endfacet 79 | facet normal 0 0 -1 80 | outer loop 81 | vertex 0 0 0 82 | vertex 10 10 0 83 | vertex 10 0 0 84 | endloop 85 | endfacet 86 | endsolid 87 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ![PyCAM Logo](img/pycam-logo-600.png) 2 | 3 | PyCAM is a toolpath generator for 3-axis CNC machining. 4 | It loads 3D models in STL format or 2D contour models from DXF or SVG files. 5 | The resulting [G-Code](https://en.wikipedia.org/wiki/G-code) can be used 6 | with [LinuxCNC](http://www.linuxcnc.org/) or any other machine controller. 7 | 8 | PyCAM supports a wide range of toolpath strategies for 3D models and 2D contour models. 9 | Take a look at the [Features](features.md) page for a full list features. 10 | 11 | PyCAM runs on Linux, Windows and MacOS. It is free software licensed under the 12 | [GPL v3](https://www.gnu.org/licenses/gpl-3.0). 13 | 14 | ## Download 15 | 16 | Work on PyCAM has paused for five years between 2012 (v0.5.1) and 2017 (v0.6.0). 17 | Currently the code base is being modernized in order to make PyCAM run on recent operating systems 18 | and libraries. 19 | 20 | New releases are available from the GitHub releases page: 21 | 22 | 23 | The Debian package installs and runs on Debian Jessie and Stretch. The 24 | source code may work on other platforms, like Windows and Mac, but is untested. 25 | Before installing you might want to take a look at the [System requirements](requirements.md). 26 | 27 | Older releases are still available on the 28 | [SourceForge download page](https://sourceforge.net/projects/pycam/files/pycam/). 29 | 30 | ## Resources 31 | 32 | * [Development](https://github.com/SebKuzminsky/pycam) 33 | * [Getting started](getting-started.md) / [FAQ](faq.md) 34 | * [Video tutorials](http://vimeo.com/channels/pycam) / [Screenshots](screenshots.md) 35 | * [Mailing lists](https://sourceforge.net/p/pycam/mailman/) 36 | * [Issue Tracker](https://github.com/SebKuzminsky/pycam/issues) 37 | -------------------------------------------------------------------------------- /pycam/PathProcessors/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2008 Lode Leroy 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | 21 | class BasePathProcessor: 22 | 23 | def __init__(self): 24 | self.paths = [] 25 | 26 | def new_direction(self, direction): 27 | pass 28 | 29 | def end_direction(self): 30 | pass 31 | 32 | def finish(self): 33 | pass 34 | 35 | def sort_layered(self, upper_first=True): 36 | if upper_first: 37 | def compare_height(path1, path2): 38 | return path1.points[0][2] < path2.points[0][2] 39 | else: 40 | def compare_height(path1, path2): 41 | return path1.points[0][2] > path2.points[0][2] 42 | finished = False 43 | while not finished: 44 | index = 0 45 | finished = True 46 | while index < len(self.paths) - 1: 47 | current_path = self.paths[index] 48 | next_path = self.paths[index + 1] 49 | if compare_height(current_path, next_path): 50 | del self.paths[index] 51 | self.paths.insert(index + 1, current_path) 52 | finished = False 53 | index += 1 54 | -------------------------------------------------------------------------------- /docs/other-programs.md: -------------------------------------------------------------------------------- 1 | Alternative programs for generating G-Code 2 | ========================================== 3 | 4 | The following list contains only Free Software (GPL/BSD/...): 5 | 6 | - [dxf2gcode](http://code.google.com/p/dxf2gcode/): useful for 7 | engravings 8 | - [FreeCAD](https://sourceforge.net/apps/mediawiki/free-cad/): 3D CAD 9 | design program; G-Code generation partly integrated (2011) 10 | - [HeeksCNC](http://code.google.com/p/heekscnc/): add-on for 11 | [HeeksCAD](http://code.google.com/p/heekscad/); requires STEP files 12 | instead of STL; activity [partly 13 | abandoned](http://code.google.com/p/heekscad/) by its main 14 | author (2011) 15 | - [cam-occ](http://code.google.com/p/cam-occ/): based on solid models 16 | instead of trimeshs (no recent releases, but development seems to be 17 | active (2010)) 18 | - [monocam](http://code.google.com/p/monocam/): no official releases, 19 | yet; no recent development activities (2010) 20 | - [camvox](http://sourceforge.net/projects/camvox/): no official 21 | releases, yet; no recent development activities (2010) 22 | - [gcnccam](http://sourceforge.net/projects/gcnccam/): uses DXF as 23 | input; quite active 24 | - [CNC Code Generator](http://sourceforge.net/projects/cnccodegen/): 25 | java-based, recently not very active (2010) 26 | - [GCAM](http://gcam.js.cx): DXF or GERBER input; sparse documentation 27 | 28 | External software lists 29 | ======================= 30 | 31 | - [LinuxCNC Wiki](http://wiki.linuxcnc.org/cgi-bin/wiki.pl?Cam): 32 | exhaustive list of CAD/CAM/conversion software 33 | - [xtronics wiki](http://wiki.xtronics.com/index.php/CAD_CAM): broad 34 | overview of CAD/CAM software 35 | - [Simple G-Code generators](https://github.com/linuxcnc/simple-gcode-generators): 36 | a list of small scripts and simple interface for generating basic 37 | toolpaths for LinuxCNC 38 | -------------------------------------------------------------------------------- /pycam/Toolpath/Steps.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2010 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import collections 21 | 22 | from pycam.Toolpath import MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID, MOVE_ARC, MOVE_SAFETY, \ 23 | MACHINE_SETTING, COMMENT 24 | 25 | 26 | def get_step_class_by_action(action): 27 | return { 28 | MOVE_STRAIGHT: MoveStraight, 29 | MOVE_STRAIGHT_RAPID: MoveStraightRapid, 30 | MOVE_ARC: MoveArc, 31 | MOVE_SAFETY: MoveSafety, 32 | MACHINE_SETTING: MachineSetting, 33 | COMMENT: Comment, 34 | }[action] 35 | 36 | 37 | MoveClass = collections.namedtuple("Move", ("action", "position")) 38 | MachineSettingClass = collections.namedtuple("MachineSetting", ("action", "key", "value")) 39 | CommentClass = collections.namedtuple("Comment", ("action", "text")) 40 | 41 | 42 | MoveStraight = lambda position: MoveClass(MOVE_STRAIGHT, position) 43 | MoveStraightRapid = lambda position: MoveClass(MOVE_STRAIGHT_RAPID, position) 44 | MoveArc = lambda position: MoveClass(MOVE_ARC, position) 45 | MoveSafety = lambda: MoveClass(MOVE_SAFETY, None) 46 | MachineSetting = lambda key, value: MachineSettingClass(MACHINE_SETTING, key, value) 47 | Comment = lambda text: CommentClass(COMMENT, text) 48 | -------------------------------------------------------------------------------- /docs/video-translations.md: -------------------------------------------------------------------------------- 1 | Video tutorials are useful for presenting the various features of PyCAM 2 | to new users. 3 | 4 | The existing video tutorials can be found at 5 | [vimeo](http://vimeo.com/channels/158481). 6 | 7 | Providing these videos in different languages is quite simple - you just 8 | need to translate a small subtitles file. This will usually take around 9 | 15 minutes. 10 | 11 | Available videos 12 | ---------------- 13 | 14 | Video Subtitle files Languages 15 | ------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------- ----------- 16 | [Toolpaths for 2D models](http://vimeo.com/18254871) (incl. simulation) [download](http://pycam.svn.sourceforge.net/viewvc/pycam/video-tutorials/20101227-2D_Toolpaths_and_Simulation/) en, de 17 | [Using multi-layered 2D models](http://vimeo.com/21502122) [download](http://pycam.svn.sourceforge.net/viewvc/pycam/video-tutorials/20110324-Multi_Layered_2D_Model/) en, de 18 | 19 | How to translate 20 | ---------------- 21 | 22 | 1. download an existing subtitle file (preferably in English) 23 | 2. copy the file to `YOUR_LANGUAGE.srt` (e.g. `danish.srt`) 24 | 3. open the file with an editor that is capable of handling the UTF-8 25 | character set (almost every modern editor should suffice) 26 | 4. replace the English strings with your translations 27 | 5. store the file - BEWARE: specify the UTF-8 encoding, please! 28 | 6. send the file to the developer's mailing list: 29 | 30 | 31 | Afterwards a PyCAM developer will transcode a new video with your 32 | subtitles and upload it to PyCAM's video channel. 33 | 34 | Thanks for your contribution! 35 | -------------------------------------------------------------------------------- /pycam/Utils/rootsolver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2008 Lode Leroy 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | 21 | def find_root_subdivide(f, x0, x1, tolerance, scale): 22 | ymin = 0 23 | xmin = 0 24 | while x1 - x0 > tolerance: 25 | for i in range(scale): 26 | x = x1 + (i / scale) * (x1 - x0) 27 | y = f(x) 28 | abs_y = abs(y) 29 | if i == 0: 30 | ymin = abs_y 31 | xmin = x 32 | else: 33 | if abs_y < ymin: 34 | ymin = abs_y 35 | xmin = x 36 | x0 = xmin - 1 / scale 37 | x1 = xmin + 1 / scale 38 | scale /= 10 39 | return xmin 40 | 41 | 42 | def find_root_newton_raphson(f, df, x0, tolerance, maxiter): 43 | x = x0 44 | iter_count = 0 45 | while iter_count < maxiter: 46 | y = f(x) 47 | if y == 0: 48 | return x 49 | dy = df(x) 50 | if dy == 0: 51 | return None 52 | dx = y / dy 53 | x = x - dx 54 | if dx < tolerance: 55 | break 56 | iter_count += 1 57 | return x 58 | 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, scale=10.0) 62 | -------------------------------------------------------------------------------- /pycam/Test/test_svg_loader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2018 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import os 21 | 22 | from pycam.Importers.SVGDirectImporter import import_model 23 | import pycam.Test 24 | 25 | 26 | BASE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.path.pardir, os.path.pardir) 27 | SAMPLES_DIR = os.path.realpath(os.path.join(BASE_DIR, "samples")) 28 | 29 | 30 | class TestSVGLoader(pycam.Test.PycamTestCase): 31 | 32 | @staticmethod 33 | def _get_svg_filenames(path): 34 | for dirpath, dirnames, filenames in os.walk(path): 35 | yield from (os.path.join(dirpath, filename) for filename in filenames 36 | if filename.lower().endswith(".svg")) 37 | 38 | def test_load_sample_svg_files(self): 39 | test_count = 0 40 | for svg_filename in self._get_svg_filenames(SAMPLES_DIR): 41 | model = import_model(svg_filename) 42 | self.assertGreater(len(model), 0, 43 | "Too few imported polygons from {}".format(svg_filename)) 44 | test_count += 1 45 | self.assertEqual(test_count, 8) 46 | 47 | def test_polygon_import(self): 48 | model = import_model(os.path.join(SAMPLES_DIR, "polygons.svg")) 49 | self.assertEqual(len(model), 3) 50 | -------------------------------------------------------------------------------- /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 | 55 | -------------------------------------------------------------------------------- /pycam/Plugins/OpenGLViewTool.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2017 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import pycam.Plugins 21 | 22 | 23 | class OpenGLViewTool(pycam.Plugins.PluginBase): 24 | 25 | DEPENDS = ["OpenGLWindow"] 26 | CATEGORIES = ["Visualization", "OpenGL", "Tool"] 27 | 28 | def setup(self): 29 | self.core.register_event("visualize-items", self.draw_tool) 30 | self.core.get("register_display_item")("show_tool", "Show Tool", 70) 31 | self.core.get("register_color")("color_tool", "Tool", 50) 32 | self.core.emit_event("visual-item-updated") 33 | return True 34 | 35 | def teardown(self): 36 | self.core.unregister_event("visualize-items", self.draw_tool) 37 | self.core.get("unregister_display_item")("show_tool") 38 | self.core.get("unregister_color")("color_tool") 39 | self.core.emit_event("visual-item-updated") 40 | 41 | def draw_tool(self): 42 | if self.core.get("show_tool"): 43 | tool = self.core.get("current_tool") 44 | if tool is not None: 45 | color = self.core.get("color_tool") 46 | GL = self._GL 47 | GL.glColor4f(color["red"], color["green"], color["blue"], color["alpha"]) 48 | GL.glFinish() 49 | tool.to_opengl() 50 | -------------------------------------------------------------------------------- /pycam/Geometry/PointKdtree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2009 Lode Leroy 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | from pycam.Geometry import epsilon 21 | from pycam.Geometry.kdtree import Node, Kdtree 22 | 23 | 24 | class PointKdtree(Kdtree): 25 | 26 | __slots__ = ["_n", "tolerance"] 27 | 28 | def __init__(self, points=None, cutoff=5, cutoff_distance=0.5, tolerance=epsilon): 29 | if points is None: 30 | points = [] 31 | self._n = None 32 | self.tolerance = tolerance 33 | nodes = [] 34 | for p in points: 35 | n = Node(p, p) 36 | nodes.append(n) 37 | Kdtree.__init__(self, nodes, cutoff, cutoff_distance) 38 | 39 | def dist(self, n1, n2): 40 | dx = n1.bound[0]-n2.bound[0] 41 | dy = n1.bound[1]-n2.bound[1] 42 | dz = n1.bound[2]-n2.bound[2] 43 | return dx*dx+dy*dy+dz*dz 44 | 45 | def point(self, x, y, z): 46 | if self._n: 47 | n = self._n 48 | n.bound = (x, y, z) 49 | else: 50 | n = Node(None, (x, y, z)) 51 | (nn, dist) = self.nearest_neighbor(n, self.dist) 52 | if nn and (dist < self.tolerance): 53 | self._n = n 54 | return nn.obj 55 | else: 56 | n.obj = (x, y, z) 57 | self._n = None 58 | self.insert(n) 59 | return n.obj 60 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pycam/Geometry/Path.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2010 Lars Kruse 3 | Copyright 2008 Lode Leroy 4 | 5 | This file is part of PyCAM. 6 | 7 | PyCAM 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 | PyCAM 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 PyCAM. If not, see . 19 | """ 20 | 21 | from collections import namedtuple 22 | 23 | from pycam.Geometry import IDGenerator 24 | 25 | """ 26 | The points of a path are only used for describing coordinates. Thus we don't really need complete 27 | "Point" instances that consume a lot of memory. 28 | Since python 2.6 the "namedtuple" factory is available. 29 | This reduces the memory consumption of a toolpath down to 1/3. 30 | """ 31 | tuple_point = namedtuple("TuplePoint", "x y z") 32 | 33 | 34 | def get_point_object(point): 35 | return tuple_point(point[0], point[1], point[2]) 36 | 37 | 38 | class Path(IDGenerator): 39 | 40 | def __init__(self): 41 | super().__init__() 42 | self.top_join = None 43 | self.bot_join = None 44 | self.winding = 0 45 | self.points = [] 46 | 47 | def __repr__(self): 48 | text = "" 49 | text += "path %d: " % self.id 50 | first = True 51 | for point in self.points: 52 | if first: 53 | first = False 54 | else: 55 | text += "-" 56 | text += "%d(%g,%g,%g)" % (id(point), point[0], point[1], point[2]) 57 | return text 58 | 59 | def insert(self, index, point): 60 | self.points.insert(index, get_point_object(point)) 61 | 62 | def append(self, point): 63 | self.points.append(get_point_object(point)) 64 | 65 | def reverse(self): 66 | self.points.reverse() 67 | -------------------------------------------------------------------------------- /docs/keyboard-shortcuts.md: -------------------------------------------------------------------------------- 1 | Global 2 | ------ 3 | 4 | Shortcut | Description 5 | ----------------- | ------------------------------------------------------------------------ 6 | F1 | open the introduction page of PyCAM with a browser 7 | [CTRL] o | load model file 8 | [CTRL] s | save model file 9 | [CTRL] [Shift] s | save model file with a different name 10 | [CTRL] [Shift] e | export all toolpaths to a gcode file 11 | [CTRL] t | load task settings file 12 | [CTRL] p | open preferences dialog 13 | [CTRL] [Shift] v | toggle the 3D view window 14 | [CTRL] l | toggle the log window 15 | [CTRL] q | quit the program 16 | [CTRL] t | open the font dialog window 17 | [CTRL] z | undo the latest model operation (scale, shift, rotate) up to ten times 18 | 19 | Visualization window 20 | -------------------- 21 | 22 | Shortcut | Description 23 | ----------------------------------------------- | ------------------------------------------------------------ 24 | Left, Down, Up, Right | move the viewport 25 | h, j, k, l | same as *Left*, *Down*, *Up*, *Right* (vi-style behaviour) 26 | [Shift] Left, Down, Up, Right (or h, j, k, l) | rotate the view 27 | + / - | zoom in and out of the view 28 | i | toggle OpenGL ligthing 29 | m | toggle polygon and line mode 30 | s | toggle OpenGL shadows 31 | p | switch between perspective and orthogonal view 32 | 1 | reset view direction 33 | 2 | view from front 34 | 3 | view from back 35 | 4 | view from left 36 | 5 | view from right 37 | 6 | view from top 38 | 7 | view from bottom 39 | 40 | -------------------------------------------------------------------------------- /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.Geometry import Box3D, Point3D 9 | from pycam.Gui.Console import ConsoleProgressBar 10 | from pycam.Importers.STLImporter import import_model 11 | from pycam.PathGenerators.DropCutter import DropCutter 12 | from pycam.PathProcessors.PathAccumulator import PathAccumulator 13 | from pycam.Toolpath import Bounds 14 | from pycam.Toolpath.MotionGrid import get_fixed_grid 15 | from pycam.Utils.locations import get_data_file_location 16 | 17 | # Disable multi processing 18 | from pycam.Utils import threading 19 | threading.__multiprocessing = False 20 | 21 | """ Profile PyCAM doing several operations, print out the top 10 22 | (sorted by actual local runtime) methods. 23 | """ 24 | 25 | model = import_model(get_data_file_location(join('samples', 'pycam-textbox.stl'))) 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, Box3D(Point3D(model.minx-5, model.miny-5, model.minz), 37 | Point3D(model.maxx+5, model.maxy+5, model.maxz))) 38 | 39 | low, high = bounds.get_absolute_limits() 40 | line_distance = 2 * tool.radius * (1.0 - overlap) 41 | 42 | motion_grid = get_fixed_grid((low, high), layer_distance, 43 | line_distance, tool.radius / 4.0) 44 | path_generator.GenerateToolPath(tool, [model], motion_grid, minz=low[2], maxz=high[2], 45 | draw_callback=progress_bar.update) 46 | 47 | 48 | if __name__ == '__main__': 49 | print(model.minx, model.miny, model.maxx, model.maxy) 50 | start_time = time() 51 | cProfile.run('run_dropcutter()', 'dropcutter.pyprof') 52 | run_time = time() - start_time 53 | print('\nDropcutter took %f seconds' % run_time) 54 | p = pstats.Stats('dropcutter.pyprof') 55 | print('Top ten time-consuming functions:') 56 | p.sort_stats('time').print_stats(10) 57 | -------------------------------------------------------------------------------- /pycam/Importers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2008 Lode Leroy 3 | Copyright 2010 Lars Kruse 4 | 5 | This file is part of PyCAM. 6 | 7 | PyCAM 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 | PyCAM 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 PyCAM. If not, see . 19 | """ 20 | 21 | import collections 22 | 23 | from pycam.Utils import URIHandler 24 | import pycam.Utils.log 25 | 26 | 27 | _log = pycam.Utils.log.get_logger() 28 | 29 | 30 | DetectedFileType = collections.namedtuple("DetectedFileType", ("extension", "importer", "uri")) 31 | 32 | 33 | def detect_file_type(filename, quiet=False): 34 | # also accept URI input 35 | uri = URIHandler(filename) 36 | filename = uri.get_path() 37 | # check all listed importers 38 | # TODO: this should be done by evaluating the header of the file 39 | if filename.lower().endswith(".stl"): 40 | import pycam.Importers.STLImporter 41 | return DetectedFileType("stl", pycam.Importers.STLImporter.import_model, uri) 42 | elif filename.lower().endswith(".dxf"): 43 | import pycam.Importers.DXFImporter 44 | return DetectedFileType("dxf", pycam.Importers.DXFImporter.import_model, uri) 45 | elif filename.lower().endswith(".svg"): 46 | import pycam.Importers.SVGDirectImporter 47 | return DetectedFileType("svg", pycam.Importers.SVGDirectImporter.import_model, uri) 48 | elif filename.lower().endswith(".eps") \ 49 | or filename.lower().endswith(".ps"): 50 | import pycam.Importers.PSImporter 51 | return DetectedFileType("ps", pycam.Importers.PSImporter.import_model, uri) 52 | else: 53 | if not quiet: 54 | _log.error("Importers: Failed to detect the model type of '%s'. Is the file extension " 55 | "(stl/dxf/svg/eps/ps) correct?", filename) 56 | return None 57 | -------------------------------------------------------------------------------- /pycam/Exporters/STLExporter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2010 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import datetime 21 | import os 22 | 23 | from pycam import VERSION 24 | from pycam.Geometry.PointUtils import pnormalized 25 | 26 | 27 | class STLExporter: 28 | 29 | def __init__(self, model, name="model", created_by="pycam", linesep=None, **kwargs): 30 | self.model = model 31 | self.name = name 32 | self.created_by = created_by 33 | if linesep is None: 34 | self.linesep = os.linesep 35 | else: 36 | self.linesep = linesep 37 | 38 | def __str__(self): 39 | return self.linesep.join(self.get_output_lines) 40 | 41 | def write(self, stream): 42 | for line in self.get_output_lines(): 43 | stream.write(line) 44 | stream.write(self.linesep) 45 | 46 | def get_output_lines(self): 47 | date = datetime.date.today().isoformat() 48 | yield ("""solid "%s"; Produced by %s (v%s), %s""" 49 | % (self.name, self.created_by, VERSION, date)) 50 | for triangle in self.model.triangles(): 51 | norm = pnormalized(triangle.normal) 52 | yield "facet normal %f %f %f" % (norm[0], norm[1], norm[2]) 53 | yield " outer loop" 54 | # Triangle vertices are stored in clockwise order - thus we need 55 | # to reverse the order (STL expects counter-clockwise orientation). 56 | for point in (triangle.p1, triangle.p3, triangle.p2): 57 | yield " vertex %f %f %f" % (point[0], point[1], point[2]) 58 | yield " endloop" 59 | yield "endfacet" 60 | yield "endsolid" 61 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pycam/Plugins/PathPatterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2011 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | 21 | import pycam.Plugins 22 | import pycam.Toolpath.MotionGrid 23 | 24 | 25 | class PathPatternSpiral(pycam.Plugins.PluginBase): 26 | 27 | DEPENDS = ["ParameterGroupManager", "PathParamPattern", "PathParamMillingStyle", 28 | "PathParamSpiralDirection", "PathParamRoundedSpiralCorners"] 29 | CATEGORIES = ["Process", "Path pattern"] 30 | 31 | def setup(self): 32 | parameters = {"milling_style": pycam.Toolpath.MotionGrid.MillingStyle.IGNORE, 33 | "spiral_direction": None, 34 | "rounded_corners": False} 35 | self.core.get("register_parameter_set")("path_pattern", "spiral", "Spiral", None, 36 | parameters=parameters, weight=30) 37 | return True 38 | 39 | def teardown(self): 40 | self.core.get("unregister_parameter_set")("path_pattern", "spiral") 41 | 42 | 43 | class PathPatternGrid(pycam.Plugins.PluginBase): 44 | 45 | DEPENDS = ["ParameterGroupManager", "PathParamPattern", "PathParamMillingStyle", 46 | "PathParamGridDirection"] 47 | CATEGORIES = ["Process", "Path pattern"] 48 | 49 | def setup(self): 50 | parameters = {"milling_style": pycam.Toolpath.MotionGrid.MillingStyle.IGNORE, 51 | "grid_direction": pycam.Toolpath.MotionGrid.GridDirection.X} 52 | self.core.get("register_parameter_set")("path_pattern", "grid", "Grid", 53 | None, parameters=parameters, weight=10) 54 | return True 55 | 56 | def teardown(self): 57 | self.core.get("unregister_parameter_set")("path_pattern", "grid") 58 | -------------------------------------------------------------------------------- /pycam/Test/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2013 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | 21 | import unittest 22 | 23 | 24 | class PycamTestCase(unittest.TestCase): 25 | 26 | def _compare_vectors(self, v1, v2, max_deviance=0.000001): 27 | """ compare two vectors and return 'None' in case of success or an error message """ 28 | # provide readable error messages 29 | result_difference = "%s != %s" % (v1, v2) 30 | result_equal = None 31 | if v1 == v2: 32 | return result_equal 33 | if v1 is None or v2 is None: 34 | return False 35 | for index in range(3): 36 | if max_deviance < abs(v1[index] - v2[index]): 37 | return result_difference 38 | return result_equal 39 | 40 | def assert_vector_equal(self, v1, v2, msg=None): 41 | self.assertIsNone(self._compare_vectors(v1, v2), msg=msg) 42 | 43 | def assert_vector_not_equal(self, v1, v2, msg=None): 44 | self.assertIsNotNone(self._compare_vectors(v1, v2), msg=msg) 45 | 46 | def assert_collision_equal(self, collision1, collision2, msg=None): 47 | ccp1, cp1, d1 = collision1 48 | ccp2, cp2, d2 = collision2 49 | self.assert_vector_equal(ccp1, ccp2, msg=("Collisions differ ({} != {}) due to ccp" 50 | .format(collision1, collision2))) 51 | self.assert_vector_equal(cp1, cp2, msg=("Collisions differ ({} != {}) due to cp" 52 | .format(collision1, collision2))) 53 | self.assertAlmostEqual(d1, d2, msg=("Collisions differ ({} != {}) due to distance" 54 | .format(collision1, collision2))) 55 | 56 | 57 | main = unittest.main 58 | -------------------------------------------------------------------------------- /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/polygon5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /docs/model-transformations.md: -------------------------------------------------------------------------------- 1 | Model transformations 2 | ===================== 3 | 4 | PyCAM can help you to prepare the model for the final processing. 5 | 6 | ![Screenshot of the Model tab](img/model-transformations.png) 7 | 8 | Rotate, Mirror, Swap 9 | -------------------- 10 | 11 | Operation | Description 12 | -------- | ----------- 13 | **rotate** | rotate the model clockwise around a given axis 14 | **flip** | mirror the model against a given plane (xy, xz or yz) 15 | **swap** | exchange two axis of the coordinate system (e.g. exchange all x values with the corresponding y values) 16 | 17 | Scale 18 | ----- 19 | 20 | Operation | Description 21 | -------- | ----------- 22 | **scale** | resize the complete model proportionally with a given factor via an input control (as a percent value). An input of 200% doubles the size of the model. The location center of the model will be preserved. 23 | **fit dimension** | scale one axis to a given value (e.g. if the unit size of your modeling software is not metric). By default all axes are scaled proportionally. This can be changed via the corresponding checkbox. 24 | 25 | Move 26 | ---- 27 | 28 | Operation | Description 29 | -------- | ----------- 30 | **move to origin** | afterwards the model should start at the center of the coordinate system pointing along the posive values of the three axes. 31 | **shift** | move the model along the three axes by the distances given in the three separate input controls. Negative or zero values are allowed. 32 | 33 | Miscellaneous 34 | ------------- 35 | 36 | Buttons referring to 2D operations are not visible if a 3D model is 37 | currently loaded (and vice versa). 38 | 39 | Operation | Description 40 | -------- | ----------- 41 | **Toggle direction** | reverse the direction of all lines of a 2D model 42 | **Revise direction** | First try to merge open polygons regardless of their directions. Secondly analyse the inside/outside relationships of all closed polygons in a 2D model. The direction of polygons with an unsuitable winding state is reversed. This usually fixes inconsistent winding combinations created by DXF/SVG export programs. 43 | **Extrude** | add a third dimension to a 2D model. The following parameters of the slope of the edges are configurable: shape, precision, height and width. ([Read more](http://fab.senselab.org/node/227)) 44 | **2D Projection** | cut a 3D model at z=0. The resulting contour polygons define the new 2D model. The contour of the bottom of the model is used if the model is completely above or below z=0. 45 | **Inch → mm** | scale the model by the factor 25.4 46 | **mm → Inch** | scale the model size down with the divider 25.4 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /share/ui/opengl_view_grid.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | False 8 | 3 9 | 10 | 11 | True 12 | False 13 | Major grid: 14 | 15 | 16 | False 17 | True 18 | 0 19 | 20 | 21 | 22 | 23 | True 24 | False 25 | 100mm 26 | 27 | 28 | False 29 | True 30 | 1 31 | 32 | 33 | 34 | 35 | True 36 | False 37 | vertical 38 | 39 | 40 | False 41 | True 42 | 3 43 | 2 44 | 45 | 46 | 47 | 48 | True 49 | False 50 | Minor grid: 51 | 52 | 53 | False 54 | True 55 | 3 56 | 57 | 58 | 59 | 60 | True 61 | False 62 | 20mm 63 | 64 | 65 | False 66 | True 67 | 4 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /docs/requirements.md: -------------------------------------------------------------------------------- 1 | System requirements for PyCAM 2 | ============================= 3 | 4 | PyCAM currently runs on Unix/Linux. 5 | 6 | Older releases of PyCAM were also running on Windows and MacOS ([via 7 | MacPorts](http://sourceforge.net/projects/pycam/forums/forum/860183/topic/3800091)). 8 | 9 | Please document your experiences here, if you successfully used PyCAM 10 | with other operating systems. 11 | 12 | Linux 13 | ----- 14 | 15 | Install the following packages with your package manager (see below): 16 | 17 | - **python3** 18 | - **python3-gi** 19 | - **python3-opengl** 20 | - **gir1.2-gtk-3.0** (GTK: at least v3.16) 21 | 22 | If your distribution does not ship GTK v3.16 or newer (e.g. Debian Jessie contains only v3.14), 23 | then you need to use an older release of PyCAM. The older releases (e.g. v0.6.x) depend on Python2 24 | and GTK2 - thus they should work well even with rather old distributions. 25 | 26 | ### Debian / Ubuntu 27 | 28 | Run the following command in a *root* terminal: 29 | 30 | apt-get install python3-gi python3-opengl gir1.2-gtk-3.0 31 | 32 | ### OpenSuSE 33 | 34 | Run the following command in a *root* terminal: 35 | 36 | zypper install python3-gi python3-opengl 37 | 38 | ### Fedora 39 | 40 | Run the following command in a *root* terminal: 41 | 42 | yum install python3-gi python3-opengl 43 | 44 | Windows 45 | ------- 46 | 47 | The latest releases do not run under Windows. 48 | 49 | You may want to use the standalone executable of [v0.5.1 (the latest release supporting 50 | Windows)](https://sourceforge.net/projects/pycam/files/pycam/0.5.1/). This old version is not 51 | maintained anymore - but maybe you are lucky and it just works for you. 52 | 53 | 54 | MacOS 55 | ----- 56 | 57 | Please take a look at [Installation MacOS](installation-macos.md) 58 | for the details of installing PyCAM's requirements via 59 | [MacPorts](http://www.macports.org/). 60 | 61 | 62 | Optional external programs 63 | ========================== 64 | 65 | Some features of PyCAM require additional external programs. 66 | 67 | 68 | SVG/PS/EPS import 69 | ----------------- 70 | 71 | PyCAM supports only STL and DXF files natively. Native SVG support is available 72 | since PyCAM v0.7. For previous versions you need to install external programs 73 | for the conversions of other file formats. 74 | 75 | ### Debian / Ubuntu 76 | 77 | apt-get install inkscape pstoedit 78 | 79 | ### OpenSuSE 80 | 81 | zypper install inkscape pstoedit 82 | 83 | ### Fedora 84 | 85 | yum install inkscape pstoedit 86 | 87 | ### Windows 88 | 89 | Download and install the following programs: 90 | 91 | * inkscape: 92 | * pstoedit: 93 | * ghostscript: 94 | 95 | *Hint: there is no need to install GraphicMagick - even though pstoedit 96 | suggests it.* 97 | -------------------------------------------------------------------------------- /pycam/Plugins/ModelPlaneMirror.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2011 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | 21 | import pycam.Plugins 22 | 23 | 24 | class ModelPlaneMirror(pycam.Plugins.PluginBase): 25 | 26 | UI_FILE = "model_plane_mirror.ui" 27 | DEPENDS = ["Models"] 28 | CATEGORIES = ["Model"] 29 | 30 | def setup(self): 31 | if self.gui: 32 | mirror_box = self.gui.get_object("ModelMirrorBox") 33 | mirror_box.unparent() 34 | self.core.register_ui("model_handling", "Mirror", mirror_box, 0) 35 | self._gtk_handlers = ((self.gui.get_object("PlaneMirrorButton"), "clicked", 36 | self._plane_mirror), ) 37 | self._event_handlers = (("model-selection-changed", self._update_plane_widgets), ) 38 | self.register_gtk_handlers(self._gtk_handlers) 39 | self.register_event_handlers(self._event_handlers) 40 | self._update_plane_widgets() 41 | return True 42 | 43 | def teardown(self): 44 | if self.gui: 45 | self.unregister_event_handlers(self._event_handlers) 46 | self.unregister_gtk_handlers(self._gtk_handlers) 47 | 48 | def _update_plane_widgets(self): 49 | plane_widget = self.gui.get_object("ModelMirrorBox") 50 | if self.core.get("models").get_selected(): 51 | plane_widget.show() 52 | else: 53 | plane_widget.hide() 54 | 55 | def _plane_mirror(self, widget=None): 56 | models = self.core.get("models").get_selected() 57 | if not models: 58 | return 59 | for plane, matrix in (("XY", [[1, 0, 0], [0, 1, 0], [0, 0, -1]]), 60 | ("XZ", [[1, 0, 0], [0, -1, 0], [0, 0, 1]]), 61 | ("YZ", [[-1, 0, 0], [0, 1, 0], [0, 0, 1]])): 62 | if self.gui.get_object("MirrorPlane%s" % plane).get_active(): 63 | break 64 | else: 65 | assert False, "No mirror plane selected" 66 | for model in models: 67 | model.extend_value("transformations", 68 | [{"action": "multiply_matrix", "matrix": matrix}]) 69 | -------------------------------------------------------------------------------- /pycam/Plugins/ModelSwapAxes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2011 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | 21 | import pycam.Plugins 22 | 23 | 24 | class ModelSwapAxes(pycam.Plugins.PluginBase): 25 | 26 | UI_FILE = "model_swap_axes.ui" 27 | DEPENDS = ["Models"] 28 | CATEGORIES = ["Model"] 29 | 30 | def setup(self): 31 | if self.gui: 32 | swap_box = self.gui.get_object("ModelSwapBox") 33 | swap_box.unparent() 34 | self.core.register_ui("model_handling", "Swap axes", swap_box, 0) 35 | self._gtk_handlers = ((self.gui.get_object("SwapAxesButton"), "clicked", 36 | self._swap_axes), ) 37 | self._event_handlers = (("model-selection-changed", self._update_controls), ) 38 | self.register_gtk_handlers(self._gtk_handlers) 39 | self.register_event_handlers(self._event_handlers) 40 | self._update_controls() 41 | return True 42 | 43 | def teardown(self): 44 | if self.gui: 45 | self.unregister_event_handlers(self._event_handlers) 46 | self.unregister_gtk_handlers(self._gtk_handlers) 47 | self.core.unregister_ui("model_handling", self.gui.get_object("ModelSwapBox")) 48 | 49 | def _update_controls(self): 50 | box = self.gui.get_object("ModelSwapBox") 51 | if self.core.get("models").get_selected(): 52 | box.show() 53 | else: 54 | box.hide() 55 | 56 | def _swap_axes(self, widget=None): 57 | models = self.core.get("models").get_selected() 58 | if not models: 59 | return 60 | for axes, matrix in (("XY", [[0, 1, 0], [1, 0, 0], [0, 0, 1]]), 61 | ("XZ", [[0, 0, 1], [0, 1, 0], [1, 0, 0]]), 62 | ("YZ", [[1, 0, 0], [0, 0, 1], [0, 1, 0]])): 63 | if self.gui.get_object("SwapAxes%s" % axes).get_active(): 64 | break 65 | else: 66 | assert False, "No axis selected" 67 | for model in models: 68 | model.extend_value("transformations", 69 | [{"action": "multiply_matrix", "matrix": matrix}]) 70 | -------------------------------------------------------------------------------- /pycam/PathProcessors/PolygonCutter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2010 Lars Kruse 3 | Copyright 2008 Lode Leroy 4 | 5 | This file is part of PyCAM. 6 | 7 | PyCAM 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 | PyCAM 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 PyCAM. If not, see . 19 | """ 20 | 21 | from pycam.Geometry.Path import Path 22 | from pycam.Geometry.PolygonExtractor import PolygonExtractor 23 | import pycam.PathProcessors 24 | from pycam.Toolpath import simplify_toolpath 25 | 26 | 27 | class PolygonCutter(pycam.PathProcessors.BasePathProcessor): 28 | def __init__(self, reverse=False): 29 | super().__init__() 30 | self.curr_path = None 31 | self.scanline = None 32 | self.polygon_extractor = PolygonExtractor(PolygonExtractor.MONOTONE) 33 | self.reverse = reverse 34 | 35 | def append(self, point): 36 | self.polygon_extractor.append(point) 37 | 38 | def new_direction(self, direction): 39 | self.polygon_extractor.new_direction(direction) 40 | 41 | def end_direction(self): 42 | self.polygon_extractor.end_direction() 43 | 44 | def new_scanline(self): 45 | self.polygon_extractor.new_scanline() 46 | 47 | def end_scanline(self): 48 | self.polygon_extractor.end_scanline() 49 | 50 | def finish(self): 51 | self.polygon_extractor.finish() 52 | paths = [] 53 | source_paths = [] 54 | if self.polygon_extractor.hor_path_list: 55 | source_paths.extend(self.polygon_extractor.hor_path_list) 56 | if self.polygon_extractor.ver_path_list: 57 | source_paths.extend(self.polygon_extractor.ver_path_list) 58 | for path in source_paths: 59 | points = path.points 60 | for i in range((len(points) + 1) // 2): 61 | new_path = Path() 62 | if i % 2 == 0: 63 | new_path.append(points[i]) 64 | new_path.append(points[-i-1]) 65 | else: 66 | new_path.append(points[-i-1]) 67 | new_path.append(points[i]) 68 | paths.append(new_path) 69 | if paths: 70 | for path in paths: 71 | simplify_toolpath(path) 72 | if self.reverse: 73 | path.reverse() 74 | self.paths.extend(paths) 75 | self.sort_layered() 76 | -------------------------------------------------------------------------------- /pycam/PathGenerators/EngraveCutter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2010 Lars Kruse 3 | Copyright 2008-2009 Lode Leroy 4 | 5 | This file is part of PyCAM. 6 | 7 | PyCAM 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 | PyCAM 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 PyCAM. If not, see . 19 | """ 20 | 21 | import pycam.Utils.log 22 | 23 | log = pycam.Utils.log.get_logger() 24 | 25 | 26 | class EngraveCutter: 27 | 28 | def generate_toolpath(self, cutter, models, motion_grid, minz=None, maxz=None, 29 | draw_callback=None): 30 | quit_requested = False 31 | 32 | model = pycam.Geometry.Model.get_combined_model(models) 33 | 34 | if draw_callback: 35 | draw_callback(text="Engrave: optimizing polygon order") 36 | 37 | # resolve the generator 38 | motion_grid = list(motion_grid) 39 | num_of_layers = len(motion_grid) 40 | 41 | push_layers = motion_grid[:-1] 42 | push_generator = pycam.PathGenerators.PushCutter.PushCutter() 43 | current_layer = 0 44 | push_moves = [] 45 | for push_layer in push_layers: 46 | # update the progress bar and check, if we should cancel the process 47 | if draw_callback and draw_callback( 48 | text="Engrave: processing layer %d/%d" % (current_layer + 1, num_of_layers)): 49 | # cancel immediately 50 | quit_requested = True 51 | break 52 | # no callback: otherwise the status text gets lost 53 | push_moves.extend(push_generator.generate_toolpath(cutter, [model], [push_layer])) 54 | if draw_callback and draw_callback(): 55 | # cancel requested 56 | quit_requested = True 57 | break 58 | current_layer += 1 59 | 60 | if quit_requested: 61 | return push_moves 62 | 63 | drop_generator = pycam.PathGenerators.DropCutter.DropCutter() 64 | drop_layers = motion_grid[-1:] 65 | if draw_callback: 66 | draw_callback( 67 | text="Engrave: processing layer %d/%d" % (current_layer + 1, num_of_layers)) 68 | drop_moves = drop_generator.generate_toolpath(cutter, [model], drop_layers, minz=minz, 69 | maxz=maxz, draw_callback=draw_callback) 70 | return push_moves + drop_moves 71 | -------------------------------------------------------------------------------- /pycam/Plugins/Units.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2011 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import pycam.Plugins 21 | 22 | 23 | class Units(pycam.Plugins.PluginBase): 24 | 25 | UI_FILE = "units.ui" 26 | CATEGORIES = ["System"] 27 | 28 | def setup(self): 29 | self._last_unit = "mm" 30 | if self.gui: 31 | self._gtk_handlers = [] 32 | unit_pref_box = self.gui.get_object("UnitPrefBox") 33 | unit_pref_box.unparent() 34 | self.core.register_ui("preferences_general", "Units", unit_pref_box, 20) 35 | # unit control (mm/inch) 36 | unit_field = self.gui.get_object("unit_control") 37 | self._gtk_handlers.append((unit_field, "changed", self.change_unit_init)) 38 | 39 | def set_unit(text): 40 | unit_field.set_active(0 if text == "mm" else 1) 41 | self._last_unit = text 42 | 43 | def get_unit_text(): 44 | model = unit_field.get_model() 45 | if model: 46 | return model[unit_field.get_active()][0] 47 | else: 48 | return self._last_unit 49 | 50 | self.core.add_item("unit", get_unit_text, set_unit) 51 | # other plugins should use "unit_string" for human readable output 52 | self.core.add_item("unit_string", get_unit_text) 53 | self.register_gtk_handlers(self._gtk_handlers) 54 | self.register_state_item("settings/unit", lambda: self.core.get("unit"), 55 | lambda value: self.core.set("unit", value)) 56 | return True 57 | 58 | def teardown(self): 59 | if self.gui: 60 | self.unregister_gtk_handlers(self._gtk_handlers) 61 | self.core.unregister_ui("preferences_general", self.gui.get_object("UnitPrefBox")) 62 | # TODO: reset setting "unit" back to a default value? 63 | self.clear_state_items() 64 | 65 | def change_unit_init(self, widget=None): 66 | # update the "_last_unit" attribute 67 | model = widget.get_model() 68 | self._last_unit = model[widget.get_active()][0] 69 | # redraw the model 70 | self.core.emit_event("model-change-after") 71 | -------------------------------------------------------------------------------- /pycam/run_cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | 4 | Copyright 2017 Lars Kruse 5 | 6 | This file is part of PyCAM. 7 | 8 | PyCAM is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | PyCAM is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with PyCAM. If not, see . 20 | """ 21 | 22 | import argparse 23 | import logging 24 | import os 25 | import sys 26 | 27 | try: 28 | from pycam import VERSION 29 | except ImportError: 30 | # running locally (without a proper PYTHONPATH) requires manual intervention 31 | sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), 32 | os.pardir))) 33 | from pycam import VERSION 34 | 35 | import pycam.errors 36 | from pycam.Flow.parser import parse_yaml 37 | import pycam.Utils 38 | import pycam.Utils.log 39 | import pycam.workspace.data_models 40 | 41 | 42 | _log = pycam.Utils.log.get_logger() 43 | 44 | LOG_LEVELS = {"debug": logging.DEBUG, 45 | "info": logging.INFO, 46 | "warning": logging.WARNING, 47 | "error": logging.ERROR, } 48 | 49 | 50 | def get_args(): 51 | parser = argparse.ArgumentParser(prog="PyCAM", description="scriptable PyCAM processing flow", 52 | epilog="PyCAM website: https://github.com/SebKuzminsky/pycam") 53 | parser.add_argument("--log-level", choices=LOG_LEVELS.keys(), default="warning", 54 | help="choose the verbosity of log messages") 55 | parser.add_argument("sources", metavar="FLOW_SPEC", type=argparse.FileType('r'), nargs="+", 56 | help="processing flow description files in yaml format") 57 | parser.add_argument("--version", action="version", version="%(prog)s {}".format(VERSION)) 58 | return parser.parse_args() 59 | 60 | 61 | def main_func(): 62 | args = get_args() 63 | _log.setLevel(LOG_LEVELS[args.log_level]) 64 | for fname in args.sources: 65 | try: 66 | parse_yaml(fname) 67 | except pycam.errors.PycamBaseException as exc: 68 | print("Flow description parse failure ({}): {}".format(fname, exc), file=sys.stderr) 69 | sys.exit(1) 70 | pycam.Utils.set_application_key("pycam-cli") 71 | for export in pycam.workspace.data_models.Export.get_collection(): 72 | export.run_export() 73 | 74 | 75 | if __name__ == "__main__": 76 | main_func() 77 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pycam/PathProcessors/ContourCutter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2008-2010 Lode Leroy 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | from pycam.Geometry.PolygonExtractor import PolygonExtractor 21 | from pycam.Geometry.PointUtils import pdot, psub 22 | import pycam.PathProcessors 23 | from pycam.Toolpath import simplify_toolpath 24 | 25 | 26 | class ContourCutter(pycam.PathProcessors.BasePathProcessor): 27 | def __init__(self): 28 | super().__init__() 29 | self.curr_path = None 30 | self.scanline = None 31 | self.polygon_extractor = None 32 | self.points = [] 33 | self.__forward = (1, 1, 0) 34 | 35 | def append(self, point): 36 | # Sort the points in positive x/y direction - otherwise the 37 | # PolygonExtractor breaks. 38 | if self.points and (pdot(psub(point, self.points[0]), self.__forward) < 0): 39 | self.points.insert(0, point) 40 | else: 41 | self.points.append(point) 42 | 43 | def new_direction(self, direction): 44 | if self.polygon_extractor is None: 45 | self.polygon_extractor = PolygonExtractor(PolygonExtractor.CONTOUR) 46 | 47 | self.polygon_extractor.new_direction(direction) 48 | 49 | def end_direction(self): 50 | self.polygon_extractor.end_direction() 51 | 52 | def new_scanline(self): 53 | self.polygon_extractor.new_scanline() 54 | self.points = [] 55 | 56 | def end_scanline(self): 57 | for i in range(1, len(self.points) - 1): 58 | self.polygon_extractor.append(self.points[i]) 59 | self.polygon_extractor.end_scanline() 60 | 61 | def finish(self): 62 | self.polygon_extractor.finish() 63 | if self.polygon_extractor.merge_path_list: 64 | paths = self.polygon_extractor.merge_path_list 65 | elif self.polygon_extractor.hor_path_list: 66 | paths = self.polygon_extractor.hor_path_list 67 | else: 68 | paths = self.polygon_extractor.ver_path_list 69 | if paths: 70 | for path in paths: 71 | path.append(path.points[0]) 72 | simplify_toolpath(path) 73 | if paths: 74 | self.paths.extend(paths) 75 | self.sort_layered() 76 | self.polygon_extractor = None 77 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/menu-items.md: -------------------------------------------------------------------------------- 1 | File menu 2 | --------- 3 | 4 | ![Screenshot of the File Menu](img/menu-file.png) 5 | 6 | **Open Model ...** Choose a model file (STL / SVG / PS / DXF) via a dialog. The currently active model is replaced with the new one. 7 | 8 | **Open Recent** Choose a model file from the list of recently used ones (the list also contains files used by other applications). 9 | 10 | **Save Model** Save the currently active model to its original location. This item is disabled if the model was loaded from a remote location (e.g. via http) or it the file is not writeable. No confirmation is requested. 11 | 12 | **Save Model as...** Store the currently active model under a new filename. A file chooser dialog will pop up. 13 | 14 | **Export visible toolpaths ...** Store all currently visible toolpaths in a single GCode file. A file chooser dialog will pop up. The order of toolpaths is important. See the Toolpaths tab for the list of all generated toolpaths and for their visible state. 15 | 16 | **Export all toolpaths ...** Store _all_ generated toolpaths in a single GCode file. A file chooser dialog will pop up. The order of toolpaths is important. See the Toolpaths tab for the list of all generated toolpaths. 17 | 18 | **Quit** Request PyCAM to quit. This may take some seconds if you are connected to remote [processing servers](server-mode.md). 19 | 20 | Edit menu 21 | --------- 22 | 23 | ![Screenshot of the Edit Menu](img/menu-edit.png) 24 | 25 | **Undo latest model change** Reverse the latest operation that changed the model. This includes all transformations (scale, rotate, flip, swap, shift) as well as drastic changes (e.g. opening another model or 3D-to-2D projection). Changes regarding the tasks settings (e.g. cutter size) are ignored. 26 | 27 | **Copy** Copy a representation of the current model to the clipboard. Other programs (e.g. Inkscape) can access the model data from the clipboard. 3D models are exported as STL data (ascii). 2D models are represented as plain SVG data. 28 | 29 | **Paste** Read model data from the clipboard and replace the currently active model. 3D models are expected as STL (binary or ascii) or DXF data. 2D models can be transferred as SVG, PS or DXF files. 30 | 31 | PyCAM announces and accepts the following target types via the 32 | clipboard: 33 | 34 | Target type | Data 35 | ------------------------ | ---------- 36 | application/sla | STL data 37 | application/postscript | PS / EPS 38 | image/svg+xml | SVG 39 | image/x-inkscape-svg | SVG 40 | image/vnd.dxf | DXF 41 | 42 | Settings menu 43 | ------------- 44 | 45 | ![Screenshot of the Settings Menu](img/menu-settings.png) 46 | 47 | Extras menu 48 | ----------- 49 | 50 | ![Screenshot of the Extras Menu](img/menu-extras.png) 51 | 52 | Windows menu 53 | ------------ 54 | 55 | ![Screenshot of the Windows Menu](img/menu-windows.png) 56 | 57 | Help menu 58 | --------- 59 | 60 | ![Screenshot of the Help Menu](img/menu-help.png) 61 | -------------------------------------------------------------------------------- /pycam/Plugins/ModelPolygons.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2011 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | 21 | import pycam.Plugins 22 | 23 | 24 | class ModelPolygons(pycam.Plugins.PluginBase): 25 | 26 | UI_FILE = "model_polygons.ui" 27 | DEPENDS = ["Models"] 28 | CATEGORIES = ["Model"] 29 | 30 | def setup(self): 31 | if self.gui: 32 | polygon_frame = self.gui.get_object("ModelPolygonFrame") 33 | polygon_frame.unparent() 34 | self.core.register_ui("model_handling", "Polygons", polygon_frame, 0) 35 | self._gtk_handlers = ( 36 | (self.gui.get_object("ToggleModelDirectionButton"), 37 | "clicked", self._adjust_polygons, "toggle_polygon_directions"), 38 | (self.gui.get_object("DirectionsGuessButton"), 39 | "clicked", self._adjust_polygons, "revise_polygon_directions")) 40 | self._event_handlers = ( 41 | ("model-change-after", self._update_polygon_controls), 42 | ("model-selection-changed", self._update_polygon_controls)) 43 | self.register_gtk_handlers(self._gtk_handlers) 44 | self.register_event_handlers(self._event_handlers) 45 | self._update_polygon_controls() 46 | return True 47 | 48 | def teardown(self): 49 | if self.gui: 50 | self.unregister_event_handlers(self._event_handlers) 51 | self.unregister_gtk_handlers(self._gtk_handlers) 52 | self.core.unregister_ui("model_handling", self.gui.get_object("ModelPolygonFrame")) 53 | 54 | def _get_polygon_models(self): 55 | models = [] 56 | for model in self.core.get("models").get_selected(): 57 | if model and hasattr(model.get_model(), "reverse_directions"): 58 | models.append(model) 59 | return models 60 | 61 | def _update_polygon_controls(self): 62 | models = self._get_polygon_models() 63 | frame = self.gui.get_object("ModelPolygonFrame") 64 | if models: 65 | frame.show() 66 | else: 67 | frame.hide() 68 | 69 | def _adjust_polygons(self, widget=None, action=None): 70 | models = self._get_polygon_models() 71 | for model in models: 72 | model.extend_value("transformations", [{"action": action}]) 73 | -------------------------------------------------------------------------------- /pycam/Importers/TestModel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2008-2009 Lode Leroy 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | from pycam.Geometry.Triangle import Triangle 21 | from pycam.Geometry.Line import Line 22 | from pycam.Geometry.Model import Model 23 | 24 | 25 | def get_test_model(): 26 | points = [] 27 | points.append((-2, 1, 4)) 28 | points.append((2, 1, 4)) 29 | points.append((0, -2, 4)) 30 | points.append((-5, 2, 2)) 31 | points.append((-1, 3, 2)) 32 | points.append((5, 2, 2)) 33 | points.append((4, -1, 2)) 34 | points.append((2, -4, 2)) 35 | points.append((-2, -4, 2)) 36 | points.append((-3, -2, 2)) 37 | 38 | lines = [] 39 | lines.append(Line(points[0], points[1])) 40 | lines.append(Line(points[1], points[2])) 41 | lines.append(Line(points[2], points[0])) 42 | lines.append(Line(points[0], points[3])) 43 | lines.append(Line(points[3], points[4])) 44 | lines.append(Line(points[4], points[0])) 45 | lines.append(Line(points[4], points[1])) 46 | lines.append(Line(points[4], points[5])) 47 | lines.append(Line(points[5], points[1])) 48 | lines.append(Line(points[5], points[6])) 49 | lines.append(Line(points[6], points[1])) 50 | lines.append(Line(points[6], points[2])) 51 | lines.append(Line(points[6], points[7])) 52 | lines.append(Line(points[7], points[2])) 53 | lines.append(Line(points[7], points[8])) 54 | lines.append(Line(points[8], points[2])) 55 | lines.append(Line(points[8], points[9])) 56 | lines.append(Line(points[9], points[2])) 57 | lines.append(Line(points[9], points[0])) 58 | lines.append(Line(points[9], points[3])) 59 | 60 | model = Model() 61 | for p1, p2, p3, l1, l2, l3 in ((0, 1, 2, 0, 1, 2), 62 | (0, 3, 4, 3, 4, 5), 63 | (0, 4, 1, 5, 6, 0), 64 | (1, 4, 5, 6, 7, 8), 65 | (1, 5, 6, 8, 9, 10), 66 | (1, 6, 2, 10, 11, 1), 67 | (2, 6, 7, 11, 12, 13), 68 | (2, 7, 8, 13, 14, 15), 69 | (2, 8, 9, 15, 16, 17), 70 | (2, 9, 0, 17, 18, 2), 71 | (0, 9, 3, 18, 19, 3)): 72 | model.append(Triangle(points[p1], points[p2], points[p3])) 73 | return model 74 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Dependencies of the graphical interface 2 | 3 | ## Windows 4 | 5 | `TODO:` Current Windows installation instructions 6 | 7 | ## Unix 8 | 9 | ### Installation 10 | 11 | Install the following packages with your package manager: 12 | 13 | ``` 14 | python3 15 | python3-gi 16 | python3-opengl 17 | python3-yaml 18 | python3-svg.path 19 | gir1.2-gtk-3.0 20 | ``` 21 | 22 | On a Debian or Ubuntu system, you would just type the following: 23 | ```bash 24 | sudo apt install python3-gi python3-opengl python3-yaml python3-svg.path gir1.2-gtk-3.0 25 | ``` 26 | Please note that you need to enable the `universe` repository in Ubuntu. 27 | 28 | ### Run with Docker 29 | 30 | If you have difficulty with the installation, you can run the application from Docker. 31 | 32 | The `docker run` command will mount your personal Documents folder to `/root/Documents` so that you 33 | can access your files. 34 | 35 | ```bash 36 | sudo docker build -t pycam/pycam . 37 | sudo docker run -it \ 38 | -v ~/Documents:/root/Documents \ 39 | -v ~/.Xauthority:/root/.Xauthority \ 40 | -e DISPLAY \ 41 | --net=host \ 42 | pycam/pycam 43 | ``` 44 | 45 | ## macOS 46 | 47 | ### Installation 48 | 49 | 1\. Install Homebrew if it has not been installed: 50 | ```bash 51 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 52 | ``` 53 | 54 | 2\. Install the dependencies (currently only for Python2): 55 | TODO: adjust for Python3 56 | ```bash 57 | brew install gtk+3 pygobject3 58 | pip install pygobject enum34 59 | ``` 60 | 61 | ### Run with Docker 62 | 63 | If you have difficulty with the installation, you can run the application from Docker. 64 | 65 | 1\. Make sure that Docker is installed, if not, you can install it with Homebrew. 66 | You will also need XQuartz and socat. 67 | 68 | ```bash 69 | brew cask install docker xquartz 70 | brew install socat 71 | ``` 72 | 73 | 2\. Start XQuartz from a terminal with `open -a XQuartz`. In the XQuartz Preferences, 74 | go to the “Security” tab and make sure you’ve got “Allow connections from network 75 | clients” ticked. 76 | 77 | 3a. Run the following in a terminal and leave it running: 78 | 79 | ```bash 80 | socat TCP-LISTEN:6000,reuseaddr,fork UNIX-CLIENT:\"$DISPLAY\" 81 | ``` 82 | 83 | 3b. Run the following in a separate terminal. Your ip address can be found by running `ifconfig`. 84 | 85 | The `docker run` command will mount your personal Documents folder to `/root/Documents` so that you 86 | can access your files. 87 | 88 | ```bash 89 | docker build -t pycam/pycam . 90 | export IP='' 91 | docker run -it \ 92 | -v ~/Documents:/root/Documents \ 93 | -v /tmp/.X11-unix:/tmp/.X11-unix \ 94 | -e DISPLAY=$IP:0 \ 95 | pycam/pycam 96 | ``` 97 | 98 | # Minimal requirements for non-GUI mode 99 | 100 | If you plan to use PyCAM only in batch mode (without a graphical user interface), 101 | then you just need to install Python. 102 | 103 | See the manpage ( `man pycam` ) or the output of `pycam --help` for further defails. 104 | -------------------------------------------------------------------------------- /docs/main-page.md: -------------------------------------------------------------------------------- 1 | Use PyCAM to generate toolpaths suitable for 3-Axis CNC machining 2 | ----------------------------------------------------------------- 3 | 4 | You can use **svg and dxf files for 2.5D milling** and **stl files for 5 | full 3-axis 3D milling!** 6 | 7 | Take a look at some [screenshots](screenshots.md) for a quick 8 | overview of the features. The [list of features](features.md) 9 | gives you more details. 10 | 11 | Read the [Installation](installation.md) instructions. 12 | 13 | Watch some [Videos Tutorials](http://vimeo.com/channels/pycam) in 14 | multiple languages. You are welcome to 15 | [translate](video-translations.md) the subtitles to your native 16 | language! 17 | 18 | Join the [mailing lists](http://sourceforge.net/mail/?group_id=237831) 19 | if you want to follow recent developments. 20 | 21 | Read our [development blog](http://fab.senselab.org/pycam) about 22 | interesting new features and plans. 23 | 24 | Check the [FAQ](faq.md) section if are looking for answers. 25 | 26 | Look for [alternative programs](other-programs.md) generating 27 | G-Code for CNC machining, if PyCAM should not fulfill your needs. In 28 | this case: please [let us know, what's 29 | missing](wanted-features)! 30 | 31 | Add the features that you want to see in PyCAM to the 32 | [wishlist](wanted-features.md) .... 33 | 34 | Common workflow 35 | --------------- 36 | 37 | A common workflow could look like this: 38 | 39 | 1. open an STL model file 40 | 2. configure cutter settings (e.g. drill shape and size) 41 | 3. configure processing settings (e.g. the toolpath strategy, remaining 42 | material, ...) 43 | 4. start the toolpath generation 44 | 5. repeat steps 2..4 if you want to add more toolpaths 45 | 6. export the generated toolpaths to a file (in GCode format) 46 | 47 | The output (GCode) can be used for [LinuxCNC](http://www.linuxcnc.org/) and other 48 | machine controller software. 49 | 50 | Graphical User Interface 51 | ------------------------ 52 | 53 | The graphical user interface of PyCAM is based on GTK. All available 54 | features are configurable in different tabs. The complete setup can be 55 | stored in task settings file for later re-use. 56 | 57 | The [3D View](3d-view) is based on OpenGL. It is not strictly 58 | required for the operation. But it is very helpful for making sure that 59 | the result meets your expectations. 60 | 61 | Alternatively you can also use most features of PyCAM via its 62 | [command line interface](cli-examples.md) (e.g. for batch 63 | processing). 64 | 65 | Command-line Interface 66 | ---------------------- 67 | 68 | PyCAM supports non-interactive toolpath generation. This is useful for 69 | batch processing. 70 | 71 | See some [examples](cli-examples.md) for the command-line 72 | usage. 73 | 74 | Requirements 75 | ------------ 76 | 77 | See the requirement list for the different platforms: [Requirements](requirements.md). 78 | 79 | Open issues / Plans 80 | ------------------- 81 | 82 | Take a look at our [TODO](todo.md) list. 83 | 84 | Development 85 | ----------- 86 | 87 | Take a look at [Developer's Guide](developers-guide.md) if you 88 | want to improve PyCAM. 89 | -------------------------------------------------------------------------------- /pycam/Plugins/OpenGLViewSupportModelPreview.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2011 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import pycam.Plugins 21 | import pycam.workspace.data_models 22 | 23 | 24 | class OpenGLViewSupportModelPreview(pycam.Plugins.PluginBase): 25 | 26 | DEPENDS = ["OpenGLWindow", "OpenGLViewModel"] 27 | CATEGORIES = ["Visualization", "OpenGL", "Support bridges"] 28 | 29 | def setup(self): 30 | self.core.register_event("visualize-items", self.draw_support_preview) 31 | self.core.get("register_display_item")("show_support_preview", 32 | "Show Support Model Preview", 30) 33 | self.core.get("register_color")("color_support_preview", "Support model", 30) 34 | self.core.emit_event("visual-item-updated") 35 | return True 36 | 37 | def teardown(self): 38 | self.core.unregister_event("visualize-items", self.draw_support_preview) 39 | self.core.get("unregister_display_item")("show_support_preview") 40 | self.core.get("unregister_color")("color_support_preview") 41 | self.core.emit_event("visual-item-updated") 42 | 43 | def draw_support_preview(self): 44 | if not self.core.get("show_support_preview"): 45 | return 46 | models = [] 47 | for model_object in (self.core.get("current_support_models") or []): 48 | model = model_object.get_model() 49 | if model: 50 | models.append(model) 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/img/2d-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 | -------------------------------------------------------------------------------- /pycam/Gui/OpenGLTools.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2010-2011 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import math 21 | 22 | # careful import 23 | try: 24 | import OpenGL.GL as GL 25 | import OpenGL.GLUT as GLUT 26 | except (ImportError, RuntimeError): 27 | pass 28 | 29 | from pycam.Geometry import sqrt 30 | from pycam.Geometry.PointUtils import pcross, pnorm, pnormalized, psub 31 | 32 | 33 | def keep_matrix(func): 34 | def keep_matrix_wrapper(*args, **kwargs): 35 | pushed_matrix_mode = GL.glGetIntegerv(GL.GL_MATRIX_MODE) 36 | GL.glPushMatrix() 37 | result = func(*args, **kwargs) 38 | final_matrix_mode = GL.glGetIntegerv(GL.GL_MATRIX_MODE) 39 | GL.glMatrixMode(pushed_matrix_mode) 40 | GL.glPopMatrix() 41 | GL.glMatrixMode(final_matrix_mode) 42 | return result 43 | return keep_matrix_wrapper 44 | 45 | 46 | @keep_matrix 47 | def draw_direction_cone(p1, p2, position=0.5, precision=12, size=0.1): 48 | distance = psub(p2, p1) 49 | length = pnorm(distance) 50 | direction = pnormalized(distance) 51 | if direction is None: 52 | # zero-length line 53 | return 54 | cone_length = length * size 55 | cone_radius = cone_length / 3.0 56 | # move the cone to the middle of the line 57 | GL.glTranslatef((p1[0] + p2[0]) * position, 58 | (p1[1] + p2[1]) * position, 59 | (p1[2] + p2[2]) * position) 60 | # rotate the cone according to the line direction 61 | # The cross product is a good rotation axis. 62 | cross = pcross(direction, (0, 0, -1)) 63 | if pnorm(cross) != 0: 64 | # The line direction is not in line with the z axis. 65 | try: 66 | angle = math.asin(sqrt(direction[0] ** 2 + direction[1] ** 2)) 67 | except ValueError: 68 | # invalid angle - just ignore this cone 69 | return 70 | # convert from radians to degree 71 | angle = angle / math.pi * 180 72 | if direction[2] < 0: 73 | angle = 180 - angle 74 | GL.glRotatef(angle, cross[0], cross[1], cross[2]) 75 | elif direction[2] == -1: 76 | # The line goes down the z axis - turn it around. 77 | GL.glRotatef(180, 1, 0, 0) 78 | else: 79 | # The line goes up the z axis - nothing to be done. 80 | pass 81 | # center the cone 82 | GL.glTranslatef(0, 0, -cone_length * position) 83 | # draw the cone 84 | GLUT.glutWireCone(cone_radius, cone_length, precision, 1) 85 | -------------------------------------------------------------------------------- /pycam/Plugins/ModelRotation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2011 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | 21 | import pycam.Plugins 22 | 23 | 24 | class ModelRotation(pycam.Plugins.PluginBase): 25 | 26 | UI_FILE = "model_rotation.ui" 27 | DEPENDS = ["Models"] 28 | CATEGORIES = ["Model"] 29 | 30 | def setup(self): 31 | if self.gui: 32 | rotation_box = self.gui.get_object("ModelRotationBox") 33 | rotation_box.unparent() 34 | self.core.register_ui("model_handling", "Rotation", rotation_box, -10) 35 | self._gtk_handlers = ((self.gui.get_object("RotateModelButton"), "clicked", 36 | self._rotate_model), ) 37 | self._event_handlers = (("model-selection-changed", self._update_controls), ) 38 | self.register_gtk_handlers(self._gtk_handlers) 39 | self.register_event_handlers(self._event_handlers) 40 | self._update_controls() 41 | return True 42 | 43 | def teardown(self): 44 | if self.gui: 45 | self.unregister_event_handlers(self._event_handlers) 46 | self.unregister_gtk_handlers(self._gtk_handlers) 47 | self.core.unregister_ui("model_handling", self.gui.get_object("ModelRotationBox")) 48 | 49 | def _update_controls(self): 50 | widget = self.gui.get_object("ModelRotationBox") 51 | if self.core.get("models").get_selected(): 52 | widget.show() 53 | else: 54 | widget.hide() 55 | 56 | def _rotate_model(self, widget=None): 57 | models = self.core.get("models").get_selected() 58 | if not models: 59 | return 60 | center = [0, 0, 0] 61 | for axis in "XYZ": 62 | if self.gui.get_object("RotationAxis%s" % axis).get_active(): 63 | break 64 | axis_vector = {"X": [1, 0, 0], "Y": [0, 1, 0], "Z": [0, 0, 1]}[axis] 65 | for control, angle in (("RotationAngle90CCKW", -90), 66 | ("RotationAngle90CKW", 90), 67 | ("RotationAngle180", 180), 68 | ("RotationAngleCustomCKW", 69 | self.gui.get_object("RotationAngle").get_value())): 70 | if self.gui.get_object(control).get_active(): 71 | break 72 | for model in models: 73 | model.extend_value("transformations", [{"action": "rotate", "center": center, 74 | "vector": axis_vector, "angle": angle}]) 75 | -------------------------------------------------------------------------------- /pycam/Plugins/OpenGLViewAxes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2011 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import pycam.Plugins 21 | from pycam.Gui.OpenGLTools import draw_direction_cone 22 | 23 | 24 | class OpenGLViewAxes(pycam.Plugins.PluginBase): 25 | 26 | DEPENDS = ["OpenGLWindow"] 27 | CATEGORIES = ["Visualization", "OpenGL"] 28 | 29 | def setup(self): 30 | self.core.register_event("visualize-items", self.draw_axes) 31 | self.core.get("register_display_item")("show_axes", "Show Coordinate System", 50) 32 | self.core.emit_event("visual-item-updated") 33 | return True 34 | 35 | def teardown(self): 36 | self.core.unregister_event("visualize-items", self.draw_axes) 37 | self.core.get("unregister_display_item")("show_axes") 38 | self.core.emit_event("visual-item-updated") 39 | 40 | def draw_axes(self): 41 | if not self.core.get("show_axes"): 42 | return 43 | GL = self._GL 44 | GL.glMatrixMode(GL.GL_MODELVIEW) 45 | GL.glLoadIdentity() 46 | low, high = [None, None, None], [None, None, None] 47 | self.core.call_chain("get_draw_dimension", low, high) 48 | if None in low or None in high: 49 | low, high = (0, 0, 0), (10, 10, 10) 50 | length = 1.2 * max(max(high), abs(min(low))) 51 | origin = (0, 0, 0) 52 | cone_length = 0.05 53 | old_line_width = GL.glGetFloatv(GL.GL_LINE_WIDTH) 54 | if self.core.get("view_light"): 55 | GL.glDisable(GL.GL_LIGHTING) 56 | GL.glLineWidth(1.5) 57 | # draw a colored line ending in a cone for each axis 58 | for index in range(3): 59 | end = [0, 0, 0] 60 | end[index] = length 61 | color = [0.0, 0.0, 0.0] 62 | # reduced brightness (not 1.0) 63 | color[index] = 0.8 64 | GL.glColor3f(*color) 65 | # we need to wait until the color change is active 66 | GL.glFinish() 67 | GL.glBegin(GL.GL_LINES) 68 | GL.glVertex3f(*origin) 69 | GL.glVertex3f(*end) 70 | GL.glEnd() 71 | # Position the cone slightly behind the end of the line - otherwise 72 | # the end of the line (width=2) is visible at the top of the cone. 73 | draw_direction_cone(origin, end, position=1.0 + cone_length, precision=32, 74 | size=cone_length) 75 | GL.glLineWidth(old_line_width) 76 | if self.core.get("view_light"): 77 | GL.glEnable(GL.GL_LIGHTING) 78 | -------------------------------------------------------------------------------- /pycam/Gui/Console.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2010 Lars Kruse 3 | 4 | This file is part of PyCAM. 5 | 6 | PyCAM is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | PyCAM is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with PyCAM. If not, see . 18 | """ 19 | 20 | import os 21 | 22 | 23 | class ConsoleProgressBar: 24 | 25 | STYLE_NONE = 0 26 | STYLE_TEXT = 1 27 | STYLE_BAR = 2 28 | STYLE_DOT = 3 29 | PROGRESS_BAR_LENGTH = 70 30 | 31 | def __init__(self, output, style=None): 32 | if style is None: 33 | style = ConsoleProgressBar.STYLE_TEXT 34 | self.output = output 35 | self.style = style 36 | self.last_length = 0 37 | self.text = "" 38 | self.percent = 0 39 | 40 | def _output_current_state(self, progress_happened=True): 41 | if self.style == ConsoleProgressBar.STYLE_TEXT: 42 | text = "%d%% %s" % (self.percent, self.text) 43 | self.last_length = len(text) 44 | elif self.style == ConsoleProgressBar.STYLE_BAR: 45 | bar_length = ConsoleProgressBar.PROGRESS_BAR_LENGTH 46 | hashes = int(bar_length * self.percent / 100.0) 47 | empty = bar_length - hashes 48 | text = "[%s%s]" % ("#" * hashes, "." * empty) 49 | # include a text like " 10% " in the middle 50 | percent_text = " %d%% " % self.percent 51 | start_text = text[:(len(text) - len(percent_text)) / 2] 52 | end_text = text[-(len(text) - len(start_text) - len(percent_text)):] 53 | text = start_text + percent_text + end_text 54 | self.last_length = len(text) 55 | elif self.style == ConsoleProgressBar.STYLE_DOT: 56 | if progress_happened: 57 | text = "." 58 | else: 59 | text = "" 60 | # don't remove any previous characters 61 | self.last_length = 0 62 | else: 63 | raise ValueError("ConsoleProgressBar: invalid style (%d)" % self.style) 64 | self.output.write(text) 65 | self.output.flush() 66 | 67 | def update(self, text=None, percent=None, **kwargs): 68 | if self.style == ConsoleProgressBar.STYLE_NONE: 69 | return 70 | if text is not None: 71 | self.text = text 72 | if percent is not None: 73 | self.percent = int(percent) 74 | if self.last_length > 0: 75 | # delete the previous line 76 | self.output.write("\x08" * self.last_length) 77 | self._output_current_state(progress_happened=(percent is not None)) 78 | 79 | def finish(self): 80 | if self.style == ConsoleProgressBar.STYLE_NONE: 81 | return 82 | # show that we are finished 83 | self.update(percent=100) 84 | # finish the line 85 | self.output.write(os.linesep) 86 | -------------------------------------------------------------------------------- /share/ui/progress_bar.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | 8 | 9 | False 10 | False 11 | vertical 12 | 13 | 14 | False 15 | end 16 | 17 | 18 | False 19 | True 20 | 0 21 | 22 | 23 | 24 | 25 | True 26 | False 27 | end 28 | 29 | 30 | False 31 | True 32 | 1 33 | 34 | 35 | 36 | 37 | True 38 | False 39 | vertical 40 | 41 | 42 | Show Progress 43 | True 44 | True 45 | True 46 | 47 | 48 | False 49 | True 50 | 0 51 | 52 | 53 | 54 | 55 | Cancel 56 | True 57 | True 58 | True 59 | 60 | 61 | False 62 | True 63 | 1 64 | 65 | 66 | 67 | 68 | False 69 | True 70 | 2 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at njh@aelius.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Copyright 2010 Lars Kruse 4 | Copyright 2010 Arthur Magill 5 | 6 | This file is part of PyCAM. 7 | 8 | PyCAM is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | PyCAM is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with PyCAM. If not, see . 20 | """ 21 | 22 | import glob 23 | import os.path 24 | from setuptools import setup, find_packages 25 | 26 | from pycam import VERSION 27 | 28 | BASE_DIR = os.path.realpath(os.path.abspath(os.path.dirname(__file__))) 29 | 30 | 31 | setup( 32 | name="pycam", 33 | version=VERSION, 34 | license="GPL v3", 35 | description="Open Source CAM - Toolpath Generation for 3-Axis CNC machining", 36 | author="Lars Kruse", 37 | author_email="devel@sumpfralle.de", 38 | provides=["pycam"], 39 | requires=["PyOpenGL", "PyYAML"], 40 | url="http://pycam.sourceforge.net/", 41 | download_url="http://sourceforge.net/projects/pycam/files", 42 | keywords=["3-axis", "cnc", "cam", "toolpath", "machining", "g-code"], 43 | long_description="""IMPORTANT NOTE: Please read the list of requirements: 44 | http://pycam.sourceforge.net/requirements 45 | Basically you will need Python3, GTK and OpenGL. 46 | 47 | Windows: select Python 3.X in the following dialog. 48 | """, 49 | # full list of classifiers at: 50 | # http://pypi.python.org/pypi?:action=list_classifiers 51 | classifiers=[ 52 | "Programming Language :: Python", 53 | "Programming Language :: Python :: 3", 54 | "Development Status :: 4 - Beta", 55 | "License :: OSI Approved :: GNU General Public License (GPL)", 56 | "Topic :: Scientific/Engineering", 57 | "Environment :: Win32 (MS Windows)", 58 | "Environment :: X11 Applications :: GTK", 59 | "Intended Audience :: Manufacturing", 60 | "Operating System :: Microsoft :: Windows", 61 | "Operating System :: MacOS :: MacOS X", 62 | "Operating System :: POSIX", 63 | ], 64 | packages=find_packages(exclude=["pycam.Test"]), 65 | entry_points={ 66 | "gui_scripts": [ 67 | "pycam = pycam.run_gui:main_func", 68 | ], 69 | "console_scripts": [ 70 | "pycam-cli = pycam.run_cli:main_func", 71 | ], 72 | }, 73 | data_files=[ 74 | ("share/pycam/doc", ["COPYING.TXT", 75 | "INSTALL.md", 76 | "LICENSE.TXT", 77 | "README.md", 78 | "Changelog", 79 | "release_info.txt"]), 80 | ("share/pycam/ui", glob.glob(os.path.join("share", "ui", "*"))), 81 | ("share/pycam/fonts", glob.glob(os.path.join("share", "fonts", "*"))), 82 | ("share/pycam", [os.path.join("share", "pycam.ico"), 83 | os.path.join("share", "misc", "DXF.gpl")]), 84 | ("share/pycam/samples", glob.glob(os.path.join("samples", "*"))), 85 | ], 86 | ) 87 | 88 | # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 89 | -------------------------------------------------------------------------------- /docs/installation-macos.md: -------------------------------------------------------------------------------- 1 | Versions, dependencies and complications 2 | ---------------------------------------- 3 | 4 | Please note that the most recent successful usage of PyCAM on MacOS was 5 | reported around 2012. Thus you should stick with PyCAM v0.5.1 and be prepared 6 | to tackle weird dependency problems, if you really need to run PyCAM on MacOS. 7 | 8 | You should use Linux instead, if you want to use a more recent release of PyCAM. 9 | 10 | Install requirements via MacPorts 11 | --------------------------------- 12 | 13 | First you need to install [MacPorts](http://www.macports.org/install.php). 14 | 15 | Afterwards you need to install the following packages: 16 | 17 | - py25-gtk 18 | - py25-gtkglext 19 | - py25-opengl (at least v3.0.1b2) 20 | 21 | Simply run the following to install all dependencies: 22 | 23 | sudo port install py25-gtk py25-gtkglext py25-opengl 24 | 25 | Run PyCAM 26 | --------- 27 | 28 | - extract the [PyCAM archive](https://sourceforge.net/projects/pycam/files/pycam/) 29 | - run */opt/local/bin/python2.5 pycam* from within PyCAM's directory 30 | - the above line refers to MacPorts' Python interpreter (instead 31 | of MacOS' native Python) - otherwise it will not find OpenGL and 32 | the other required modules 33 | 34 | Problems? Solutions! 35 | -------------------- 36 | 37 | Below you will find a list of potential problems reported by PyCAM 38 | users. 39 | 40 | If these workarounds do not help: please ask a question in the 41 | [*Help* forum](http://sourceforge.net/projects/pycam/forums/forum/860184) and 42 | provide the following relevant information about your setup. 43 | 44 | Get the list of installed python-related packages: 45 | 46 | port installed | grep py 47 | 48 | Get Python's output for every interesting *import* statement: 49 | 50 | foo@bar:~$ /opt/local/bin/python2.5 51 | Python 2.5.5 (r255:77872, Nov 28 2010, 19:00:19) 52 | [GCC 4.4.5] on linux2 53 | Type "help", "copyright", "credits” or "license" for more information. 54 | >>> import gtk.gtkgl 55 | >>> import OpenGL.GL as GL 56 | >>> import OpenGL.GLU as GLU 57 | >>> import OpenGL.GLUT as GLUT 58 | 59 | (the above output of a successful test was taken on Linux - your 60 | installed versions may differ slightly) 61 | 62 | ### OpenGL (python-gtkglext1) missing 63 | 64 | Some users reported the following warning from PyCAM, even though they 65 | installed all required packages: 66 | 67 | > Please install 'python-gtkglext1' 68 | 69 | Since none of the PyCAM developers is a Mac OS user, it is not easy for 70 | us to track down this issue. Currently it looks like a problem of 71 | [Python 2.5 and *ctypes*](https://trac.macports.org/ticket/26186) on Mac 72 | OS X. Please try the following steps to fix it: 73 | 74 | 1. install the libffi package: `port install libffi` 75 | 2. rebuild Python: `port -v upgrade --force python25` 76 | 77 | (suggested on 78 | [stackoverflow](http://stackoverflow.com/questions/4535725/ctypes-import-not-working-on-python-2-5/4536064#4536064)) 79 | 80 | Alternatively you could try to update Python to v2.6 and install the 81 | corresponding packages for GTK, OpenGL and so on. 82 | 83 | Please report back, if one of the above suggestions fixes the problem 84 | for you - thanks! 85 | 86 | ### OpenGL is too old 87 | 88 | According to a [forum post from 89 | lilalinux](http://sourceforge.net/projects/pycam/forums/forum/860183/topic/3800091) 90 | you need at least OpenGL v3.0.1b2. Otherwise you will probably get a 91 | blank 3D visualization window. 92 | --------------------------------------------------------------------------------