Commit 6caf64e4 authored by sumpfralle's avatar sumpfralle

added a pyinstaller spec file for creating standalone binaries for Windows

git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@627 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 0169a353
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
$Id$ $Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de> Copyright 2010 Lars Kruse <devel@sumpfralle.de>
Copyright 2008-2009 Lode Leroy Copyright 2008-2009 Lode Leroy
This file is part of PyCAM. This file is part of PyCAM.
PyCAM is free software: you can redistribute it and/or modify PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
PyCAM is distributed in the hope that it will be useful, PyCAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>. along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from optparse import OptionParser from optparse import OptionParser
import sys import sys
import os import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from pycam.Physics.ode_physics import override_ode_availability from pycam.Physics.ode_physics import override_ode_availability
import pycam.Gui.common as GuiCommon import pycam.Gui.common as GuiCommon
import pycam.Gui.Settings import pycam.Gui.Settings
import pycam.Gui.Console import pycam.Gui.Console
import pycam.Importers.TestModel import pycam.Importers.TestModel
import pycam.Importers import pycam.Importers
import pycam.Exporters.SimpleGCodeExporter import pycam.Exporters.SimpleGCodeExporter
import pycam.Toolpath.Generator import pycam.Toolpath.Generator
from pycam.Toolpath import Bounds, Toolpath from pycam.Toolpath import Bounds, Toolpath
from pycam import VERSION from pycam import VERSION
import pycam.Utils.log import pycam.Utils.log
import logging import logging
import time import time
log = pycam.Utils.log.get_logger() log = pycam.Utils.log.get_logger()
EXAMPLE_MODEL_LOCATIONS = ( EXAMPLE_MODEL_LOCATIONS = [
os.path.join(os.path.dirname(__file__), "samples"), os.path.join(os.path.dirname(__file__), "samples"),
os.path.join(sys.prefix, "share", "pycam", "samples"), os.path.join(sys.prefix, "share", "pycam", "samples"),
os.path.join("usr", "share", "pycam", "samples")) os.path.join("usr", "share", "pycam", "samples")]
DEFAULT_MODEL_FILE = "pycam.stl" # for pyinstaller (windows distribution)
EXIT_CODES = {"ok": 0, "requirements": 1, "load_model_failed": 2, if "_MEIPASS2" in os.environ:
"write_output_failed": 3, "parsing_failed": 4} EXAMPLE_MODEL_LOCATIONS.insert(0, os.path.join(os.environ["_MEIPASS2"], "samples"))
DEFAULT_MODEL_FILE = "pycam.stl"
EXIT_CODES = {"ok": 0, "requirements": 1, "load_model_failed": 2,
def show_gui(inputfile=None, task_settings_file=None): "write_output_failed": 3, "parsing_failed": 4}
deps_gtk = GuiCommon.requirements_details_gtk()
report_gtk = GuiCommon.get_dependency_report(deps_gtk, prefix="\t")
if GuiCommon.check_dependencies(deps_gtk): def show_gui(inputfile=None, task_settings_file=None):
from pycam.Gui.Project import ProjectGui deps_gtk = GuiCommon.requirements_details_gtk()
gui_class = ProjectGui report_gtk = GuiCommon.get_dependency_report(deps_gtk, prefix="\t")
else: if GuiCommon.check_dependencies(deps_gtk):
full_report = [] from pycam.Gui.Project import ProjectGui
full_report.append("PyCAM dependency problem") gui_class = ProjectGui
full_report.append("Error: Failed to load the GTK interface.") else:
full_report.append("Details:") full_report = []
full_report.append(report_gtk) full_report.append("PyCAM dependency problem")
full_report.append("") full_report.append("Error: Failed to load the GTK interface.")
full_report.append("Detailed list of requirements: %s" % GuiCommon.REQUIREMENTS_LINK) full_report.append("Details:")
log.critical(os.linesep.join(full_report)) full_report.append(report_gtk)
sys.exit(EXIT_CODES["requirements"]) full_report.append("")
full_report.append("Detailed list of requirements: %s" % GuiCommon.REQUIREMENTS_LINK)
gui = gui_class() log.critical(os.linesep.join(full_report))
sys.exit(EXIT_CODES["requirements"])
# load the given model or the default
if inputfile is None: gui = gui_class()
default_model = get_default_model()
if isinstance(default_model, basestring): # load the given model or the default
gui.load_model_file(filename=default_model) if inputfile is None:
else: default_model = get_default_model()
gui.load_model(default_model) if isinstance(default_model, basestring):
else: gui.load_model_file(filename=default_model)
gui.load_model_file(filename=inputfile) else:
gui.load_model(default_model)
# load task settings file else:
if not task_settings_file is None: gui.load_model_file(filename=inputfile)
gui.open_task_settings_file(task_settings_file)
# load task settings file
# open the GUI if not task_settings_file is None:
gui.mainloop() gui.open_task_settings_file(task_settings_file)
# open the GUI
def get_default_model(): gui.mainloop()
""" return a filename or a Model instance """
# try to load the default model file ("pycam" logo)
for inputdir in EXAMPLE_MODEL_LOCATIONS: def get_default_model():
inputfile = os.path.join(inputdir, DEFAULT_MODEL_FILE) """ return a filename or a Model instance """
if os.path.isfile(inputfile): # try to load the default model file ("pycam" logo)
return inputfile for inputdir in EXAMPLE_MODEL_LOCATIONS:
else: inputfile = os.path.join(inputdir, DEFAULT_MODEL_FILE)
# fall back to the simple test model if os.path.isfile(inputfile):
log.warn("Failed to find the default model (%s) in the " \ return inputfile
"following locations: %s" % (DEFAULT_MODEL_FILE, else:
", ".join(EXAMPLE_MODEL_LOCATIONS))) # fall back to the simple test model
return pycam.Importers.TestModel.get_test_model() log.warn("Failed to find the default model (%s) in the " \
"following locations: %s" % (DEFAULT_MODEL_FILE,
def load_model_file(filename, program_locations): ", ".join(EXAMPLE_MODEL_LOCATIONS)))
filename = os.path.expanduser(filename) return pycam.Importers.TestModel.get_test_model()
if not os.path.isfile(filename):
log.warn("The input file ('%') was not found!" % filename) def load_model_file(filename, program_locations):
return None filename = os.path.expanduser(filename)
filetype, importer = pycam.Importers.detect_file_type(filename) if not os.path.isfile(filename):
model = importer(filename, program_locations=program_locations) log.warn("The input file ('%') was not found!" % filename)
if model is None: return None
log.warn("Failed to load the model file (%s)." % filename) filetype, importer = pycam.Importers.detect_file_type(filename)
return None model = importer(filename, program_locations=program_locations)
else: if model is None:
return model log.warn("Failed to load the model file (%s)." % filename)
return None
def get_output_handler(destination): else:
if destination == "-": return model
handler = sys.stdout
closer = lambda: None def get_output_handler(destination):
else: if destination == "-":
try: handler = sys.stdout
handler = open(destination, "w") closer = lambda: None
except IOError, err_msg: else:
log.error("Failed to open output file (%s) for writing: %s" \ try:
% (destination, err_msg)) handler = open(destination, "w")
return None, None except IOError, err_msg:
closer = handler.close log.error("Failed to open output file (%s) for writing: %s" \
return (handler, closer) % (destination, err_msg))
return None, None
closer = handler.close
# check if we were started as a separate program return (handler, closer)
if __name__ == "__main__":
parser = OptionParser(prog="PyCAM",
usage="usage: pycam [options] [inputfile]\n\n" \ # check if we were started as a separate program
+ "Start the PyCAM toolpath generator. Supplying one of " \ if __name__ == "__main__":
+ "the '--export-?' parameters will cause PyCAM to start " \ parser = OptionParser(prog="PyCAM",
+ "in batch mode. Most parameters are useful only for " \ usage="usage: pycam [options] [inputfile]\n\n" \
+ "batch mode.", + "Start the PyCAM toolpath generator. Supplying one of " \
epilog="Take a look at the wiki for more information: " \ + "the '--export-?' parameters will cause PyCAM to start " \
+ "http://sourceforge.net/apps/mediawiki/pycam/.\n" \ + "in batch mode. Most parameters are useful only for " \
+ "Bug reports: http://sourceforge.net/tracker/?group_id=237831&atid=1104176") + "batch mode.",
group_general = parser.add_option_group("General options") epilog="Take a look at the wiki for more information: " \
group_export = parser.add_option_group("Export formats", + "http://sourceforge.net/apps/mediawiki/pycam/.\n" \
"Export the resulting toolpath or meta-data in various formats. " \ + "Bug reports: http://sourceforge.net/tracker/?group_id=237831&atid=1104176")
+ "These options trigger the non-interactive mode. Thus the GUI " \ group_general = parser.add_option_group("General options")
+ "is disabled.") group_export = parser.add_option_group("Export formats",
group_tool = parser.add_option_group("Tool definition", "Export the resulting toolpath or meta-data in various formats. " \
"Specify the tool paramters. The default tool is spherical and " \ + "These options trigger the non-interactive mode. Thus the GUI " \
+ "has a diameter of 1 unit. The default speeds are 1000 " \ + "is disabled.")
+ "units/minute (feedrate) and 250 (spindle rotations per minute)") group_tool = parser.add_option_group("Tool definition",
group_process = parser.add_option_group("Process definition", "Specify the tool paramters. The default tool is spherical and " \
"Specify the process parameters: toolpath strategy, layer height," \ + "has a diameter of 1 unit. The default speeds are 1000 " \
+ " and others. A typical roughing operation is configured by " \ + "units/minute (feedrate) and 250 (spindle rotations per minute)")
+ "default.") group_process = parser.add_option_group("Process definition",
group_bounds = parser.add_option_group("Boundary definition", "Specify the process parameters: toolpath strategy, layer height," \
"Specify the outer limits of the processing area (x/y/z). " \ + " and others. A typical roughing operation is configured by " \
+ "You may choose between 'relative_margin' (margin is given as " \ + "default.")
+ "percentage of the respective model dimension), 'fixed_margin' " \ group_bounds = parser.add_option_group("Boundary definition",
+ "(margin for each face given in absolute units of length) " \ "Specify the outer limits of the processing area (x/y/z). " \
+ "and 'custom' (absolute coordinates of the bounding box - " \ + "You may choose between 'relative_margin' (margin is given as " \
+ "regardless of the model size and position). Negative values " \ + "percentage of the respective model dimension), 'fixed_margin' " \
+ "are allowed and can make sense (e.g. negative margin).") + "(margin for each face given in absolute units of length) " \
group_support_grid = parser.add_option_group("Support grid", + "and 'custom' (absolute coordinates of the bounding box - " \
"An optional support grid can be used to keep the object in " \ + "regardless of the model size and position). Negative values " \
+ "place during the mill operation. The support grid can be " \ + "are allowed and can make sense (e.g. negative margin).")
+ "removed manually afterwards. The support grid can have a " \ group_support_grid = parser.add_option_group("Support grid",
+ "rectangular profile. By default the support grid is disabled.") "An optional support grid can be used to keep the object in " \
group_external_programs = parser.add_option_group("External programs", + "place during the mill operation. The support grid can be " \
"Some optional external programs are used for format conversions.") + "removed manually afterwards. The support grid can have a " \
# general options + "rectangular profile. By default the support grid is disabled.")
group_general.add_option("-c", "--config", dest="config_file", group_external_programs = parser.add_option_group("External programs",
default=None, action="store", type="string", "Some optional external programs are used for format conversions.")
help="load a task settings file") # general options
group_general.add_option("", "--unit", dest="unit_size", group_general.add_option("-c", "--config", dest="config_file",
default="mm", action="store", type="choice", default=None, action="store", type="string",
choices=["mm", "inch"], help="choose 'mm' or 'inch' for all " \ help="load a task settings file")
+ "numbers. By default 'mm' is assumed.") group_general.add_option("", "--unit", dest="unit_size",
group_general.add_option("", "--collision-engine", dest="collision_engine", default="mm", action="store", type="choice",
default="triangles", action="store", type="choice", choices=["mm", "inch"], help="choose 'mm' or 'inch' for all " \
choices=["triangles", "ode"], + "numbers. By default 'mm' is assumed.")
help="choose a specific collision detection engine. The default " \ group_general.add_option("", "--collision-engine", dest="collision_engine",
+ "is 'triangles'. Use 'help' to get a list of possible " \ default="triangles", action="store", type="choice",
+ "engines.") choices=["triangles", "ode"],
group_general.add_option("", "--boundary-mode", dest="boundary_mode", help="choose a specific collision detection engine. The default " \
default="along", action="store", type="choice", + "is 'triangles'. Use 'help' to get a list of possible " \
choices=["inside", "along", "outside"], + "engines.")
help="specify if the mill tool (including its radius) should " \ group_general.add_option("", "--boundary-mode", dest="boundary_mode",
+ "move completely 'inside', 'along' or 'outside' the " \ default="along", action="store", type="choice",
+ "defined processing boundary.") choices=["inside", "along", "outside"],
group_general.add_option("", "--disable-psyco", dest="disable_psyco", help="specify if the mill tool (including its radius) should " \
default=False, action="store_true", help="disable the Psyco " \ + "move completely 'inside', 'along' or 'outside' the " \
+ "just-in-time-compiler even when it is available") + "defined processing boundary.")
group_general.add_option("-q", "--quiet", dest="quiet", group_general.add_option("", "--disable-psyco", dest="disable_psyco",
default=False, action="store_true", help="show only warnings and " \ default=False, action="store_true", help="disable the Psyco " \
+ "errors.") + "just-in-time-compiler even when it is available")
group_general.add_option("", "--progress", dest="progress", group_general.add_option("-q", "--quiet", dest="quiet",
default="text", action="store", type="choice", default=False, action="store_true", help="show only warnings and " \
choices=["none", "text", "bar", "dot"], + "errors.")
help="specify the type of progress bar used in non-GUI mode. " \ group_general.add_option("", "--progress", dest="progress",
+ "The following options are available: text, none, bar, dot.") default="text", action="store", type="choice",
group_general.add_option("-v", "--version", dest="show_version", choices=["none", "text", "bar", "dot"],
default=False, action="store_true", help="show the current " \ help="specify the type of progress bar used in non-GUI mode. " \
+ "version of PyCAM and exit") + "The following options are available: text, none, bar, dot.")
# export options group_general.add_option("-v", "--version", dest="show_version",
group_export.add_option("", "--export-gcode", dest="export_gcode", default=False, action="store_true", help="show the current " \
default=None, action="store", type="string", + "version of PyCAM and exit")
help="export the generated toolpaths to a file") # export options
group_export.add_option("", "--export-task-config", group_export.add_option("", "--export-gcode", dest="export_gcode",
dest="export_task_config", default=None, action="store", default=None, action="store", type="string",
type="string", help="export the generated toolpaths to a file")
help="export the current task configuration (mainly for debugging)") group_export.add_option("", "--export-task-config",
# tool options dest="export_task_config", default=None, action="store",
group_tool.add_option("", "--tool-shape", dest="tool_shape", type="string",
default="cylindrical", action="store", type="choice", help="export the current task configuration (mainly for debugging)")
choices=["cylindrical", "spherical", "toroidal"], # tool options
help="tool shape for the operation (cylindrical, spherical, " \ group_tool.add_option("", "--tool-shape", dest="tool_shape",
+ "toroidal)") default="cylindrical", action="store", type="choice",
group_tool.add_option("", "--tool-size", dest="tool_diameter", choices=["cylindrical", "spherical", "toroidal"],
default=1.0, action="store", type="float", help="tool shape for the operation (cylindrical, spherical, " \
help="diameter of the tool") + "toroidal)")
group_tool.add_option("", "--tool-torus-size", dest="tool_torus_diameter", group_tool.add_option("", "--tool-size", dest="tool_diameter",
default=0.25, action="store", type="float", default=1.0, action="store", type="float",
help="torus diameter of the tool (only for toroidal tool shape)") help="diameter of the tool")
group_tool.add_option("", "--tool-feedrate", dest="tool_feedrate", group_tool.add_option("", "--tool-torus-size", dest="tool_torus_diameter",
default=1000, action="store", type="float", default=0.25, action="store", type="float",
help="allowed movement velocity of the tool (units/minute)") help="torus diameter of the tool (only for toroidal tool shape)")
group_tool.add_option("", "--tool-spindle-speed", dest="tool_spindle_speed", group_tool.add_option("", "--tool-feedrate", dest="tool_feedrate",
default=250, action="store", type="float", default=1000, action="store", type="float",
help="rotation speed of the tool (per minute)") help="allowed movement velocity of the tool (units/minute)")
group_tool.add_option("", "--tool-id", dest="tool_id", group_tool.add_option("", "--tool-spindle-speed", dest="tool_spindle_speed",
default=1, action="store", type="int", default=250, action="store", type="float",
help="tool ID - to be used for tool selection via GCode " \ help="rotation speed of the tool (per minute)")
+ "(default: 1)") group_tool.add_option("", "--tool-id", dest="tool_id",
# process options default=1, action="store", type="int",
group_process.add_option("", "--process-path-direction", help="tool ID - to be used for tool selection via GCode " \
dest="process_path_direction", default="x", action="store", + "(default: 1)")
type="choice", choices=["x", "y", "xy"], # process options
help="primary direction of the generated toolpath (x/y/xy)") group_process.add_option("", "--process-path-direction",
group_process.add_option("", "--process-path-generator", dest="process_path_direction", default="x", action="store",
dest="process_path_generator", default="layer", action="store", type="choice", choices=["x", "y", "xy"],
type="choice", choices=["layer", "surface", "engrave"], help="primary direction of the generated toolpath (x/y/xy)")
help="one of the available toolpath strategies (layer, surface, " \ group_process.add_option("", "--process-path-generator",
+ "engrave)") dest="process_path_generator", default="layer", action="store",
group_process.add_option("", "--process-path-postprocessor", type="choice", choices=["layer", "surface", "engrave"],
dest="process_path_postprocessor", default="polygon", help="one of the available toolpath strategies (layer, surface, " \
action="store", type="choice", + "engrave)")
choices=["polygon", "contour", "zigzag"], help="one of the " \ group_process.add_option("", "--process-path-postprocessor",
+ "available toolpath postprocessors (polygon, zigzag, contour)") dest="process_path_postprocessor", default="polygon",
group_process.add_option("", "--process-material-allowance", action="store", type="choice",
dest="process_material_allowance", default=0.0, action="store", choices=["polygon", "contour", "zigzag"], help="one of the " \
type="float", help="minimum distance between the tool and the " \ + "available toolpath postprocessors (polygon, zigzag, contour)")
+ "object (for rough processing)") group_process.add_option("", "--process-material-allowance",
group_process.add_option("", "--process-step-down", dest="process_material_allowance", default=0.0, action="store",
dest="process_step_down", default=3.0, action="store", type="float", type="float", help="minimum distance between the tool and the " \
help="the maximum thickness of each processed material layer " \ + "object (for rough processing)")
+ "(only for 'layer' strategy)") group_process.add_option("", "--process-step-down",
group_process.add_option("", "--process-overlap-percent", dest="process_step_down", default=3.0, action="store", type="float",
dest="process_overlap_percent", default=0, action="store", help="the maximum thickness of each processed material layer " \
type="int", help="how much should two adjacent parallel " \ + "(only for 'layer' strategy)")
+ "toolpaths overlap each other (0..99)") group_process.add_option("", "--process-overlap-percent",
group_process.add_option("", "--safety-height", dest="safety_height", dest="process_overlap_percent", default=0, action="store",
default=25.0, action="store", type="float", type="int", help="how much should two adjacent parallel " \
help="height for safe re-positioning moves") + "toolpaths overlap each other (0..99)")
group_process.add_option("", "--process-engrave-offset", group_process.add_option("", "--safety-height", dest="safety_height",
dest="process_engrave_offset", default=0.0, action="store", default=25.0, action="store", type="float",
type="float", help="engrave along the contour of a model with a " \ help="height for safe re-positioning moves")
+ "given distance (only for 'engrave' strategy)") group_process.add_option("", "--process-engrave-offset",
# bounds settings dest="process_engrave_offset", default=0.0, action="store",
group_bounds.add_option("", "--bounds-type", dest="bounds_type", type="float", help="engrave along the contour of a model with a " \
default="fixed-margin", action="store", type="choice", + "given distance (only for 'engrave' strategy)")
choices=["relative-margin", "fixed-margin", "custom"], # bounds settings
help="type of the boundary definition (relative-margin, " \ group_bounds.add_option("", "--bounds-type", dest="bounds_type",
+ "fixed-margin, custom)") default="fixed-margin", action="store", type="choice",
group_bounds.add_option("", "--bounds-lower", dest="bounds_lower", choices=["relative-margin", "fixed-margin", "custom"],
default="", action="store", type="string", help="type of the boundary definition (relative-margin, " \
help="comma-separated x/y/z combination of the lower boundary " \ + "fixed-margin, custom)")
+ "(e.g. '4,4,-0.5')") group_bounds.add_option("", "--bounds-lower", dest="bounds_lower",
group_bounds.add_option("", "--bounds-upper", dest="bounds_upper", default="", action="store", type="string",
default="", action="store", type="string", help="comma-separated x/y/z combination of the lower boundary " \
help="comma-separated x/y/z combination of the upper boundary " \ + "(e.g. '4,4,-0.5')")
+ "(e.g. '12,5.5,0')") group_bounds.add_option("", "--bounds-upper", dest="bounds_upper",
# support grid settings default="", action="store", type="string",
group_support_grid.add_option("", "--enable-support-grid", help="comma-separated x/y/z combination of the upper boundary " \
dest="support_grid_enabled", default=False, action="store_true", + "(e.g. '12,5.5,0')")
help="enable the support grid") # support grid settings
group_support_grid.add_option("", "--support-grid-distance-x", group_support_grid.add_option("", "--enable-support-grid",
dest="support_grid_distance_x", default=10.0, action="store", dest="support_grid_enabled", default=False, action="store_true",
type="float", help="distance along the x-axis between two " \ help="enable the support grid")
+ "adjacent parallel lines of the support grid pattern") group_support_grid.add_option("", "--support-grid-distance-x",
group_support_grid.add_option("", "--support-grid-distance-y", dest="support_grid_distance_x", default=10.0, action="store",
dest="support_grid_distance_y", default=10.0, action="store", type="float", help="distance along the x-axis between two " \
type="float", help="distance along the y-axis between two " \ + "adjacent parallel lines of the support grid pattern")
+ "adjacent parallel lines of the support grid pattern") group_support_grid.add_option("", "--support-grid-distance-y",
group_support_grid.add_option("", "--support-grid-height", dest="support_grid_distance_y", default=10.0, action="store",
dest="support_grid_height", default=2.0, action="store", type="float", help="distance along the y-axis between two " \
type="float", help="height of the support grid profile") + "adjacent parallel lines of the support grid pattern")
group_support_grid.add_option("", "--support-grid-thickness", group_support_grid.add_option("", "--support-grid-height",
dest="support_grid_thickness", default=0.5, action="store", dest="support_grid_height", default=2.0, action="store",
type="float", help="width of the support grid profile") type="float", help="height of the support grid profile")
# external program settings group_support_grid.add_option("", "--support-grid-thickness",
group_external_programs.add_option("", "--location-inkscape", dest="support_grid_thickness", default=0.5, action="store",
dest="external_program_inkscape", default="", action="store", type="float", help="width of the support grid profile")
type="string", help="location of the Inkscape executable. " \ # external program settings
+ "This program is required for importing SVG files.") group_external_programs.add_option("", "--location-inkscape",
group_external_programs.add_option("", "--location-pstoedit", dest="external_program_inkscape", default="", action="store",
dest="external_program_pstoedit", default="", action="store", type="string", help="location of the Inkscape executable. " \
type="string", help="location of the PStoEdit executable. " \ + "This program is required for importing SVG files.")
+ "This program is required for importing SVG files.") group_external_programs.add_option("", "--location-pstoedit",
(opts, args) = parser.parse_args() dest="external_program_pstoedit", default="", action="store",
type="string", help="location of the PStoEdit executable. " \
if len(args) > 0: + "This program is required for importing SVG files.")
inputfile = os.path.expanduser(args[0]) (opts, args) = parser.parse_args()
else:
inputfile = None if len(args) > 0:
inputfile = os.path.expanduser(args[0])
if opts.quiet: else:
log.setLevel(logging.WARNING) inputfile = None
# disable the progress bar
opts.progress = "none" if opts.quiet:
log.setLevel(logging.WARNING)
# show version and exit # disable the progress bar
if opts.show_version: opts.progress = "none"
if opts.quiet:
# print only the bare version number # show version and exit
print VERSION if opts.show_version:
else: if opts.quiet:
print "PyCAM %s" % VERSION # print only the bare version number
print "Copyright (C) 2008-2010 Lode Leroy" print VERSION
print "Copyright (C) 2010 Lars Kruse" else:
print print "PyCAM %s" % VERSION
print "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>." print "Copyright (C) 2008-2010 Lode Leroy"
print "This is free software: you are free to change and redistribute it." print "Copyright (C) 2010 Lars Kruse"
print "There is NO WARRANTY, to the extent permitted by law." print
sys.exit(EXIT_CODES["ok"]) print "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>."
print "This is free software: you are free to change and redistribute it."
if not opts.disable_psyco: print "There is NO WARRANTY, to the extent permitted by law."
try: sys.exit(EXIT_CODES["ok"])
import psyco
psyco.full() if not opts.disable_psyco:
log.info("Psyco enabled") try:
except ImportError: import psyco
log.info("Psyco is not available (performance will probably " \ psyco.full()
+ "suffer slightly)") log.info("Psyco enabled")
else: except ImportError:
log.info("Psyco was disabled via the commandline") log.info("Psyco is not available (performance will probably " \
+ "suffer slightly)")
# initialize the progress bar else:
progress_styles = {"none": pycam.Gui.Console.ConsoleProgressBar.STYLE_NONE, log.info("Psyco was disabled via the commandline")
"text": pycam.Gui.Console.ConsoleProgressBar.STYLE_TEXT,
"bar": pycam.Gui.Console.ConsoleProgressBar.STYLE_BAR, # initialize the progress bar
"dot": pycam.Gui.Console.ConsoleProgressBar.STYLE_DOT, progress_styles = {"none": pycam.Gui.Console.ConsoleProgressBar.STYLE_NONE,
} "text": pycam.Gui.Console.ConsoleProgressBar.STYLE_TEXT,
progress_bar = pycam.Gui.Console.ConsoleProgressBar(sys.stdout, "bar": pycam.Gui.Console.ConsoleProgressBar.STYLE_BAR,
progress_styles[opts.progress]) "dot": pycam.Gui.Console.ConsoleProgressBar.STYLE_DOT,
}
if opts.config_file: progress_bar = pycam.Gui.Console.ConsoleProgressBar(sys.stdout,
opts.config_file = os.path.expanduser(opts.config_file) progress_styles[opts.progress])
if not opts.export_gcode and not opts.export_task_config: if opts.config_file:
show_gui(inputfile, opts.config_file) opts.config_file = os.path.expanduser(opts.config_file)
else:
# generate toolpath if not opts.export_gcode and not opts.export_task_config:
tps = pycam.Gui.Settings.ToolpathSettings() show_gui(inputfile, opts.config_file)
tool_shape = {"cylindrical": "CylindricalCutter", else:
"spherical": "SphericalCutter", # generate toolpath
"toroidal": "ToroidalCutter", tps = pycam.Gui.Settings.ToolpathSettings()
}[opts.tool_shape] tool_shape = {"cylindrical": "CylindricalCutter",
tps.set_tool(opts.tool_id, tool_shape, 0.5 * opts.tool_diameter, "spherical": "SphericalCutter",
0.5 * opts.tool_torus_diameter, opts.tool_spindle_speed, "toroidal": "ToroidalCutter",
opts.tool_feedrate) }[opts.tool_shape]
if opts.support_grid_enabled: tps.set_tool(opts.tool_id, tool_shape, 0.5 * opts.tool_diameter,
tps.set_support_grid(opts.support_grid_distance_x, 0.5 * opts.tool_torus_diameter, opts.tool_spindle_speed,
opts.support_grid_distance_y, opts.support_grid_thickness, opts.tool_feedrate)
opts.support_grid_height) if opts.support_grid_enabled:
if opts.collision_engine == "ode": tps.set_support_grid(opts.support_grid_distance_x,
tps.set_calculation_backend("ODE") opts.support_grid_distance_y, opts.support_grid_thickness,
tps.set_unit_size(opts.unit_size) opts.support_grid_height)
path_generator = {"layer": "PushCutter", if opts.collision_engine == "ode":
"surface": "DropCutter", tps.set_calculation_backend("ODE")
"engrave": "EngraveCutter", tps.set_unit_size(opts.unit_size)
}[opts.process_path_generator] path_generator = {"layer": "PushCutter",
postprocessor = {"zigzag": "ZigZagCutter", "surface": "DropCutter",
"contour": "ContourCutter", "engrave": "EngraveCutter",
"polygon": "PolygonCutter", }[opts.process_path_generator]
}[opts.process_path_postprocessor] postprocessor = {"zigzag": "ZigZagCutter",
tps.set_process_settings(path_generator, postprocessor, "contour": "ContourCutter",
opts.process_path_direction, opts.process_material_allowance, "polygon": "PolygonCutter",
opts.safety_height, opts.process_overlap_percent / 100.0, }[opts.process_path_postprocessor]
opts.process_step_down, opts.process_engrave_offset) tps.set_process_settings(path_generator, postprocessor,
# set locations of external programs opts.process_path_direction, opts.process_material_allowance,
program_locations = {} opts.safety_height, opts.process_overlap_percent / 100.0,
if opts.external_program_inkscape: opts.process_step_down, opts.process_engrave_offset)
program_locations["inkscape"] = opts.external_program_inkscape # set locations of external programs
if opts.external_program_pstoedit: program_locations = {}
program_locations["pstoedit"] = opts.external_program_pstoedit if opts.external_program_inkscape:
# load the model program_locations["inkscape"] = opts.external_program_inkscape
if inputfile is None: if opts.external_program_pstoedit:
model = get_default_model() program_locations["pstoedit"] = opts.external_program_pstoedit
# the "get_default_model" function returns a string or a model # load the model
if isinstance(model, basestring): if inputfile is None:
model = load_model_file(model, model = get_default_model()
program_locations=program_locations, # the "get_default_model" function returns a string or a model
unit=opts.unit_size) if isinstance(model, basestring):
else: model = load_model_file(model,
model = load_model_file(inputfile, program_locations=program_locations,
program_locations=program_locations, unit=opts.unit_size) unit=opts.unit_size)
if model is None: else:
# something went wrong - we quit model = load_model_file(inputfile,
sys.exit(EXIT_CODES["load_model_failed"]) program_locations=program_locations, unit=opts.unit_size)
# calculate the processing boundary if model is None:
bounds = Bounds() # something went wrong - we quit
bounds.set_reference(model.get_bounds()) sys.exit(EXIT_CODES["load_model_failed"])
# set the bounds type and let the default bounding box match the model # calculate the processing boundary
if opts.bounds_type == "relative-margin": bounds = Bounds()
bounds.set_type(Bounds.TYPE_RELATIVE_MARGIN) bounds.set_reference(model.get_bounds())
bounds_default_low = (0, 0, 0) # set the bounds type and let the default bounding box match the model
bounds_default_high = (0, 0, 0) if opts.bounds_type == "relative-margin":
elif opts.bounds_type == "fixed-margin": bounds.set_type(Bounds.TYPE_RELATIVE_MARGIN)
bounds.set_type(Bounds.TYPE_FIXED_MARGIN) bounds_default_low = (0, 0, 0)
bounds_default_low = (0, 0, 0) bounds_default_high = (0, 0, 0)
bounds_default_high = (0, 0, 0) elif opts.bounds_type == "fixed-margin":
else: bounds.set_type(Bounds.TYPE_FIXED_MARGIN)
# custom boundary setting bounds_default_low = (0, 0, 0)
bounds.set_type(Bounds.TYPE_CUSTOM) bounds_default_high = (0, 0, 0)
bounds_default_low = (model.minx, model.miny, model.minz) else:
bounds_default_high = (model.maxx, model.maxy, model.maxz) # custom boundary setting
# TODO: use the optparse conversion callback instead bounds.set_type(Bounds.TYPE_CUSTOM)
def parse_triple_float(text): bounds_default_low = (model.minx, model.miny, model.minz)
nums = text.split(",") bounds_default_high = (model.maxx, model.maxy, model.maxz)
if len(nums) != 3: # TODO: use the optparse conversion callback instead
return None def parse_triple_float(text):
result = [] nums = text.split(",")
for num in nums: if len(nums) != 3:
try: return None
result.append(float(num)) result = []
except ValueError: for num in nums:
if num == "": try:
result.append(0.0) result.append(float(num))
else: except ValueError:
return None if num == "":
return result result.append(0.0)
bounds_lower_nums = parse_triple_float(opts.bounds_lower) else:
if opts.bounds_lower and not bounds_lower_nums: return None
log.error("Failed to parse the lower boundary limit: %s" \ return result
% opts.bounds_lower) bounds_lower_nums = parse_triple_float(opts.bounds_lower)
sys.exit(EXIT_CODES["parsing_failed"]) if opts.bounds_lower and not bounds_lower_nums:
bounds_upper_nums = parse_triple_float(opts.bounds_upper) log.error("Failed to parse the lower boundary limit: %s" \
if opts.bounds_upper and not bounds_upper_nums: % opts.bounds_lower)
log.error("Failed to parse the upper boundary limit: %s" \ sys.exit(EXIT_CODES["parsing_failed"])
% opts.bounds_upper) bounds_upper_nums = parse_triple_float(opts.bounds_upper)
sys.exit(EXIT_CODES["parsing_failed"]) if opts.bounds_upper and not bounds_upper_nums:
if bounds_lower_nums is None: log.error("Failed to parse the upper boundary limit: %s" \
bounds_lower_nums = bounds_default_low % opts.bounds_upper)
if bounds_upper_nums is None: sys.exit(EXIT_CODES["parsing_failed"])
bounds_upper_nums = bounds_default_high if bounds_lower_nums is None:
# both lower and upper bounds were specified bounds_lower_nums = bounds_default_low
bounds.set_bounds(bounds_lower_nums, bounds_upper_nums) if bounds_upper_nums is None:
# adjust the bounding box according to the "boundary_mode" bounds_upper_nums = bounds_default_high
if opts.boundary_mode == "along": # both lower and upper bounds were specified
offset = (0, 0, 0) bounds.set_bounds(bounds_lower_nums, bounds_upper_nums)
elif opts.boundary_mode == "inside": # adjust the bounding box according to the "boundary_mode"
offset = (-0.5 * opts.tool_diameter, -0.5 * opts.tool_diameter, 0) if opts.boundary_mode == "along":
else: offset = (0, 0, 0)
# "outside" elif opts.boundary_mode == "inside":
offset = (0.5 * opts.tool_diameter, 0.5 * opts.tool_diameter, 0) offset = (-0.5 * opts.tool_diameter, -0.5 * opts.tool_diameter, 0)
process_bounds = Bounds(Bounds.TYPE_FIXED_MARGIN, offset, offset) else:
process_bounds.set_reference(bounds) # "outside"
tps.set_bounds(process_bounds) offset = (0.5 * opts.tool_diameter, 0.5 * opts.tool_diameter, 0)
if opts.export_gcode: process_bounds = Bounds(Bounds.TYPE_FIXED_MARGIN, offset, offset)
# generate the toolpath process_bounds.set_reference(bounds)
start_time = time.time() tps.set_bounds(process_bounds)
toolpath = pycam.Toolpath.Generator.generate_toolpath_from_settings( if opts.export_gcode:
model, tps, callback=progress_bar.update) # generate the toolpath
progress_bar.finish() start_time = time.time()
log.info("Toolpath generation time: %f" \ toolpath = pycam.Toolpath.Generator.generate_toolpath_from_settings(
% (time.time() - start_time)) model, tps, callback=progress_bar.update)
# write result progress_bar.finish()
if isinstance(toolpath, basestring): log.info("Toolpath generation time: %f" \
# an error occoured % (time.time() - start_time))
log.error(toolpath) # write result
else: if isinstance(toolpath, basestring):
description = "Toolpath generated via PyCAM v%s" % VERSION # an error occoured
tp_obj = Toolpath(toolpath, description, tps) log.error(toolpath)
handler, closer = get_output_handler(opts.export_gcode) else:
if handler is None: description = "Toolpath generated via PyCAM v%s" % VERSION
sys.exit(EXIT_CODES["write_output_failed"]) tp_obj = Toolpath(toolpath, description, tps)
pycam.Exporters.SimpleGCodeExporter.ExportPathList(handler, handler, closer = get_output_handler(opts.export_gcode)
tp_obj.get_path(), opts.unit_size, opts.tool_feedrate, if handler is None:
opts.tool_spindle_speed, sys.exit(EXIT_CODES["write_output_failed"])
safety_height=opts.safety_height, pycam.Exporters.SimpleGCodeExporter.ExportPathList(handler,
max_skip_safety_distance=opts.tool_diameter, tp_obj.get_path(), opts.unit_size, opts.tool_feedrate,
comment=tp_obj.get_meta_data()) opts.tool_spindle_speed,
closer() safety_height=opts.safety_height,
if opts.export_task_config: max_skip_safety_distance=opts.tool_diameter,
handler, closer = get_output_handler(opts.export_task_config) comment=tp_obj.get_meta_data())
if handler is None: closer()
sys.exit(EXIT_CODES["write_output_failed"]) if opts.export_task_config:
print >>handler, tps.get_string() handler, closer = get_output_handler(opts.export_task_config)
closer() if handler is None:
sys.exit(EXIT_CODES["write_output_failed"])
print >>handler, tps.get_string()
closer()
# keysyms does not seem to be recognized by pyinstaller
# There will be exceptions after any keypress without this line.
hiddenimports = ["gtk.keysyms"]
\ No newline at end of file
# -*- mode: python -*-
BASE_DIR = os.getcwd()
a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), os.path.join(BASE_DIR, 'pycam')],
pathex=[os.path.join(BASE_DIR, "src")],
hookspath=[os.path.join(BASE_DIR, "pyinstaller", "hooks")])
pyz = PYZ(a.pure)
data = [("pycam-project.ui", os.path.join(BASE_DIR, "share", "gtk-interface", "pycam-project.ui"), "DATA"),
("menubar.xml", os.path.join(BASE_DIR, "share", "gtk-interface", "menubar.xml"), "DATA"),
("logo_gui.bmp", os.path.join(BASE_DIR, "share", "gtk-interface", "logo_gui.bmp"), "DATA"),
]
# import VERSION for the output filename
sys.path.insert(0, os.path.join(BASE_DIR, "src"))
from pycam import VERSION
samples = Tree(os.path.join(BASE_DIR, "samples"), prefix="samples")
exe = EXE(pyz, data, samples,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name=os.path.join(BASE_DIR, "pycam-%s_standalone.exe" % VERSION),
debug=False,
strip=False,
upx=True,
console=True )
PyInstaller (http://pyinstaller.org) can be used to create standalone binaries for Windows.
How to build a standalone exe file (on Windows only):
1) download pyinstaller
2) run "cmd"
3) "cd PATH_TO_PYCAM"
4) "python PYINSTALLER_PATH\Build.py pyinstaller\pycam.spec"
5) test and upload the binary file "pycam-VERSION_standalone.exe"
Known issues:
- the "logo_gui.png" image is not displayed in the "Preferences" window
(the dll for png support is not included; the "pixbuf.loaders" file does not exist)
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment