removed cura plugin, machine pos, terminal, controls

This commit is contained in:
Teja 2015-01-07 16:45:06 +01:00
parent de58b08e90
commit cc68a06ce0
16 changed files with 416 additions and 2273 deletions

View file

@ -1,407 +0,0 @@
# 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 = {
"cura_engine": None,
"default_profile": None,
"debug_logging": False
}
s = octoprint.plugin.plugin_settings("cura", defaults=default_settings)
from .profile import Profile
blueprint = flask.Blueprint("plugin.cura", __name__)
@blueprint.route("/import", methods=["POST"])
def importCuraProfile():
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_cura_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_cura_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("cura",
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="cura", 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 CuraPlugin(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.cura")
self._cura_logger = logging.getLogger("octoprint.plugins.cura.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
cura_logging_handler = logging.handlers.RotatingFileHandler(s.getPluginLogfilePath(postfix="engine"), maxBytes=2*1024*1024)
cura_logging_handler.setFormatter(logging.Formatter("%(asctime)s %(message)s"))
cura_logging_handler.setLevel(logging.DEBUG)
self._cura_logger.addHandler(cura_logging_handler)
self._cura_logger.setLevel(logging.DEBUG if s.getBoolean(["debug_logging"]) else logging.CRITICAL)
self._cura_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/cura.js"],
"less": ["less/cura.less"],
"css": ["css/cura.css"]
}
##~~ SettingsPlugin API
def on_settings_load(self):
return dict(
cura_engine=s.get(["cura_engine"]),
default_profile=s.get(["default_profile"]),
debug_logging=s.getBoolean(["debug_logging"])
)
def on_settings_save(self, data):
if "cura_engine" in data and data["cura_engine"]:
s.set(["cura_engine"], data["cura_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._cura_logger.setLevel(logging.DEBUG)
else:
self._cura_logger.setLevel(logging.CRITICAL)
s.setBoolean(["debug_logging"], new_debug_logging)
##~~ TemplatePlugin API
def get_template_vars(self):
return dict(
_settings_menu_entry="Cura"
)
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):
cura_engine = s.get(["cura_engine"])
return cura_engine is not None and os.path.exists(cura_engine)
def get_slicer_properties(self):
return dict(
type="cura",
name="CuraEngine",
same_device=True,
progress_report=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._cura_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(["cura_engine"])
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]
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
layer_count = None
step_factor = dict(
inset=0,
skin=1,
export=2
)
while p.returncode is None:
line = p.stderr.readline(timeout=0.5)
if not line:
if line_seen:
break
else:
continue
line_seen = True
self._cura_logger.debug(line.strip())
if on_progress is not None:
# The Cura slicing process has three individual steps, each consisting of <layer_count> substeps:
#
# - inset
# - skin
# - export
#
# So each layer will be processed three times, once for each step, resulting in a total amount of
# substeps of 3 * <layer_count>.
#
# The CuraEngine reports the calculated layer count and the continuous progress on stderr.
# The layer count gets reported right at the beginning in a line of the format:
#
# Layer count: <layer_count>
#
# The individual progress per each of the three steps gets reported on stderr in a line of
# the format:
#
# Progress:<step>:<current_layer>:<layer_count>
#
# Thus, for determining the overall progress the following formula applies:
#
# progress = <step_factor> * <layer_count> + <current_layer> / <layer_count> * 3
#
# with <step_factor> being 0 for "inset", 1 for "skin" and 2 for "export".
if line.startswith("Layer count:") and layer_count is None:
try:
layer_count = float(line[len("Layer count:"):].strip())
except:
pass
elif line.startswith("Progress:"):
split_line = line[len("Progress:"):].strip().split(":")
if len(split_line) == 3:
step, current_layer, _ = split_line
try:
current_layer = float(current_layer)
except:
pass
else:
if not step in step_factor:
continue
on_progress_kwargs["_progress"] = (step_factor[step] * layer_count + current_layer) / (layer_count * 3)
on_progress(*on_progress_args, **on_progress_kwargs)
finally:
p.close()
with self._cancelled_jobs_mutex:
if machinecode_path in self._cancelled_jobs:
self._cura_logger.info("### Cancelled")
raise octoprint.slicing.SlicingCancelled()
self._cura_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._cura_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__ = "cura"
__plugin_version__ = "0.1"
__plugin_implementations__ = [CuraPlugin()]

View file

@ -1,956 +0,0 @@
# 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_cura_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):
result = dict()
for key in defaults.keys():
r = cls.merge_profile_key(key, profile, overrides=overrides)
if r is not None:
result[key] = r
return result
@classmethod
def merge_profile_key(cls, key, profile, overrides=None):
profile_value = None
override_value = None
if not key in defaults:
return None
import copy
result = copy.deepcopy(defaults[key])
if key in profile:
profile_value = profile[key]
if overrides and key in overrides:
override_value = overrides[key]
if profile_value is None and override_value is None:
# neither override nor profile, no need to handle this key further
return None
if key in ("filament_diameter", "print_temperature", "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)):
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[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[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 = override_value
elif profile_value is not None:
result = profile_value
return result
def __init__(self, profile, overrides=None):
self._profile = self.__class__.merge_profile(profile, overrides=overrides)
def profile(self):
import copy
return copy.deepcopy(self._profile)
def get(self, key):
if key in ("machine_width", "machine_depth", "machine_center_is_zero"):
bedDimensions = s.globalGet(["printerParameters", "bedDimensions"])
circular = bedDimensions["circular"] if "circular" in bedDimensions else False
radius = bedDimensions["radius"] if "radius" in bedDimensions and bedDimensions["radius"] is not None else 0
if key == "machine_width":
return radius * 2 if circular else bedDimensions["x"]
elif key == "machine_depth":
return radius * 2 if circular else bedDimensions["y"]
elif key == "machine_center_is_zero":
return circular
else:
return None
elif key == "extruder_amount":
return s.globalGetInt(["printerParameters", "numExtruders"])
elif key.startswith("extruder_offset_"):
extruder_offsets = s.globalGet(["printerParameters", "extruderOffsets"])
match = Profile.regex_extruder_offset.match(key)
if not match:
return 0.0
axis, number = match.groups()
axis = axis.lower()
number = int(number)
if not axis in ("x", "y"):
return 0.0
if number >= len(extruder_offsets):
return 0.0
if not axis in extruder_offsets[number]:
return 0.0
return extruder_offsets[number][axis]
elif key.startswith("filament_diameter"):
match = Profile.regex_filament_diameter.match(key)
if not match:
return 0.0
diameters = self._get("filament_diameter")
if not match.group(1):
return diameters[0]
index = int(match.group(1))
if index >= len(diameters):
return 0.0
return diameters[index]
elif key.startswith("print_temperature"):
match = Profile.regex_print_temperature.match(key)
if not match:
return 0.0
temperatures = self._get("print_temperature")
if not match.group(1):
return temperatures[0]
index = int(match.group(1))
if index >= len(temperatures):
return 0.0
return temperatures[index]
else:
return self._get(key)
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 + 'CURA_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;CURA_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):
edge_width, line_count = self.calculate_edge_width_and_line_count()
solid_layer_count = self.calculate_solid_layer_count()
extruder_count = s.globalGetInt(["printerParameters", "numExtruders"])
minimal_extruder_count = self.calculate_minimal_extruder_count()
settings = {
"layerThickness": 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"),
"filamentDiameter": self.get_microns("filament_diameter"),
"filamentFlow": self.get_int("filament_flow"),
"extrusionWidth": edge_width * 1000,
"layer0extrusionWidth": int(edge_width * self.get_float("first_layer_width_factor") / 100 * 1000),
"insetCount": line_count,
"downSkinCount": solid_layer_count if self.get_boolean("solid_bottom") else 0,
"upSkinCount": solid_layer_count if self.get_boolean("solid_top") else 0,
"infillOverlap": self.get_int("fill_overlap"),
"initialSpeedupLayers": int(4),
"initialLayerSpeed": self.get_int("bottom_layer_speed"),
"printSpeed": self.get_int("print_speed"),
"infillSpeed": self.get_int("infill_speed") if self.get_int("infill_speed") > 0 else self.get_int("print_speed"),
"inset0Speed": self.get_int("outer_shell_speed") if self.get_int("outer_shell_speed") > 0 else self.get_int("print_speed"),
"insetXSpeed": self.get_int("inner_shell_speed") if self.get_int("inner_shell_speed") > 0 else self.get_int("print_speed"),
"moveSpeed": self.get_int("travel_speed"),
"fanSpeedMin": self.get_int("fan_speed") if self.get_boolean("fan_enabled") else 0,
"fanSpeedMax": self.get_int("fan_speed_max") if self.get_boolean("fan_enabled") else 0,
"supportAngle": int(-1) if self.get("support") == SupportLocationTypes.NONE else self.get_int("support_angle"),
"supportEverywhere": int(1) if self.get("support") == SupportLocationTypes.EVERYWHERE else int(0),
"supportLineDistance": int(100 * edge_width * 1000 / self.get_float("support_fill_rate")) if self.get_float("support_fill_rate") > 0 else -1,
"supportXYDistance": int(1000 * self.get_float("support_xy_distance")),
"supportZDistance": int(1000 * self.get_float("support_z_distance")),
"supportExtruder": 0 if self.get("support_dual_extrusion") == SupportDualTypes.FIRST else (1 if self.get("support_dual_extrusion") == SupportDualTypes.SECOND and minimal_extruder_count > 1 else -1),
"retractionAmount": self.get_microns("retraction_amount") if self.get_boolean("retraction_enable") else 0,
"retractionSpeed": self.get_int("retraction_speed"),
"retractionMinimalDistance": self.get_microns("retraction_min_travel"),
"retractionAmountExtruderSwitch": self.get_microns("retraction_dual_amount"),
"retractionZHop": self.get_microns("retraction_hop"),
"minimalExtrusionBeforeRetraction": self.get_microns("retraction_minimal_extrusion"),
"enableCombing": 1 if self.get_boolean("retraction_combing") else 0,
"multiVolumeOverlap": self.get_microns("overlap_dual"),
"objectSink": max(0, self.get_microns("object_sink")),
"minimalLayerTime": self.get_int("cool_min_layer_time"),
"minimalFeedrate": self.get_int("cool_min_feedrate"),
"coolHeadLift": 1 if self.get_boolean("cool_head_lift") else 0,
# model positioning
"posx": int(self.get_float("machine_width") / 2.0 * 1000) if not self.get_boolean("machine_center_is_zero") else 0.0,
"posy": int(self.get_float("machine_depth") / 2.0 * 1000) if not self.get_boolean("machine_center_is_zero") else 0.0,
# gcodes
"startCode": self.get_gcode("start_gcode"),
"endCode": self.get_gcode("end_gcode"),
"preSwitchExtruderCode": self.get_gcode("preSwitchExtruder_gcode"),
"postSwitchExtruderCode": self.get_gcode("postSwitchExtruder_gcode"),
# fixing
"fixHorrible": 0,
}
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

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

View file

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

View file

@ -1,191 +0,0 @@
$(function() {
function CuraViewModel(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-cura-import");
self.uploadButton = $("#settings-cura-import-start");
self.profiles = new ItemListHelper(
"plugin_cura_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-cura-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_cura_import").modal("show");
};
self.requestData = function() {
$.ajax({
url: API_BASEURL + "slicing/cura/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([CuraViewModel, ["loginStateViewModel", "settingsViewModel", "slicingViewModel"], document.getElementById("settings_plugin_cura_dialog")]);
});

View file

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

View file

@ -1,247 +0,0 @@
<div id="settings_plugin_cura_dialog" data-bind="allowBindings: true">
<h4>{{ _('General') }}</h4>
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="settings-cura-path">{{ _('Path to CuraEngine') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.plugins.cura.cura_engine">
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: settings.plugins.cura.debug_logging"> {{ _('Log the output of CuraEngine to plugin_cura_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_cura_profiles_key">{{ _('Identifier') }}</th>
<th class="settings_plugin_cura_profiles_name">{{ _('Name') }}</th>
<th class="settings_plugin_cura_profiles_actions">{{ _('Actions') }}</th>
</tr>
</thead>
<tbody data-bind="foreach: profiles.paginatedItems">
<tr data-bind="attr: {title: description}">
<td class="settings_plugin_cura_profiles_key"><span class="icon-star" data-bind="invisible: !isdefault()"></span> <span data-bind="text: key"></span></td>
<td class="settings_plugin_cura_profiles_name" data-bind="text: name"></td>
<td class="settings_plugin_cura_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_cura_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-cura-import" type="file" name="file" data-url="{{ url_for("plugin.cura.importCuraProfile") }}">
</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-cura-import-start">{{ _('Confirm') }}</button>
</div>
</div>
<div id="settings_plugin_cura_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_cura_edit_general" data-toggle="tab">{{ _('General') }}</a></li>
<li><a href="#settings_plugin_cura_edit_basic" data-toggle="tab">{{ _('Basic') }}</a></li>
<li><a href="#settings_plugin_cura_edit_advanced" data-toggle="tab">{{ _('Advanced') }}</a></li>
<li><a href="#settings_plugin_cura_edit_expert" data-toggle="tab">{{ _('Expert') }}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="settings_plugin_cura_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_cura_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_cura_edit_advanced"></div>
<div class="tab-pane" id="settings_plugin_cura_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

@ -16,34 +16,7 @@ function WorkingAreaViewModel(loginStateViewModel, settingsViewModel, printerSta
self.isError = ko.observable(undefined);
self.isReady = ko.observable(undefined);
self.isLoading = ko.observable(undefined);
self.currentPos = ko.observable(undefined);
self.laserPos = ko.computed(function(){
var pos = self.currentPos();
if(!pos){
return "(?, ?)";
} else {
return "("+ pos.x + ", "+ pos.y + ")";
}
}, this);
self._processPos = function(posStr) {
// example posStr: "X: 73.0000 Y: 192.0000 Z: 0.0000"
var parts = posStr.split(" ");
var x = parseFloat(parts[1]).toFixed(2)
var y = parseFloat(parts[3]).toFixed(2)
self.currentPos({x:x, y:y});
};
self._fromData = function(data) {
if(data.workPosition){
self._processPos(data.workPosition);
}
};
self.fromCurrentData = function(data) {
self._fromData(data);
};
self.move_laser = function(el){
var x = event.offsetX;
@ -56,66 +29,22 @@ function WorkingAreaViewModel(loginStateViewModel, settingsViewModel, printerSta
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({"command": command})
});
}
};
self.laser_start = function(e){
console.log("start lasering...", e);
return false;
self.crosshairX = function(){
var pos = self.state.currentPos();
return pos !== undefined ? (pos.x - 15) : -100; // subtract width/2;
};
self.crosshairY = function(){
var h = document.getElementById('area_preview').clientHeight;
var pos = self.state.currentPos();
return pos !== undefined ? (h - pos.y - 15) : -100; // subtract height/2;
};
self.titlePrintButton = self.state.titlePrintButton;
self.titlePauseButton = self.state.titlePauseButton;
self.pause = self.state.pause;
self.cancel = self.state.cancel;
//
// self.getLaserPos = function(){
// console.log("foo")
// x = self.x === undefined ? '?' : self.x;
// y = self.y === undefined ? '?' : self.y;
// return "x"+ x + ", y"+ y;
// }
//
// self.sendCommand = function() {
// var command = self.command();
// if (!command) {
// return;
// }
//
//
// };
//
// self.handleKeyDown = function(event) {
// var keyCode = event.keyCode;
//
// if (keyCode == 38 || keyCode == 40) {
// if (keyCode == 38 && self.cmdHistory.length > 0 && self.cmdHistoryIdx > 0) {
// console.log("keycode 38")
// } else if (keyCode == 40 && self.cmdHistoryIdx < self.cmdHistory.length - 1) {
// console.log("keycode 40")
// }
//
// // prevent the cursor from being moved to the beginning of the input field (this is actually the reason
// // why we do the arrow key handling in the keydown event handler, keyup would be too late already to
// // prevent this from happening, causing a jumpy cursor)
// return false;
// }
//
// // do not prevent default action
// return true;
// };
//
// self.handleKeyUp = function(event) {
// if (event.keyCode == 13) {
// self.sendCommand();
// }
//
// // do not prevent default action
// return true;
// };
}

View file

@ -43,29 +43,28 @@
<link href="{{ url_for('static', filename='css/mrbeam.css') }}" rel="stylesheet" media="screen">
<script type="text/javascript" >
var BASEURL = "{{ url_for('index') }}";
var API_BASEURL = BASEURL + "api/";
var GCODE_WORKER = "{{ url_for('static', filename='gcodeviewer/js/Worker.js') }}";
var CONFIG_GCODEFILESPERPAGE = 5000;
var CONFIG_TIMELAPSEFILESPERPAGE = 10;
var CONFIG_LOGFILESPERPAGE = 10;
var CONFIG_USERSPERPAGE = 10;
var CONFIG_WEBCAM_STREAM = "{{ webcamStream }}";
var CONFIG_ACCESS_CONTROL = {% if enableAccessControl -%} true; {% else %} false; {%- endif %}
var CONFIG_SD_SUPPORT = {% if enableSdSupport -%} true; {% else %} false; {%- endif %}
var CONFIG_FIRST_RUN = {% if firstRun -%} true; {% else %} false; {%- endif %}
var CONFIG_TEMPERATURE_GRAPH = {% if enableTemperatureGraph -%} true; {% else %} false; {%- endif %}
var CONFIG_GCODE_SIZE_THRESHOLD = {{ gcodeThreshold }};
var CONFIG_GCODE_MOBILE_SIZE_THRESHOLD = {{ gcodeMobileThreshold }};
var SOCKJS_URI = window.location.protocol.slice(0, - 1) + "://" + (window.document ? window.document.domain : window.location.hostname) + ":" + window.location.port + BASEURL + "sockjs";
var SOCKJS_DEBUG = {% if debug -%} true; {% else %} false; {%- endif %}
var BASEURL = "{{ url_for('index') }}";
var API_BASEURL = BASEURL + "api/";
var GCODE_WORKER = "{{ url_for('static', filename='gcodeviewer/js/Worker.js') }}";
var CONFIG_GCODEFILESPERPAGE = 5000;
var CONFIG_TIMELAPSEFILESPERPAGE = 10;
var CONFIG_LOGFILESPERPAGE = 10;
var CONFIG_USERSPERPAGE = 10;
var CONFIG_WEBCAM_STREAM = "{{ webcamStream }}";
var CONFIG_ACCESS_CONTROL = {% if enableAccessControl -%} true; {% else %} false; {%- endif %}
var CONFIG_SD_SUPPORT = {% if enableSdSupport -%} true; {% else %} false; {%- endif %}
var CONFIG_FIRST_RUN = {% if firstRun -%} true; {% else %} false; {%- endif %}
var CONFIG_TEMPERATURE_GRAPH = {% if enableTemperatureGraph -%} true; {% else %} false; {%- endif %}
var CONFIG_GCODE_SIZE_THRESHOLD = {{ gcodeThreshold }};
var CONFIG_GCODE_MOBILE_SIZE_THRESHOLD = {{ gcodeMobileThreshold }};
var SOCKJS_URI = window.location.protocol.slice(0, - 1) + "://" + (window.document ? window.document.domain : window.location.hostname) + ":" + window.location.port + BASEURL + "sockjs";
var SOCKJS_DEBUG = {% if debug -%} true; {% else %} false; {%- endif %}
var UI_API_KEY = "{{ uiApiKey }}";
var VERSION = "{{ version }}";
var DISPLAY_VERSION = "{{ display_version }}";
var LOCALE = "{{ g.locale }}";
var ADDITIONAL_VIEWMODELS = [];
</script>
var UI_API_KEY = "{{ uiApiKey }}";
var VERSION = "{{ version }}";
var DISPLAY_VERSION = "{{ display_version }}";
var LOCALE = "{{ g.locale }}";
var ADDITIONAL_VIEWMODELS = []; </script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
@ -79,8 +78,9 @@
<li class="active"><a href="#workingarea" data-toggle="tab">working area</a></li>
<li><a href="#designlib" data-toggle="tab">design library</a></li>
<li><a href="#focus" data-toggle="tab">focus</a></li>
<li><a href="#term" data-toggle="tab">terminal</a></li>
<li xstyle="display: none;" xdata-bind="visible: loginState.isAdmin">
<a id="navbar_show_settings" class="pull-right" href="#settings" data-toggle="tab">{{ _('settings') }}</a>
<a id="navbar_show_settings" class="pull-right" href="#settings" data-toggle="tab"><i class="icon-gear"></i></a>
</li>
</ul>
<ul class="nav pull-right">
@ -128,223 +128,234 @@
<div class="row-fluid">
<div class="span4 accordion">
<!-- <div class="accordion-group" data-bind="visible: loginState.isUser" id="connection_accordion">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" href="#connection"><i class="icon-signal"></i> {{ _('Connection') }}</a>
</div>
</div>-->
<div class="accordion-group" id="state_accordion">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" href="#state"><i class="icon-info-sign"></i> {{ _('State') }}</a>
</div>
<div class="accordion-body collapse in" id="connection" data-bind="visible: isErrorOrClosed() && loginState.isUser()">
<div class="accordion-inner">
<label for="connection_ports" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Serial Port') }}</label>
<select id="connection_ports" data-bind="options: portOptions, optionsCaption: 'AUTO', value: selectedPort, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
<label for="connection_baudrates" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Baudrate') }}</label>
<select id="connection_baudrates" data-bind="options: baudrateOptions, optionsCaption: 'AUTO', value: selectedBaudrate, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
<label class="checkbox">
<input type="checkbox" id="connection_save" data-bind="checked: saveSettings, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> {{ _('Save connection settings') }}
</label>
<label class="checkbox">
<input type="checkbox" id="connection_autoconnect" data-bind="checked: settings.serial_autoconnect, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> {{ _('Auto-connect on server startup') }}
</label>
<button class="btn btn-block" id="printer_connect" data-bind="click: connect, text: buttonText(), enable: loginState.isUser()">{{ _('Connect') }}</button>
</div>
</div>
<div class="accordion-body collapse in" id="state" data-bind="visible: !isErrorOrClosed() && loginState.isUser()">
<div class="accordion-inner">
{{ _('Machine State') }}: <strong data-bind="text: stateString"></strong><br>
{{ _('File') }}: <strong data-bind="text: filename"></strong>&nbsp;<strong data-bind="visible: sd">(SD)</strong><br>
<!-- {{ _('Timelapse') }}: <strong data-bind="text: timelapseString"></strong><br>-->
<!-- ko foreach: filament -->
<!--<span data-bind="text: 'Filament (' + name() + '): '"></span><strong data-bind="text: formatFilament(data())"></strong><br>-->
<!-- /ko -->
{{ _('Approx. Total Job Time') }}: <strong data-bind="text: estimatedPrintTimeString"></strong><br>
<!-- {{ _('Print Time') }}: <strong data-bind="text: printTimeString"></strong><br>
{{ _('Print Time Left') }}: <strong data-bind="text: printTimeLeftString"></strong><br>-->
{{ _('Processed') }}: <strong data-bind="text: byteString"></strong><br>
<!-- <div class="accordion-group" data-bind="visible: loginState.isUser" id="connection_accordion">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" href="#connection"><i class="icon-signal"></i> {{ _('Connection') }}</a>
</div>
</div>-->
<div class="progress">
<div class="bar" id="job_progressBar" data-bind="style: { width: progressString() + '%' }"></div>
</div>
<div class="accordion-group" id="state_accordion">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" href="#state"><i class="icon-info-sign"></i> {{ _('State') }}</a>
</div>
<div id="control" class="accordion-inner" >
{{ _('Position') }}: <strong data-bind="text: laserPos"></strong>
<a href="#control_btns" class="btn btn-xs pull-right" data-toggle="collapse" aria-expanded="false">
<i class="icon-move sr-only"></i>
<span class="caret"></span>
</a>
<div class="xxaccordion-inner collapse" id="control_btns">
<div class="jog-panel" >
<div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('y',1) }"><i class="icon-arrow-up"></i></button>
</div>
<div>
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('x',-1) }"><i class="icon-arrow-left"></i></button>
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendHomeCommand(['x', 'y']) }"><i class="icon-home"></i></button>
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('x',1) }"><i class="icon-arrow-right"></i></button>
</div>
<div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('y',-1) }"><i class="icon-arrow-down"></i></button>
</div>
</div>
{% if hasZAxis %}
Z jogging control panel
<div class="jog-panel" id="control_zaxis">
<div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('z',1) }"><i class="icon-arrow-up"></i></button>
</div>
<div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendHomeCommand(['z']) }"><i class="icon-home"></i></button>
</div>
<div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('z',-1) }"><i class="icon-arrow-down"></i></button>
</div>
</div>
{% endif %}
<div class="clearfix"></div>
Jog distance
<div class="distance">
<!--<input type="range" min="0" max="50" value="0" step="10" onchange="" />-->
<div class="btn-group" data-toggle="buttons-radio" id="jog_distance">
<button type="button" class="btn distance" data-distance="0.1" data-bind="enable: loginState.isUser()">0.1</button>
<button type="button" class="btn distance" data-distance="1" data-bind="enable: loginState.isUser()">1</button>
<button type="button" class="btn distance active" data-distance="10" data-bind="enable: loginState.isUser()">10</button>
<button type="button" class="btn distance" data-distance="100" data-bind="enable: loginState.isUser()">100</button>
</div>
</div>
</div>
</div>
<div class="row-fluid print-control" style="display: none;" data-bind="visible: loginState.isUser">
<button class="btn btn-primary span4" data-bind="click: print, enable: isOperational() && isReady() && !isPrinting() && loginState.isUser(), css: {'btn-danger': isPaused()}, attr: {title: titlePrintButton}" id="job_print"><i class="icon-white" data-bind="css: {'icon-fire': !isPaused(), 'icon-undo': isPaused()}"></i> <span data-bind="text: (isPaused() ? '{{ _('Restart') }}' : '{{ _('Laser') }}')">{{ _('Laser') }}</span></button>
<button class="btn span4" id="job_pause" data-bind="click: pause, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser(), css: {active: isPaused()}, attr: {title: titlePauseButton}"><i data-bind="css: {'icon-pause': !isPaused(), 'icon-play': isPaused()}"></i> <span data-bind="visible: !isPaused()">{{ _('Pause') }}</span><span data-bind="visible: isPaused()">{{ _('Resume') }}</span></button>
<button class="btn span4" id="job_cancel" data-bind="click: cancel, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser()" title="{{ _('Cancels the job') }}"><i class="icon-stop"></i> {{ _('Cancel') }}</button>
</div>
</div>
</div>
</div>
<div id="state_accordion"></div>
<div data-bind="visible: loginState.isUser" id="connection_accordion"></div>
<div class="accordion-body collapse in" id="connection" data-bind="visible: isErrorOrClosed() && loginState.isUser()">
<div class="accordion-inner">
<label for="connection_ports" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Serial Port') }}</label>
<select id="connection_ports" data-bind="options: portOptions, optionsCaption: 'AUTO', value: selectedPort, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
<label for="connection_baudrates" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Baudrate') }}</label>
<select id="connection_baudrates" data-bind="options: baudrateOptions, optionsCaption: 'AUTO', value: selectedBaudrate, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
<label class="checkbox">
<input type="checkbox" id="connection_save" data-bind="checked: saveSettings, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> {{ _('Save connection settings') }}
</label>
<label class="checkbox">
<input type="checkbox" id="connection_autoconnect" data-bind="checked: settings.serial_autoconnect, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> {{ _('Auto-connect on server startup') }}
</label>
<button class="btn btn-block" id="printer_connect" data-bind="click: connect, text: buttonText(), enable: loginState.isUser()">{{ _('Connect') }}</button>
</div>
</div>
<div class="accordion-body collapse in" id="state" data-bind="visible: !isErrorOrClosed() && loginState.isUser()">
<div class="accordion-inner">
{{ _('Machine State') }}: <strong data-bind="text: stateString"></strong><br>
{{ _('File') }}: <strong data-bind="text: filename"></strong>&nbsp;<strong data-bind="visible: sd">(SD)</strong><br>
<!-- {{ _('Timelapse') }}: <strong data-bind="text: timelapseString"></strong><br>-->
<!-- ko foreach: filament -->
<!--<span data-bind="text: 'Filament (' + name() + '): '"></span><strong data-bind="text: formatFilament(data())"></strong><br>-->
<!-- /ko -->
{{ _('Approx. Total Job Time') }}: <strong data-bind="text: estimatedPrintTimeString"></strong><br>
<!-- {{ _('Print Time') }}: <strong data-bind="text: printTimeString"></strong><br>
{{ _('Print Time Left') }}: <strong data-bind="text: printTimeLeftString"></strong><br>-->
{{ _('Processed') }}: <strong data-bind="text: byteString"></strong><br>
<div class="progress">
<div class="bar" id="job_progressBar" data-bind="style: { width: progressString() + '%' }"></div>
</div>
<div class="row-fluid print-control" style="display: none;" data-bind="visible: loginState.isUser">
<button class="btn btn-primary span4" data-bind="click: print, enable: isOperational() && isReady() && !isPrinting() && loginState.isUser(), css: {'btn-danger': isPaused()}, attr: {title: titlePrintButton}" id="job_print"><i class="icon-white" data-bind="css: {'icon-fire': !isPaused(), 'icon-undo': isPaused()}"></i> <span data-bind="text: (isPaused() ? '{{ _('Restart') }}' : '{{ _('Laser') }}')">{{ _('Laser') }}</span></button>
<button class="btn span4" id="job_pause" data-bind="click: pause, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser(), css: {active: isPaused()}, attr: {title: titlePauseButton}"><i data-bind="css: {'icon-pause': !isPaused(), 'icon-play': isPaused()}"></i> <span data-bind="visible: !isPaused()">{{ _('Pause') }}</span><span data-bind="visible: isPaused()">{{ _('Resume') }}</span></button>
<button class="btn span4" id="job_cancel" data-bind="click: cancel, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser()" title="{{ _('Cancels the job') }}"><i class="icon-stop"></i> {{ _('Cancel') }}</button>
</div>
</div>
</div>
</div>
<div id="state_accordion"></div>
<div data-bind="visible: loginState.isUser" id="connection_accordion"></div>
<div class="" id="files_accordion">
<div class="">
<a class="accordion-toggle" data-toggle="collapse" href="#files"><i class="icon-list"></i> {{ _('Files') }}</a>
<div class="settings-trigger accordion-heading-button btn-group">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<span class="icon-wrench"></span>
</a>
<ul class="dropdown-menu">
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('name'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> {{ _('Sort by name') }} ({{ _('ascending') }})</a></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('upload'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'upload' ? 'visible' : 'hidden'}"></i> {{ _('Sort by upload date') }} ({{ _('descending') }})</a></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('size'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> {{ _('Sort by file size') }} ({{ _('descending') }})</a></li>
<li class="divider"></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('machinecode'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'machinecode') ? 'visible' : 'hidden'}"></i> {{ _('Only show GCode files') }}</a></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('model'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'model') ? 'visible' : 'hidden'}"></i> {{ _('Only show STL files') }}</a></li>
<li class="divider"></li>
</ul>
</div>
</div>
<div class="accordion-body collapse in overflow_visible" id="files">
<div class="accordion-inner">
<form class="form-search">
<input type="text" class="input-block search-query" data-bind="value: searchQuery, valueUpdate: 'input'" placeholder="{{ _('Search...') }}">
</form>
<div class="gcode_files" data-bind="slimScrolledForeach: listHelper.paginatedItems">
<div class="entry" data-bind="attr: { id: $root.getEntryId($data) }, template: { name: $root.templateFor($data), data: $data }"></div>
<script type="text/html" id="files_template_machinecode">
<div class="file_list_entry">
<div class="title" data-bind="css: $root.getSuccessClass($data), style: { 'font-weight': $root.listHelper.isSelected($data) ? 'bold' : 'normal' }, 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="additionalInfo hide" data-bind="html: $root.getAdditionalData($data)"></div>
<div class="btn-group action-buttons">
<!--<div class="btn btn-mini toggleAdditionalData" data-bind="click: function() { if ($root.enableAdditionalData($data)) { $root.toggleAdditionalData($data); } else { return; } }, css: { disabled: !$root.enableAdditionalData($data) }"><i class="icon-chevron-down"></i></div>-->
<a class="btn btn-mini" data-bind="attr: {href: $root.downloadLink($data), css: {disabled: !$root.downloadLink($data)}}"><i class="icon-download-alt" title="{{ _('Download') }}"></i></a>
<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.enableSelect($data)) { $root.loadFile($data, false); } else { return; } }, css: {disabled: !$root.enableSelect($data)}"><i class="icon-folder-open" title="{{ _('Load') }}"></i></div>
<div class="btn btn-mini" data-bind="click: function() { if ($root.enableSelect($data)) { $root.loadFile($data, true); } else { return; } }, css: {disabled: !$root.enableSelect($data)}"><i class="icon-fire" title="{{ _('Load and Laser') }}"></i></div>
</div>
</div>
</script>
<script type="text/html" id="files_template_model_svg">
<div class="file_list_entry">
<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() { $root.convertSVG($data); }, css: {disabled: !$root.enableSVGConversion($data)}"><i class="icon-fire" title="{{ _('Convert to Laserpath') }}"></i></div>
</div>
</div>
</script>
<script type="text/html" id="files_template_folder">
<div class="title" data-bind="text: name"></div>
</script>
</div>
<div class="muted text-right">
<small>{{ _('Free') }}: <span data-bind="text: freeSpaceString"></span></small>
</div>
<div style="display: none;" data-bind="visible: loginState.isUser">
<div class="row-fluid upload-buttons">
<span class="btn btn-primary fileinput-button span12" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
<i class="icon-upload-alt icon-white"></i>
<span>{{ _('Upload') }}</span>
<input id="gcode_upload" type="file" name="file" class="fileinput-button" data-bind="enable: loginState.isUser()">
</span>
</div>
<div id="gcode_upload_progress" class="progress" style="width: 100%;">
<div class="bar" style="width: 0%"></div>
</div>
<div>
<small>{{ _('Hint: You can also drag and drop files on this page to upload them.') }}</small>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="span8">
<div id="area_preview" class="workingarea" data-bind="click: move_laser, style: {backgroundPosition: crosshairX()+'px'+' '+crosshairY()+'px'}">
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="term">
<div class="" id="files_accordion">
<div class="">
<a class="accordion-toggle" data-toggle="collapse" href="#files"><i class="icon-list"></i> {{ _('Files') }}</a>
<div class="settings-trigger accordion-heading-button btn-group">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<span class="icon-wrench"></span>
</a>
<ul class="dropdown-menu">
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('name'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> {{ _('Sort by name') }} ({{ _('ascending') }})</a></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('upload'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'upload' ? 'visible' : 'hidden'}"></i> {{ _('Sort by upload date') }} ({{ _('descending') }})</a></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('size'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> {{ _('Sort by file size') }} ({{ _('descending') }})</a></li>
<li class="divider"></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('machinecode'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'machinecode') ? 'visible' : 'hidden'}"></i> {{ _('Only show GCode files') }}</a></li>
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('model'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'model') ? 'visible' : 'hidden'}"></i> {{ _('Only show STL files') }}</a></li>
<li class="divider"></li>
</ul>
</div>
</div>
<div class="accordion-body collapse in overflow_visible" id="files">
<div class="accordion-inner">
<form class="form-search">
<input type="text" class="input-block search-query" data-bind="value: searchQuery, valueUpdate: 'input'" placeholder="{{ _('Search...') }}">
</form>
<div class="gcode_files" data-bind="slimScrolledForeach: listHelper.paginatedItems">
<div class="entry" data-bind="attr: { id: $root.getEntryId($data) }, template: { name: $root.templateFor($data), data: $data }"></div>
<script type="text/html" id="files_template_machinecode">
<div class="file_list_entry">
<div class="title" data-bind="css: $root.getSuccessClass($data), style: { 'font-weight': $root.listHelper.isSelected($data) ? 'bold' : 'normal' }, 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="additionalInfo hide" data-bind="html: $root.getAdditionalData($data)"></div>
<div class="btn-group action-buttons">
<!--<div class="btn btn-mini toggleAdditionalData" data-bind="click: function() { if ($root.enableAdditionalData($data)) { $root.toggleAdditionalData($data); } else { return; } }, css: { disabled: !$root.enableAdditionalData($data) }"><i class="icon-chevron-down"></i></div>-->
<a class="btn btn-mini" data-bind="attr: {href: $root.downloadLink($data), css: {disabled: !$root.downloadLink($data)}}"><i class="icon-download-alt" title="{{ _('Download') }}"></i></a>
<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.enableSelect($data)) { $root.loadFile($data, false); } else { return; } }, css: {disabled: !$root.enableSelect($data)}"><i class="icon-folder-open" title="{{ _('Load') }}"></i></div>
<div class="btn btn-mini" data-bind="click: function() { if ($root.enableSelect($data)) { $root.loadFile($data, true); } else { return; } }, css: {disabled: !$root.enableSelect($data)}"><i class="icon-fire" title="{{ _('Load and Laser') }}"></i></div>
</div>
</div>
</script>
<script type="text/html" id="files_template_model_svg">
<div class="file_list_entry">
<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() { $root.convertSVG($data); }, css: {disabled: !$root.enableSVGConversion($data)}"><i class="icon-fire" title="{{ _('Convert to Laserpath') }}"></i></div>
</div>
</div>
</script>
<script type="text/html" id="files_template_folder">
<div class="title" data-bind="text: name"></div>
</script>
</div>
<div class="muted text-right">
<small>{{ _('Free') }}: <span data-bind="text: freeSpaceString"></span></small>
</div>
<div style="display: none;" data-bind="visible: loginState.isUser">
<div class="row-fluid upload-buttons">
<span class="btn btn-primary fileinput-button span12" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
<i class="icon-upload-alt icon-white"></i>
<span>{{ _('Upload') }}</span>
<input id="gcode_upload" type="file" name="file" class="fileinput-button" data-bind="enable: loginState.isUser()">
</span>
</div>
<div id="gcode_upload_progress" class="progress" style="width: 100%;">
<div class="bar" style="width: 0%"></div>
</div>
<div>
<small>{{ _('Hint: You can also drag and drop files on this page to upload them.') }}</small>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row-fluid">
<div class="span4 accordion">
</div>
<div class="span8">
<div id="area_preview" class="workingarea" data-bind="click: move_laser">
<div id="crosshair"></div>
<div class="laserpos" data-bind="text: laserPos">x,y</div>
<div class="" id="control">
<div class="jog-panel" style="display: none;" data-bind="visible: loginState.isUser">
<!-- XY jogging control panel -->
<div class="jog-panel">
<div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('y',1) }"><i class="icon-arrow-up"></i></button>
</div>
<div>
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('x',-1) }"><i class="icon-arrow-left"></i></button>
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendHomeCommand(['x', 'y']) }"><i class="icon-home"></i></button>
<button class="btn box pull-left" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('x',1) }"><i class="icon-arrow-right"></i></button>
</div>
<div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('y',-1) }"><i class="icon-arrow-down"></i></button>
</div>
</div>
{% if hasZAxis %}
<!-- Z jogging control panel -->
<div class="jog-panel" id="control_zaxis">
<div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('z',1) }"><i class="icon-arrow-up"></i></button>
</div>
<div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendHomeCommand(['z']) }"><i class="icon-home"></i></button>
</div>
<div>
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('z',-1) }"><i class="icon-arrow-down"></i></button>
</div>
</div>
{% endif %}
<!-- Jog distance -->
<div class="distance">
<div class="btn-group" data-toggle="buttons-radio" id="jog_distance">
<button type="button" class="btn distance" data-distance="0.1" data-bind="enable: loginState.isUser()">0.1</button>
<button type="button" class="btn distance" data-distance="1" data-bind="enable: loginState.isUser()">1</button>
<button type="button" class="btn distance active" data-distance="10" data-bind="enable: loginState.isUser()">10</button>
<button type="button" class="btn distance" data-distance="100" data-bind="enable: loginState.isUser()">100</button>
</div>
</div>
</div>
</div>
</div>
<div class="" id="term">
<pre id="terminal-output" class="pre-scrollable"></pre>
<label class="checkbox">
<input type="checkbox" id="terminal-autoscroll" data-bind="checked: autoscrollEnabled"> {{ _('Autoscroll') }}
</label>
<div data-bind="foreach: filters">
<label class="checkbox">
<input type="checkbox" id="terminal-autoscroll" data-bind="checked: autoscrollEnabled"> {{ _('Autoscroll') }}
<input type="checkbox" data-bind="attr: { value: regex }, checked: $parent.activeFilters"> <span data-bind="text: name"></span>
</label>
<div data-bind="foreach: filters">
<label class="checkbox">
<input type="checkbox" data-bind="attr: { value: regex }, checked: $parent.activeFilters"> <span data-bind="text: name"></span>
</label>
</div>
<div class="input-append" style="display: none;" data-bind="visible: loginState.isUser">
<input type="text" id="terminal-command" data-bind="value: command, event: { keyup: function(d,e) { return handleKeyUp(e); }, keydown: function(d,e) { return handleKeyDown(e); } }, enable: isOperational() && loginState.isUser()">
<button class="btn" type="button" id="terminal-send" data-bind="click: sendCommand, enable: isOperational() && loginState.isUser()">{{ _('Send') }}</button>
</div>
</div>
</div>
<div class="span8 ">
<pre id="terminal-output" class="pre-scrollable"></pre>
<div class="input-append" style="display: none;" data-bind="visible: loginState.isUser">
<input type="text" id="terminal-command" style="width:88%;" data-bind="value: command, event: { keyup: function(d,e) { return handleKeyUp(e); }, keydown: function(d,e) { return handleKeyDown(e); } }, enable: isOperational() && loginState.isUser()">
<button class="btn" type="button" id="terminal-send" data-bind="click: sendCommand, enable: isOperational() && loginState.isUser()">{{ _('Send') }}</button>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="designlib">
<div class="container-fluid">
<div class="row-fluid">
@ -363,12 +374,13 @@
</div>
</div>
</div>
<div class="tab-pane" id="focus">
<div class="container-fluid">
<div class="row-fluid">
<div class="span2">
<button id="btn_focus_on" class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: enable_focus">{{ _('Enable Focus') }}</button>
<button id="btn_focus_off" class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: disable_focus">{{ _('Disable Focus') }}</button>
<button id="btn_focus_on" class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: enable_focus">{{ _('Enable Focus') }}</button>
<button id="btn_focus_off" class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: disable_focus">{{ _('Disable Focus') }}</button>
steps... 1,2,3
</div>
<div class="span10">
@ -377,122 +389,124 @@
</div>
</div>
</div>
<div class="tab-pane" id="settings">
<div class="container-fluid">
<div class="row-fluid">
<div class="settings" >
{% include 'settings_mrbeam.jinja2' %}
</div>
<div class="settings" >
{% include 'settings_mrbeam.jinja2' %}
</div>
</div>
</div>
</div>
</div>
</div>
<!------->
<!------>
<div class="container octoprint-container">
<footer class="footer">
<ul class="pull-left muted">
<li><small>{{ _('Version') }}: <span class="version">{{ display_version }}</span></small></li>
</ul>
<ul class="pull-right">
<li><a href="http://www.mr-beam.org"><i class="icon-home"></i> {{ _('Homepage') }}</a></li>
<li><a href="https://github.com/mrbeam/OctoPrint/"><i class="icon-download"></i> {{ _('Sourcecode') }}</a></li>
<li><a href="https://wiki.mr-beam.org"><i class="icon-book"></i> {{ _('Documentation') }}</a></li>
<li><a href="https://github.com/mrbeam/OctoPrint/issues"><i class="icon-flag"></i> {{ _('Bugs and Requests') }}</a></li>
</ul>
</footer>
</div>
</div>
<!------->
{% include 'dialogs.jinja2' %}
<!------>
<div class="container octoprint-container">
<footer class="footer">
<ul class="pull-left muted">
<li><small>{{ _('Version') }}: <span class="version">{{ display_version }}</span></small></li>
</ul>
<ul class="pull-right">
<li><a href="http://www.mr-beam.org"><i class="icon-home"></i> {{ _('Homepage') }}</a></li>
<li><a href="https://github.com/mrbeam/OctoPrint/"><i class="icon-download"></i> {{ _('Sourcecode') }}</a></li>
<li><a href="https://wiki.mr-beam.org"><i class="icon-book"></i> {{ _('Documentation') }}</a></li>
<li><a href="https://github.com/mrbeam/OctoPrint/issues"><i class="icon-flag"></i> {{ _('Bugs and Requests') }}</a></li>
</ul>
</footer>
</div>
<!-- Plugin template files -->
{% if templatePlugins %}
{% for plugin_name in templatePlugins %}
{% include plugin_name+".jinja2" ignore missing %}
{% endfor %}
{% endif %}
<!-- End plugin template files -->
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/modernizr.custom.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/underscore-min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/underscore.string.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/knockout.js') }}"></script>
<!--<script type="text/javascript" src="{{ url_for('static', filename='js/lib/knockout-2.2.1.debug.js') }}"></script>-->
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/knockout.mapping-latest.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/babel.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/avltree.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-modalmanager.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-modal.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-slider.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.ui.core.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.ui.widget.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.ui.mouse.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.flot.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.iframe-transport.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.fileupload.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.slimscroll.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/sockjs-0.3.4.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/moment-with-locales.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/pusher.color.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/detectmobilebrowser.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/md5.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/pnotify.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/appearance.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/connection.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/control.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/firstrun.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/gcode.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/files.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/loginstate.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/navigation.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/printerstate.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/settings.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/slicing.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/temperature.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/terminal.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/timelapse.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/users.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/log.js') }}"></script>
{% include 'dialogs.jinja2' %}
{% for name, assets in assetPlugins.items() %}
{% if "js" in assets %}
{% for asset in assets["js"] %}
<script type="text/javascript" src="{{ url_for('plugin_assets', name=name, filename=asset) }}"></script>
{% endfor %}
{% endif %}
{% endfor %}
<script type="text/javascript" src="{{ url_for('static', filename='js/app/dataupdater.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/helpers.js') }}"></script>
<!-- Plugin template files -->
{% if templatePlugins %}
{% for plugin_name in templatePlugins %}
{% include plugin_name+".jinja2" ignore missing %}
{% endfor %}
{% endif %}
<!-- End plugin template files -->
<script type="text/javascript" src="{{ url_for('static', filename='js/app/main.js') }}"></script>
<!-- /Include OctoPrint files -->
<!-- Include i18n language files -->
{% if g.locale != 'en' %}
<script type="text/javascript" src="{{ url_for('static', filename='js/i18n/%s.js' % g.locale) }}"></script>
{% endif %}
<!-- /Include i18n language files -->
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/modernizr.custom.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/underscore-min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/underscore.string.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/knockout.js') }}"></script>
<!--<script type="text/javascript" src="{{ url_for('static', filename='js/lib/knockout-2.2.1.debug.js') }}"></script>-->
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/knockout.mapping-latest.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/babel.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/avltree.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-modalmanager.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-modal.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-slider.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.ui.core.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.ui.widget.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.ui.mouse.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.flot.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.iframe-transport.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.fileupload.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.slimscroll.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/sockjs-0.3.4.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/moment-with-locales.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/pusher.color.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/detectmobilebrowser.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/md5.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/pnotify.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/ui.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/gCodeReader.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/renderer.js') }}"></script>
</body>
</html>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/appearance.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/connection.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/control.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/firstrun.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/gcode.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/files.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/loginstate.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/navigation.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/printerstate.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/settings.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/slicing.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/temperature.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/terminal.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/timelapse.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/users.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/log.js') }}"></script>
{% for name, assets in assetPlugins.items() %}
{% if "js" in assets %}
{% for asset in assets["js"] %}
<script type="text/javascript" src="{{ url_for('plugin_assets', name=name, filename=asset) }}"></script>
{% endfor %}
{% endif %}
{% endfor %}
<script type="text/javascript" src="{{ url_for('static', filename='js/app/dataupdater.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/helpers.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/main.js') }}"></script>
<!-- /Include OctoPrint files -->
<!-- Include i18n language files -->
{% if g.locale != 'en' %}
<script type="text/javascript" src="{{ url_for('static', filename='js/i18n/%s.js' % g.locale) }}"></script>
{% endif %}
<!-- /Include i18n language files -->
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/ui.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/gCodeReader.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/renderer.js') }}"></script>
</body>
</html>

