#!/usr/bin/env python # coding: utf-8 # # `ev3devsim` Widget # # Experiments trying to get [`ev3devsim`](https://www.aposteriori.com.sg/Ev3devSim/index.html) running inside a Jupyter `ipywidget`. # ## `jp_proxy_widget` # # Can we use # [`jp_proxy_widget`](https://github.com/AaronWatters/jp_proxy_widget/blob/master/notebooks/Tutorial.ipynb) to conveniently wrap the `ev3devsim` components in a Jupyter `ipywidget`? # In[58]: html = '''
X :
Y :
Angle :
Large Font Long Window
# Demo of a simple proportional line follower using two sensors # It's deliberately flawed and will exit with errors in some circumstances; # try fixing it! from ev3dev2.motor import MoveSteering, OUTPUT_B, OUTPUT_C from ev3dev2.sensor import INPUT_1, INPUT_2, INPUT_3, INPUT_4 from ev3dev2.sensor.lego import ColorSensor, GyroSensor, UltrasonicSensor steering_drive = MoveSteering(OUTPUT_B, OUTPUT_C) colorLeft = ColorSensor(INPUT_2) colorRight = ColorSensor(INPUT_3) gyro = GyroSensor(INPUT_4) ultrasonic = UltrasonicSensor(INPUT_1) GAIN = 0.5 while True: print('Gyro: ' + str(gyro.angle_and_rate)) print('Ultrasonic: ' + str(ultrasonic.distance_centimeters)) error = colorLeft.reflected_light_intensity - colorRight.reflected_light_intensity correction = error * GAIN steering_drive.on(correction, 20)

    
* Dimensions are in mm and degrees.
Show ultrasonic rays
Walls around map
Obstacles
''' # In[2]: script = ''' function setPos(x, y, angle) { var x = parseFloat(x); var y = parseFloat(y); var angleRadian = parseFloat(angle) / 180 * Math.PI; if (isNaN(x)) { x = 0; } if (isNaN(y)) { y = 0; } if (isNaN(angleRadian)) { angleRadian = 0; angle = 0; } document.getElementById('xPos').value = x; document.getElementById('yPos').value = y; document.getElementById('angle').value = angle; sim.setRobotPos(x, y, angleRadian); sim.drawAll(); } // Main var sim = new EV3devSim('field'); setPos(1181, 571, 0); document.getElementById('map').value = 'Empty Map'; document.getElementById('walls').checked = true; document.getElementById('obstacles').checked = true; document.getElementById('configureObstacles').addEventListener('click', function() { document.getElementById('obstaclesConfigurator').classList.remove('closed'); var obstacles = '// Obstacles should be configured in json using the following format...\n' + '//\n' + '// [\n' + '// [x, y, width, height],\n' + '// [x2, y2, width2, height2]\n' + '// ]\n' + '// \n' + '// ...where x/y/width/height are the position and size of the obstacle.\n' + '// See the "Obstacles Test" map for an example.\n\n'; obstacles += JSON.stringify(sim.obstacles, null, 2); obstacles = obstacles.replace(/,\n /g, ',').replace(/\[\n /g,'[').replace(/\n \],/g, '],'); document.getElementById('obstaclesConfiguratorEditor').value = obstacles; }); document.getElementById('obstaclesConfiguratorApply').addEventListener('click', function() { var json = document.getElementById('obstaclesConfiguratorEditor').value; json = json.replace(/^\/\/.*\n/gm,''); var obstacles = JSON.parse(json); sim.clearObstacles(); sim.clearObstaclesLayer(); sim.loadObstacles(obstacles); document.getElementById('obstaclesConfigurator').classList.add('closed'); }); document.getElementById('obstaclesConfiguratorClose').addEventListener('click', function() { document.getElementById('obstaclesConfigurator').classList.add('closed'); }); document.getElementById('obstaclesConfiguratordownload').addEventListener('click', function() { var obstaclesSpecs = document.getElementById('obstaclesConfiguratorEditor').value obstaclesSpecs = obstaclesSpecs.replace(/^\/\/.*\n/gm,''); var hiddenElement = document.createElement('a'); hiddenElement.href = 'data:application/json;base64,' + btoa(obstaclesSpecs); hiddenElement.target = '_blank'; hiddenElement.download = 'obstacles_config.json'; hiddenElement.dispatchEvent(new MouseEvent('click')); }); document.getElementById('obstaclesConfiguratorupload').addEventListener('click', function() { var hiddenElement = document.createElement('input'); hiddenElement.type = 'file'; hiddenElement.accept = 'application/json,.json,.js'; hiddenElement.dispatchEvent(new MouseEvent('click')); hiddenElement.addEventListener('change', function(e){ var reader = new FileReader(); reader.onload = function() { document.getElementById('obstaclesConfiguratorEditor').value = this.result; }; reader.readAsText(e.target.files[0]); }); }); document.getElementById('showRays').addEventListener('click', function() { if (document.getElementById('showRays').checked) { sim.drawUltrasonic = true; } else { sim.drawUltrasonic = false; } sim.drawAll(); }); document.getElementById('walls').addEventListener('click', function() { if (document.getElementById('walls').checked) { sim.setWallsPresent(true); } else { sim.setWallsPresent(false); } }); document.getElementById('obstacles').addEventListener('click', function() { if (document.getElementById('obstacles').checked) { sim.obstaclesPresent = true; sim.clearObstaclesLayer(); sim.drawObstacles(); } else { sim.obstaclesPresent = false; sim.clearObstaclesLayer(); } }); document.getElementById('move').addEventListener('click', function() { setPos( document.getElementById('xPos').value, document.getElementById('yPos').value, document.getElementById('angle').value ); }); document.getElementById('stop').addEventListener('click', function() { sim.stopAnimation(); Sk.hardInterrupt = true; }); document.getElementById('largeFont').addEventListener('change', function(e) { if (e.target.checked) { document.getElementById('editor').classList.add('large'); } else { document.getElementById('editor').classList.remove('large'); } }); document.getElementById('longWindow').addEventListener('change', function(e) { if (e.target.checked) { document.getElementById('editor').classList.add('long'); } else { document.getElementById('editor').classList.remove('long'); } window.dispatchEvent(new Event('resize')); }); document.getElementById('download').addEventListener('click', function() { var hiddenElement = document.createElement('a'); hiddenElement.href = 'data:text/x-python;base64,' + btoa(editor.getValue()); hiddenElement.target = '_blank'; hiddenElement.download = 'robot.py'; hiddenElement.dispatchEvent(new MouseEvent('click')); }); document.getElementById('upload').addEventListener('click', function() { var hiddenElement = document.createElement('input'); hiddenElement.type = 'file'; hiddenElement.accept = 'text/x-python,.py'; hiddenElement.dispatchEvent(new MouseEvent('click')); hiddenElement.addEventListener('change', function(e){ var reader = new FileReader(); reader.onload = function() { editor.setValue(this.result); }; reader.readAsText(e.target.files[0]); }); }); document.getElementById('map').addEventListener('input', function() { var map = document.getElementById('map').value; if (map == 'WRO 2020 Regular Junior') { sim.loadBackground('WRO-2020-Regular-Junior.png'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(130, 130, 0); } else if (map == 'WRO 2019 Regular Junior') { sim.loadBackground('WRO-2019-Regular-Junior.jpg'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(2215, 150, 90); } else if (map == 'WRO 2018 Regular Junior') { sim.loadBackground('WRO-2018-Regular-Junior.png'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(1181, 150, 90); } else if (map == 'FLL 2019 - City Shaper') { sim.loadBackground('FLL2019.jpg'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(500, 150, 90); } else if (map == 'FLL 2018 - Into Orbit') { sim.loadBackground('FLL2018.jpg'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(150, 150, 90); } else if (map == 'Line Following Test') { sim.loadBackground('Line_Following_Test.png'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(141, 125, 90); } else if (map == 'Junction Handling Test') { sim.loadBackground('Junction_Handling_Test.png'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(698, 130, 90); } else if (map == 'Obstacles Test') { sim.loadBackground('Obstacles_Test.png'); setPos(121, 125, 90); sim.clearObstacles(); sim.clearObstaclesLayer(); sim.loadObstacles([ [46, 388, 150, 150], [479, 704, 150, 150], [852, 388, 150, 150], [1374, 388, 150, 150], [1758, 900, 150, 150], [2126, 388, 150, 150] ]); } else if (map == 'Upload Image (2362x1143px)...') { console.log('upload'); var hiddenElement = document.createElement('input'); hiddenElement.type = 'file'; hiddenElement.accept = 'image/*'; console.log(hiddenElement); hiddenElement.dispatchEvent(new MouseEvent('click')); hiddenElement.addEventListener('change', function(e){ var reader = new FileReader(); reader.onload = function() { sim.loadBackground(this.result); sim.clearObstacles(); sim.clearObstaclesLayer(); }; reader.readAsDataURL(e.target.files[0]); }); var select = document.getElementById('map'); select.selectedIndex = select.options.length - 1; } else { sim.clearBackground(); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(2362/2, 1143/2, 0); } }); document.getElementById('robotConfiguratorOpen').addEventListener('click', function() { document.getElementById('robotConfiguratorEditor').value = JSON.stringify(sim.robotSpecs, null, 2); document.getElementById('robotConfigurator').classList.remove('closed'); }); document.getElementById('robotConfiguratorCancel').addEventListener('click', function() { document.getElementById('robotConfigurator').classList.add('closed'); }); document.getElementById('robotConfiguratorApply').addEventListener('click', function() { var robotSpecs = JSON.parse(document.getElementById('robotConfiguratorEditor').value); sim.loadRobot(robotSpecs); sim.drawAll(); document.getElementById('robotConfigurator').classList.add('closed'); }); document.getElementById('robotConfiguratordownload').addEventListener('click', function() { var robotSpecs = document.getElementById('robotConfiguratorEditor').value var hiddenElement = document.createElement('a'); hiddenElement.href = 'data:application/json;base64,' + btoa(robotSpecs); hiddenElement.target = '_blank'; hiddenElement.download = 'robot_config.json'; hiddenElement.dispatchEvent(new MouseEvent('click')); }); document.getElementById('robotConfiguratorupload').addEventListener('click', function() { var hiddenElement = document.createElement('input'); hiddenElement.type = 'file'; hiddenElement.accept = 'application/json,.json,.js'; hiddenElement.dispatchEvent(new MouseEvent('click')); hiddenElement.addEventListener('change', function(e){ var reader = new FileReader(); reader.onload = function() { document.getElementById('robotConfiguratorEditor').value = this.result; }; reader.readAsText(e.target.files[0]); }); }); function openWindow(url) { var win = document.getElementById('window'); var content = win.getElementsByClassName('content')[0]; win.classList.remove('closed'); content.innerHTML = ''; fetch(url) .then(function(response) { response.text() .then(function(text) { content.innerHTML = text; }); }) } document.getElementById('guideWindow').addEventListener('click', function() { openWindow('guide.html'); }); document.getElementById('aboutWindow').addEventListener('click', function() { openWindow('about.html'); }); document.getElementById('closeWindow').addEventListener('click', function() { document.getElementById('window').classList.add('closed'); }); // Load Ace editor var editor = ace.edit("pythonCode"); editor.setTheme("ace/theme/monokai"); editor.session.setMode("ace/mode/python"); // Load Skulpt function outf(text) { var mypre = document.getElementById("output"); mypre.innerHTML = mypre.innerHTML + text; mypre.scrollTop = mypre.scrollHeight - mypre.clientHeight; } function builtinRead(x) { var externalLibs = { './ev3dev2/__init__.py': 'ev3dev2/__init__.py', './ev3dev2/motor.py': 'ev3dev2/motor.py', './ev3dev2/sensor/__init__.py': 'ev3dev2/sensor/__init__.py', './ev3dev2/sensor/lego.py': 'ev3dev2/sensor/lego.py', './ev3dev2_glue.js': 'ev3dev2_glue.js' } if (Sk.builtinFiles === undefined || Sk.builtinFiles["files"][x] === undefined) { if (x in externalLibs) { return Sk.misceval.promiseToSuspension( fetch(externalLibs[x]) .then(r => r.text()) ); } else { throw "File not found: '" + x + "'"; } } return Sk.builtinFiles["files"][x]; } var interruptHandler = function (susp) { if (Sk.hardInterrupt === true) { delete Sk.hardInterrupt; throw new Sk.builtin.ExternalError('aborted execution'); } else { return null; } }; function runit() { if (typeof Sk.hardInterrupt != 'undefined') { delete Sk.hardInterrupt; } if (Sk.running == true) { return; } Sk.running = true; sim.reset(); sim.startAnimation(); var prog = editor.getValue(); var mypre = document.getElementById("output"); mypre.innerHTML = ''; Sk.pre = "output"; Sk.configure({output:outf, read:builtinRead}); var myPromise = Sk.misceval.asyncToPromise( function() { return Sk.importMainWithBody("", false, prog, true); }, { '*': interruptHandler } ); myPromise.then( function(mod) { sim.stopAnimation(); Sk.running = false; }, function(err) { Sk.running = false; sim.stopAnimation(); var mypre = document.getElementById("output"); mypre.innerHTML = mypre.innerHTML + '' + err.toString() + ''; mypre.scrollTop = mypre.scrollHeight - mypre.clientHeight; } ); } document.getElementById('runCode').addEventListener('click', runit); ''' # In[127]: # regular expressions for stripping comment lines removed script_base = ''' function setPos(x, y, angle) { var x = parseFloat(x); var y = parseFloat(y); var angleRadian = parseFloat(angle) / 180 * Math.PI; if (isNaN(x)) { x = 0; } if (isNaN(y)) { y = 0; } if (isNaN(angleRadian)) { angleRadian = 0; angle = 0; } document.getElementById('xPos').value = x; document.getElementById('yPos').value = y; document.getElementById('angle').value = angle; sim.setRobotPos(x, y, angleRadian); sim.drawAll(); } var sim = new EV3devSim('field'); setPos(1181, 571, 0); document.getElementById('map').value = 'Empty Map'; document.getElementById('walls').checked = true; document.getElementById('obstacles').checked = true; document.getElementById('configureObstacles').addEventListener('click', function() { document.getElementById('obstaclesConfigurator').classList.remove('closed'); var obstacles = JSON.stringify(sim.obstacles, null, 2); document.getElementById('obstaclesConfiguratorEditor').value = obstacles; }); document.getElementById('obstaclesConfiguratorApply').addEventListener('click', function() { var json = document.getElementById('obstaclesConfiguratorEditor').value; var obstacles = JSON.parse(json); sim.clearObstacles(); sim.clearObstaclesLayer(); sim.loadObstacles(obstacles); document.getElementById('obstaclesConfigurator').classList.add('closed'); }); document.getElementById('obstaclesConfiguratorClose').addEventListener('click', function() { document.getElementById('obstaclesConfigurator').classList.add('closed'); }); document.getElementById('obstaclesConfiguratordownload').addEventListener('click', function() { var obstaclesSpecs = document.getElementById('obstaclesConfiguratorEditor').value var hiddenElement = document.createElement('a'); hiddenElement.href = 'data:application/json;base64,' + btoa(obstaclesSpecs); hiddenElement.target = '_blank'; hiddenElement.download = 'obstacles_config.json'; hiddenElement.dispatchEvent(new MouseEvent('click')); }); document.getElementById('obstaclesConfiguratorupload').addEventListener('click', function() { var hiddenElement = document.createElement('input'); hiddenElement.type = 'file'; hiddenElement.accept = 'application/json,.json,.js'; hiddenElement.dispatchEvent(new MouseEvent('click')); hiddenElement.addEventListener('change', function(e){ var reader = new FileReader(); reader.onload = function() { document.getElementById('obstaclesConfiguratorEditor').value = this.result; }; reader.readAsText(e.target.files[0]); }); }); document.getElementById('showRays').addEventListener('click', function() { if (document.getElementById('showRays').checked) { sim.drawUltrasonic = true; } else { sim.drawUltrasonic = false; } sim.drawAll(); }); document.getElementById('walls').addEventListener('click', function() { if (document.getElementById('walls').checked) { sim.setWallsPresent(true); } else { sim.setWallsPresent(false); } }); document.getElementById('obstacles').addEventListener('click', function() { if (document.getElementById('obstacles').checked) { sim.obstaclesPresent = true; sim.clearObstaclesLayer(); sim.drawObstacles(); } else { sim.obstaclesPresent = false; sim.clearObstaclesLayer(); } }); document.getElementById('move').addEventListener('click', function() { setPos( document.getElementById('xPos').value, document.getElementById('yPos').value, document.getElementById('angle').value ); }); document.getElementById('stop').addEventListener('click', function() { sim.stopAnimation(); Sk.hardInterrupt = true; }); document.getElementById('largeFont').addEventListener('change', function(e) { if (e.target.checked) { document.getElementById('editor').classList.add('large'); } else { document.getElementById('editor').classList.remove('large'); } }); document.getElementById('longWindow').addEventListener('change', function(e) { if (e.target.checked) { document.getElementById('editor').classList.add('long'); } else { document.getElementById('editor').classList.remove('long'); } window.dispatchEvent(new Event('resize')); }); document.getElementById('download').addEventListener('click', function() { var hiddenElement = document.createElement('a'); hiddenElement.href = 'data:text/x-python;base64,' + btoa(editor.getValue()); hiddenElement.target = '_blank'; hiddenElement.download = 'robot.py'; hiddenElement.dispatchEvent(new MouseEvent('click')); }); document.getElementById('upload').addEventListener('click', function() { var hiddenElement = document.createElement('input'); hiddenElement.type = 'file'; hiddenElement.accept = 'text/x-python,.py'; hiddenElement.dispatchEvent(new MouseEvent('click')); hiddenElement.addEventListener('change', function(e){ var reader = new FileReader(); reader.onload = function() { editor.setValue(this.result); }; reader.readAsText(e.target.files[0]); }); }); sim.loadBackground('WRO-2020-Regular-Junior.png'); document.getElementById('map').addEventListener('input', function() { var map = document.getElementById('map').value; if (map == 'WRO 2020 Regular Junior') { sim.loadBackground('WRO-2020-Regular-Junior.png'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(130, 130, 0); } else if (map == 'WRO 2019 Regular Junior') { sim.loadBackground('WRO-2019-Regular-Junior.jpg'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(2215, 150, 90); } else if (map == 'WRO 2018 Regular Junior') { sim.loadBackground('WRO-2018-Regular-Junior.png'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(1181, 150, 90); } else if (map == 'FLL 2019 - City Shaper') { sim.loadBackground('FLL2019.jpg'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(500, 150, 90); } else if (map == 'FLL 2018 - Into Orbit') { sim.loadBackground('FLL2018.jpg'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(150, 150, 90); } else if (map == 'Line Following Test') { sim.loadBackground('Line_Following_Test.png'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(141, 125, 90); } else if (map == 'Junction Handling Test') { sim.loadBackground('Junction_Handling_Test.png'); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(698, 130, 90); } else if (map == 'Obstacles Test') { sim.loadBackground('Obstacles_Test.png'); setPos(121, 125, 90); sim.clearObstacles(); sim.clearObstaclesLayer(); sim.loadObstacles([ [46, 388, 150, 150], [479, 704, 150, 150], [852, 388, 150, 150], [1374, 388, 150, 150], [1758, 900, 150, 150], [2126, 388, 150, 150] ]); } else if (map == 'Upload Image (2362x1143px)...') { console.log('upload'); var hiddenElement = document.createElement('input'); hiddenElement.type = 'file'; hiddenElement.accept = 'image/*'; console.log(hiddenElement); hiddenElement.dispatchEvent(new MouseEvent('click')); hiddenElement.addEventListener('change', function(e){ var reader = new FileReader(); reader.onload = function() { sim.loadBackground(this.result); sim.clearObstacles(); sim.clearObstaclesLayer(); }; reader.readAsDataURL(e.target.files[0]); }); var select = document.getElementById('map'); select.selectedIndex = select.options.length - 1; } else { sim.clearBackground(); sim.clearObstacles(); sim.clearObstaclesLayer(); setPos(2362/2, 1143/2, 0); } }); document.getElementById('robotConfiguratorOpen').addEventListener('click', function() { document.getElementById('robotConfiguratorEditor').value = JSON.stringify(sim.robotSpecs, null, 2); document.getElementById('robotConfigurator').classList.remove('closed'); }); document.getElementById('robotConfiguratorCancel').addEventListener('click', function() { document.getElementById('robotConfigurator').classList.add('closed'); }); document.getElementById('robotConfiguratorApply').addEventListener('click', function() { var robotSpecs = JSON.parse(document.getElementById('robotConfiguratorEditor').value); sim.loadRobot(robotSpecs); sim.drawAll(); document.getElementById('robotConfigurator').classList.add('closed'); }); document.getElementById('robotConfiguratordownload').addEventListener('click', function() { var robotSpecs = document.getElementById('robotConfiguratorEditor').value var hiddenElement = document.createElement('a'); hiddenElement.href = 'data:application/json;base64,' + btoa(robotSpecs); hiddenElement.target = '_blank'; hiddenElement.download = 'robot_config.json'; hiddenElement.dispatchEvent(new MouseEvent('click')); }); document.getElementById('robotConfiguratorupload').addEventListener('click', function() { var hiddenElement = document.createElement('input'); hiddenElement.type = 'file'; hiddenElement.accept = 'application/json,.json,.js'; hiddenElement.dispatchEvent(new MouseEvent('click')); hiddenElement.addEventListener('change', function(e){ var reader = new FileReader(); reader.onload = function() { document.getElementById('robotConfiguratorEditor').value = this.result; }; reader.readAsText(e.target.files[0]); }); }); function openWindow(url) { var win = document.getElementById('window'); var content = win.getElementsByClassName('content')[0]; win.classList.remove('closed'); content.innerHTML = ''; fetch(url) .then(function(response) { response.text() .then(function(text) { content.innerHTML = text; }); }) } // Load Ace editor var editor = ace.edit("pythonCode"); editor.setTheme("ace/theme/monokai"); editor.session.setMode("ace/mode/python"); // Load Skulpt function outf(text) { var mypre = document.getElementById("output"); mypre.innerHTML = mypre.innerHTML + text; mypre.scrollTop = mypre.scrollHeight - mypre.clientHeight; } function builtinRead(x) { var externalLibs = { './ev3dev2/__init__.py': 'ev3dev2/__init__.py', './ev3dev2/motor.py': 'ev3dev2/motor.py', './ev3dev2/sensor/__init__.py': 'ev3dev2/sensor/__init__.py', './ev3dev2/sensor/lego.py': 'ev3dev2/sensor/lego.py', './ev3dev2_glue.js': 'ev3dev2_glue.js' } if (Sk.builtinFiles === undefined || Sk.builtinFiles["files"][x] === undefined) { if (x in externalLibs) { alert("load "+x) return Sk.misceval.promiseToSuspension( fetch(externalLibs[x]) .then(r => r.text()) ); } else { throw "File not found: '" + x + "'"; } } return Sk.builtinFiles["files"][x]; } var interruptHandler = function (susp) { if (Sk.hardInterrupt === true) { delete Sk.hardInterrupt; throw new Sk.builtin.ExternalError('aborted execution'); } else { return null; } }; function runit() { if (typeof Sk.hardInterrupt != 'undefined') { delete Sk.hardInterrupt; } if (Sk.running == true) { return; } Sk.running = true; sim.reset(); sim.startAnimation(); var prog = editor.getValue(); var mypre = document.getElementById("output"); mypre.innerHTML = ''; Sk.pre = "output"; Sk.configure({output:outf, read:builtinRead}); var myPromise = Sk.misceval.asyncToPromise( function() { return Sk.importMainWithBody("", false, prog, true); }, { '*': interruptHandler } ); myPromise.then( function(mod) { sim.stopAnimation(); Sk.running = false; }, function(err) { Sk.running = false; sim.stopAnimation(); var mypre = document.getElementById("output"); mypre.innerHTML = mypre.innerHTML + '' + err.toString() + ''; mypre.scrollTop = mypre.scrollHeight - mypre.clientHeight; } ); } document.getElementById('runCode').addEventListener('click', runit); ''' # The following doesnlt work, I think because Skulpt is confused trying to load in the external Python packages. Not sure how that can be addressed? Or whether the ev3dev packages and the glue can all be preloaded into Skulpt prior to each terminal programme execution. # # This might actually be more useful for 101 use case because then there will be no requirement to load in ev3dev packages explicitly? # In[129]: import jp_proxy_widget from jp_proxy_widget import js_context from IPython.display import HTML, display # In[130]: test1 = jp_proxy_widget.JSProxyWidget() e = test1.element # Call some standard jQuery method on the widget element: e.empty() e.width("1000px") e.height("1000px") test1.load_js_files(["skulpt.min.js", "skulpt-stdlib.js", "EV3devSim.js"]) test1.require_js("ace/ace", "ace-src-min/ace.js") test1.load_css("main.css") e.html(html) test1.js_init(script_base) #test1.js_init(script_everything_else) #test1.js_init(script) display(test1) # In[ ]: # In[ ]: