* added a svg to gcode plugin (copied from cura-plugin, so there is probably still a lot of unused code in it)

* added 'svg' as a supported model file extension / format
* added a new dialog for customizing gcode conversion of svgs
This commit is contained in:
Philipp Engel 2014-11-07 09:23:12 -08:00
parent 39bd44bf70
commit e753ec6b4f
11 changed files with 1724 additions and 2 deletions

View file

@ -17,7 +17,7 @@ from .storage import LocalFileStorage
extensions = dict(
# extensions for 3d model files
model=dict(
stl=["stl"]
stl=["stl","svg"]
),
# extensions for printable machine code
machinecode=dict(

View file

@ -0,0 +1,363 @@
# coding=utf-8
from __future__ import absolute_import
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = "GNU Affero General Public License http://www.gnu.org/licenses/agpl.html"
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
import logging
import logging.handlers
import os
import flask
import octoprint.plugin
import octoprint.util
import octoprint.slicing
import octoprint.settings
default_settings = {
"svgtogcode_engine": None,
"default_profile": None,
"debug_logging": False
}
s = octoprint.plugin.plugin_settings("svgtogcode", defaults=default_settings)
from .profile import Profile
blueprint = flask.Blueprint("plugin.svgtogcode", __name__)
@blueprint.route("/import", methods=["POST"])
def importSvgToGcodeProfile():
import datetime
import tempfile
from octoprint.server import slicingManager
input_name = "file"
input_upload_name = input_name + "." + s.globalGet(["server", "uploads", "nameSuffix"])
input_upload_path = input_name + "." + s.globalGet(["server", "uploads", "pathSuffix"])
if input_upload_name in flask.request.values and input_upload_path in flask.request.values:
filename = flask.request.values[input_upload_name]
try:
profile_dict = Profile.from_svgtogcode_ini(flask.request.values[input_upload_path])
except Exception as e:
return flask.make_response("Something went wrong while converting imported profile: {message}".format(e.message), 500)
elif input_name in flask.request.files:
temp_file = tempfile.NamedTemporaryFile("wb", delete=False)
try:
temp_file.close()
upload = flask.request.files[input_name]
upload.save(temp_file.name)
profile_dict = Profile.from_svgtogcode_ini(temp_file.name)
except Exception as e:
return flask.make_response("Something went wrong while converting imported profile: {message}".format(e.message), 500)
finally:
os.remove(temp_file)
filename = upload.filename
else:
return flask.make_response("No file included", 400)
if profile_dict is None:
return flask.make_response("Could not convert Cura profile", 400)
name, _ = os.path.splitext(filename)
# default values for name, display name and description
profile_name = _sanitize_name(name)
profile_display_name = name
profile_description = "Imported from {filename} on {date}".format(filename=filename, date=octoprint.util.getFormattedDateTime(datetime.datetime.now()))
profile_allow_overwrite = False
# overrides
if "name" in flask.request.values:
profile_name = flask.request.values["name"]
if "displayName" in flask.request.values:
profile_display_name = flask.request.values["displayName"]
if "description" in flask.request.values:
profile_description = flask.request.values["description"]
if "allowOverwrite" in flask.request.values:
from octoprint.server.api import valid_boolean_trues
profile_allow_overwrite = flask.request.values["allowOverwrite"] in valid_boolean_trues
slicingManager.save_profile("svgtogcode",
profile_name,
profile_dict,
allow_overwrite=profile_allow_overwrite,
display_name=profile_display_name,
description=profile_description)
result = dict(
resource=flask.url_for("api.slicingGetSlicerProfile", slicer="svgtogcode", name=profile_name, _external=True),
displayName=profile_display_name,
description=profile_description
)
r = flask.make_response(flask.jsonify(result), 201)
r.headers["Location"] = result["resource"]
return r
class SvgToGcodePlugin(octoprint.plugin.SlicerPlugin,
octoprint.plugin.SettingsPlugin,
octoprint.plugin.TemplatePlugin,
octoprint.plugin.AssetPlugin,
octoprint.plugin.BlueprintPlugin,
octoprint.plugin.StartupPlugin):
def __init__(self):
self._logger = logging.getLogger("octoprint.plugins.svgtogcode")
self._svgtogcode_logger = logging.getLogger("octoprint.plugins.svgtogcode.engine")
# setup job tracking across threads
import threading
self._slicing_commands = dict()
self._slicing_commands_mutex = threading.Lock()
self._cancelled_jobs = []
self._cancelled_jobs_mutex = threading.Lock()
##~~ StartupPlugin API
def on_startup(self, host, port):
# setup our custom logger
svgtogcode_logging_handler = logging.handlers.RotatingFileHandler(s.getPluginLogfilePath(postfix="engine"), maxBytes=2*1024*1024)
svgtogcode_logging_handler.setFormatter(logging.Formatter("%(asctime)s %(message)s"))
svgtogcode_logging_handler.setLevel(logging.DEBUG)
self._svgtogcode_logger.addHandler(svgtogcode_logging_handler)
self._svgtogcode_logger.setLevel(logging.DEBUG if s.getBoolean(["debug_logging"]) else logging.CRITICAL)
self._svgtogcode_logger.propagate = False
##~~ BlueprintPlugin API
def get_blueprint(self):
global blueprint
return blueprint
##~~ AssetPlugin API
def get_asset_folder(self):
import os
return os.path.join(os.path.dirname(os.path.realpath(__file__)), "static")
def get_assets(self):
return {
"js": ["js/svgtogcode.js"],
"less": ["less/svgtogcode.less"],
"css": ["css/svgtogcode.css"]
}
##~~ SettingsPlugin API
def on_settings_load(self):
return dict(
svgtogcode_engine=s.get(["svgtogcode_engine"]),
default_profile=s.get(["default_profile"]),
debug_logging=s.getBoolean(["debug_logging"])
)
def on_settings_save(self, data):
if "svgtogcode_engine" in data and data["svgtogcode_engine"]:
s.set(["svgtogcode_engine"], data["svgtogcode_engine"])
if "default_profile" in data and data["default_profile"]:
s.set(["default_profile"], data["default_profile"])
if "debug_logging" in data:
old_debug_logging = s.getBoolean(["debug_logging"])
new_debug_logging = data["debug_logging"] in octoprint.settings.valid_boolean_trues
if old_debug_logging != new_debug_logging:
if new_debug_logging:
self._svgtogcode_logger.setLevel(logging.DEBUG)
else:
self._svgtogcode_logger.setLevel(logging.CRITICAL)
s.setBoolean(["debug_logging"], new_debug_logging)
##~~ TemplatePlugin API
def get_template_vars(self):
return dict(
_settings_menu_entry="SvgToGCode"
)
def get_template_folder(self):
import os
return os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates")
##~~ SlicerPlugin API
def is_slicer_configured(self):
svgtogcode_engine = s.get(["svgtogcode_engine"])
# return svgtogcode_engine is not None and os.path.exists(svgtogcode_engine)
return True
def get_slicer_properties(self):
return dict(
type="svgtogcode",
name="SvgToGCode",
same_device=True
)
def get_slicer_default_profile(self):
path = s.get(["default_profile"])
if not path:
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "profiles", "default.profile.yaml")
return self.get_slicer_profile(path)
def get_slicer_profile(self, path):
profile_dict = self._load_profile(path)
display_name = None
description = None
if "_display_name" in profile_dict:
display_name = profile_dict["_display_name"]
del profile_dict["_display_name"]
if "_description" in profile_dict:
description = profile_dict["_description"]
del profile_dict["_description"]
properties = self.get_slicer_properties()
return octoprint.slicing.SlicingProfile(properties["type"], "unknown", profile_dict, display_name=display_name, description=description)
def save_slicer_profile(self, path, profile, allow_overwrite=True, overrides=None):
new_profile = Profile.merge_profile(profile.data, overrides=overrides)
if profile.display_name is not None:
new_profile["_display_name"] = profile.display_name
if profile.description is not None:
new_profile["_description"] = profile.description
self._save_profile(path, new_profile, allow_overwrite=allow_overwrite)
def do_slice(self, model_path, machinecode_path=None, profile_path=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None):
if not profile_path:
profile_path = s.get(["default_profile"])
if not machinecode_path:
path, _ = os.path.splitext(model_path)
machinecode_path = path + ".gco"
if on_progress:
if not on_progress_args:
on_progress_args = ()
if not on_progress_kwargs:
on_progress_kwargs = dict()
self._svgtogcode_logger.info("### Slicing %s to %s using profile stored at %s" % (model_path, machinecode_path, profile_path))
engine_settings = self._convert_to_engine(profile_path)
# executable = s.get(["svgtogcode_engine"])
executable = "/Users/philipp/Documents/dev/MrBeam/mrbeaminkscapeextension/standalone.py"
if not executable:
return False, "Path to CuraEngine is not configured "
working_dir, _ = os.path.split(executable)
# args = ['"%s"' % executable, '-v', '-p']
# for k, v in engine_settings.items():
# args += ["-s", '"%s=%s"' % (k, str(v))]
# args += ['-o', '"%s"' % machinecode_path, '"%s"' % model_path]
dest_dir, dest_file = os.path.split(machinecode_path)
# args = ['"%s"' % executable, '-f "%s"' % dest_file, '-d "%s"' % dest_dir, '"%s"' % model_path]
args = ['"%s"' % executable, '-f "%s"' % dest_file, '-d "%s"' % dest_dir, '"%s"' % model_path]
import sarge
command = " ".join(args)
self._logger.info("Running %r in %s" % (command, working_dir))
try:
p = sarge.run(command, cwd=working_dir, async=True, stdout=sarge.Capture(), stderr=sarge.Capture())
try:
with self._slicing_commands_mutex:
self._slicing_commands[machinecode_path] = p.commands[0]
line_seen = False
while p.returncode is None:
line = p.stdout.readline(timeout=0.5)
if not line:
if line_seen:
break
else:
continue
line_seen = True
self._svgtogcode_logger.debug(line.strip())
if on_progress is not None:
pass
finally:
p.close()
with self._cancelled_jobs_mutex:
if machinecode_path in self._cancelled_jobs:
self._svgtogcode_logger.info("### Cancelled")
raise octoprint.slicing.SlicingCancelled()
self._svgtogcode_logger.info("### Finished, returncode %d" % p.returncode)
if p.returncode == 0:
return True, None
else:
self._logger.warn("Could not slice via Cura, got return code %r" % p.returncode)
return False, "Got returncode %r" % p.returncode
except octoprint.slicing.SlicingCancelled as e:
raise e
except:
self._logger.exception("Could not slice via Cura, got an unknown error")
return False, "Unknown error, please consult the log file"
finally:
with self._cancelled_jobs_mutex:
if machinecode_path in self._cancelled_jobs:
self._cancelled_jobs.remove(machinecode_path)
with self._slicing_commands_mutex:
if machinecode_path in self._slicing_commands:
del self._slicing_commands[machinecode_path]
self._svgtogcode_logger.info("-" * 40)
def cancel_slicing(self, machinecode_path):
with self._slicing_commands_mutex:
if machinecode_path in self._slicing_commands:
with self._cancelled_jobs_mutex:
self._cancelled_jobs.append(machinecode_path)
self._slicing_commands[machinecode_path].terminate()
self._logger.info("Cancelled slicing of %s" % machinecode_path)
def _load_profile(self, path):
import yaml
profile_dict = dict()
with open(path, "r") as f:
try:
profile_dict = yaml.safe_load(f)
except:
raise IOError("Couldn't read profile from {path}".format(path=path))
return profile_dict
def _save_profile(self, path, profile, allow_overwrite=True):
if not allow_overwrite and os.path.exists(path):
raise IOError("Cannot overwrite {path}".format(path=path))
import yaml
with open(path, "wb") as f:
yaml.safe_dump(profile, f, default_flow_style=False, indent=" ", allow_unicode=True)
def _convert_to_engine(self, profile_path):
profile = Profile(self._load_profile(profile_path))
return profile.convert_to_engine()
def _sanitize_name(name):
if name is None:
return None
if "/" in name or "\\" in name:
raise ValueError("name must not contain / or \\")
import string
valid_chars = "-_.() {ascii}{digits}".format(ascii=string.ascii_letters, digits=string.digits)
sanitized_name = ''.join(c for c in name if c in valid_chars)
sanitized_name = sanitized_name.replace(" ", "_")
return sanitized_name.lower()
__plugin_name__ = "svgtogcode"
__plugin_version__ = "0.1"
__plugin_implementations__ = [SvgToGcodePlugin()]

View file

@ -0,0 +1,825 @@
# coding=utf-8
from __future__ import absolute_import
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
from . import s
import re
class SupportLocationTypes(object):
NONE = "none"
TOUCHING_BUILDPLATE = "buildplate"
EVERYWHERE = "everywhere"
class SupportDualTypes(object):
BOTH = "both"
FIRST = "first"
SECOND = "second"
class SupportTypes(object):
GRID = "grid"
LINES = "lines"
class PlatformAdhesionTypes(object):
NONE = "none"
BRIM = "brim"
RAFT = "raft"
class MachineShapeTypes(object):
SQUARE = "square"
CIRCULAR = "circular"
class GcodeFlavors(object):
REPRAP = "reprap"
REPRAP_VOLUME = "reprap_volume"
ULTIGCODE = "ultigcode"
MAKERBOT = "makerbot"
BFB = "bfb"
MACH3 = "mach3"
defaults = dict(
layer_height=0.1,
wall_thickness=0.8,
solid_layer_thickness=0.6,
nozzle_size=0.4,
print_temperature=[220, 0, 0, 0],
print_bed_temperature=70,
platform_adhesion=PlatformAdhesionTypes.NONE,
filament_diameter=[2.85, 0, 0, 0],
filament_flow=100.0,
bottom_thickness=0.3,
first_layer_width_factor=100.0,
object_sink=0.0,
fill_density=20,
solid_top=True,
solid_bottom=True,
fill_overlap=15,
# speeds
print_speed=50.0,
travel_speed=150.0,
bottom_layer_speed=20.0,
infill_speed=0.0,
outer_shell_speed=0.0,
inner_shell_speed=0.0,
# dual extrusion
overlap_dual=0.15,
wipe_tower=False,
wipe_tower_volume=15,
ooze_shield=False,
# retraction
retraction_enable=True,
retraction_speed=40.0,
retraction_amount=4.5,
retraction_dual_amount=16.5,
retraction_min_travel=1.5,
retraction_combing=True,
retraction_minimal_extrusion=0.02,
retraction_hop=0.0,
# cooling
cool_min_layer_time=5,
fan_enabled=True,
fan_full_height=0.5,
fan_speed=100,
fan_speed_max=100,
cool_min_feedrate=10,
cool_head_lift=False,
# support
support=SupportLocationTypes.NONE,
support_type=SupportTypes.GRID,
support_angle=60.0,
support_fill_rate=15,
support_xy_distance=0.7,
support_z_distance=0.15,
support_dual_extrusion=SupportDualTypes.BOTH,
# platform adhesion
skirt_line_count=1,
skirt_gap=3.0,
skirt_minimal_length=150.0,
brim_line_count=20,
raft_margin=5.0,
raft_line_spacing=3.0,
raft_base_thickness=0.3,
raft_base_linewidth=1.0,
raft_interface_thickness=0.27,
raft_interface_linewidth=0.4,
raft_airgap=0.22,
raft_surface_layers=2,
# repairing
fix_horrible_union_all_type_a=True,
fix_horrible_union_all_type_b=False,
fix_horrible_use_open_bits=False,
fix_horrible_extensive_stitching=False,
# extras
spiralize=False,
follow_surface=False,
machine_width=205,
machine_depth=205,
machine_center_is_zero=False,
has_heated_bed=False,
gcode_flavor=GcodeFlavors.REPRAP,
extruder_amount=1,
steps_per_e=0,
start_gcode=[
# 1 extruder
""";Sliced at: {day} {date} {time}
;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line
;M109 S{print_temperature} ;Uncomment to add your own temperature line
G21 ;metric values
G90 ;absolute positioning
M82 ;set extruder to absolute mode
M107 ;start with the fan off
G28 X0 Y0 ;move X/Y to min endstops
G28 Z0 ;move Z to min endstops
G1 Z15.0 F{travel_speed} ;move the platform down 15mm
G92 E0 ;zero the extruded length
G1 F200 E3 ;extrude 3mm of feed stock
G92 E0 ;zero the extruded length again
G1 F{travel_speed}
;Put printing message on LCD screen
M117 Printing...
""",
# 2 extruders
""";Sliced at: {day} {date} {time}
;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line
;M104 S{print_temperature} ;Uncomment to add your own temperature line
;M109 T1 S{print_temperature2} ;Uncomment to add your own temperature line
;M109 T0 S{print_temperature} ;Uncomment to add your own temperature line
G21 ;metric values
G90 ;absolute positioning
M107 ;start with the fan off
G28 X0 Y0 ;move X/Y to min endstops
G28 Z0 ;move Z to min endstops
G1 Z15.0 F{travel_speed} ;move the platform down 15mm
T1 ;Switch to the 2nd extruder
G92 E0 ;zero the extruded length
G1 F200 E10 ;extrude 10mm of feed stock
G92 E0 ;zero the extruded length again
G1 F200 E-{retraction_dual_amount}
T0 ;Switch to the first extruder
G92 E0 ;zero the extruded length
G1 F200 E10 ;extrude 10mm of feed stock
G92 E0 ;zero the extruded length again
G1 F{travel_speed}
;Put printing message on LCD screen
M117 Printing...
""",
# 3 extruders
""";Sliced at: {day} {date} {time}
;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line
;M104 S{print_temperature} ;Uncomment to add your own temperature line
;M109 T1 S{print_temperature2} ;Uncomment to add your own temperature line
;M109 T0 S{print_temperature} ;Uncomment to add your own temperature line
G21 ;metric values
G90 ;absolute positioning
M107 ;start with the fan off
G28 X0 Y0 ;move X/Y to min endstops
G28 Z0 ;move Z to min endstops
G1 Z15.0 F{travel_speed} ;move the platform down 15mm
T2 ;Switch to the 2nd extruder
G92 E0 ;zero the extruded length
G1 F200 E10 ;extrude 10mm of feed stock
G92 E0 ;zero the extruded length again
G1 F200 E-{retraction_dual_amount}
T1 ;Switch to the 2nd extruder
G92 E0 ;zero the extruded length
G1 F200 E10 ;extrude 10mm of feed stock
G92 E0 ;zero the extruded length again
G1 F200 E-{retraction_dual_amount}
T0 ;Switch to the first extruder
G92 E0 ;zero the extruded length
G1 F200 E10 ;extrude 10mm of feed stock
G92 E0 ;zero the extruded length again
G1 F{travel_speed}
;Put printing message on LCD screen
M117 Printing...
""",
# 4 extruders
""";Sliced at: {day} {date} {time}
;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line
;M104 S{print_temperature} ;Uncomment to add your own temperature line
;M109 T2 S{print_temperature2} ;Uncomment to add your own temperature line
;M109 T1 S{print_temperature2} ;Uncomment to add your own temperature line
;M109 T0 S{print_temperature} ;Uncomment to add your own temperature line
G21 ;metric values
G90 ;absolute positioning
M107 ;start with the fan off
G28 X0 Y0 ;move X/Y to min endstops
G28 Z0 ;move Z to min endstops
G1 Z15.0 F{travel_speed} ;move the platform down 15mm
T3 ;Switch to the 4th extruder
G92 E0 ;zero the extruded length
G1 F200 E10 ;extrude 10mm of feed stock
G92 E0 ;zero the extruded length again
G1 F200 E-{retraction_dual_amount}
T2 ;Switch to the 3th extruder
G92 E0 ;zero the extruded length
G1 F200 E10 ;extrude 10mm of feed stock
G92 E0 ;zero the extruded length again
G1 F200 E-{retraction_dual_amount}
T1 ;Switch to the 2nd extruder
G92 E0 ;zero the extruded length
G1 F200 E10 ;extrude 10mm of feed stock
G92 E0 ;zero the extruded length again
G1 F200 E-{retraction_dual_amount}
T0 ;Switch to the first extruder
G92 E0 ;zero the extruded length
G1 F200 E10 ;extrude 10mm of feed stock
G92 E0 ;zero the extruded length again
G1 F{travel_speed}
;Put printing message on LCD screen
M117 Printing...
"""
],
end_gcode=[
# 1 extruder
""";End GCode
M104 S0 ;extruder heater off
M140 S0 ;heated bed heater off (if you have it)
G91 ;relative positioning
G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way
M84 ;steppers off
G90 ;absolute positioning
;{profile_string}
""",
# 2 extruders
""";End GCode
M104 T0 S0 ;extruder heater off
M104 T1 S0 ;extruder heater off
M140 S0 ;heated bed heater off (if you have it)
G91 ;relative positioning
G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way
M84 ;steppers off
G90 ;absolute positioning
;{profile_string}
""",
# 3 extruders
""";End GCode
M104 T0 S0 ;extruder heater off
M104 T1 S0 ;extruder heater off
M104 T2 S0 ;extruder heater off
M140 S0 ;heated bed heater off (if you have it)
G91 ;relative positioning
G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way
M84 ;steppers off
G90 ;absolute positioning
;{profile_string}
""",
# 4 extruders
""";End GCode
M104 T0 S0 ;extruder heater off
M104 T1 S0 ;extruder heater off
M104 T2 S0 ;extruder heater off
M104 T3 S0 ;extruder heater off
M140 S0 ;heated bed heater off (if you have it)
G91 ;relative positioning
G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way
M84 ;steppers off
G90 ;absolute positioning
;{profile_string}
"""
],
preSwitchExtruder_gcode=""";Switch between the current extruder and the next extruder, when printing with multiple extruders.
;This code is added before the T(n)
""",
postSwitchExtruder_gcode=""";Switch between the current extruder and the next extruder, when printing with multiple extruders.
;This code is added after the T(n)
"""
)
class Profile(object):
regex_extruder_offset = re.compile("extruder_offset_([xy])(\d)")
regex_filament_diameter = re.compile("filament_diameter(\d?)")
regex_print_temperature = re.compile("print_temperature(\d?)")
regex_strip_comments = re.compile(";.*$", flags=re.MULTILINE)
@classmethod
def from_svgtogcode_ini(cls, path):
import os
if not os.path.exists(path) or not os.path.isfile(path):
return None
import ConfigParser
config = ConfigParser.ConfigParser()
try:
config.read(path)
except:
return None
arrayified_options = ["print_temperature", "filament_diameter", "start.gcode", "end.gcode"]
translated_options = dict(
inset0_speed="outer_shell_speed",
insetx_speed="inner_shell_speed",
layer0_width_factor="first_layer_width_factor",
simple_mode="follow_surface",
)
translated_options["start.gcode"] = "start_gcode"
translated_options["end.gcode"] = "end_gcode"
value_conversions = dict(
platform_adhesion={
"None": PlatformAdhesionTypes.NONE,
"Brim": PlatformAdhesionTypes.BRIM,
"Raft": PlatformAdhesionTypes.RAFT
},
support={
"None": SupportLocationTypes.NONE,
"Touching buildplate": SupportLocationTypes.TOUCHING_BUILDPLATE,
"Everywhere": SupportLocationTypes.EVERYWHERE
},
support_type={
"Lines": SupportTypes.LINES,
"Grid": SupportTypes.GRID
},
support_dual_extrusion={
"Both": SupportDualTypes.BOTH,
"First extruder": SupportDualTypes.FIRST,
"Second extruder": SupportDualTypes.SECOND
}
)
result = dict()
for section in config.sections():
if not section in ("profile", "alterations"):
continue
for option in config.options(section):
ignored = False
key = option
# try to fetch the value in the correct type
try:
value = config.getboolean(section, option)
except:
# no boolean, try int
try:
value = config.getint(section, option)
except:
# no int, try float
try:
value = config.getfloat(section, option)
except:
# no float, use str
value = config.get(section, option)
index = None
for opt in arrayified_options:
if key.startswith(opt):
if key == opt:
index = 0
else:
try:
# try to convert the target index, e.g. print_temperature2 => print_temperature[1]
index = int(key[len(opt):]) - 1
except ValueError:
# ignore entries for which that fails
ignored = True
key = opt
break
if ignored:
continue
if key in translated_options:
# if the key has to be translated to a new value, do that now
key = translated_options[key]
if key in value_conversions and value in value_conversions[key]:
value = value_conversions[key][value]
if index is not None:
# if we have an array to fill, make sure the target array exists and has the right size
if not key in result:
result[key] = []
if len(result[key]) <= index:
for n in xrange(index - len(result[key]) + 1):
result[key].append(None)
result[key][index] = value
else:
# just set the value if there's no array to fill
result[key] = value
# merge it with our default settings, the imported profile settings taking precedence
return cls.merge_profile(result)
@classmethod
def merge_profile(cls, profile, overrides=None):
import copy
result = copy.deepcopy(defaults)
for k in result.keys():
profile_value = None
override_value = None
if k in profile:
profile_value = profile[k]
if overrides and k in overrides:
override_value = overrides[k]
if profile_value is None and override_value is None:
# neither override nor profile, no need to handle this key further
continue
if k in ("start_gcode", "end_gcode"):
# the array fields need some special treatment. Basically something like this:
#
# override_value: [None, "b"]
# profile_value : ["a" , None, "c"]
# default_value : ["d" , "e" , "f", "g"]
#
# should merge to something like this:
#
# ["a" , "b" , "c", "g"]
#
# So override > profile > default, if neither override nor profile value are available
# the default value should just be left as is
for x in xrange(len(result[k])):
if override_value is not None and x < len(override_value) and override_value[x] is not None:
# we have an override value for this location, so we use it
result[k][x] = override_value[x]
elif profile_value is not None and x < len(profile_value) and profile_value[x] is not None:
# we have a profile value for this location, so we use it
result[k][x] = profile_value[x]
else:
# just change the result value to the override_value if available, otherwise to the profile_value if
# that is given, else just leave as is
if override_value is not None:
result[k] = override_value
elif profile_value is not None:
result[k] = profile_value
return result
def __init__(self, profile):
self.profile = profile
def get(self, key):
if key in self.profile:
return self.profile[key]
elif key in defaults:
return defaults[key]
else:
return None
def get_int(self, key, default=None):
value = self.get(key)
if value is None:
return default
try:
return int(value)
except ValueError:
return default
def get_float(self, key, default=None):
value = self.get(key)
if value is None:
return default
if isinstance(value, (str, unicode, basestring)):
value = value.replace(",", ".").strip()
try:
return float(value)
except ValueError:
return default
def get_boolean(self, key, default=None):
value = self.get(key)
if value is None:
return default
if isinstance(value, bool):
return value
elif isinstance(value, (str, unicode, basestring)):
return value.lower() == "true" or value.lower() == "yes" or value.lower() == "on" or value == "1"
elif isinstance(value, (int, float)):
return value > 0
else:
return value == True
def get_microns(self, key, default=None):
value = self.get_float(key, default=None)
if value is None:
return default
return int(value * 1000)
def get_gcode_template(self, key):
extruder_count = s.globalGetInt(["printerParameters", "numExtruders"])
if key in self.profile:
gcode = self.profile[key]
else:
gcode = defaults[key]
if key in ("start_gcode", "end_gcode"):
return gcode[extruder_count-1]
else:
return gcode
def get_machine_extruder_offset(self, extruder, axis):
extruder_offsets = s.globalGet(["printerParameters", "extruderOffsets"])
if extruder >= len(extruder_offsets):
return 0.0
if axis.lower() not in ("x", "y"):
return 0.0
return extruder_offsets[extruder][axis.lower()]
def get_profile_string(self):
import base64
import zlib
import copy
profile = copy.deepcopy(defaults)
profile.update(self.profile)
for key in ("print_temperature", "print_temperature2", "print_temperature3", "print_temperature4",
"filament_diameter", "filament_diameter2", "filament_diameter3", "filament_diameter4"):
profile[key] = self.get(key)
result = []
for k, v in profile.items():
if isinstance(v, (str, unicode)):
result.append("{k}={v}".format(k=k, v=v.encode("utf-8")))
else:
result.append("{k}={v}".format(k=k, v=v))
return base64.b64encode(zlib.compress("\b".join(result), 9))
def replaceTagMatch(self, m):
import time
pre = m.group(1)
tag = m.group(2)
if tag == 'time':
return pre + time.strftime('%H:%M:%S')
if tag == 'date':
return pre + time.strftime('%d-%m-%Y')
if tag == 'day':
return pre + ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][int(time.strftime('%w'))]
if tag == 'profile_string':
return pre + 'svgtogcode_OCTO_PROFILE_STRING:%s' % (self.get_profile_string())
if pre == 'F' and tag == 'max_z_speed':
f = self.get_float("travel_speed") * 60
elif pre == 'F' and tag in ['print_speed', 'retraction_speed', 'travel_speed', 'bottom_layer_speed', 'cool_min_feedrate']:
f = self.get_float(tag) * 60
elif self.get(tag):
f = self.get(tag)
else:
return '%s?%s?' % (pre, tag)
if (f % 1) == 0:
return pre + str(int(f))
return pre + str(f)
def get_gcode(self, key):
extruder_count = s.globalGetInt(["printerParameters", "numExtruders"])
prefix = ""
postfix = ""
if self.get("gcode_flavor") == GcodeFlavors.ULTIGCODE:
if key == "end_gcode":
return "M25 ;Stop reading from this point on.\n;svgtogcode_PROFILE_STRING:%s\n" % (self.get_profile_string())
return ""
if key == "start_gcode":
contents = self.get_gcode_template("start_gcode")
e_steps = self.get_float("steps_per_e")
if e_steps > 0:
prefix += "M92 E{e_steps}\n" % (e_steps)
temp = self.get_float("print_temperature")
bedTemp = 0
if self.get_boolean("has_heated_bed"):
bedTemp = self.get_float("print_bed_temperature")
include_bed_temps = bedTemp > 0 and not "{print_bed_temperature}" in Profile.regex_strip_comments.sub("", contents)
if include_bed_temps:
prefix += "M140 S{print_bed_temperature}\n"
if temp > 0 and not "{print_temperature}" in Profile.regex_strip_comments.sub("", contents):
if extruder_count > 0:
def temp_line(temp, extruder, template):
t = temp
if extruder > 0:
print_temp = self.get_float("print_temperature%d" % (extruder + 1))
if print_temp > 0:
t = print_temp
return template.format(extruder=extruder, temp=t)
for n in xrange(1, extruder_count):
prefix += temp_line(temp, n, "M104 T{extruder} S{temp}\n")
for n in xrange(0, extruder_count):
prefix += temp_line(temp, n, "M109 T{extruder} S{temp}\n")
prefix += "T0\n"
else:
prefix += "M109 S{temp}\n".format(temp=temp)
if include_bed_temps:
prefix += "M190 S{print_bed_temperature}\n".format(bedTemp=bedTemp)
else:
contents = self.get_gcode_template(key)
return unicode(prefix + re.sub("(.)\{([^\}]*)\}", self.replaceTagMatch, contents).rstrip() + '\n' + postfix).strip().encode('utf-8') + '\n'
def calculate_edge_width_and_line_count(self):
wall_thickness = self.get_float("wall_thickness")
nozzle_size = self.get_float("nozzle_size")
if self.get_boolean("spiralize") or self.get_boolean("follow_surface"):
return wall_thickness, 1
if wall_thickness < 0.01:
return nozzle_size, 0
if wall_thickness < nozzle_size:
return wall_thickness, 1
edge_width = None
line_count = int(wall_thickness / (nozzle_size - 0.0001))
if line_count < 1:
edge_width = nozzle_size
line_count = 1
line_width = wall_thickness / line_count
line_width_alt = wall_thickness / (line_count + 1)
if line_width > nozzle_size * 1.5:
return line_width_alt, line_count + 1
if not edge_width:
edge_width = line_width
return edge_width, line_count
def calculate_solid_layer_count(self):
layer_height = self.get_float("layer_height")
solid_thickness = self.get_float("solid_layer_thickness")
if layer_height == 0.0:
return 1
import math
return int(math.ceil(solid_thickness / (layer_height - 0.0001)))
def calculate_minimal_extruder_count(self):
extruder_count = s.globalGetInt(["printerParameters", "numExtruders"])
if extruder_count < 2:
return 1
if self.get("support") == SupportLocationTypes.NONE:
return 1
if self.get("support_dual_extrusion") == SupportDualTypes.SECOND:
return 2
return 1
def convert_to_engine(self):
settings = {
"--engraving-laser-speed": self.get_microns("layer_height"),
"initialLayerThickness": self.get_microns("bottom_thickness") if self.get_float("bottom_thickness") > 0.0 else self.get_microns("layer_height"),
}
for extruder in range(1, extruder_count):
for axis in ("x", "y"):
settings["extruderOffset[{extruder}].{axis}".format(extruder=extruder, axis=axis.upper())] = self.get_machine_extruder_offset(extruder, axis)
fanFullHeight = self.get_microns("fan_full_height")
settings["fanFullOnLayerNr"] = (fanFullHeight - settings["initialLayerThickness"] - 1) / settings["layerThickness"] + 1
if settings["fanFullOnLayerNr"] < 0:
settings["fanFullOnLayerNr"] = 0
if self.get("support_type") == SupportTypes.LINES:
settings["supportType"] = 1
# infill
if self.get_float("fill_density") == 0:
settings["sparseInfillLineDistance"] = -1
elif self.get_float("fill_density") == 100:
settings["sparseInfillLineDistance"] = settings["extrusionWidth"]
settings["downSkinCount"] = 10000
settings["upSkinCount"] = 10000
else:
settings["sparseInfillLineDistance"] = int(100 * edge_width * 1000 / self.get_float("fill_density"))
# brim/raft/skirt
if self.get("platform_adhesion") == PlatformAdhesionTypes.BRIM:
settings["skirtDistance"] = 0
settings["skirtLineCount"] = self.get_int("brim_line_count")
elif self.get("platform_adhesion") == PlatformAdhesionTypes.RAFT:
settings["skirtDistance"] = 0
settings["skirtLineCount"] = 0
settings["raftMargin"] = self.get_microns("raft_margin")
settings["raftLineSpacing"] = self.get_microns("raft_line_spacing")
settings["raftBaseThickness"] = self.get_microns("raft_base_thickness")
settings["raftBaseLinewidth"] = self.get_microns("raft_base_linewidth")
settings["raftInterfaceThickness"] = self.get_microns("raft_interface_thickness")
settings["raftInterfaceLinewidth"] = self.get_microns("raft_interface_linewidth")
settings["raftInterfaceLineSpacing"] = self.get_microns("raft_interface_linewidth") * 2
settings["raftAirGapLayer0"] = self.get_microns("raft_airgap")
settings["raftBaseSpeed"] = self.get_int("bottom_layer_speed")
settings["raftFanSpeed"] = 100
settings["raftSurfaceThickness"] = settings["raftInterfaceThickness"]
settings["raftSurfaceLinewidth"] = int(edge_width * 1000)
settings["raftSurfaceLineSpacing"] = int(edge_width * 1000 * 0.9)
settings["raftSurfaceLayers"] = self.get_int("raft_surface_layers")
settings["raftSurfaceSpeed"] = self.get_int("bottom_layer_speed")
else:
settings["skirtDistance"] = self.get_microns("skirt_gap")
settings["skirtLineCount"] = self.get_int("skirt_line_count")
settings["skirtMinLength"] = self.get_microns("skirt_minimal_length")
# fixing
if self.get_boolean("fix_horrible_union_all_type_a"):
settings["fixHorrible"] |= 0x01
if self.get_boolean("fix_horrible_union_all_type_b"):
settings["fixHorrible"] |= 0x02
if self.get_boolean("fix_horrible_use_open_bits"):
settings["fixHorrible"] |= 0x10
if self.get_boolean("fix_horrible_extensive_stitching"):
settings["fixHorrible"] |= 0x04
if settings["layerThickness"] <= 0:
settings["layerThickness"] = 1000
# gcode flavor
if self.get("gcode_flavor") == GcodeFlavors.ULTIGCODE:
settings["gcodeFlavor"] = 1
elif self.get("gcode_flavor") == GcodeFlavors.MAKERBOT:
settings["gcodeFlavor"] = 2
elif self.get("gcode_flavor") == GcodeFlavors.BFB:
settings["gcodeFlavor"] = 3
elif self.get("gcode_flavor") == GcodeFlavors.MACH3:
settings["gcodeFlavor"] = 4
elif self.get("gcode_flavor") == GcodeFlavors.REPRAP_VOLUME:
settings["gcodeFlavor"] = 5
# extras
if self.get_boolean("spiralize"):
settings["spiralizeMode"] = 1
if self.get_boolean("follow_surface"):
settings["simpleMode"] = 1
# dual extrusion
if self.get_boolean("wipe_tower") and extruder_count > 1:
import math
settings["wipeTowerSize"] = int(math.sqrt(self.get_float("wipe_tower_volume") * 1000 * 1000 * 1000 / settings["layerThickness"]))
if self.get_boolean("ooze_shield"):
settings["enableOozeShield"] = 1
return settings

View file

@ -0,0 +1,4 @@
layer_height: 0.2
print_temperature:
- 220.0
- 220.0

View file

@ -0,0 +1 @@
table th.settings_plugin_svgtogcode_profiles_key,table td.settings_plugin_svgtogcode_profiles_key{text-overflow:ellipsis;text-align:left;width:200px}table th.settings_plugin_svgtogcode_profiles_name,table td.settings_plugin_svgtogcode_profiles_name{text-overflow:ellipsis;text-align:left}table th.settings_plugin_svgtogcode_profiles_actions,table td.settings_plugin_svgtogcode_profiles_actions{text-align:center;width:100px}table th.settings_plugin_svgtogcode_profiles_actions a,table td.settings_plugin_svgtogcode_profiles_actions a{text-decoration:none;color:#000}table th.settings_plugin_svgtogcode_profiles_actions a.disabled,table td.settings_plugin_svgtogcode_profiles_actions a.disabled{color:#ccc;cursor:default}

View file

@ -0,0 +1,191 @@
$(function() {
function SvgToGcodeViewModel(parameters) {
var self = this;
self.loginState = parameters[0];
self.settingsViewModel = parameters[1];
self.slicingViewModel = parameters[2];
self.fileName = ko.observable();
self.placeholderName = ko.observable();
self.placeholderDisplayName = ko.observable();
self.placeholderDescription = ko.observable();
self.profileName = ko.observable();
self.profileDisplayName = ko.observable();
self.profileDescription = ko.observable();
self.profileAllowOverwrite = ko.observable(true);
self.uploadElement = $("#settings-svgtogcode-import");
self.uploadButton = $("#settings-svgtogcode-import-start");
self.profiles = new ItemListHelper(
"plugin_svgtogcode_profiles",
{
"id": function(a, b) {
if (a["key"].toLocaleLowerCase() < b["key"].toLocaleLowerCase()) return -1;
if (a["key"].toLocaleLowerCase() > b["key"].toLocaleLowerCase()) return 1;
return 0;
},
"name": function(a, b) {
// sorts ascending
var aName = a.name();
if (aName === undefined) {
aName = "";
}
var bName = b.name();
if (bName === undefined) {
bName = "";
}
if (aName.toLocaleLowerCase() < bName.toLocaleLowerCase()) return -1;
if (aName.toLocaleLowerCase() > bName.toLocaleLowerCase()) return 1;
return 0;
}
},
{},
"id",
[],
[],
5
);
self._sanitize = function(name) {
return name.replace(/[^a-zA-Z0-9\-_\.\(\) ]/g, "").replace(/ /g, "_");
};
self.uploadElement.fileupload({
dataType: "json",
maxNumberOfFiles: 1,
autoUpload: false,
add: function(e, data) {
if (data.files.length == 0) {
return false;
}
self.fileName(data.files[0].name);
var name = self.fileName().substr(0, self.fileName().lastIndexOf("."));
self.placeholderName(self._sanitize(name).toLowerCase());
self.placeholderDisplayName(name);
self.placeholderDescription("Imported from " + self.fileName() + " on " + formatDate(new Date().getTime() / 1000));
self.uploadButton.on("click", function() {
var form = {
allowOverwrite: self.profileAllowOverwrite()
};
if (self.profileName() !== undefined) {
form["name"] = self.profileName();
}
if (self.profileDisplayName() !== undefined) {
form["displayName"] = self.profileDisplayName();
}
if (self.profileDescription() !== undefined) {
form["description"] = self.profileDescription();
}
data.formData = form;
data.submit();
});
},
done: function(e, data) {
self.fileName(undefined);
self.placeholderName(undefined);
self.placeholderDisplayName(undefined);
self.placeholderDescription(undefined);
self.profileName(undefined);
self.profileDisplayName(undefined);
self.profileDescription(undefined);
self.profileAllowOverwrite(true);
$("#settings-plugin-svgtogcode-import").modal("hide");
self.requestData();
self.slicingViewModel.requestData();
}
});
self.removeProfile = function(data) {
if (!data.resource) {
return;
}
self.profiles.removeItem(function(item) {
return (item.key == data.key);
});
$.ajax({
url: data.resource(),
type: "DELETE",
success: function() {
self.requestData();
self.slicingViewModel.requestData();
}
});
};
self.makeProfileDefault = function(data) {
if (!data.resource) {
return;
}
_.each(self.profiles.items(), function(item) {
item.isdefault(false);
});
var item = self.profiles.getItem(function(item) {
return item.key == data.key;
});
if (item !== undefined) {
item.isdefault(true);
}
$.ajax({
url: data.resource(),
type: "PATCH",
dataType: "json",
data: JSON.stringify({default: true}),
contentType: "application/json; charset=UTF-8",
success: function() {
self.requestData();
}
});
};
self.showImportProfileDialog = function() {
$("#settings_plugin_svgtogcode_import").modal("show");
};
self.requestData = function() {
$.ajax({
url: API_BASEURL + "slicing/svgtogcode/profiles",
type: "GET",
dataType: "json",
success: self.fromResponse
});
};
self.fromResponse = function(data) {
var profiles = [];
_.each(_.keys(data), function(key) {
profiles.push({
key: key,
name: ko.observable(data[key].displayName),
description: ko.observable(data[key].description),
isdefault: ko.observable(data[key].default),
resource: ko.observable(data[key].resource)
});
});
self.profiles.updateItems(profiles);
};
self.onBeforeBinding = function () {
self.settings = self.settingsViewModel.settings;
self.requestData();
};
}
// view model class, parameters for constructor, container to bind to
ADDITIONAL_VIEWMODELS.push([SvgToGcodeViewModel, ["loginStateViewModel", "settingsViewModel", "slicingViewModel"], document.getElementById("settings_plugin_svgtogcode_dialog")]);
});

View file

@ -0,0 +1,29 @@
table {
th, td {
&.settings_plugin_svgtogcode_profiles_key {
text-overflow: ellipsis;
text-align: left;
width: 200px;
}
&.settings_plugin_svgtogcode_profiles_name {
text-overflow: ellipsis;
text-align: left;
}
&.settings_plugin_svgtogcode_profiles_actions {
text-align: center;
width: 100px;
a {
text-decoration: none;
color: #000;
&.disabled {
color: #ccc;
cursor: default;
}
}
}
}
}

View file

@ -0,0 +1,247 @@
<div id="settings_plugin_svgtogcode_dialog" data-bind="allowBindings: true">
<h4>{{ _('General') }}</h4>
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="settings-svgtogcode-path">{{ _('Path to CuraEngine') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.plugins.svgtogcode.svgtogcode_engine">
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: settings.plugins.svgtogcode.debug_logging"> {{ _('Log the output of CuraEngine to plugin_svgtogcode_engine.log') }}
</label>
</div>
</div>
</form>
<h4>{{ _('Profiles') }}</h4>
<div class="pull-right">
<small>{{ _('Sort by') }}: <a href="#" data-bind="click: function() { profiles.changeSorting('id'); }">{{ _('Identifier') }} ({{ _('ascending') }})</a> | <a href="#" data-bind="click: function() { profiles.changeSorting('name'); }">{{ _('Name') }} ({{ _('ascending') }})</a></small>
</div>
<table class="table table-striped table-hover table-condensed table-hover">
<thead>
<tr>
<th class="settings_plugin_svgtogcode_profiles_key">{{ _('Identifier') }}</th>
<th class="settings_plugin_svgtogcode_profiles_name">{{ _('Name') }}</th>
<th class="settings_plugin_svgtogcode_profiles_actions">{{ _('Actions') }}</th>
</tr>
</thead>
<tbody data-bind="foreach: profiles.paginatedItems">
<tr data-bind="attr: {title: description}">
<td class="settings_plugin_svgtogcode_profiles_key"><span class="icon-star" data-bind="invisible: !isdefault()"></span> <span data-bind="text: key"></span></td>
<td class="settings_plugin_svgtogcode_profiles_name" data-bind="text: name"></td>
<td class="settings_plugin_svgtogcode_profiles_actions">
<a href="#" class="icon-star" title="{{ _('Make default') }}" data-bind="enable: !isdefault(), css: {disabled: isdefault()}, click: function() { if (!$data.isdefault()) { $root.makeProfileDefault($data); } }"></a>&nbsp;|&nbsp;<a href="#" class="icon-trash" title="{{ _('Delete Profile') }}" data-bind="enable: !isdefault(), css: {disabled: isdefault()}, click: function() { if (!$data.isdefault()) { $root.removeProfile($data); } }"></a>
</td>
</tr>
</tbody>
</table>
<div class="pagination pagination-mini pagination-centered">
<ul>
<li data-bind="css: {disabled: profiles.currentPage() === 0}"><a href="#" data-bind="click: profiles.prevPage">«</a></li>
</ul>
<ul data-bind="foreach: profiles.pages">
<li data-bind="css: { active: $data.number === $root.profiles.currentPage(), disabled: $data.number === -1 }"><a href="#" data-bind="text: $data.text, click: function() { $root.profiles.changePage($data.number); }"></a></li>
</ul>
<ul>
<li data-bind="css: {disabled: profiles.currentPage() === profiles.lastPage()}"><a href="#" data-bind="click: profiles.nextPage">»</a></li>
</ul>
</div>
<button class="btn pull-right" data-bind="click: function() { $root.showImportProfileDialog() }">{{ _('Import Profile...') }}</button>
<div id="settings_plugin_svgtogcode_import" class="modal hide fade">
<div class="modal-header">
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">&times;</a>
<h3>{{ _('Import Existing Cura Profile') }}</h3>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="control-group">
<label class="control-label">{{ _('Profile ini file') }}</label>
<div class="controls">
<div class="input-prepend">
<span class="btn fileinput-button">
<span>{{ _('Browse...') }}</span>
<input id="settings-svgtogcode-import" type="file" name="file" data-url="{{ url_for("plugin.svgtogcode.importSvgToGcodeProfile") }}">
</span>
<span class="add-on" data-bind="text: fileName"></span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Identifier') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: profileName, attr: {placeholder: placeholderName}">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Name') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: profileDisplayName, attr: {placeholder: placeholderDisplayName}">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Description') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: profileDescription, attr: {placeholder: placeholderDescription}">
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: profileAllowOverwrite"> {{ _('Overwrite existing file') }}
</label>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Abort') }}</button>
<button class="btn btn-primary" id="settings-svgtogcode-import-start">{{ _('Confirm') }}</button>
</div>
</div>
<div id="settings_plugin_svgtogcode_edit" class="modal hide fade">
<div class="modal-header">
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">&times;</a>
<h3>{{ _('Edit Cura Profile') }}</h3>
</div>
<div class="modal-body">
<div class="tabbable">
<ul class="nav nav-tabs">
<li class="active"><a href="#settings_plugin_svgtogcode_edit_general" data-toggle="tab">{{ _('General') }}</a></li>
<li><a href="#settings_plugin_svgtogcode_edit_basic" data-toggle="tab">{{ _('Basic') }}</a></li>
<li><a href="#settings_plugin_svgtogcode_edit_advanced" data-toggle="tab">{{ _('Advanced') }}</a></li>
<li><a href="#settings_plugin_svgtogcode_edit_expert" data-toggle="tab">{{ _('Expert') }}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="settings_plugin_svgtogcode_edit_general">
<form class="form-horizontal">
<div class="control-group">
<label class="control-label">{{ _('Identifier') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Name') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Descripton') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
</form>
</div>
<div class="tab-pane" id="settings_plugin_svgtogcode_edit_basic">
<form class="form-horizontal">
<fieldset>
<legend>{{ _('Quality') }}</legend>
<div class="control-group">
<label class="control-label">{{ _('Layer height (mm)') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Shell thickness (mm)') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
<div class="controls">
<label class="checkbox">
<input type="checkbox"> {{ _('Enable retraction') }}
</label>
</div>
</fieldset>
<fieldset>
<legend>{{ _('Fill') }}</legend>
<div class="control-group">
<label class="control-label">{{ _('Bottom/top thickness (mm)') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Fill density (%%)') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
</fieldset>
<fieldset>
<legend>{{ _('Speed and Temperature') }}</legend>
<div class="control-group">
<label class="control-label">{{ _('Print speed (mm/s)') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Print temperature (°C)') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
</fieldset>
<fieldset>
<legend>{{ _('Support') }}</legend>
<div class="control-group">
<label class="control-label">{{ _('Support type') }}</label>
<select>
<option value="none">{{ _('None') }}</option>
<option value="buildplate">{{ _('Touching buildplate') }}</option>
<option value="everywhere">{{ _('Everywhere') }}</option>
</select>
</div>
<div class="control-group">
<label class="control-label">{{ _('Platform adhesion type') }}</label>
<select>
<option value="none">{{ _('None') }}</option>
<option value="brim">{{ _('Brim') }}</option>
<option value="raft">{{ _('Raft') }}</option>
</select>
</div>
</fieldset>
<fieldset>
<legend>{{ _('Filament') }}</legend>
<div class="control-group">
<label class="control-label">{{ _('Diameter (mm)') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Flow (%%)') }}</label>
<div class="controls">
<input type="text" class="input-block-level">
</div>
</div>
</fieldset>
</form>
</div>
<div class="tab-pane" id="settings_plugin_svgtogcode_edit_advanced"></div>
<div class="tab-pane" id="settings_plugin_svgtogcode_edit_expert"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Abort') }}</button>
<button class="btn btn-primary" data-bind="click: function() { $root.confirmEditProfile() }">{{ _('Confirm') }}</button>
</div>
</div>
</div>

View file

@ -189,6 +189,12 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel, slicing
self.slicing.show(file.origin, file.name);
};
self.convertSVG = function(file) {
if (!file) return;
self.slicing.show(file.origin, file.name);
};
self.initSdCard = function() {
self._sendSdCommand("init");
};
@ -235,7 +241,12 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel, slicing
};
self.templateFor = function(data) {
return "files_template_" + data.type;
var extension = data.name.split('.').pop().toLowerCase();
if (extension == "svg") {
return "files_template_" + data.type + "_svg";
} else {
return "files_template_" + data.type;
}
};
self.getEntryId = function(data) {
@ -265,6 +276,10 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel, slicing
return self.loginState.isUser() && !(self.isPrinting() || self.isPaused());
};
self.enableSVGConversion = function(data) {
return self.loginState.isUser() && !(self.isPrinting() || self.isPaused());
};
self.enableAdditionalData = function(data) {
return data["gcodeAnalysis"] || data["prints"] && data["prints"]["last"];
};

View file

@ -133,4 +133,41 @@
<a href="#" class="btn btn-danger" data-bind="click: disableAccessControl">{{ _('Disable Access Control') }}</a>
<a href="#" class="btn btn-primary" data-bind="click: keepAccessControl, enable: validData(), css: {disabled: !validData()}">{{ _('Keep Access Control Enabled') }}</a>
</div>
</div>
<div id="svg_conversion_dialog" class="modal hide fade">
<div class="modal-header">
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">&times;</a>
<h3 data-bind="text: title"></h3>
</div>
<div class="modal-body">
<p>{{ _('Please configure which slicer and which slicing profile to use and name the GCode file to slice to below, or click "Cancel" if you do not wish to slice the file now.') }}</p>
<form class="form-horizontal">
<div class="control-group">
<label class="control-label">{{ _('Slicer') }}</label>
<div class="controls">
<select data-bind="options: slicers, optionsText: 'name', optionsValue: 'key', optionsCaption: '{{ _('Select a slicer...') }}', value: slicer, valueAllowUnset: true"></select>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Slicing Profile') }}</label>
<div class="controls">
<select data-bind="options: profiles, optionsText: 'name', optionsValue: 'key', optionsCaption: '{{ _('Select a profile...') }}', value: profile, valueAllowUnset: true"></select>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('GCode Filename') }}</label>
<div class="controls">
<div class="input-append">
<input type="text" data-bind="value: gcodeFilename">
<span class="add-on">.gco</span>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<a href="#" class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Cancel') }}</a>
<a href="#" class="btn btn-primary" data-bind="click: function() { if ($root.enableSliceButton()) { $root.slice() } }, enabled: enableSliceButton, css: {disabled: !$root.enableSliceButton()}">{{ _('Slice') }}</a>
</div>
</div>

View file

@ -241,6 +241,16 @@
</div>
</script>
<script type="text/html" id="files_template_model_svg">
<div class="title muted" data-bind="text: name"></div>
<div class="uploaded">{{ _('Uploaded') }}: <span data-bind="text: formatTimeAgo(date)"></span></div>
<div class="size">{{ _('Size') }}: <span data-bind="text: formatSize(size)"></span></div>
<div class="btn-group action-buttons">
<div class="btn btn-mini" data-bind="click: function() { if ($root.enableRemove($data)) { $root.removeFile($data); } else { return; } }, css: {disabled: !$root.enableRemove($data)}"><i class="icon-trash" title="{{ _('Remove') }}"></i></div>
<div class="btn btn-mini" data-bind="click: function() { if ($root.enableSVGConversion($data)) { $root.convertSVG($data); } else { return; } }, css: {disabled: !$root.enableSVGConversion($data)}"><i class="icon-print" title="{{ _('Convert to Laserpath') }}"></i></div>
</div>
</script>
<script type="text/html" id="files_template_folder">
<div class="title" data-bind="text: name"></div>
</script>