From 7b00090a66e049cdbc889a30c6e06354456637c4 Mon Sep 17 00:00:00 2001 From: cfry Date: Fri, 9 Feb 2024 17:41:52 -0500 Subject: [PATCH] release --- LICENSE | 12 + core/dextersim.js | 8 + core/index.js | 5 +- core/instruction_j_move.js | 1087 ++++++++++++++++++++------- core/instruction_j_move_dec_2023.js | 482 ++++++++++++ core/robot.js | 2 +- core/storage.js | 122 ++- doc/guide.html | 4 +- doc/known_issues.html | 2 + doc/ref_man.html | 39 +- doc/release_notes.html | 34 + editor.js | 42 +- examples/opencv_face_reco.js | 3 +- examples/opencv_process_webcam.js | 3 +- package.json | 4 +- picture1.js | 8 +- ready.js | 77 +- test_suite/test_suite.js | 15 +- user_tools/ezTeach_base.js | 6 +- video.js | 37 +- 20 files changed, 1595 insertions(+), 397 deletions(-) create mode 100644 LICENSE create mode 100644 core/instruction_j_move_dec_2023.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..bf434b68 --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Dexter Development Environment (DDE) +Copyright (C) 2016-2024 Christopher Fry + +GPLv3 + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. \ No newline at end of file diff --git a/core/dextersim.js b/core/dextersim.js index a5b8e39c..bb61901d 100644 --- a/core/dextersim.js +++ b/core/dextersim.js @@ -117,7 +117,15 @@ DexterSim = class DexterSim{ static array_buffer_to_oplet_array(arr_buff){ let str = this.array_buffer_to_string(arr_buff) + if(str.endsWith(";")) { + str = str.substring(0, str.length - 1) //cut off the semicolon on the end + } let split_str = str.split(" ") + //if its a var length instruction, then an integer is in place of the oplet and the oplet is one later + let orig_oplet_maybe = split_str[Instruction.INSTRUCTION_TYPE] + if(!Robot.is_oplet(orig_oplet_maybe)) { //assume its an integer for a variable-length instruction + split_str.splice(Instruction.INSTRUCTION_TYPE, 1) //removes integer from var length array. makign it 1 shorter + } let oplet_array = [] let oplet for(let i = 0; i < split_str.length; i++) { diff --git a/core/index.js b/core/index.js index f8cd2912..36de9901 100644 --- a/core/index.js +++ b/core/index.js @@ -1,6 +1,5 @@ -global.dde_version = "3.8.19" //require("../package.json").version -global.dde_release_date = "Jan 11, 2024" //require("../package.json").release_date - +global.dde_version = "3.9.0" //require("../package.json").version +global.dde_release_date = "Feb 9, 2024" //require("../package.json").release_dat9 console.log("dde_version: " + global.dde_version + " dde_release_date: " + global.dde_release_date + "\nRead electron_dde/core/job_engine_doc.txt for how to use the Job Engine.\n") diff --git a/core/instruction_j_move.js b/core/instruction_j_move.js index c7d01055..a9b43275 100644 --- a/core/instruction_j_move.js +++ b/core/instruction_j_move.js @@ -1,10 +1,10 @@ -/* Dexter_j_move_proposal.dde -Written by: James Wigglesworth -Started: 11/13/2023 -Modified: 11/27/2023 - +/* +//j_move_examples.dde +//Written by: James Wigglesworth +//Started: 11/13/2023 +//Modified: 2/5/2024 -Proposed new functions: +New functions: -Dexter.j_move() -Dexter.j_reset() -Dexter.j_set_peak_velocity() @@ -21,8 +21,6 @@ Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [30, 0, 0, 0, 0, 0]}), Dexter.j_move([0, 0, 0, 0, 0, 0], {end_acceleration: [30, 0, 0, 0, 0, 0]}), Dexter.j_move([0, 30, 0, 0, 0, 0], {async: true}), - - Dexter.j_move([0, 30, 0, 0, 0, 0], { end_velocity: [0, 0, 0, 0, 0, 0], end_acceleration: [0, 0, 0, 0, 0, 0], @@ -30,29 +28,42 @@ Dexter.j_move([0, 30, 0, 0, 0, 0], { async: undefined, //defualts true if coming to stop, false if moving through point, will always be overwritten by user value }), - +load_files("Libraries/lib_j_moves.dde") //Limits: -var speed = [30, 30, 30, 30, 30, 30] -var accel = [150, 150, 150, 150, 150, 150] -var jerk = [3000, 3000, 3000, 3000, 3000, 3000] -define_j_move_functions() //this is just to show the code for the Jobs first in this file +var vel = [30, 30, 30, 30, 30, 30] +var accel = [100, 100, 100, 100, 100, 100] +var jerk = [1000, 1000, 1000, 1000, 1000, 1000] + +//Run once per boot up to initialize speed settings that work best for these examples. +new Job({ + name: "Init", + show_instructions: false, + do_list: [ + init + ] +}) //Job Examples: -//Example 1 - simple motion +//Example 0 - simple motion new Job({ - name: "j_move_example_1", + name: "Example_0", + show_instructions: false, + do_list: [ + Dexter.j_move([30, 0, 0, 0, 0]), + Dexter.j_move([0, 0, 0, 0, 0]), + ] +}) + +//Example 1 - simple motion loop (try stopping mid motion) +new Job({ + name: "Example_1", show_instructions: false, when_stopped: function(){ return Dexter.j_reset() }, do_list: [ - j_set_hardware_limits(), - Dexter.j_set_peak_velocity(speed), - Dexter.j_set_peak_acceleration(accel), - Dexter.j_set_peak_jerk(jerk), - Control.loop(true, function(){ return [ Dexter.j_move([0, -45, 0, 0, 0]), @@ -62,71 +73,188 @@ new Job({ ] }) -Example 1 oplet strings sent per loop - j p 0,-162000,0,0,0,0; - j v 0,0,0,0,0,0; - j a 0,0,0,0,0,0 ?; - F 50; - j p 0,162000,0,0,0,0; - j v 0,0,0,0,0,0; - j a 0,0,0,0,0,0 ?; - F 50; - - +//Example 2 - Plotting J moves and how to use second argument of 'options' +var example_2_data +function record_example_2_data(){ + return [ + function(){ + let obj = parse_g5(this.robot.robot_status) + obj.time = Date.now()*_ms - this.user_data.start_time + example_2_data.push(obj) + } + ] +} -//Example 2 - simple motion with duration new Job({ - name: "j_move_example_2", + name: "Example_2", show_instructions: false, when_stopped: function(){ return Dexter.j_reset() }, do_list: [ - j_set_hardware_limits(), - Dexter.j_set_peak_velocity(speed), - Dexter.j_set_peak_acceleration(accel), - Dexter.j_set_peak_jerk(jerk), + Dexter.j_move([0, 0, 0, 0, 0]), + + function(){ + example_2_data = [] + this.user_data.start_time = Date.now()*_ms + }, + + Dexter.j_move([30, 0, 0, 0, 0], { + monitor_callback: record_example_2_data + }), Dexter.j_move([0, 0, 0, 0, 0]), - Control.loop(true, function(){ - return [ - Dexter.j_move([0, -10, 0, 0, 0], {duration: 1}), - Dexter.j_move([0, -5, 0, 0, 0], {duration: 1}), - Dexter.j_move([0, 10, 0, 0, 0], {duration: 1}), - ] - }) + + function(){ + //Plotting positional data: + plot_trajectory(example_2_data, { + title: "J_Move Position vs. Time", + data_types: ["MEASURED_ANGLE", "STEP_ANGLE", "J_POSITION"], + joints_to_plot: [true, false, false, false, false, false], + y_axis: "Joint Angle (deg)" + }) + }, + + function(){ + //Plotting velocity data (notice the 'd', it can be used with other vairables too): + plot_trajectory(example_2_data, { + title: "J_Move Velocity vs. Time", + data_types: ["dMEASURED_ANGLE", "dSTEP_ANGLE", "J_VELOCITY"], + joints_to_plot: [true, false, false, false, false, false], + y_axis: "Joint Velocity (deg/s)" + }) + }, + + function(){ + //Plotting acceleration data (notice that 'd' can be applied multiple times): + plot_trajectory(example_2_data, { + title: "J_Move Acceleration vs. Time", + data_types: ["ddMEASURED_ANGLE", "ddSTEP_ANGLE", "J_ACCELERATION"], //you could add the + joints_to_plot: [true, false, false, false, false, false], + y_axis: "Joint Acceleration (deg/s^2)" + }) + }, + + ] }) -Example 2 oplet strings sent per loop - j p 0,-36000,0,0,0,0; - j v 0,0,0,0,0,0; - j a 0,0,0,0,0,0 1; - F 50; - j p 0,-18000,0,0,0,0; - j v 0,0,0,0,0,0; - j a 0,0,0,0,0,0 1; - F 50; - j p 0,36000,0,0,0,0; - j v 0,0,0,0,0,0; - j a 0,0,0,0,0,0 1; - F 50; +//Example 3 - Setting 'peak' values +new Job({ + name: "Example_3", + show_instructions: false, + when_stopped: function(){ + return Dexter.j_reset() + }, + do_list: [ + init, // sets speeds to default values + Dexter.j_move([0, 0, 0, 0, 0]), + function(){ + example_3_data = [] + this.user_data.start_time = Date.now()*_ms + }, + + //Default settings: + Dexter.j_move([30, 0, 0, 0, 0], { + peak_velocity: [30, 30, 30, 30, 30, 30], + peak_acceleration: [100, 100, 100, 100, 100, 100], + peak_jerk: [1000, 1000, 1000, 1000, 1000, 1000], + monitor_callback: record_example_3_data + }), + + //2x higher velocity: + Dexter.j_move([60, 0, 0, 0, 0], { + peak_velocity: [40, 30, 30, 30, 30, 30], + peak_acceleration: [100, 100, 100, 100, 100, 100], + peak_jerk: [1000, 1000, 1000, 1000, 1000, 1000], + monitor_callback: record_example_3_data + }), + + //3x higher acceleration: + Dexter.j_move([90, 0, 0, 0, 0], { + peak_velocity: [30, 30, 30, 30, 30, 30], + peak_acceleration: [3*100, 100, 100, 100, 100, 100], + peak_jerk: [1000, 1000, 1000, 1000, 1000, 1000], + monitor_callback: record_example_3_data + }), + + //30x higher jerk: + Dexter.j_move([120, 0, 0, 0, 0], { + peak_velocity: [30, 30, 30, 30, 30, 30], + peak_acceleration: [100, 100, 100, 100, 100, 100], + peak_jerk: [30*1000, 1000, 1000, 1000, 1000, 1000], + monitor_callback: record_example_3_data + }), + + //higher everything: + Dexter.j_move([150, 0, 0, 0, 0], { + peak_velocity: [40, 30, 30, 30, 30, 30], + peak_acceleration: [3*100, 100, 100, 100, 100, 100], + peak_jerk: [30*1000, 1000, 1000, 1000, 1000, 1000], + monitor_callback: record_example_3_data + }), + + //Go to Home at normal settings: + Dexter.j_move([0, 0, 0, 0, 0], { + peak_velocity: [30, 30, 30, 30, 30, 30], + peak_acceleration: [100, 100, 100, 100, 100, 100], + peak_jerk: [1000, 1000, 1000, 1000, 1000, 1000], + }), + + function(){ + //Plotting positional data: + plot_trajectory(example_3_data, { + title: "J_Move Position vs. Time", + data_types: ["MEASURED_ANGLE", "STEP_ANGLE", "J_POSITION"], + joints_to_plot: [true, false, false, false, false, false], + y_axis: "Joint Angle (deg)" + }) + }, + + function(){ + //Plotting velocity data (notice the 'd', it can be used with other vairables too): + plot_trajectory(example_3_data, { + title: "J_Move Velocity vs. Time", + data_types: ["dMEASURED_ANGLE", "dSTEP_ANGLE", "J_VELOCITY"], + joints_to_plot: [true, false, false, false, false, false], + y_axis: "Joint Velocity (deg/s)" + }) + }, + + + //function(){ + //Plotting acceleration data (notice that 'd' can be applied multiple times): + // plot_trajectory(example_3_data, { + // title: "J_Move Acceleration vs. Time", + // data_types: ["ddMEASURED_ANGLE", "ddSTEP_ANGLE", "J_ACCELERATION"], // + // joints_to_plot: [true, false, false, false, false, false], + // y_axis: "Joint Acceleration (deg/s^2)" + // }) + // }, + ] +}) + +var example_3_data +function record_example_3_data(){ + return [ + function(){ + let obj = parse_g5(this.robot.robot_status) + obj.time = Date.now()*_ms - this.user_data.start_time + example_3_data.push(obj) + } + ] +} -//Example 3 - Motion through a waypoint +//Example 4 - Setting 'end' values new Job({ - name: "j_move_example_3", + name: "Example_4", show_instructions: false, when_stopped: function(){ return Dexter.j_reset() }, do_list: [ - j_set_hardware_limits(), - Dexter.j_set_peak_velocity(speed), - Dexter.j_set_peak_acceleration(accel), - Dexter.j_set_peak_jerk(jerk), - Control.loop(true, function(){ return [ Dexter.j_move([-30, -30, 0, 0, 0, 0]), @@ -137,88 +265,28 @@ new Job({ ] }) -Example 3 oplet strings sent per loop - j p -108000,-108000,0,0,0,0; - j v 0,0,0,0,0,0; - j a 0,0,0,0,0,0 ?; - F 50; - j p 0,0,0,0,0,0; - j v 108000,0,0,0,0,0; - j a 0,0,0,0,0,0 ?; - j p 108000,0,0,0,0,0; - j v 0,0,0,0,0,0; - j a 0,0,0,0,0,0 ?; - F 50; - - -//Example 4 - Sync vs. Async calls (This won't work until we impliment 'F' to work for j moves) +//Example 5 - Controlling speeds with 'duration' new Job({ - name: "j_move_example_4", + name: "Example_5", show_instructions: false, when_stopped: function(){ return Dexter.j_reset() }, - user_data: { - move_count: 0, - }, do_list: [ - j_set_hardware_limits(), - Dexter.j_set_peak_velocity(speed), - Dexter.j_set_peak_acceleration(accel), - Dexter.j_set_peak_jerk(jerk), - + Dexter.j_move([0, 0, 0, 0, 0]), Control.loop(true, function(){ return [ - function(){ - this.user_data.move_count++ - out("Synchronous j_move " + this.user_data.move_count + " starting...") - this.user_data.start_time = Date.now() - }, - Dexter.j_move([0, -30, 0, 0, 0, 0]), - function(){ - let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5) - out("Synchronous j_move " + this.user_data.move_count + " completed in " + duration) - }, - - function(){ - out("Asynchronous j_move " + this.user_data.move_count + " starting...") - this.user_data.start_time = Date.now() - }, - Dexter.j_move([0, 30, 0, 0, 0, 0], {async: true}), - function(){ - let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5) - out("Asynchronous j_move " + this.user_data.move_count + " completed in " + duration) - }, - - function(){ - this.user_data.move_count++ - out("Synchronous j_move " + this.user_data.move_count + " starting...") - this.user_data.start_time = Date.now() - }, - Dexter.j_move([0, 0, 0, 0, 0, 0]), - function(){ - let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5) - out("Synchronous j_move " + this.user_data.move_count + " completed in " + duration) - }, - + Dexter.j_move([0, -10, 0, 0, 0], {duration: 1}), + Dexter.j_move([0, -5, 0, 0, 0], {duration: 1}), + Dexter.j_move([0, 10, 0, 0, 0], {duration: 1}), ] }) ] }) -Example 4 oplet strings sent per loop - j p -108000,-108000,0,0,0,0; - j v 0,0,0,0,0,0; - j a 0,0,0,0,0,0 ?; - F 50; - j p 0,0,0,0,0,0; - j v 108000,0,0,0,0,0; - j a 0,0,0,0,0,0 ?; - - -//Example 5 - Sync calls while moving (This won't work until we impliment sync_delay for 'F') +//Example 6 - Asynchronous j_moves (watch when the job button turns purple) new Job({ - name: "j_move_example_5", + name: "Example_6", show_instructions: false, when_stopped: function(){ return Dexter.j_reset() @@ -227,39 +295,142 @@ new Job({ move_count: 0, }, do_list: [ - j_set_hardware_limits(), - Dexter.j_set_peak_velocity(speed), - Dexter.j_set_peak_acceleration(accel), - Dexter.j_set_peak_jerk(jerk), + Dexter.j_move([0, -30, 0, 0, 0, 0], {async: false}), + ] +}) +//Example 7 - Synchronous j_moves during motion (listen for beeps mid-motion) +new Job({ + name: "Example_7", + show_instructions: false, + inter_do_item_dur: 0, + when_stopped: function(){ + return Dexter.j_reset() + }, + user_data: { + move_count: 0, + }, + do_list: [ Control.loop(true, function(){ return [ - Dexter.j_move([0, -30, 0, 0, 0, 0]), - Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [0, 30, 0, 0, 0, 0], async: false, sync_delay: -0.1}), - function(){ - this.user_data.move_count++ - out("Midpoint " + this.user_data.move_count) - }, Dexter.j_move([0, 30, 0, 0, 0, 0]), + Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [0, -30, 0, 0, 0, 0]}), + make_sound, + Dexter.j_move([0, -30, 0, 0, 0, 0]), + Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [0, 30, 0, 0, 0, 0]}), + make_sound ] }) ] }) -Example 5 oplet strings sent per loop - j p 0,-108000,0,0,0,0; - j v 0,0,0,0,0,0; - j a 0,0,0,0,0,0 ?; - F 50; - j p 0,0,0,0,0,0; - j v 0,108000,0,0,0,0; - j a 0,0,0,0,0,0 ?; - F -100; - j p 0,108000,0,0,0,0; - j v 0,0,0,0,0,0; - j a 0,0,0,0,0,0 ?; - F 50; +function make_sound(){ + beep({dur: 0.1, frequency: 600, volume: 1}) + this.user_data.move_count++ + out("Midpoint " + this.user_data.move_count) +} +//Example 8 - Using 'this.user_data.j_move' to track which j_move it's currently on, even with async = true +var example_8_data +var delay = 0.5 +new Job({ + name: "Example_8", + show_instructions: false, + inter_do_item_dur: 0, + when_stopped: function(){ + return Dexter.j_reset() + }, + do_list: [ + function(){ + clear_output() + example_8_data = [] + }, + Dexter.j_move([0, 0, 0, 0, 0]), + Control.wait_until(1), + + function(){this.user_data.start_time = Date.now() * _ms}, + Dexter.j_move([30, 0, 0, 0, 0], {async: true}), + Dexter.j_move([60, 0, 0, 0, 0], {async: true}), + loop_for_dur(delay, record_data_example_8), + Dexter.j_move([90, 0, 0, 0, 0], {monitor_callback: record_data_example_8}), + + function(){ + inspect(this.user_data.j_move) + }, + + function(){ + let joints_to_plot = [true, false, false, false, false, false] + let plot_data = [] + let time_stack = this.user_data.j_move.time_stack + let ys = [0, 40] + for(let i = 0; i < time_stack.length; i++){ + let label_str = "" + let j_cmd = this.user_data.j_move.cmd_stack[i] + for(let j = 0; j < joints_to_plot.length; j++){ + if(joints_to_plot[j]){ + if(j !== 0){ + label_str += "
" + } + label_str += "J" + (j+1) + ": [p:" + j_cmd.p[j] + ",v:" + j_cmd.v[j] + ",a:" + j_cmd.a[j] + "]" + } + } + plot_data.push({ + type: "scatter", + name: label_str, + //mode: "markers", + mode: "lines", + x: [time_stack[i], time_stack[i]], + y: ys, + marker: { + width: 1, + } + }) + } + + let times = [] + let stack_idxs = [] + let num_sent_cmds = [] + for(let i = 0; i < example_8_data.length; i++){ + times.push(example_8_data[i].time) + stack_idxs.push(example_8_data[i].cur_stack_idx) + num_sent_cmds.push(example_8_data[i].num_sent_cmds) + } + + plot_data.push({ + type: "scatter", + name: "this.user_data.j_move.cur_stack_idx", + //mode: "markers", + mode: "lines", + x: times, + y: stack_idxs, + marker: { + width: 1, + } + }) + + plot_trajectory(example_8_data, { + title: "J Move Completion Prediction, Delay = " + delay + "s", + data_types: ["dMEASURED_ANGLE", "J_VELOCITY"], + joints_to_plot: joints_to_plot, + y_axis: "Joint Velocity (deg/s)", + additional_plot_data: plot_data + }) + } + ] +}) + +function record_data_example_8(){ + return [ + function(){ + let obj = parse_g5(this.robot.robot_status) + obj.time = Date.now()*_ms - this.user_data.start_time + obj.num_sent_cmds = this.user_data.j_move.num_sent_cmds + obj.cur_stack_idx = this.user_data.j_move.cur_stack_idx + + example_8_data.push(obj) + } + ] +} new Job({ name: "stop_j_move", @@ -275,109 +446,465 @@ new Job({ inter_do_item_dur: 0, show_instructions: false, do_list: [ - j_set_hardware_limits(), - Dexter.j_set_peak_velocity(speed), + init, + Dexter.j_move([0, 0, 0, 0, 0]) + ] +}) + +new Job({ + name: "Print_Strs", + robot: Brain.brain0, + inter_do_item_dur: 0, + show_instructions: false, + do_list: [ + function(){ + let keys = Job.all_names + let completed_jobs = [] + debugger + for(let i = 0; i < keys.length; i++){ + if(Job[keys[i]].status_code === "completed"){ + completed_jobs.push({ + job_name: keys[i], + stop_time: Job[keys[i]].stop_time + }) + } + } + if(completed_jobs.length === 0){ + out("No completed jobs found.
Button must be purple.
Don't 'Eval' after completion.") + return + } + completed_jobs = completed_jobs.sort(function(a, b){ + let date_a = Date(a.stop_time) + let date_b = Date(b.stop_time) + return date_a - date_b + }) + + let last_job = Job[completed_jobs[0].job_name] + out(last_job + " sent instruction strings:") + inspect(last_job.sent_instructions_strings) + } + ] +}) + +function init(){ + return [ + j_set_hardware_limits(), //in case they aren't already set in Defaults.make_ins + Dexter.j_set_peak_velocity(vel), Dexter.j_set_peak_acceleration(accel), Dexter.j_set_peak_jerk(jerk), + ] +} - Dexter.j_move([0, 0, 0, 0, 0]) +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Ignore all code below here: + +function plot_trajectory(data, options = { + data_types: ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_JERK"], + joints_to_plot: [true, true, true, true, true, true], + x_axis: "Time (s)", + y_axis: "Joint Angle (deg)", + title: "Trajectory", + absolute_values: false, + additional_plot_data: [] +}){ + let default_options = { + data_types: ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_JERK"], + joints_to_plot: [true, true, true, true, true, true], + x_axis: "Time (s)", + y_axis: "Joint Angle (deg)", + title: "Trajectory", + absolute_values: false, + additional_plot_data: [] + } + let keys = Object.keys(options) + for(let i = 0; i < keys.length; i++){ + if(default_options[keys[i]] === undefined){ + dde_error("The key of '" + keys[i] + "' is not valid for plot_trajectory()") + } + default_options[keys[i]] = options[keys[i]] + } + + let color_array = [ + "red", "green", "blue", "magenta", "cyan", "orange", "brown", "lime", "purple", + rgb(153,195,167), rgb(92,50,91), rgb(240,20,120), rgb(200,134,65), rgb(93,51,93), + rgb(169,246,74), rgb(81,150,118), rgb(64,71,239), rgb(110,165,222), rgb(133,85,217), + rgb(77,29,17), rgb(43,200,132), rgb(160,212,211), rgb(74,249,166), rgb(90,237,21), + rgb(130,186,240), rgb(118,46,24), rgb(143,226,182), rgb(35,146,158), rgb(88,97,161) ] -}) -*/ - //stops Dexter from moving where ever it is and leaves it there. - Dexter.j_reset = function(){ - return make_ins("j") + + if(!Array.isArray(data)){ + data = obj_of_arrs_to_arr_objs(data) } - Dexter.j_set_peak_velocity = function(velocity = [30, 30, 30, 30, 30, 30]){ - let cmd = [] - for(let i = 0; i < velocity.length; i++){ - cmd.push(make_ins("S", "JointSpeed " + (i+1) + " " + Math.round(velocity[i]/_arcsec))) + let plot_data = [] + let T = 0 + + let time_range = [Infinity, -Infinity] + for(let i = 0; i < default_options.data_types.length; i++){ + let key = default_options.data_types[i] + let d_count = get_d_chars(key) + key = key.slice(d_count) + let values = [] + let times = [] + for(let k = 0; k < data.length; k++){ + values.push(data[k][key]) + if(data[k].time === undefined && data[k].times){ + times.push(data[k].times) + }else if(data[k].time && data[k].times === undefined){ + times.push(data[k].time) + }else{ + out("data[k]:") + inspect(data[k]) + dde_error("plot_trajectory() given incorrectly formatted time feild") + } + } + + let time = Vector.min(times) + if(time < time_range[0]){ + time_range[0] = time + } + time = Vector.max(times) + if(time > time_range[1]){ + time_range[1] = time + } + + let values_T = Vector.transpose(values) + for(let k = 0; k < values_T.length; k++){ + if(default_options.joints_to_plot[k] === false){ + continue + } + let ds = "" + for(let l = 0; l < d_count; l++){ + ds += "d" + values_T[k] = derive(times, values_T[k]) + } + + if(default_options.absolute_values){ + values_T[k] = Vector.abs(values_T[k]) + } + + plot_data.push({ + type: "scatter", + name: "J" + (k+1) + " " + ds + key, + //mode: "markers", + mode: "lines", + x: times, + y: values_T[k], + marker: { + width: 1, + //color: get_joint_color([T++]) + color: color_array[T++] + } + }) } - return cmd } - Dexter.j_set_peak_acceleration = function(acceleration = [300, 300, 300, 300, 300, 300]){ - let cmd = [] - for(let i = 0; i < accel.length; i++){ - cmd.push(make_ins("S", "JointAcceleration " + (i+1) + " " + Math.round(acceleration[i]/_arcsec))) + for(let i = 0; i < default_options.additional_plot_data.length; i++){ + let obj = default_options.additional_plot_data[i] + if(obj.marker.color === undefined){ + obj.marker.color = color_array[T++] } - return cmd + plot_data.push(obj) } - Dexter.j_set_peak_jerk = function(jerk = [3000, 3000, 3000, 3000, 3000, 3000]){ - let cmd = [] - for(let i = 0; i < jerk.length; i++){ - cmd.push(make_ins("S", "JointJerk " + (i+1) + " " + Math.round(jerk[i]/_arcsec))) + Plot.show( + undefined, + plot_data, + { + title: default_options.title, + xaxis: {title: {text: options.x_axis}}, + yaxis: {title: {text: options.y_axis}}, + }, + undefined, + { + title: default_options.title, + x: 580, + y: 40, } - return cmd + ) +} + +function derive(times, values){ + let result = [] + for(let i = 0; i < values.length-1; i++){ + result.push((values[i+1] - values[i]) / (times[i+1] - times[i])) } + return result +} +function get_d_chars(string){ + let count = 0 + for(let i = 0; i < string.length; i++){ + if(string[i] === "d"){ + count++ + }else{ + break + } + } + return count +} - //goal_position is an array of angles, its lengths should be the same as the - //measured angles that we compare to. - //Truncate rs[Dexter.J1_MEASURED_ANGLE], based on goal_position length - Dexter.wait_until_measured_angles = function(goal_position, tolerance = 0.05, callback){ - return Control.loop(true, function() { - let cmd = [] - - let rs = this.robot.robot_status - let status_mode = rs[Dexter.STATUS_MODE] - let meas - if (status_mode === 0) { - meas = [ - rs[Dexter.J1_MEASURED_ANGLE], - rs[Dexter.J2_MEASURED_ANGLE], - rs[Dexter.J3_MEASURED_ANGLE], - rs[Dexter.J4_MEASURED_ANGLE], - rs[Dexter.J5_MEASURED_ANGLE], - ] - } else if (status_mode === 5) { - meas = [ - rs[Dexter.g5_J1_MEASURED_ANGLE], - rs[Dexter.g5_J2_MEASURED_ANGLE], - rs[Dexter.g5_J3_MEASURED_ANGLE], - rs[Dexter.g5_J4_MEASURED_ANGLE], - rs[Dexter.g5_J5_MEASURED_ANGLE], +function loop_for_dur(duration, callback = function(){}){ + return [ + function(){this.user_data.loop_for_dur_start_time = Date.now()*_ms}, + Control.loop(true, function(){ + let dur = Date.now()*_ms - this.user_data.loop_for_dur_start_time + if(dur >= duration){ + return Control.break + }else{ + return [ + make_ins("g 5"), + callback ] - meas = Vector.multiply(meas, _arcsec) - } - - let error = Vector.max(Vector.abs(Vector.subtract(meas, goal_position.slice(0, 5)))) - cmd.push(callback) - if (error < tolerance) { - cmd.push(Control.break()) - } else { - //cmd.push(Dexter.get_robot_status()) - cmd.push(make_ins("g", 5)) } - return cmd }) + ] +} + +function parse_g5(rs){ + let result = {} + let keys = ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_JERK", "J_temp_reserved_2"] + for(let j = 0; j < keys.length; j++){ + result[keys[j]] = [] + for(let i = 0; i < 6; i++){ + let key = "g5_J" + (i+1) + "_" + keys[j] + result[keys[j]].push(rs[Dexter[key]] * _arcsec) + } + } + result.JTQ_DUR = rs[Dexter.g5_JTQ_DUR] / 100000 + return result +} + + + + +function arr_of_objs_to_obj_of_arrs(arr_obj){ + //array of objects to object of arrays + //[{key_1: val, key_2: val}, {key_1: val, key_2: val}] -> {key_1: [], key_2: []} + + let keys = Object.keys(arr_obj[0]) + let obj_arr = {} + for(let i = 0; i < keys.length; i++){ + let key = keys[i] + obj_arr[key] = [] + for(let j = 0; j < arr_obj.length; j++){ + obj_arr[key].push(arr_obj[j][key]) + } + } + return obj_arr +} + +function obj_of_arrs_to_arr_objs(obj_arr){ + //object of arrays to array of objects + //{key_1: [], key_2: []} -> [{key_1: val, key_2: val}, {key_1: val, key_2: val}] + + let keys = Object.keys(obj_arr) + let arr_obj = [] + for(let i = 0; i < obj_arr[keys[0]].length; i++){ + let obj = {} + for(let j = 0; j < keys.length; j++){ + let key = keys[j] + obj[key] = obj_arr[key][i] + } + arr_obj.push(obj) } + return arr_obj +} + +function get_joint_color(J_idx, shade_scale = 0){ + let color_array = ["red", "green", "blue", "magenta", "cyan", "orange"] + if(shade_scale > 1 || -1 > shade_scale){ + dde_error("get_joint_color() shade_scale must be -1 to 1.
It was passed a shade_scale of: "+ shade_scale) + } + let color_a + if(typeof J_idx === "number"){ + if(J_idx >= color_array.length){ + dde_error("get_joint_color() J_idx must be 0 to " + (color_array.length-1) + ".
It was passed a J_idx of: "+ J_idx) + } + color_a = color_array[J_idx] + }else if(typeof J_idx === "string"){ + color_a = color_str_to_arr(J_idx) + }else if(J_idx === undefined){ + color_a = [Math.round(Math.random()*255), Math.round(Math.random()*255), Math.round(Math.random()*255)] + } + let color_b + if(shade_scale < 0){ + color_b = [0, 0, 0] + shade_scale = Math.abs(shade_scale) + }else{ + color_b = [240, 240, 240] + } + + let color = interp_color(color_a, color_b, shade_scale) + return color +} + +//This should not be a DDE function, these should be defined correctly when a robot is first made and never changed. +function j_set_hardware_limits(){ + //I'll come back and make this have args for everything later + return [ + "S, JointHardwareMaxSpeed, 1, 28800000;", + "S, JointHardwareMaxSpeed, 2, 28800000;", + "S, JointHardwareMaxSpeed, 3, 28800000;", + "S, JointHardwareMaxSpeed, 4, 432000000;", + "S, JointHardwareMaxSpeed, 5, 432000000;", + "S, JointHardwareMaxSpeed, 6, 576000000;", + + "S, JointHardwareMaxAcceleration, 1, 2880000000;", + "S, JointHardwareMaxAcceleration, 2, 2880000000;", + "S, JointHardwareMaxAcceleration, 3, 2880000000;", + "S, JointHardwareMaxAcceleration, 4, 432000000;", + "S, JointHardwareMaxAcceleration, 5, 432000000;", + "S, JointHardwareMaxAcceleration, 6, 576000000;", + + "S, JointHardwareMaxJerk, 1, 32400000000;", + "S, JointHardwareMaxJerk, 2, 32400000000;", + "S, JointHardwareMaxJerk, 3, 32400000000;", + "S, JointHardwareMaxJerk, 4, 324000000000;", + "S, JointHardwareMaxJerk, 5, 324000000000;", + "S, JointHardwareMaxJerk, 6, 324000000000;" + ] +} +*/ - Dexter.j_move = function(j_angles = [], options = { - end_velocity: [0, 0, 0, 0, 0, 0], - end_acceleration: [0, 0, 0, 0, 0, 0], - duration: "?", - async: false, - sync_delay: 0.05, - peak_velocity: undefined, - peak_acceleration: undefined, - peak_jerk: undefined, - monitor_callback: function(){} - }){ +Dexter.g5_JTQ_DUR = 58 +Dexter.j_reset = function(){ + return function(){ + return [ + make_ins("j"), + Dexter.j_init, + ] + } +} + +Dexter.j_init = function(){ + return function(){ + this.user_data.j_move = {} + this.user_data.j_move.j_reset_start_time = Date.now() * _ms + this.user_data.j_move.time_stack = [] + let experimental_fudge_factor = 0.09//0.06693 + let replay_delay = 0.05 + this.user_data.j_move.dur_sum = replay_delay + experimental_fudge_factor + + this.user_data.j_move.cmd_stack = [] + this.user_data.j_move.cur_stack_idx = 0 + this.user_data.j_move.num_sent_cmds = -1 + + return "g 5" + } +} + +Dexter.j_set_peak_velocity = function(velocity = [30, 30, 30, 30, 30, 30]){ + let cmd = [] + for(let i = 0; i < velocity.length; i++){ + cmd.push("S JointSpeed " + (i+1) + " " + Math.round(velocity[i]/_arcsec) + ";") + } + return cmd +} + +Dexter.j_set_peak_acceleration = function(acceleration = [300, 300, 300, 300, 300, 300]){ + let cmd = [] + for(let i = 0; i < accel.length; i++){ + cmd.push("S JointAcceleration " + (i+1) + " " + Math.round(acceleration[i]/_arcsec) + ";") + } + return cmd +} + +Dexter.j_set_peak_jerk = function(jerk = [3000, 3000, 3000, 3000, 3000, 3000]){ + let cmd = [] + for(let i = 0; i < jerk.length; i++){ + cmd.push("S JointJerk " + (i+1) + " " + Math.round(jerk[i]/_arcsec) + ";") + } + return cmd +} + +Dexter.wait_until_measured_angles = function(goal_position, tolerance = 0.05, callback){ + return Control.loop(true, function(){ + let cmd = [] + + let rs = this.robot.robot_status + let status_mode = rs[Dexter.STATUS_MODE] + let meas + if(status_mode === 0){ + meas = [ + rs[Dexter.J1_MEASURED_ANGLE], + rs[Dexter.J2_MEASURED_ANGLE], + rs[Dexter.J3_MEASURED_ANGLE], + rs[Dexter.J4_MEASURED_ANGLE], + rs[Dexter.J5_MEASURED_ANGLE], + ] + }else if(status_mode === 5){ + meas = [ + rs[Dexter.g5_J1_MEASURED_ANGLE], + rs[Dexter.g5_J2_MEASURED_ANGLE], + rs[Dexter.g5_J3_MEASURED_ANGLE], + rs[Dexter.g5_J4_MEASURED_ANGLE], + rs[Dexter.g5_J5_MEASURED_ANGLE], + ] + meas = Vector.multiply(meas, _arcsec) + } + + let error = Vector.max(Vector.abs(Vector.subtract(meas, goal_position.slice(0, 5)))) + cmd.push(callback) + if(error < tolerance){ + cmd.push(Control.break()) + }else{ + //cmd.push(Dexter.get_robot_status()) + cmd.push(make_ins("g 5")) + } + return cmd + }) +} + +Dexter.j_wait_until_idx = function(idx, sync_delay, callback){ + //This allows j moves to be synchronous via jtq remaining duration + return Control.loop(true, function(){ + let cmd = [] + let dur = Date.now() * _ms - this.user_data.j_move.j_reset_start_time + + for(let i = this.user_data.j_move.cur_stack_idx; i < this.user_data.j_move.time_stack.length; i++){ + if(dur >= this.user_data.j_move.time_stack[this.user_data.j_move.cur_stack_idx]){ + this.user_data.j_move.cur_stack_idx++ + } + } + + if(dur >= sync_delay + this.user_data.j_move.time_stack[idx]){ + return Control.break() + } + + cmd.push(make_ins("g 5")) + cmd.push(callback) + return cmd + }) +} + +Dexter.j_move = function(j_angles = [], options = { + peak_velocity: undefined, + peak_acceleration: undefined, + peak_jerk: undefined, + end_velocity: [0, 0, 0, 0, 0, 0], + end_acceleration: [0, 0, 0, 0, 0, 0], + duration: "?", + async: false, + sync_delay: 0.05, + in_motion_sync_delay: -0.2, + monitor_callback: function(){} +}){ + return function(){ + let cmd = [] let default_options = { - end_velocity: [0, 0, 0, 0, 0, 0], - end_acceleration: [0, 0, 0, 0, 0, 0], - duration: "?", //'?' means get there as fast as possible + peak_velocity: undefined, // peak velocity allowed during this motion. 'undefined' means keep previous value. + peak_acceleration: undefined, // peak acceleration allowed during this motion. 'undefined' means keep previous value. + peak_jerk: undefined, // peak jerk allowed during this motion. 'undefined' means keep previous value. + + end_velocity: [0, 0, 0, 0, 0, 0], // velocity that this motion ends at. Start velocity comes from previous j move's end_velocity. + end_acceleration: [0, 0, 0, 0, 0, 0], // velocity that this motion ends at. Start acceleration comes from previous j move's end_acceleration. + duration: "?", //'?' means get there as fast as possible given peak values async: false, //defualts true if coming to stop, false if moving through point, will always be overwritten by user value sync_delay: 0.05, //time from JtQ completion. Negative value means 'time before completion'. - - peak_velocity: undefined, - peak_acceleration: undefined, - peak_jerk: undefined, + in_motion_sync_delay: -0.2, //time from JtQ completion. Is only used if end vel/accel are not 0. monitor_callback: function(){} //This function will get called over and over waiting for movement to complete. It returns to the do_list. } @@ -387,6 +914,18 @@ new Job({ }else if(j_angles.length !== 6){ dde_error("Dexter.j_move() was passed: " + j_angles + "
It must have 5 or 6 joint angles as arguments") } + if(this.user_data.j_move === undefined){ + //This means it's the first j move in a job + this.user_data.j_move = {} + this.user_data.j_move.j_reset_start_time = Date.now() * _ms + this.user_data.j_move.time_stack = [] + this.user_data.j_move.dur_sum = 0.05 + + this.user_data.j_move.cmd_stack = [] + this.user_data.j_move.cur_stack_idx = 0 + this.user_data.j_move.num_sent_cmds = -1 + cmd.push(Dexter.j_reset) + } let option_keys = Object.keys(options) let default_option_keys = Object.keys(default_options) @@ -397,14 +936,6 @@ new Job({ default_options[option_keys[i]] = options[option_keys[i]] } - /* - if(default_options.async === false || Vector.is_equal(default_options.end_velocity, [0, 0, 0, 0, 0, 0]) && Vector.is_equal(default_options.end_acceleration, [0, 0, 0, 0, 0, 0])){ - default_options.async = false - }else{ - default_options.async = true - } - */ - if(default_options.end_velocity.length === 5){ default_options.end_velocity.push(0) } @@ -413,9 +944,6 @@ new Job({ default_options.end_acceleration.push(0) } - - let cmd = [] - if(default_options.peak_velocity !== undefined){ cmd.push(Dexter.j_set_peak_velocity(default_options.peak_velocity)) } @@ -426,22 +954,47 @@ new Job({ cmd.push(Dexter.j_set_peak_jerk(default_options.peak_jerk)) } - cmd.push(make_ins("j", "p", ...Vector.round(Vector.multiply(j_angles, 1/_arcsec), 0))) - cmd.push(make_ins("j", "v", ...Vector.round(Vector.multiply(default_options.end_velocity, 1/_arcsec), 0))) - cmd.push(make_ins("j", "a", ...Vector.round(Vector.multiply(default_options.end_acceleration, 1/_arcsec), 0), default_options.duration)) + cmd.push(make_ins("j p " + Vector.round(Vector.multiply(j_angles, 1/_arcsec), 0))) + cmd.push(make_ins("j v " + Vector.round(Vector.multiply(default_options.end_velocity, 1/_arcsec), 0))) + cmd.push(make_ins("j a " + Vector.round(Vector.multiply(default_options.end_acceleration, 1/_arcsec), 0) + " " + default_options.duration)) + cmd.push(function(){ + //Algorithm for syncing j moves via time information + this.user_data.j_move.num_sent_cmds++ + let jtq_dur = this.robot.robot_status[Dexter.g5_JTQ_DUR] * 0.00001 + + //new method: + let delta_time = Date.now()*_ms + jtq_dur - this.user_data.j_move.j_reset_start_time + this.user_data.j_move.time_stack.push(delta_time) + + this.user_data.j_move.cmd_stack.push({ + p: j_angles, + v: default_options.end_velocity, + a: default_options.end_acceleration, + d: default_options.duration + }) + }) if(default_options.async === false){ - //Eventually this will be the F oplet but for now: - cmd.push(Dexter.wait_until_measured_angles(j_angles, undefined, options.monitor_callback)) - cmd.push(Control.wait_until(options.sync_delay)) - //cmd.push(make_ins("F", Math.round(default_options.sync_delay/_ms))) //This may or may not become a different oplet specifically for j moves. Units could become seconds too. - } + + + //Old Method: + if(Vector.is_equal(default_options.end_velocity, [0, 0, 0, 0, 0, 0]) && Vector.is_equal(default_options.end_acceleration, [0, 0, 0, 0, 0, 0])){ + //If coming to a stop reset + //This prevents the j queue underruning or getting too large + + cmd.push(function(){return Dexter.j_wait_until_idx(this.user_data.j_move.num_sent_cmds, default_options.sync_delay, default_options.monitor_callback)}) + cmd.push(Dexter.j_reset) + }else{ + cmd.push(function(){return Dexter.j_wait_until_idx(this.user_data.j_move.num_sent_cmds, default_options.in_motion_sync_delay, default_options.monitor_callback)}) + } + } return cmd } +} function init_g5_robot_status_indexes() { - let keys = ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_temp_reserved_1", "J_temp_reserved_2"] + let keys = ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_JERK", "J_temp_reserved_2"] for (let i = 0; i < 6; i++) { for (let j = 0; j < keys.length; j++) { let key = "g5_J" + (i + 1) + "_" + keys[j] @@ -450,33 +1003,5 @@ function init_g5_robot_status_indexes() { } } } -init_g5_robot_status_indexes() - -/* -//This should not be a DDE function, these should be defined correctly when a robot is first made and never changed. -function j_set_hardware_limits(){ - //I'll come back and make this have args for everything later - return [ - "S, JointHardwareMaxSpeed, 1, 2880000;", - "S, JointHardwareMaxSpeed, 2, 2880000;", - "S, JointHardwareMaxSpeed, 3, 2880000;", - "S, JointHardwareMaxSpeed, 4, 4320000;", - "S, JointHardwareMaxSpeed, 5, 4320000;", - "S, JointHardwareMaxSpeed, 6, 5760000;", - - "S, JointHardwareMaxAcceleration, 1, 28800000;", - "S, JointHardwareMaxAcceleration, 2, 28800000;", - "S, JointHardwareMaxAcceleration, 3, 28800000;", - "S, JointHardwareMaxAcceleration, 4, 43200000;", - "S, JointHardwareMaxAcceleration, 5, 43200000;", - "S, JointHardwareMaxAcceleration, 6, 57600000;", - - "S, JointHardwareMaxJerk, 1, 324000000;", - "S, JointHardwareMaxJerk, 2, 324000000;", - "S, JointHardwareMaxJerk, 3, 324000000;", - "S, JointHardwareMaxJerk, 4, 324000000;", - "S, JointHardwareMaxJerk, 5, 324000000;", - "S, JointHardwareMaxJerk, 6, 324000000;" - ] -}*/ +init_g5_robot_status_indexes() \ No newline at end of file diff --git a/core/instruction_j_move_dec_2023.js b/core/instruction_j_move_dec_2023.js new file mode 100644 index 00000000..c7d01055 --- /dev/null +++ b/core/instruction_j_move_dec_2023.js @@ -0,0 +1,482 @@ +/* Dexter_j_move_proposal.dde +Written by: James Wigglesworth +Started: 11/13/2023 +Modified: 11/27/2023 + + +Proposed new functions: + -Dexter.j_move() + -Dexter.j_reset() + -Dexter.j_set_peak_velocity() + -Dexter.j_set_peak_acceleration() + -Dexter.j_set_peak_jerk() + + +Example Dexter.j_move() calls: + +Dexter.j_move([0, -45, 0, 0, 0]), +Dexter.j_move([0, -45, 0, 0, 0, 0]), +Dexter.j_move([0, -10, 0, 0, 0], {duration: 1}), +Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [30, 0, 0, 0, 0, 0]}), +Dexter.j_move([0, 0, 0, 0, 0, 0], {end_acceleration: [30, 0, 0, 0, 0, 0]}), +Dexter.j_move([0, 30, 0, 0, 0, 0], {async: true}), + + + +Dexter.j_move([0, 30, 0, 0, 0, 0], { + end_velocity: [0, 0, 0, 0, 0, 0], + end_acceleration: [0, 0, 0, 0, 0, 0], + duration: "?", //'?' means get there as fast as possible + async: undefined, //defualts true if coming to stop, false if moving through point, will always be overwritten by user value +}), + + + +//Limits: +var speed = [30, 30, 30, 30, 30, 30] +var accel = [150, 150, 150, 150, 150, 150] +var jerk = [3000, 3000, 3000, 3000, 3000, 3000] +define_j_move_functions() //this is just to show the code for the Jobs first in this file + +//Job Examples: + +//Example 1 - simple motion +new Job({ + name: "j_move_example_1", + show_instructions: false, + when_stopped: function(){ + return Dexter.j_reset() + }, + do_list: [ + j_set_hardware_limits(), + Dexter.j_set_peak_velocity(speed), + Dexter.j_set_peak_acceleration(accel), + Dexter.j_set_peak_jerk(jerk), + + Control.loop(true, function(){ + return [ + Dexter.j_move([0, -45, 0, 0, 0]), + Dexter.j_move([0, 45, 0, 0, 0]), + ] + }) + ] +}) + +Example 1 oplet strings sent per loop + j p 0,-162000,0,0,0,0; + j v 0,0,0,0,0,0; + j a 0,0,0,0,0,0 ?; + F 50; + j p 0,162000,0,0,0,0; + j v 0,0,0,0,0,0; + j a 0,0,0,0,0,0 ?; + F 50; + + + +//Example 2 - simple motion with duration +new Job({ + name: "j_move_example_2", + show_instructions: false, + when_stopped: function(){ + return Dexter.j_reset() + }, + do_list: [ + j_set_hardware_limits(), + Dexter.j_set_peak_velocity(speed), + Dexter.j_set_peak_acceleration(accel), + Dexter.j_set_peak_jerk(jerk), + + Dexter.j_move([0, 0, 0, 0, 0]), + Control.loop(true, function(){ + return [ + Dexter.j_move([0, -10, 0, 0, 0], {duration: 1}), + Dexter.j_move([0, -5, 0, 0, 0], {duration: 1}), + Dexter.j_move([0, 10, 0, 0, 0], {duration: 1}), + ] + }) + ] +}) + +Example 2 oplet strings sent per loop + j p 0,-36000,0,0,0,0; + j v 0,0,0,0,0,0; + j a 0,0,0,0,0,0 1; + F 50; + j p 0,-18000,0,0,0,0; + j v 0,0,0,0,0,0; + j a 0,0,0,0,0,0 1; + F 50; + j p 0,36000,0,0,0,0; + j v 0,0,0,0,0,0; + j a 0,0,0,0,0,0 1; + F 50; + + + +//Example 3 - Motion through a waypoint +new Job({ + name: "j_move_example_3", + show_instructions: false, + when_stopped: function(){ + return Dexter.j_reset() + }, + do_list: [ + j_set_hardware_limits(), + Dexter.j_set_peak_velocity(speed), + Dexter.j_set_peak_acceleration(accel), + Dexter.j_set_peak_jerk(jerk), + + Control.loop(true, function(){ + return [ + Dexter.j_move([-30, -30, 0, 0, 0, 0]), + Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [30, 0, 0, 0, 0, 0]}), + Dexter.j_move([30, 0, 0, 0, 0, 0]), + ] + }) + ] +}) + +Example 3 oplet strings sent per loop + j p -108000,-108000,0,0,0,0; + j v 0,0,0,0,0,0; + j a 0,0,0,0,0,0 ?; + F 50; + j p 0,0,0,0,0,0; + j v 108000,0,0,0,0,0; + j a 0,0,0,0,0,0 ?; + j p 108000,0,0,0,0,0; + j v 0,0,0,0,0,0; + j a 0,0,0,0,0,0 ?; + F 50; + + +//Example 4 - Sync vs. Async calls (This won't work until we impliment 'F' to work for j moves) +new Job({ + name: "j_move_example_4", + show_instructions: false, + when_stopped: function(){ + return Dexter.j_reset() + }, + user_data: { + move_count: 0, + }, + do_list: [ + j_set_hardware_limits(), + Dexter.j_set_peak_velocity(speed), + Dexter.j_set_peak_acceleration(accel), + Dexter.j_set_peak_jerk(jerk), + + Control.loop(true, function(){ + return [ + function(){ + this.user_data.move_count++ + out("Synchronous j_move " + this.user_data.move_count + " starting...") + this.user_data.start_time = Date.now() + }, + Dexter.j_move([0, -30, 0, 0, 0, 0]), + function(){ + let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5) + out("Synchronous j_move " + this.user_data.move_count + " completed in " + duration) + }, + + function(){ + out("Asynchronous j_move " + this.user_data.move_count + " starting...") + this.user_data.start_time = Date.now() + }, + Dexter.j_move([0, 30, 0, 0, 0, 0], {async: true}), + function(){ + let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5) + out("Asynchronous j_move " + this.user_data.move_count + " completed in " + duration) + }, + + function(){ + this.user_data.move_count++ + out("Synchronous j_move " + this.user_data.move_count + " starting...") + this.user_data.start_time = Date.now() + }, + Dexter.j_move([0, 0, 0, 0, 0, 0]), + function(){ + let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5) + out("Synchronous j_move " + this.user_data.move_count + " completed in " + duration) + }, + + ] + }) + ] +}) + +Example 4 oplet strings sent per loop + j p -108000,-108000,0,0,0,0; + j v 0,0,0,0,0,0; + j a 0,0,0,0,0,0 ?; + F 50; + j p 0,0,0,0,0,0; + j v 108000,0,0,0,0,0; + j a 0,0,0,0,0,0 ?; + + +//Example 5 - Sync calls while moving (This won't work until we impliment sync_delay for 'F') +new Job({ + name: "j_move_example_5", + show_instructions: false, + when_stopped: function(){ + return Dexter.j_reset() + }, + user_data: { + move_count: 0, + }, + do_list: [ + j_set_hardware_limits(), + Dexter.j_set_peak_velocity(speed), + Dexter.j_set_peak_acceleration(accel), + Dexter.j_set_peak_jerk(jerk), + + Control.loop(true, function(){ + return [ + Dexter.j_move([0, -30, 0, 0, 0, 0]), + Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [0, 30, 0, 0, 0, 0], async: false, sync_delay: -0.1}), + function(){ + this.user_data.move_count++ + out("Midpoint " + this.user_data.move_count) + }, + Dexter.j_move([0, 30, 0, 0, 0, 0]), + ] + }) + ] +}) + +Example 5 oplet strings sent per loop + j p 0,-108000,0,0,0,0; + j v 0,0,0,0,0,0; + j a 0,0,0,0,0,0 ?; + F 50; + j p 0,0,0,0,0,0; + j v 0,108000,0,0,0,0; + j a 0,0,0,0,0,0 ?; + F -100; + j p 0,108000,0,0,0,0; + j v 0,0,0,0,0,0; + j a 0,0,0,0,0,0 ?; + F 50; + + +new Job({ + name: "stop_j_move", + inter_do_item_dur: 0, + show_instructions: false, + do_list: [ + Dexter.j_reset() + ] +}) + +new Job({ + name: "Home", + inter_do_item_dur: 0, + show_instructions: false, + do_list: [ + j_set_hardware_limits(), + Dexter.j_set_peak_velocity(speed), + Dexter.j_set_peak_acceleration(accel), + Dexter.j_set_peak_jerk(jerk), + + Dexter.j_move([0, 0, 0, 0, 0]) + ] +}) +*/ + //stops Dexter from moving where ever it is and leaves it there. + Dexter.j_reset = function(){ + return make_ins("j") + } + + Dexter.j_set_peak_velocity = function(velocity = [30, 30, 30, 30, 30, 30]){ + let cmd = [] + for(let i = 0; i < velocity.length; i++){ + cmd.push(make_ins("S", "JointSpeed " + (i+1) + " " + Math.round(velocity[i]/_arcsec))) + } + return cmd + } + + Dexter.j_set_peak_acceleration = function(acceleration = [300, 300, 300, 300, 300, 300]){ + let cmd = [] + for(let i = 0; i < accel.length; i++){ + cmd.push(make_ins("S", "JointAcceleration " + (i+1) + " " + Math.round(acceleration[i]/_arcsec))) + } + return cmd + } + + Dexter.j_set_peak_jerk = function(jerk = [3000, 3000, 3000, 3000, 3000, 3000]){ + let cmd = [] + for(let i = 0; i < jerk.length; i++){ + cmd.push(make_ins("S", "JointJerk " + (i+1) + " " + Math.round(jerk[i]/_arcsec))) + } + return cmd + } + + + //goal_position is an array of angles, its lengths should be the same as the + //measured angles that we compare to. + //Truncate rs[Dexter.J1_MEASURED_ANGLE], based on goal_position length + Dexter.wait_until_measured_angles = function(goal_position, tolerance = 0.05, callback){ + return Control.loop(true, function() { + let cmd = [] + + let rs = this.robot.robot_status + let status_mode = rs[Dexter.STATUS_MODE] + let meas + if (status_mode === 0) { + meas = [ + rs[Dexter.J1_MEASURED_ANGLE], + rs[Dexter.J2_MEASURED_ANGLE], + rs[Dexter.J3_MEASURED_ANGLE], + rs[Dexter.J4_MEASURED_ANGLE], + rs[Dexter.J5_MEASURED_ANGLE], + ] + } else if (status_mode === 5) { + meas = [ + rs[Dexter.g5_J1_MEASURED_ANGLE], + rs[Dexter.g5_J2_MEASURED_ANGLE], + rs[Dexter.g5_J3_MEASURED_ANGLE], + rs[Dexter.g5_J4_MEASURED_ANGLE], + rs[Dexter.g5_J5_MEASURED_ANGLE], + ] + meas = Vector.multiply(meas, _arcsec) + } + + let error = Vector.max(Vector.abs(Vector.subtract(meas, goal_position.slice(0, 5)))) + cmd.push(callback) + if (error < tolerance) { + cmd.push(Control.break()) + } else { + //cmd.push(Dexter.get_robot_status()) + cmd.push(make_ins("g", 5)) + } + return cmd + }) + } + + Dexter.j_move = function(j_angles = [], options = { + end_velocity: [0, 0, 0, 0, 0, 0], + end_acceleration: [0, 0, 0, 0, 0, 0], + duration: "?", + async: false, + sync_delay: 0.05, + peak_velocity: undefined, + peak_acceleration: undefined, + peak_jerk: undefined, + monitor_callback: function(){} + }){ + + let default_options = { + end_velocity: [0, 0, 0, 0, 0, 0], + end_acceleration: [0, 0, 0, 0, 0, 0], + duration: "?", //'?' means get there as fast as possible + + async: false, //defualts true if coming to stop, false if moving through point, will always be overwritten by user value + sync_delay: 0.05, //time from JtQ completion. Negative value means 'time before completion'. + + peak_velocity: undefined, + peak_acceleration: undefined, + peak_jerk: undefined, + + monitor_callback: function(){} //This function will get called over and over waiting for movement to complete. It returns to the do_list. + } + + if(j_angles.length === 5){ + j_angles.push(0) + }else if(j_angles.length !== 6){ + dde_error("Dexter.j_move() was passed: " + j_angles + "
It must have 5 or 6 joint angles as arguments") + } + + let option_keys = Object.keys(options) + let default_option_keys = Object.keys(default_options) + for(let i = 0; i < option_keys.length; i++){ + if(!default_option_keys.includes(option_keys[i])){ + dde_error("Dexter.j_move() passed an invalid option: " + option_keys[i] + "
Valid options: " + JSON.stringify(default_option_keys)) + } + default_options[option_keys[i]] = options[option_keys[i]] + } + + /* + if(default_options.async === false || Vector.is_equal(default_options.end_velocity, [0, 0, 0, 0, 0, 0]) && Vector.is_equal(default_options.end_acceleration, [0, 0, 0, 0, 0, 0])){ + default_options.async = false + }else{ + default_options.async = true + } + */ + + if(default_options.end_velocity.length === 5){ + default_options.end_velocity.push(0) + } + + if(default_options.end_acceleration.length === 5){ + default_options.end_acceleration.push(0) + } + + + let cmd = [] + + if(default_options.peak_velocity !== undefined){ + cmd.push(Dexter.j_set_peak_velocity(default_options.peak_velocity)) + } + if(default_options.peak_acceleration !== undefined){ + cmd.push(Dexter.j_set_peak_acceleration(default_options.peak_acceleration)) + } + if(default_options.peak_jerk !== undefined){ + cmd.push(Dexter.j_set_peak_jerk(default_options.peak_jerk)) + } + + cmd.push(make_ins("j", "p", ...Vector.round(Vector.multiply(j_angles, 1/_arcsec), 0))) + cmd.push(make_ins("j", "v", ...Vector.round(Vector.multiply(default_options.end_velocity, 1/_arcsec), 0))) + cmd.push(make_ins("j", "a", ...Vector.round(Vector.multiply(default_options.end_acceleration, 1/_arcsec), 0), default_options.duration)) + + if(default_options.async === false){ + //Eventually this will be the F oplet but for now: + cmd.push(Dexter.wait_until_measured_angles(j_angles, undefined, options.monitor_callback)) + cmd.push(Control.wait_until(options.sync_delay)) + //cmd.push(make_ins("F", Math.round(default_options.sync_delay/_ms))) //This may or may not become a different oplet specifically for j moves. Units could become seconds too. + } + + return cmd + } + +function init_g5_robot_status_indexes() { + let keys = ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_temp_reserved_1", "J_temp_reserved_2"] + for (let i = 0; i < 6; i++) { + for (let j = 0; j < keys.length; j++) { + let key = "g5_J" + (i + 1) + "_" + keys[j] + let idx = i * keys.length + 10 + j + Dexter[key] = idx + } + } +} +init_g5_robot_status_indexes() + +/* + +//This should not be a DDE function, these should be defined correctly when a robot is first made and never changed. +function j_set_hardware_limits(){ + //I'll come back and make this have args for everything later + return [ + "S, JointHardwareMaxSpeed, 1, 2880000;", + "S, JointHardwareMaxSpeed, 2, 2880000;", + "S, JointHardwareMaxSpeed, 3, 2880000;", + "S, JointHardwareMaxSpeed, 4, 4320000;", + "S, JointHardwareMaxSpeed, 5, 4320000;", + "S, JointHardwareMaxSpeed, 6, 5760000;", + + "S, JointHardwareMaxAcceleration, 1, 28800000;", + "S, JointHardwareMaxAcceleration, 2, 28800000;", + "S, JointHardwareMaxAcceleration, 3, 28800000;", + "S, JointHardwareMaxAcceleration, 4, 43200000;", + "S, JointHardwareMaxAcceleration, 5, 43200000;", + "S, JointHardwareMaxAcceleration, 6, 57600000;", + + "S, JointHardwareMaxJerk, 1, 324000000;", + "S, JointHardwareMaxJerk, 2, 324000000;", + "S, JointHardwareMaxJerk, 3, 324000000;", + "S, JointHardwareMaxJerk, 4, 324000000;", + "S, JointHardwareMaxJerk, 5, 324000000;", + "S, JointHardwareMaxJerk, 6, 324000000;" + ] +}*/ diff --git a/core/robot.js b/core/robot.js index 6a68fdec..574f10d5 100644 --- a/core/robot.js +++ b/core/robot.js @@ -113,7 +113,7 @@ var Robot = class Robot { } else { return false } } - else { return true } + else { return !is_digit(oplet) } //ie its not a letter or puncutation, so probably a digit was when we have variable-length instructions, If so, return false } else { return false } } diff --git a/core/storage.js b/core/storage.js index 39198850..0f15ec25 100644 --- a/core/storage.js +++ b/core/storage.js @@ -397,36 +397,62 @@ function read_file_async_from_dexter_using_job(dex_instance, path, callback){ module.exports.read_file_async = read_file_async -function choose_file(show_dialog_options={}) { - const dialog = app.dialog; - const paths = dialog.showOpenDialog(app.getCurrentWindow(), - //passing this first arg of the window - // makes the dialog "modal" ie can't do anything but choose a file or cancel - //you can't even click outside the window ahd have it do anything. - //This prevents certain error states you can get into with - //the file dialog and DDE. Not *always* what you want but - //on average, mostly what you want, particulary on WindowsOS which is worse - //than MacOS when we DON'T pass this. - //Note that "title" option doesn't show up on Mac. - show_dialog_options) - if (paths) { - if (Array.isArray(paths) && (paths.length == 1)) { - return convert_backslashes_to_slashes(paths[0]) } - else { - let slashed_paths = [] - for (let p of paths){ - slashed_paths.push(convert_backslashes_to_slashes(p)) - } - return slashed_paths +function choose_file_default_callback(err, paths){ + if(err){ + if(paths.length === 0){ + warning("choose_file: no files chosen") + } + else { + warning("choose_file errored with: " + err) } } - else { return null } + else { + out(paths) + } +} +//For the options areg to showOpenDialog +// passing this first arg of the window +// makes the dialog "modal" ie can't do anything but choose a file or cancel +//you can't even click outside the window ahd have it do anything. +//This prevents certain error states you can get into with +//the file dialog and DDE. Not *always* what you want but +//on average, mostly what you want, particulary on WindowsOS which is worse +//than MacOS when we DON'T pass this. +//Note that "title" option doesn't show up on Mac. +function choose_file(show_dialog_options={}, + callback=choose_file_default_callback) { + const dialog = app.dialog; + dialog.showOpenDialog(app.getCurrentWindow(), show_dialog_options).then( + function(an_obj){ + let paths = an_obj.filePaths + if(an_obj.canceled) { + callback(true, paths) + } + else { + let path + if (Array.isArray(paths) && (paths.length == 1)) { + path = convert_backslashes_to_slashes(paths[0]) } + else { + let slashed_paths = [] + for (let p of paths){ + slashed_paths.push(convert_backslashes_to_slashes(p)) + } + path = slashed_paths + } + callback(null, path) + } + }, + function (reason){ + callback(true, reason) + dde_warning("choose_file got error: " + reason) + } + ) } module.exports.choose_file = choose_file -function choose_folder(show_dialog_options={}) { +function choose_folder(show_dialog_options={}, callback=choose_file_default_callback) { let props = show_dialog_options.properties if(!props) { props = ["openDirectory"] @@ -437,25 +463,59 @@ function choose_folder(show_dialog_options={}) { } } show_dialog_options.properties = props - return choose_file(show_dialog_options) + choose_file(show_dialog_options, callback) } module.exports.choose_folder = choose_folder -function choose_file_and_get_content(show_dialog_options={}, encoding="utf8") { - var path = choose_file(show_dialog_options) - if (path){ - if (Array.isArray(path)) { path = path[0] } - return read_file(path, encoding) +function choose_file_and_get_content_default_callback(err, content){ + if(err){ + warning("choose_file_and_get_content got error: " + content ) + } + else { + out("" + content.replaceAll("\n", "
") + "
") } } + +function choose_file_and_get_content(show_dialog_options={}, encoding="utf8", callback=choose_file_and_get_content_default_callback) { + choose_file(show_dialog_options, function(err, path){ + if(err){ + warning("choose_file_and_get_content got error: " + err) + } + else { + read_file_async(path, encoding, callback) + } + }) +} module.exports.choose_file_and_get_content = choose_file_and_get_content -function choose_save_file(show_dialog_options={}) { //todo document +/*function choose_save_file(show_dialog_options={}) { //todo document const dialog = app.dialog; //use {defaultPath: '~/foo.xml'} to set default file name let result = dialog.showSaveDialog(app.getCurrentWindow(), show_dialog_options) return convert_backslashes_to_slashes(result) +}*/ + +//very similar to choose_file BUT calls dialog.showSaveDialog +function choose_save_file(show_dialog_options={}, callback) { //todo document + const dialog = app.dialog; + dialog.showSaveDialog(app.getCurrentWindow(), show_dialog_options).then( + function (an_obj) { + let path = an_obj.filePath //NOT filePaths + if (an_obj.canceled) { + callback(true, path) + } + else { + path = convert_backslashes_to_slashes(path) + callback(null, path) + } + }, + function (reason) { + callback(true, reason) + dde_warning("choose_file got error: " + reason) + } + ) } + module.exports.choose_save_file = choose_save_file function write_file(path, content, encoding="utf8"){ @@ -532,7 +592,7 @@ function write_file_async_default_callback(err){ dde_error("write_file_async error: " + err.message) } else { - out("saved: file", undefined, true) + out("saved file", undefined, true) } } diff --git a/doc/guide.html b/doc/guide.html index 60732e90..f795bf86 100644 --- a/doc/guide.html +++ b/doc/guide.html @@ -8,8 +8,8 @@
About This is Dexter Development Environment
- version: 3.8.19
- released: Jan 11, 2024 + version: 3.9.0
+ released: Feb 9, 2024

DDE helps you create, debug, and send software to a Dexter robot. You can use any JavaScript augmented with DDE-specific functions to help find out about, diff --git a/doc/known_issues.html b/doc/known_issues.html index d5e07021..cd0b059f 100644 --- a/doc/known_issues.html +++ b/doc/known_issues.html @@ -10,6 +10,8 @@ with this DDE release. All of them are on our "do list".

+
 choose_save_file({buttonLabel: "pick the best"},
+                function(err, path){ if(err) {warning("No file chosen")}
+                                     else { out("you chose: " + path) }
+                })
choose_folder Pops up a dialog box allowing you to navigate the file system to choose a folder. Returns a string of the full path of the folder.
- Parameter:
+ Parameters:
show_dialog_options A JS literal object of name-value pairs, all of which are optional. See the options under "showOpenDialog" in showOpenDialog for details.
+ callback a function of 2 argumets:
+ err: if true, there was an error in choosing or the user canceled.
+ if false or null, the user chose a file.
+ path: A string of the full path of the chosen file.
+
Example:
- choose_folder() Just lets you choose folders.
+
 choose_folder({buttonLabel: "pick the best"},
+                function(err, path){ if(err) {warning("No folder chosen")}
+                                     else { out("you chose: " + path) }
+                })
+ If the user chooses "cancel" in the dialog box, this function returns null. If the options indicate that the user can choose more than one path, and they do, (using shift-click) diff --git a/doc/release_notes.html b/doc/release_notes.html index 092c47e6..cdcd8606 100644 --- a/doc/release_notes.html +++ b/doc/release_notes.html @@ -6,6 +6,40 @@ .doc_details summary { font-weight: 600; } +
v 3.9.0, Feb 9,, 2024 +Highlights: File menu items requiring choosing a file fixed. + New Dexter.j_move instuction +
    +
  • Variable lengthed instructions now working in simulator.
  • +
  • Fixed bug in opencv examples:
    + dde menu Insert/machine Vision/Process WebCam, Face Recognition, Locate Object
  • + Now all Insert/Machine Vision/ examples working. + + +
  • choose_file, choose_folder, choose_save_file, choose_file_and_get_content, + and the File menu items that + depend on them fixed, using new asnchronous function calling + required by the lastest Electron release. + Note that if you are calling choose_file or choose_folder, + you will have to extend your calls to take a callback of 2 arguments, + an "err" and a "path". + The following menn items have changed: + open
    + load file
    + insert file content
    + load and start job
    + insert file path into editor
    + insert file path into command line
    + save as
    + TestSuite.run_ts_in_file_ui
    + show_in_misc_pane --- choose file.
    + Caution: EZTeach_base.js, Init_Run() has not been updated yet as this + is tricky. It is accessible from DDE menu/Insert/ezTeach, + but is now broken
  • +
  • Added LICENSE file to source code.
  • +
+
+
v 3.8.19, Jan 11, 2024 Highlights: Now works on new Apple Silicon Macs (M1 thru M3)
    diff --git a/editor.js b/editor.js index 0e9bde46..45fd2f26 100644 --- a/editor.js +++ b/editor.js @@ -632,10 +632,14 @@ Editor.open_from_dexter_computer = function(){ } Editor.open_on_dde_computer = function(){ - const path = choose_file({title: "Choose a file to edit", properties: ['openFile']}) - if (path){ - Editor.edit_file(path) - } + choose_file({title: "Choose a file to edit", properties: ['openFile']}, + function(err, path) { + if (err) { + warning("Editor.open_on_dde_computer canceled") + } else { + Editor.edit_file(path) + } + }) } //can't be a closure, can't be in a class'es namespace. yuck. @@ -1147,19 +1151,25 @@ function save_as_cb(vals){ ) }*/ -Editor.save_as = function(){ - const title = 'save "' + Editor.current_file_path + '" as' +Editor.save_as = function() { + const title = 'save "' + Editor.current_file_path + '" as' const default_path = ((Editor.current_file_path == "new buffer") ? dde_apps_folder : Editor.current_file_path) - const path = choose_save_file({title: title, defaultPath: default_path}) //sychronous! good - if(path) { //path will be undefined IF user canceled the dialog - let content = Editor.get_javascript() - write_file_async(path, content) - Editor.add_path_to_files_menu(path) - Editor.current_file_path = path - Editor.remove("new buffer") //if any - myCodeMirror.focus() - Editor.unmark_as_changed() - } + choose_save_file({title: title, defaultPath: default_path}, + function (err, path){ + if(err){ + warning("Could not save as " + path) + } + else { //path will be undefined IF user canceled the dialog + let content = Editor.get_javascript() + write_file_async(path, content) + Editor.add_path_to_files_menu(path) + Editor.current_file_path = path + Editor.remove("new buffer") //if any + myCodeMirror.focus() + Editor.unmark_as_changed() + } + } + ) } //can't be a closure, can't be in a class'es namespace. yuck. diff --git a/examples/opencv_face_reco.js b/examples/opencv_face_reco.js index fa7813fe..cffa3776 100644 --- a/examples/opencv_face_reco.js +++ b/examples/opencv_face_reco.js @@ -37,7 +37,8 @@ function handle_webcam_video(vals){ //vals contains name-value pairs for each if(vals.clicked_button_value == "init"){ // Clicked button value holds the name of the clicked button. if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({ video: true }).then(function(stream) { - video_id.src = window.URL.createObjectURL(stream); + //video_id.src = window.URL.createObjectURL(stream); + video_id.srcObject = stream //jan 2024 video_id.play(); }) } diff --git a/examples/opencv_process_webcam.js b/examples/opencv_process_webcam.js index 560cf278..cd1b2fa5 100644 --- a/examples/opencv_process_webcam.js +++ b/examples/opencv_process_webcam.js @@ -7,7 +7,8 @@ function handle_webcam_video(vals){ //vals contains name-value pairs for each if(vals.clicked_button_value == "init"){ // Clicked button value holds the name of the clicked button. if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({ video: true }).then(function(stream) { - video_id.src = window.URL.createObjectURL(stream); + //video_id.src = window.URL.createObjectURL(stream); + video_id.srcObject = stream //jan 2024 video_id.play(); }) } diff --git a/package.json b/package.json index 8310ac93..756ecfac 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "dexter_dev_env", "productName": "dexter_dev_env", - "version": "3.8.19", - "release_date": "Jan 11, 2024", + "version": "3.9.0", + "release_date": "Feb 9, 2024", "description": "Dexter Development Environment for programming the Dexter robot.", "author": "Fry", "license": "GPL-3.0", diff --git a/picture1.js b/picture1.js index 6a58a3a5..a0bb0436 100644 --- a/picture1.js +++ b/picture1.js @@ -9,7 +9,7 @@ var Picture = class Picture{ //the width and height are for the show_window made (if any) //iF the picture pixels are more than the window dimensions, the window will scroll. static init({width=320, height=240}={}){ - if(typeof(cv) == "string") { //calling init a 2nd time some times screws up do to timing, + if(typeof(cv) === "string") { //calling init a 2nd time some times screws up do to timing, //probably due to the show_video call. cv = require("./node_modules/opencv.js/opencv") RotatingCalipers = require("rotating-calipers/rotating-calipers.js") @@ -801,6 +801,9 @@ var Picture = class Picture{ static mat_to_min_area_rect({mat_in, threshold=1, avg_center=true}){ let points = Picture.mat_to_points(mat_in, threshold) if(points.length == 0) { return null} + if(typeof(RotatingCalipers) === "string"){ + Picture.init() + } let solver = new RotatingCalipers(points) let mar = solver.minAreaEnclosingRectangle() //.vertices //all the values in mar are epsilon differnt than an integer so clean it up @@ -857,6 +860,9 @@ var Picture = class Picture{ //https://github.com/sntran/RotatingCalipers/blob/master/demo.html static mat_to_convex_hull(mat_in, threshold=1){ let points = Picture.mat_to_points(mat_in, threshold) + if(typeof(RotatingCalipers) === "string"){ + Picture.init() + } let solver = new RotatingCalipers(points) let hull = solver.convexHull() return hull diff --git a/ready.js b/ready.js index 85fcac78..0b8b4275 100644 --- a/ready.js +++ b/ready.js @@ -525,51 +525,74 @@ open_from_dexter_id.onclick = Editor.open_from_dexter_computer open_system_file_id.onclick = Editor.open_system_file -load_file_id.onclick=function(e) { +load_file_id.onclick=function() { if (window.HCA && (Editor.view === "HCA")){ HCA.load_node_definition() } else { //presume JS for this clause - const path = choose_file({title: "Choose a file to load"}) - if (path){ - if(path.endsWith(".py")){ - Py.load_file_ask_for_as_name(path) - } - else { - out(load_files(path)) - } - } + choose_file({title: "Choose a file to load"}, + function(err, path){ + if(err){ + warning("Could not load: " + path) + } + else { + if (path.endsWith(".py")) { + Py.load_file_ask_for_as_name(path) + } else { + out(load_files(path)) + } + } + } + ) } } load_and_start_job_id.onclick = function(){ - const path = choose_file({title: "Choose a file to load"}) - if (path){ - Job.define_and_start_job(path) - } + const path = choose_file({title: "Choose a file to load"}, + function(err, path){ + if(err){ + warning("Could not load and start jub " + path) + } + else { + Job.define_and_start_job(path) + } + }) } DDE_NPM.init() install_npm_pkg_id.onclick = DDE_NPM.show_ui insert_file_content_id.onclick=function(e) { - const path = choose_file({title: "Choose a file to insert into DDE's editor"}) - if (path){ - const content = read_file(path) - Editor.insert(content) - } + choose_file({title: "Choose a file to insert into DDE's editor"}, + function(err, path) { + if (err) { + warning("could not insert file content " + path) + } else { + const content = read_file(path) + Editor.insert(content) + } + }) } insert_file_path_into_editor_id.onclick=function(e){ - const path = choose_file({title: "Choose a file to insert into DDE's editor"}) - if (path){ - Editor.insert('"' + path + '"') - } + choose_file({title: "Choose a file to insert into DDE's editor"}, + function(err, path){ + if(err){ + warning("Could not insert file path " + path) + } + else { + Editor.insert('"' + path + '"') + } + }) } insert_file_path_into_cmd_input_id.onclick=function(e){ -const path = choose_file({title: "Choose a file to insert into DDE's editor"}) -if (path){ - Editor.insert_into_cmd_input('"' + path + '"') -} + choose_file({title: "Choose a file to insert into DDE's editor"}, + function(err, path) { + if (err) { + warning("Could not insert file path " + path) + } else { + Editor.insert_into_cmd_input('"' + path + '"') + } + }) } save_id.onclick = function() { diff --git a/test_suite/test_suite.js b/test_suite/test_suite.js index b93721c2..3d677948 100644 --- a/test_suite/test_suite.js +++ b/test_suite/test_suite.js @@ -343,13 +343,18 @@ var TestSuite = class TestSuite{ return result } - static run_ts_in_file_ui(){ - let path = choose_file() - if(path){ - TestSuite.run_ts_in_file(path) - } + static run_ts_in_file_ui() { + let path = choose_file({}, + function (err, path) { + if (err) { + warning("No file chosen to run as test suite.") + } else { + TestSuite.run_ts_in_file(path) + } + }) } + static run_ts_in_file(path){ let ts_array if(path.endsWith("guide.html")) { diff --git a/user_tools/ezTeach_base.js b/user_tools/ezTeach_base.js index fa15a669..10fb4787 100755 --- a/user_tools/ezTeach_base.js +++ b/user_tools/ezTeach_base.js @@ -595,7 +595,11 @@ function Init_Run(){ // Load in saved points file if(points_filepath == "choose_file"){ - points_filepath = choose_file({buttonLabel: "Open"}) + points_filepath = choose_file({buttonLabel: "Open"}) //feb 2024: this will error because + //we needed to make all choose_file calls take a callback to do their work with + //the chosen path. BUT given that Init_Run is used as a do_list item + //that is supposed to return an array of instruction at Job run time, + //that's quite hard to figure out how to make it work. So I didn't do it. if(points_filepath === undefined){ return Control.stop_job } diff --git a/video.js b/video.js index 13e17bad..1292dbab 100644 --- a/video.js +++ b/video.js @@ -79,23 +79,26 @@ function show_in_misc_pane(content, arg1 = "", arg2){ //But if user doesn't cancel, we DO want to change the combo box value, and, //if the value is good, persistent save it. if (content === "Choose File") { - content = choose_file() //will be undefined if user cancels the dialog box. - if(content) { - $("#misc_pane_menu_id").jqxComboBox('unselectItem', "Choose File") //must do! - // just let it fall through ////old: return show_in_misc_pane(content) //$('#misc_pane_menu_id').jqxComboBox('val', content) //causes show_in_misc_pane to be called with the chosen value - set_misc_pane_menu_selection(content) - destroySimulation(); - - } - else { //user canceled from choose file dialog so don't persistent-save the value. - //leave combo_box val at "choose file" which won't match content, but we might - // not want to "refresh" the content, and since that always happens - //if we did $('#misc_pane_menu_id').jqxComboBox('val', the_prev_val), - //just leave it as "Choose File" - let prev_val = persistent_get("misc_pane_content") - set_misc_pane_menu_selection(prev_val) - return - } + choose_file({}, + function(err, path) { + if(err) { //user canceled from choose file dialog so don't persistent-save the value. + //leave combo_box val at "choose file" which won't match content, but we might + // not want to "refresh" the content, and since that always happens + //if we did $('#misc_pane_menu_id').jqxComboBox('val', the_prev_val), + //just leave it as "Choose File" + let prev_val = persistent_get("misc_pane_content") + set_misc_pane_menu_selection(prev_val) + return + } + else { + $("#misc_pane_menu_id").jqxComboBox('unselectItem', "Choose File") //must do! + // just let it fall through ////old: return show_in_misc_pane(content) //$('#misc_pane_menu_id').jqxComboBox('val', content) //causes show_in_misc_pane to be called with the chosen value + set_misc_pane_menu_selection(path) + destroySimulation(); + show_in_misc_pane(path) + return + } + }) } //let orig_content = $('#misc_pane_menu_id').jqxComboBox('val') //warning: this doesn't always get the //content showing in the combo box. Bug in jqxwidgets. So just always set it.