diff --git a/package.json b/package.json index 5d5f1d2..2cdbd17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "FlexBE App", - "version": "2.1.5", + "version": "2.2.0", "main": "src/main.js", "window": { "icon": "src/img/icon-128.png", diff --git a/package.xml b/package.xml index 65dc104..865cccb 100644 --- a/package.xml +++ b/package.xml @@ -1,6 +1,6 @@ flexbe_app - 2.1.5 + 2.2.0 flexbe_app provides a user interface (editor + runtime control) for the FlexBE behavior engine. diff --git a/src/_helper/checking.js b/src/_helper/checking.js index f9531cc..3f5cadf 100644 --- a/src/_helper/checking.js +++ b/src/_helper/checking.js @@ -186,6 +186,7 @@ Checking = new (function() { var sparams = state.getParameterValues(); for (var i = 0; i < sparams.length; i++) { if (sparams[i] == "") return "parameter " + state.getParameters()[i] + " of state " + state.getStatePath() + " has empty value"; + if (state instanceof BehaviorState && sparams[i] == undefined) continue; if (!that.isValidExpressionSyntax(sparams[i], false)) return "parameter " + state.getParameters()[i] + " of state " + state.getStatePath() + " has invalid value"; } } @@ -195,6 +196,7 @@ Checking = new (function() { var imap = state.getInputMapping(); for (var i = 0; i < imap.length; i++) { if (imap[i] == "") return "input key " + state.getInputKeys()[i] + " of state " + state.getStatePath() + " has empty value"; + if (state instanceof BehaviorState && imap[i] == undefined) continue; if (!imap[i].match(python_varname_pattern)) return "input key " + state.getInputKeys()[i] + " of state " + state.getStatePath() + " has invalid value: " + imap[i]; } } diff --git a/src/_helper/tools.js b/src/_helper/tools.js index a273c86..cb16a04 100644 --- a/src/_helper/tools.js +++ b/src/_helper/tools.js @@ -30,7 +30,7 @@ Tools = new (function() { new_state.setInitialState(new_state.getStateByName(s.getInitialState().getStateName())); } } else if (s instanceof BehaviorState) { - new_state = new BehaviorState(s.getBehaviorName(), WS.Behaviorlib.getByName(s.getBehaviorName()), s.getDefaultKeys().clone()); + new_state = new BehaviorState(s.getBehaviorName(), WS.Behaviorlib.getByName(s.getBehaviorName())); new_state.setStateName(s.getStateName()); } else if (s instanceof State) { var state_def = WS.Statelib.getFromLib(s.getStateType()); diff --git a/src/_model/behaviorstate.js b/src/_model/behaviorstate.js index 33da5af..b5bbaa0 100644 --- a/src/_model/behaviorstate.js +++ b/src/_model/behaviorstate.js @@ -1,4 +1,4 @@ -BehaviorState = function(be_name, be_definition, be_defkeys) { +BehaviorState = function(be_name, be_definition) { State.apply(this, [be_name, be_definition]); var that = this; @@ -6,7 +6,6 @@ BehaviorState = function(be_name, be_definition, be_defkeys) { var behavior_manifest = be_definition.getBehaviorManifest(); var behavior_statemachine = be_definition.cloneBehaviorStatemachine(); behavior_statemachine.setBehavior(that); - var default_keys = be_defkeys; this.getBehaviorName = function() { return behavior_name; @@ -20,21 +19,13 @@ BehaviorState = function(be_name, be_definition, be_defkeys) { return behavior_manifest; } - this.getDefaultKeys = function() { - return default_keys; - } - - this.addDefaultKey = function(new_key) { - if (default_keys.contains(new_key)) return; - default_keys.push(new_key); - } - - this.removeDefaultKey = function(key) { - if (!default_keys.contains(key)) return; - default_keys.remove(key); + this.getParameterDefinition = function(param) { + return behavior_manifest.params.findElement(el => { + return el.name == param; + }); } - this.getDefaultValue = function(key) { + this.getDefaultUserdataValue = function(key) { var element = be_definition.getDefaultUserdata().findElement(function(el) { return el.key == key; }); diff --git a/src/_model/statemachine.js b/src/_model/statemachine.js index c29591c..7ca271d 100644 --- a/src/_model/statemachine.js +++ b/src/_model/statemachine.js @@ -178,7 +178,7 @@ Statemachine = function(sm_name, sm_definition) { var added_keys = [] state.getInputMapping().forEach(function(key, i) { if (added_keys.contains(key)) return; - if (state instanceof BehaviorState && state.getDefaultKeys().contains(state.getInputKeys()[i])) return; + if (state instanceof BehaviorState && key == undefined) return; added_keys.push(key); addDataEdgeForPredecessors(state, state, key, []); }); diff --git a/src/io/io_codegenerator.js b/src/io/io_codegenerator.js index a7fb5d0..3383bf2 100644 --- a/src/io/io_codegenerator.js +++ b/src/io/io_codegenerator.js @@ -298,15 +298,24 @@ IO.CodeGenerator = new (function() { } else if (s instanceof BehaviorState) { var defkeys_str = ""; - var be_defkeys = s.getDefaultKeys(); - if (be_defkeys.length > 0) { - var be_defkeys_str = [] - for (var j = 0; j < be_defkeys.length; j++) { - be_defkeys_str.push("'"+be_defkeys[j]+"'"); - } - defkeys_str = ", default_keys=[" + be_defkeys_str.join(',') + "]"; + var be_defkeys_str = []; + for (var j = 0; j < s.getInputKeys().length; j++) { + if (s.getInputMapping()[j] != undefined) continue; + be_defkeys_str.push("'"+s.getInputKeys()[j]+"'"); + } + if (be_defkeys_str.length > 0) { + defkeys_str = ",\n"+ws+ws+ws+ws+ws+ws+ws+ws+ws+ws+ws+"default_keys=[" + be_defkeys_str.join(',') + "]"; + } + var params_str = ""; + var be_params_str = []; + for (var j = 0; j < s.getParameters().length; j++) { + if (s.getParameterValues()[j] == undefined) continue; + be_params_str.push("'"+s.getParameters()[j]+"': "+s.getParameterValues()[j]); } - code += ws+ws+ws+ws+ws+ws+ws+ws+ws+ws+"self.use_behavior(" + s.getStateClass() + ", '" + s.getStatePath().substr(1) + "'" + defkeys_str + "),\n"; + if (be_params_str.length > 0) { + params_str = ",\n"+ws+ws+ws+ws+ws+ws+ws+ws+ws+ws+ws+"parameters={" + be_params_str.join(', ') + "}"; + } + code += ws+ws+ws+ws+ws+ws+ws+ws+ws+ws+"self.use_behavior(" + s.getStateClass() + ", '" + s.getStatePath().substr(1) + "'" + defkeys_str + params_str + "),\n"; } else { var class_key = (!UI.Settings.isExplicitStates() && WS.Statelib.isClassUnique(s.getStateClass()))? @@ -352,18 +361,21 @@ IO.CodeGenerator = new (function() { // remapping if (s.getInputKeys().length + s.getOutputKeys().length > 0) { - code += ",\n"; - code += ws+ws+ws+ws+ws+ws+ws+ws+ws+ws+"remapping={"; var remapping_strings = []; for (var j=0; j 0) { + code += ",\n"; + code += ws+ws+ws+ws+ws+ws+ws+ws+ws+ws+"remapping={"; + code += remapping_strings.join(", "); + code += "}"; + } } code += ")\n\n"; diff --git a/src/io/io_codeparser.js b/src/io/io_codeparser.js index 809baaa..3618686 100644 --- a/src/io/io_codeparser.js +++ b/src/io/io_codeparser.js @@ -86,8 +86,10 @@ IO.CodeParser = new (function() { var string_quotes_pattern = /^["'](.*)["']/; // [1] - name of the state class var state_class_pattern = /^\s*(\w+)\(/; - // [1] - name of the behavior class , [2] - (optional) list of default keys - var state_behavior_pattern = /^\s*self\.use_behavior\((\w+)(?:, ?['"](?:[^'"]*)['"])?(?:, ?default_keys ?= ?\[([^\]]*)\])?\)/; + // [1] - name of the behavior class , [2+2*i,3+2*i] - (optional) list of default keys or parameters + // if [2] is "default_keys", [3] is a list of default keys + // if [2] or [4] is "parameters", [3] or [5] is a dict of behavior parameters + var state_behavior_pattern = /^\s*self\.use_behavior\((\w+)(?:, ?['"](?:[^'"]*)['"])?(?:,\s*(default_keys) ?= ?\[([^\]]*)\])?(?:,\s*(parameters) ?= ?\{([^\}]*)\})?\)/; // [1] - kind of data (transitions/autonomy/remapping), [2] - comma separated list of values including surrounding braces var state_interface_pattern = /(transitions|autonomy|remapping)\s*=\s*(\{[^}]*\})/; @@ -461,11 +463,14 @@ IO.CodeParser = new (function() { var parseStateParams = function(params) { // get name var state_name = helper_removeQuotes(params[0]); - - // get state class and params var state_class = ""; var state_type = "state"; var parameter_values = []; + var transitions = []; + var autonomy = []; + var remapping = []; + + // get state class and params var class_result = params[1].match(state_class_pattern); if (class_result != null) { state_class = class_result[1]; @@ -487,10 +492,23 @@ IO.CodeParser = new (function() { if (behavior_use_result != null) { state_class = behavior_use_result[1]; state_type = "behavior"; - if (behavior_use_result[2] != undefined) { - parameter_values = behavior_use_result[2].replace(/["'\s]/g, '').split(','); - } else { - parameter_values = []; + if (behavior_use_result.length > 2 && behavior_use_result[2] == 'default_keys') { + behavior_use_result[3].replace(/["'\s]/g, '').split(',').forEach(key => { + remapping.push({ + key: key, + value: undefined + }); + }); + } + if (behavior_use_result.length > 2 && behavior_use_result[2] == 'parameters' + || behavior_use_result.length > 4 && behavior_use_result[4] == 'parameters') { + var dict_def = (behavior_use_result.length > 4)? behavior_use_result[5] : behavior_use_result[3]; + var dict_split = helper_splitOnTopCommas('{'+dict_def+'}'); + dict_split.forEach(element => { + var keyvalue = helper_splitKeyValue(element, ':'); + keyvalue.key = helper_removeQuotes(keyvalue.key); + parameter_values.push(keyvalue); + }); } } else { state_class = params[1]; @@ -500,9 +518,6 @@ IO.CodeParser = new (function() { } // get further parameters - var transitions = []; - var autonomy = []; - var remapping = []; for(var i=2; i { + return element.key == helper_removeQuotes(remapping_kv.key); + }) + if (default_input != undefined) + continue; if (remapping_kv != undefined) { remapping.push({ key: helper_removeQuotes(remapping_kv.key), @@ -596,11 +616,11 @@ IO.CodeParser = new (function() { // split into relevant methods var code_init_split = code_class_split[3].split(init_def_pattern); if (code_init_split.length == 1) throw "behavior constructor definition could not be found"; - var code_init = code_init_split[1].split(/\tdef/)[0]; + var code_init = code_init_split[1].split(/\sdef\s/)[0]; var code_create_split = code_class_split[3].split(create_def_pattern); if (code_create_split.length == 1) throw "behavior state machine creation section could not be found"; - var code_create = code_create_split[1].split(/\tdef/)[0]; + var code_create = code_create_split[1].split(/\sdef\s/)[0]; // parse init section var init_result = parseInitSection(code_init); @@ -673,7 +693,7 @@ IO.CodeParser = new (function() { var code_create_split = code_class_split[3].split(create_def_pattern); if (code_create_split.length == 1) throw "behavior state machine creation section could not be found"; - var code_create = code_create_split[1].split(/\tdef/)[0]; + var code_create = code_create_split[1].split(/\sdef\s/)[0]; // parse create section var create_result = parseCreateSection(code_create, true); diff --git a/src/io/io_modelgenerator.js b/src/io/io_modelgenerator.js index 4bda708..98e2a64 100644 --- a/src/io/io_modelgenerator.js +++ b/src/io/io_modelgenerator.js @@ -86,7 +86,8 @@ IO.ModelGenerator = new (function() { T.logInfo("Please check your workspace settings."); continue; } - s = new BehaviorState(s_def.state_name, state_def, s_def.parameter_values); + s = new BehaviorState(s_def.state_name, state_def); + s.setParameterValues(helper_getSortedValueList(s.getParameters(), s.getParameterValues(), s_def.parameter_values)); } else { var state_def = undefined; if (s_def.state_class.includes("__")) { diff --git a/src/ui/panels/ui_panels_stateproperties.js b/src/ui/panels/ui_panels_stateproperties.js index 7416b69..b1149c9 100644 --- a/src/ui/panels/ui_panels_stateproperties.js +++ b/src/ui/panels/ui_panels_stateproperties.js @@ -11,8 +11,12 @@ UI.Panels.StateProperties = new (function() { document.getElementById(id).style.background = ""; } - var addHoverDocumentation = function(el, type, name, state_class) { - var def = WS.Statelib.getFromLib(state_class); + var addHoverDocumentation = function(el, type, name, state_class, behavior_name) { + if (state_class) { + var def = WS.Statelib.getFromLib(state_class); + } else if (behavior_name) { + var def = WS.Behaviorlib.getByName(behavior_name); + } var doc = undefined; switch (type) { case "param": doc = def.getParamDesc().findElement(function(el) { return el.name == name; }); break; @@ -41,8 +45,8 @@ UI.Panels.StateProperties = new (function() { }); } - var addAutocomplete = function(el, state_class, mode, state) { - var additional_keywords = []; + var addAutocomplete = function(el, state_class, mode, state, additional_keywords) { + var additional_keywords = additional_keywords || []; if (state_class != undefined) { var class_vars = WS.Statelib.getFromLib(state_class).getClassVariables(); for (var i = 0; i < class_vars.length; i++) { @@ -437,6 +441,111 @@ UI.Panels.StateProperties = new (function() { document.getElementById("label_prop_be_package").innerText = state.getStatePackage(); document.getElementById("label_prop_be_desc").innerText = WS.Behaviorlib.getByName(state.getBehaviorName()).getBehaviorDesc(); + // Parameters + //----------- + params = state.getParameters(); + values = state.getParameterValues(); + if (params.length > 0) { + document.getElementById("panel_prop_be_parameters").style.display = "block"; + document.getElementById("panel_prop_be_parameters_content").innerHTML = ""; + for (var i=0; i { + additional_keywords.push({text: opt, hint: "enum", fill: opt}) + }); + } + addAutocomplete(input_field, undefined, undefined, undefined, additional_keywords); + + var default_button = document.createElement("input"); + default_button.setAttribute("type", "checkbox"); + default_button.setAttribute("param_key", params[i]); + if (values[i] == undefined) { + default_button.setAttribute("checked", "checked"); + } + default_button.addEventListener("change", function() { + if (RC.Controller.isReadonly() + || UI.Statemachine.getDisplayedSM().isInsideDifferentBehavior() + || Behavior.isReadonly() + || RC.Controller.isLocked() && RC.Controller.isStateLocked(current_prop_state.getStatePath()) + || RC.Controller.isOnLockedPath(current_prop_state.getStatePath()) + ) return; + var input_field = this.parentNode.parentNode.childNodes[1].firstChild; + var label = this.parentNode.parentNode.firstChild; + if(this.checked) { + input_field.setAttribute("style", "text-decoration: line-through; color: rgba(0,0,0,.4);"); + input_field.setAttribute("disabled", "disabled"); + input_field.setAttribute("class", "inline_text_edit_readonly"); + label.setAttribute("style", "color: gray"); + var idx = state.getParameters().indexOf(this.getAttribute("param_key")); + state.getParameterValues()[idx] = undefined; + input_field.setAttribute("title", "Value: " + input_field.getAttribute("default_value")); + } else { + input_field.removeAttribute("style"); + input_field.removeAttribute("disabled"); + input_field.removeAttribute("title"); + input_field.setAttribute("class", "inline_text_edit"); + label.setAttribute("style", "color: black"); + var idx = state.getParameters().indexOf(this.getAttribute("param_key")); + state.getParameterValues()[idx] = input_field.value; + } + if (UI.Statemachine.isDataflow()) UI.Statemachine.refreshView(); + if (UI.Menu.isPageControl()) UI.RuntimeControl.resetParameterTableClicked(); + }); + var default_button_txt = document.createElement("label"); + default_button_txt.innerText = "default"; + var default_button_td = document.createElement("td"); + default_button_td.setAttribute("title", "Use the default value as defined by the behavior."); + default_button_td.appendChild(default_button); + default_button_td.appendChild(default_button_txt); + + var row = document.createElement("tr"); + row.appendChild(label); + row.appendChild(input_field_td); + row.appendChild(default_button_td); + document.getElementById("panel_prop_be_parameters_content").appendChild(row); + + addHoverDocumentation(row, "param", params[i], undefined, state.getBehaviorName()); + } + } else { + document.getElementById("panel_prop_be_parameters").style.display = "none"; + } + // Outcomes //---------- var outcome_list_complete = state.getOutcomes(); @@ -468,12 +577,14 @@ UI.Panels.StateProperties = new (function() { var input_field = document.createElement("input"); input_field.setAttribute("class", "inline_text_edit"); input_field.setAttribute("type", "text"); - input_field.setAttribute("value", input_mapping[i]); + input_field.setAttribute("value", input_mapping[i] || input_keys[i]); input_field.setAttribute("input_key", input_keys[i]); - if (state.getDefaultKeys().contains(input_keys[i])) { + if (input_mapping[i] == undefined) { input_field.setAttribute("style", "text-decoration: line-through; color: rgba(0,0,0,.4);"); input_field.setAttribute("disabled", "disabled"); - input_field.setAttribute("title", "Value: " + state.getDefaultValue(input_keys[i])); + input_field.setAttribute("title", "Value: " + state.getDefaultUserdataValue(input_keys[i])); + input_field.setAttribute("class", "inline_text_edit_readonly"); + label.setAttribute("style", "color: gray"); } input_field.addEventListener("blur", function() { if (RC.Controller.isReadonly() @@ -493,7 +604,7 @@ UI.Panels.StateProperties = new (function() { var default_button = document.createElement("input"); default_button.setAttribute("type", "checkbox"); default_button.setAttribute("input_key", input_keys[i]); - if (state.getDefaultKeys().contains(input_keys[i])) { + if (input_mapping[i] == undefined) { default_button.setAttribute("checked", "checked"); } default_button.addEventListener("change", function() { @@ -504,16 +615,23 @@ UI.Panels.StateProperties = new (function() { || RC.Controller.isOnLockedPath(current_prop_state.getStatePath()) ) return; var input_field = this.parentNode.parentNode.childNodes[1].firstChild; + var label = this.parentNode.parentNode.firstChild; if(this.checked) { input_field.setAttribute("style", "text-decoration: line-through; color: rgba(0,0,0,.4);"); input_field.setAttribute("disabled", "disabled"); - state.addDefaultKey(this.getAttribute("input_key")); - input_field.setAttribute("title", "Value: " + state.getDefaultValue(this.getAttribute("input_key"))); + input_field.setAttribute("class", "inline_text_edit_readonly"); + label.setAttribute("style", "color: gray"); + var idx = state.getInputKeys().indexOf(this.getAttribute("input_key")); + state.getInputMapping()[idx] = undefined; + input_field.setAttribute("title", "Value: " + state.getDefaultUserdataValue(this.getAttribute("input_key"))); } else { input_field.removeAttribute("style"); input_field.removeAttribute("disabled"); input_field.removeAttribute("title"); - state.removeDefaultKey(this.getAttribute("input_key")); + input_field.setAttribute("class", "inline_text_edit"); + label.setAttribute("style", "color: black"); + var idx = state.getInputKeys().indexOf(this.getAttribute("input_key")); + state.getInputMapping()[idx] = input_field.value; } if (UI.Statemachine.isDataflow()) UI.Statemachine.refreshView(); }); diff --git a/src/ui/ui_runtimecontrol.js b/src/ui/ui_runtimecontrol.js index e49627e..8365f43 100644 --- a/src/ui/ui_runtimecontrol.js +++ b/src/ui/ui_runtimecontrol.js @@ -249,7 +249,9 @@ UI.RuntimeControl = new (function() { for (var i = 0; i < embedded_behaviors.length; i++) { if (embedded_behaviors[i] == undefined) continue; var b = embedded_behaviors[i]; - var ps = b.getBehaviorManifest().params.clone(); + var ps = b.getBehaviorManifest().params.filter(p => { + return b.getParameterValues()[b.getParameters().indexOf(p.name)] == undefined; + }); params = params.concat(ps.filter(function(el) { return params.findElement(function(p) { return p.name == el.name; diff --git a/src/window.html b/src/window.html index ab34006..9721f6c 100644 --- a/src/window.html +++ b/src/window.html @@ -667,6 +667,13 @@

Behavior Synthesis

+
+ Parameters + + +
+
+
Required Autonomy Levels diff --git a/src/ws/ws_behaviorstatedefinition.js b/src/ws/ws_behaviorstatedefinition.js index f8d6c56..4aa4854 100644 --- a/src/ws/ws_behaviorstatedefinition.js +++ b/src/ws/ws_behaviorstatedefinition.js @@ -16,8 +16,31 @@ WS.BehaviorStateDefinition = function(manifest, outcomes, input_keys, output_key if (bsm_loaded_callback != undefined) bsm_loaded_callback(); }); - this.__proto__ = new WS.StateDefinition(manifest.class_name, new WS.Documentation(manifest.description), - path, [], outcomes, input_keys, output_keys, [], autonomy, []); + var documentation = new WS.Documentation(manifest.description); + var parameters = []; + var parameterDefaults = []; + manifest.params.forEach(param => { + parameters.push(param.name); + parameterDefaults.push(undefined); + var defaultValue = (param.type == "text")? '"' + param.default + '"' : param.default; + var desc = "
Default: " + defaultValue + "
" + param.label + ": " + param.hint; + var info = ""; + if (param.type == "numeric") { + info = "Value range: " + param.additional.min + " - " + param.additional.max; + } else if (param.type == "enum") { + info = "Possible values:"; + param.additional.forEach(opt => { + info += "
    - " + opt; + }); + } + if (info != "") { + desc += "
" + info + "
"; + } + documentation.addDescription('--', param.name, param.type, desc); + }); + + this.__proto__ = new WS.StateDefinition(manifest.class_name, documentation, + path, parameters, outcomes, input_keys, output_keys, parameterDefaults, autonomy, []); this.getBehaviorName = function() { return behavior_name; } this.getBehaviorManifest = function() { return behavior_manifest; }