* 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:
parent
39bd44bf70
commit
e753ec6b4f
11 changed files with 1724 additions and 2 deletions
|
|
@ -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(
|
||||
|
|
|
|||
363
src/octoprint/plugins/svgtogcode/__init__.py
Normal file
363
src/octoprint/plugins/svgtogcode/__init__.py
Normal 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()]
|
||||
825
src/octoprint/plugins/svgtogcode/profile.py
Normal file
825
src/octoprint/plugins/svgtogcode/profile.py
Normal 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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
layer_height: 0.2
|
||||
print_temperature:
|
||||
- 220.0
|
||||
- 220.0
|
||||
|
|
@ -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}
|
||||
191
src/octoprint/plugins/svgtogcode/static/js/svgtogcode.js
Normal file
191
src/octoprint/plugins/svgtogcode/static/js/svgtogcode.js
Normal 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")]);
|
||||
});
|
||||
29
src/octoprint/plugins/svgtogcode/static/less/svgtogcode.less
Normal file
29
src/octoprint/plugins/svgtogcode/static/less/svgtogcode.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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> | <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">×</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">×</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>
|
||||
|
|
@ -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"];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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">×</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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue