Preplanner: surface plan failures so the Processing dialog exits
When a plan fails (e.g. AuxPreprocessor Z-A coupling rejection at
planner-load time, or any other gplan error in the plan.py
subprocess), the Plan future was never resolved. PathHandler then
1-second-times-out forever returning {progress:0}, and the JS poll
loop in load_toolpath kept the 'Processing New File' dialog up
indefinitely.
- Preplanner.Plan now records .error and always resolves the future.
- PathHandler returns {progress:1, error:...} when the plan failed.
- load_toolpath closes the dialog and alerts the operator on error,
and breaks out of the poll loop on api errors instead of looping.
- FileHandler upload-time AuxPreprocessor coupling errors now post a
visible state message instead of being silently swallowed.
This commit is contained in:
@@ -232,6 +232,17 @@ module.exports = {
|
|||||||
const toolpath = await api.get(`path/${file}`);
|
const toolpath = await api.get(`path/${file}`);
|
||||||
this.toolpath_progress = toolpath.progress;
|
this.toolpath_progress = toolpath.progress;
|
||||||
|
|
||||||
|
// Planner failure (e.g. AuxPreprocessor Z-A coupling
|
||||||
|
// rejection). Close the dialog and surface the message
|
||||||
|
// instead of polling the same broken plan forever.
|
||||||
|
if (toolpath.error) {
|
||||||
|
this.showGcodeMessage = false;
|
||||||
|
this.toolpath_progress = 0;
|
||||||
|
console.error("Plan failed:", toolpath.error);
|
||||||
|
alert("Could not plan G-code:\n\n" + toolpath.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (toolpath.progress === 1 || typeof toolpath.progress == "undefined") {
|
if (toolpath.progress === 1 || typeof toolpath.progress == "undefined") {
|
||||||
this.showGcodeMessage = false;
|
this.showGcodeMessage = false;
|
||||||
|
|
||||||
@@ -248,7 +259,11 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// api.get throws on non-2xx; log and break the loop so the
|
||||||
|
// dialog doesn't stay up forever.
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
this.showGcodeMessage = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -107,15 +107,27 @@ class FileHandler(bbctrl.APIHandler):
|
|||||||
# auxcnc stepper is exposed as a virtual A axis (see
|
# auxcnc stepper is exposed as a virtual A axis (see
|
||||||
# ExternalAxis).
|
# ExternalAxis).
|
||||||
try:
|
try:
|
||||||
from bbctrl.AuxPreprocessor import preprocess_file
|
from bbctrl.AuxPreprocessor import (
|
||||||
|
preprocess_file, AuxPreprocessorError)
|
||||||
log = self.get_log('AuxPreprocessor')
|
log = self.get_log('AuxPreprocessor')
|
||||||
ext = getattr(self.get_ctrl(), 'ext_axis', None)
|
ext = getattr(self.get_ctrl(), 'ext_axis', None)
|
||||||
coupling = (ext.coupling_for_preprocessor()
|
coupling = (ext.coupling_for_preprocessor()
|
||||||
if ext is not None else None)
|
if ext is not None else None)
|
||||||
|
try:
|
||||||
if preprocess_file(filename.decode('utf8'),
|
if preprocess_file(filename.decode('utf8'),
|
||||||
log=log, coupling=coupling):
|
log=log, coupling=coupling):
|
||||||
log.info('Rewrote upload (ATC / Z-A coupling) in %s'
|
log.info('Rewrote upload (ATC / Z-A coupling) in %s'
|
||||||
% self.uploadFilename)
|
% self.uploadFilename)
|
||||||
|
except AuxPreprocessorError as e:
|
||||||
|
# Surface coupling-violation errors to the operator
|
||||||
|
# via the message stream so the upload doesn't go
|
||||||
|
# silently un-rewritten and then trip the runtime
|
||||||
|
# check (which can hang the planner dialog).
|
||||||
|
log.warning('Aux preprocess refused upload: %s' % e)
|
||||||
|
try:
|
||||||
|
self.get_ctrl().state.add_message(
|
||||||
|
'Z-A coupling: ' + str(e))
|
||||||
|
except Exception: pass
|
||||||
except Exception:
|
except Exception:
|
||||||
self.get_log('AuxPreprocessor').exception(
|
self.get_log('AuxPreprocessor').exception(
|
||||||
'Aux preprocess failed; uploading unchanged')
|
'Aux preprocess failed; uploading unchanged')
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ class Plan(object):
|
|||||||
self.progress = 0
|
self.progress = 0
|
||||||
self.cancel = False
|
self.cancel = False
|
||||||
self.pid = None
|
self.pid = None
|
||||||
|
self.error = None
|
||||||
|
|
||||||
root = ctrl.get_path()
|
root = ctrl.get_path()
|
||||||
self.gcode = '%s/upload/%s' % (root, filename)
|
self.gcode = '%s/upload/%s' % (root, filename)
|
||||||
@@ -202,8 +203,16 @@ class Plan(object):
|
|||||||
if not self._exists(): yield self._exec()
|
if not self._exists(): yield self._exec()
|
||||||
self.future.set_result(self._read())
|
self.future.set_result(self._read())
|
||||||
|
|
||||||
except:
|
except Exception as e:
|
||||||
self.preplanner.log.exception("Failed to load file - doesn't appear to be GCode.")
|
# Record the error and ALWAYS resolve the future, otherwise
|
||||||
|
# PathHandler.get keeps timing out at 1s forever and the UI
|
||||||
|
# gets stuck on the "Processing New File" dialog.
|
||||||
|
self.preplanner.log.exception(
|
||||||
|
"Failed to plan file: " + str(e))
|
||||||
|
self.error = str(e) or 'Plan failed'
|
||||||
|
self.progress = 1
|
||||||
|
if not self.future.done():
|
||||||
|
self.future.set_result(None)
|
||||||
|
|
||||||
|
|
||||||
class Preplanner(object):
|
class Preplanner(object):
|
||||||
@@ -268,3 +277,6 @@ class Preplanner(object):
|
|||||||
|
|
||||||
def get_plan_progress(self, filename):
|
def get_plan_progress(self, filename):
|
||||||
return self.plans[filename].progress if filename in self.plans else 0
|
return self.plans[filename].progress if filename in self.plans else 0
|
||||||
|
|
||||||
|
def get_plan_error(self, filename):
|
||||||
|
return self.plans[filename].error if filename in self.plans else None
|
||||||
|
|||||||
@@ -411,11 +411,22 @@ class PathHandler(bbctrl.APIHandler):
|
|||||||
|
|
||||||
except gen.TimeoutError:
|
except gen.TimeoutError:
|
||||||
progress = preplanner.get_plan_progress(filename)
|
progress = preplanner.get_plan_progress(filename)
|
||||||
self.write_json(dict(progress = progress))
|
err = preplanner.get_plan_error(filename)
|
||||||
|
resp = dict(progress = progress)
|
||||||
|
if err: resp['error'] = err
|
||||||
|
self.write_json(resp)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if data is None: return
|
# Plan finished but produced no data (planner subprocess
|
||||||
|
# failed, e.g. AuxPreprocessor coupling rejection at
|
||||||
|
# planner-load time). Surface the error so the UI can
|
||||||
|
# close the "Processing New File" dialog instead of
|
||||||
|
# polling forever.
|
||||||
|
if data is None:
|
||||||
|
err = preplanner.get_plan_error(filename) or 'Plan failed'
|
||||||
|
self.write_json(dict(progress = 1, error = err))
|
||||||
|
return
|
||||||
meta, positions, speeds = data
|
meta, positions, speeds = data
|
||||||
|
|
||||||
if dataType == '/positions': data = positions
|
if dataType == '/positions': data = positions
|
||||||
|
|||||||
Reference in New Issue
Block a user