View file

@ -159,8 +159,8 @@ default_settings = {
"allowCrossOrigin": False
},
"terminalFilters": [
{ "name": "Suppress M105 requests/responses", "regex": "(Send: M105)|(Recv: ok T\d*:)" },
{ "name": "Suppress M27 requests/responses", "regex": "(Send: M27)|(Recv: SD printing byte)" }
{ "name": "Suppress status & confirmation messages", "regex": "(Send: \?)|(Recv: ok)" }
#{ "name": "Suppress M27 requests/responses", "regex": "(Send: M27)|(Recv: SD printing byte)" }
],
"plugins": {},
"devel": {

View file

@ -1123,8 +1123,7 @@ ul.dropdown-menu li a {
overflow: hidden;
}
#control .jog-panel {
float: left;
margin-right: 19px;
margin: 1em 1em 0;
}
#control h1 {
text-align: left;
@ -1562,8 +1561,13 @@ input.search-query,
display: block;
}
.jog-panel {
position: absolute;
right: 0;
bottom: 0;
}
#area_preview {
background-image: url(../img/crosshair.png);
background-repeat: no-repeat;
background-size: 30px auto;
}
.footer>* {
padding-top: 1em;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

View file

@ -74,7 +74,7 @@ $(function() {
var printerStateViewModel = new PrinterStateViewModel(loginStateViewModel, timelapseViewModel);
var appearanceViewModel = new AppearanceViewModel(settingsViewModel);
var temperatureViewModel = new TemperatureViewModel(loginStateViewModel, settingsViewModel);
var controlViewModel = new ControlViewModel(loginStateViewModel, settingsViewModel);
var controlViewModel = new ControlViewModel(loginStateViewModel, settingsViewModel, printerStateViewModel);
var terminalViewModel = new TerminalViewModel(loginStateViewModel, settingsViewModel);
var slicingViewModel = new SlicingViewModel(loginStateViewModel);
var vectorConversionViewModel = new VectorConversionViewModel(loginStateViewModel);

View file

@ -1,8 +1,9 @@
function ControlViewModel(loginStateViewModel, settingsViewModel) {
function ControlViewModel(loginStateViewModel, settingsViewModel, printerStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
self.settings = settingsViewModel;
self.printerState = printerStateViewModel;
self._createToolEntry = function() {
return {
@ -106,6 +107,18 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
}
return control;
};
self.laserPos = ko.computed(function(){
var pos = self.printerState.currentPos();
console.log(pos)
if(!pos){
console.log("foo");
return "(?, ?)";
} else {
console.log("bar")
return "("+ pos.x + ", "+ pos.y + ")";
}
}, this);
self.sendJogCommand = function(axis, multiplier, distance) {
if (typeof distance === "undefined")

View file

@ -29,6 +29,9 @@ function PrinterStateViewModel(loginStateViewModel) {
self.lastPrintTime = ko.observable(undefined);
self.currentHeight = ko.observable(undefined);
self.currentPos = ko.observable(undefined);
self.TITLE_PRINT_BUTTON_PAUSED = gettext("Restarts the print job from the beginning");
self.TITLE_PRINT_BUTTON_UNPAUSED = gettext("Starts the print job");
@ -112,6 +115,17 @@ function PrinterStateViewModel(loginStateViewModel) {
self._processProgressData(data.progress);
self._processZData(data.currentZ);
self._processBusyFiles(data.busyFiles);
if(data.workPosition){
self._processPos(data.workPosition);
}
};
self._processPos = function(posStr) {
// example posStr: "X: 73.0000 Y: 192.0000 Z: 0.0000"
var parts = posStr.split(" ");
var x = parseFloat(parts[1]).toFixed(2)
var y = parseFloat(parts[3]).toFixed(2)
self.currentPos({x:x, y:y});
};
self._processStateData = function(data) {

View file

@ -1,8 +1,5 @@
<div id="settings_dialog" class="container" xtabindex="-1" role="xdialog" aria-labelledby="settings_dialog_label" aria-hidden="true">
<div class="xmodal-header">
<!--<button type="button" class="close" data-dismiss="modal">&times;</button>-->
<!--<h3 id="settings_dialog_label">{{ _('Mr Beam Settings') }}</h3>-->
</div>
<div class="">
<div class="tabbable">
<ul class="nav nav-list span3" id="settingsTabs">
@ -72,7 +69,8 @@
</div>
</form>
</div>
<div class="tab-pane" id="settings_printerParameters">
<div class="tab-pane" id="settings_printerParameters">
<form class="form-horizontal">
@ -101,7 +99,8 @@
</div>
</form>
</div>
<div class="tab-pane" id="settings_features">
<div class="tab-pane" id="settings_features">
<form class="form-horizontal">
<div class="control-group">
<div class="controls">
@ -161,7 +160,8 @@
</div>
</form>
</div>
<div class="tab-pane" id="settings_folder">
<div class="tab-pane" id="settings_folder">
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="settings-folderUploads">{{ _('Upload Folder') }}</label>
@ -179,7 +179,8 @@
</form>
</div>
<div class="tab-pane" id="settings_temperature">
<div class="tab-pane" id="settings_temperature">
<form class="form-horizontal">
<div class="row-fluid">
<div class="offset3 span3"><h4>{{ _('Extruder') }}</h4></div>
@ -210,7 +211,8 @@
</div>
</form>
</div>
<div class="tab-pane" id="settings_terminalFilters">
<div class="tab-pane" id="settings_terminalFilters">
<form class="form-horizontal">
<div class="row-fluid">
<div class="span4"><h4>{{ _('Name') }}</h4></div>
@ -236,7 +238,8 @@
</div>
</form>
</div>
<div class="tab-pane" id="settings_appearance">
<div class="tab-pane" id="settings_appearance">
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="settings-appearanceName">{{ _('Title') }}</label>
@ -253,7 +256,8 @@
</div>
</form>
</div>
<div class="tab-pane" id="settings_api">
<div class="tab-pane" id="settings_api">
<form class="form-horizontal">
<div class="control-group">
<div class="controls">