执行Blockly生成代码

  • 路由
    • 下一篇
    • 历史回顾
      • Babylon.js部分
      • Blockly部分
  • 前言
  • 最终实现效果
  • 本文内容
    • 实现思路
    • 问题分析
      • 问题
      • 原因
      • 不优雅解决
      • 优雅解决
    • 完整代码
  • 后续计划
  • 开源项目GitHub链接
  • 资源下载链接
  • 你的点赞是我继续编写的动力

路由

下一篇

  • Vue实现图形化积木式编程(十三) ---- 步骤运行代码块高亮

历史回顾

Babylon.js部分

  • Vue实现图形化积木式编程(一) ---- Babylon.js基础场景搭建
  • Vue实现图形化积木式编程(二) ---- Babylon.js加载模型到场景中
  • Vue实现图形化积木式编程(三) ---- Babylon.js点击拖拽移动模型
  • Vue实现图形化积木式编程(四) ---- Babylon.js实现碰撞效果
  • Vue实现图形化积木式编程(五) ---- Babylon.js自定义启动界面
  • Vue实现图形化积木式编程(六) ---- Babylon.js相机控制与相机动画
  • Vue实现图形化积木式编程(七) ---- babylonjs-gui 按钮实现
  • Vue实现图形化积木式编程(八) ---- 将3d界面放入可拖动窗口中

Blockly部分

  • Vue实现图形化积木式编程(九) ---- Blockly代码块编辑区域基本场景搭建
  • Vue实现图形化积木式编程(十) ---- Blockly自定义块
  • Vue实现图形化积木式编程(十一) ---- Blockly插件使用

前言

TIPS:该案例设计主要参考iRobot Coding,只用做学习用途,侵删。

https://code.irobot.com/#/

最终实现效果

本文内容

  • 在 Blockly代码块编辑区域基本场景搭建 这篇文章里笔者提到过在之后的文章中会对使用eval方法执行代码进行优化,对于通过Blockly代码块生成的代码字符串,本文介绍如何将代码字符串转化为执行语句来调用控制模型运动的方法。

本文主要内容是执行Blockly生成的代码字符串,会忽略通过babylonjs来控制模型对象运动的细节。实际上控制模型运动并不难,主要解决的是顺序执行当前生成的代码指令时,如move、arc等指令,需要设定在一定时间内模型的x、y、rotation等参数如何变化。

实现思路

如果想要将字符串转为可执行代码,可以使用eval()new Function([arg1,arg2,...], 代码字符串)

  • 现在存在一个robot的实例对象,包含一些控制运动的方法
//  robot.js
class Robot {constructor() {this.isRun = false}init() {console.log("robot模块化程序初始化")this.isRun = true}stop() {console.log("robot模块运行结束")this.isRun = false}checkStatus() {if (!this.isRun) {throw '程序需要初始化模块'}}async move(distance) {this.checkStatus()// 模拟小车运动return new Promise(resolve => {let moveDis = 0let interval = setInterval(() => {if (moveDis < distance) {console.log(`move ${moveDis++}`)} else {clearInterval(interval)interval = undefinedresolve()}}, 100)})}async arc(direction, degree, distance) {this.checkStatus()// 模拟小车运动return new Promise(resolve => {let moveDis = 0let interval = setInterval(() => {if (moveDis < distance) {console.log(`direction: ${direction}, move:${moveDis++}, degree: ${degree}`)} else {clearInterval(interval)interval = undefinedresolve()}}, 100)})}
}
export default Robot
 import Robot from 'robot.js'const robot = new Robot()
  • 然后使用 Blockly自定义块 自定义了move、arc等指令的代码块,并通过这些代码块拼接并生成了如下代码字符串:
// 现在需要执行这一代码字符串
let code = `robot.init();robot.move(50);robot.arc(0, 90,50);robot.stop();
`
  1. 使用eval
  • 使用eval执行,需要将robot对象挂载到window对象上
 window.robot = roboteval(code)
  1. 使用 new Function ([arg1, arg2, ...], '代码字符串')
  • 使用new Function,最后一个参数是执行代码字符串,前面的参数都是提供给代码字符串的变量
 /** 这里相当于定义了一个函数function(robot) {//执行的code内容}*/let fn = new Function('robot', code)// 执行函数, 将robot作为变量传入fn(robot)

问题分析

问题

  • 执行函数后会发现,如果movearc都是一个异步函数,程序执行顺序有问题,执行顺序为init->stop->move和arc交替执行

原因

  • 上面生成的代码字符串是顺序执行的,然后move和arc是异步任务,会放进入宏任务列表,宏任务会在主线程的同步任务执行完之后再执行。

不优雅解决

  • 在有异步操作的代码块生成的代码中加入await关键字,异步代码需要在有async标记的函数中执行,所以需要在执行的代码块中包裹一个立即执行的异步匿名函数
Blockly.JavaScript['while_program_start'] = function () {var while_content = Blockly.JavaScript.statementToCode(block, 'while_content');const code = `
(async ()=>{
robot.init();\n${while_content}robot.stop();
})()
`return code;
};
Blockly.JavaScript['move'] = function (block) {var text_move_distance = block.getFieldValue('move_distance');var code = `await robot.move(${text_move_distance});\n`;return code;
};
Blockly.JavaScript['arc'] = function (block) {var dropdown_dirction = block.getFieldValue('dirction');var angle_degree = block.getFieldValue('degree');var radius = block.getFieldValue('radius');var code = `await robot.arc(${dropdown_dirction}, ${angle_degree}, ${radius});\n`;return code;
};
// 生成的代码字符串变成如下形式
(async ()=>{robot.init();await robot.move(50);await robot.arc(0, 90, 50);
robot.stop();
})()

优雅解决

  • 优雅的方案是使用 JS-Interpreter - JavaScript语法解析器来将字符串代码可执行的代码。在解析器在创建时可自定义属性、自定函数和其对应执行的同步或异步方法,同时,可以通过step方法来步骤执行代码,这就可以配合Blockly的highlightBlock(blockId)方法来实现步骤执行代码块高亮的效果。
  • 接入js-interpreter,步骤运行block块将会再下一篇文章中详细描述哦

完整代码

  • 测试用例
<template><div id="blockly"><!-- 工作区 --><div id="blocklyDiv" ref="blocklyDiv" style="height: 500px; width: 800px;"></div><button style="position: fixed;left: 50px;top: 10px;" @click="block2code">生成代码</button><!-- 代码显示区 --><div style="background-color: lightgrey;width: 800px;text-align: left"><pre v-html="code?code:'请点击生成代码按钮'"></pre></div><button style="position: fixed;left: 150px;top: 10px;" @click="runCode">eval执行代码</button><button style="position: fixed;left: 300px;top: 10px;" @click="runCode2">new Function执行代码</button></div>
</template><script>
import Blockly from 'blockly'
import BlocklyJS from 'blockly/javascript';
import './customBlock'
import Robot from './robot'
export default {name: "blocklyClass3",data() {return {code:'',options: {horizontalLayout: true,//工具箱水平toolboxPosition: "end",//工具箱在底部toolbox: {"kind": "flyoutToolbox","contents": [{"kind": "block","type": "while_program_start",},{"kind": "block","type": "move",},{"kind": "block","type": "arc"},{"kind": "block","type": "controls_repeat_ext"},{"kind": "block","type": "controls_whileUntil"},{"kind": "block","type": "controls_for"},{"kind": "block","type": "controls_if"},{"kind": "block","type": "logic_compare"},{"kind": "block","type": "logic_operation"},{"kind": "block","type": "logic_negate"},{"kind": "block","type": "logic_boolean"},{"kind": "sep","gap": "32"},{"kind": "block","blockxml": "<block type='math_number'><field name='NUM'>10</field></block>"},{"kind": "block","type": "math_arithmetic"},{"kind": "block","type": "math_single"},{"kind": "block","type": "text"},{"kind": "block","type": "text_length"},{"kind": "block","type": "text_print"},{"kind": "block","type": "variables_get"},{"kind": "block","type": "variables_set"},]}}}},mounted() {Blockly.inject(this.$refs.blocklyDiv, this.options);this.robot = new Robot()},methods:{/*** block代码块转为代码*/block2code(){this.code = BlocklyJS.workspaceToCode(this.$refs.blocklyDiv.workspace)},/*** 执行生成代码*/runCode(){if(!this.code){alert('请先点击生成代码');return}window.robot = this.roboteval(this.code)},runCode2(){if(!this.code){alert('请先点击生成代码');return}let fn = new Function('robot', this.code)fn(this.robot)}},
}
</script><style scoped>
#blockly {position: absolute;left: 50px;top: 50px;bottom: 0;width: calc(100vw - 50px);height: calc(100vh - 50px);display: flex;flex-direction: column;
}
</style>
  • Blockly自定义组件
import * as Blockly from 'blockly/core'import * as hans from 'blockly/msg/zh-hans'Blockly.setLocale(hans);//汉化/*** 自定义组件注册*/
Blockly.defineBlocksWithJsonArray([//事件{"type": "while_program_start","message0": "当程序运行 %1 %2","args0": [{"type": "input_dummy"},{"type": "input_statement","name": "while_content"}],"previousStatement": null,"nextStatement": null,"colour": "#609FD6","strokeColour": "#4088C8","tooltip": "123","helpUrl": "1"},//指令{"type": "move","message0": "移动 %1 CM","args0": [{"type": "field_input","name": "move_distance","text": "50"}],"previousStatement": null,"nextStatement": null,"colour": "#F7D233","strokeColour": "#CCAD2B","tooltip": "","helpUrl": ""},{"type": "arc","message0": "弧形 %1 %2 ,半径 %3 CM","args0": [{"type": "field_dropdown","name": "dirction","options": [["向左","0"],["向右","1"]]},{"type": "field_angle","name": "degree","angle": 90},{"type": "field_number","name": "radius","value": 50,"min": 1,"max": 100}],"previousStatement": null,"nextStatement": null,"colour": "#F7D233","strokeColour": "#CCAD2B","tooltip": "","helpUrl": ""}],"previousStatement": null,"nextStatement": null,"colour": "#81C679","tooltip": "","helpUrl": ""},]
);/*** 自定义组件生成代码* @param block* @returns {string}*/
Blockly.JavaScript['while_program_start'] = function (block) {var while_content = Blockly.JavaScript.statementToCode(block, 'while_content');const code = `
(async ()=>{
robot.init();\n${while_content}robot.stop();
})()
`return code;
};Blockly.JavaScript['move'] = function (block) {var text_move_distance = block.getFieldValue('move_distance');var code = `await robot.move(${text_move_distance});\n`;return code;
};Blockly.JavaScript['arc'] = function (block) {var dropdown_dirction = block.getFieldValue('dirction');var angle_degree = block.getFieldValue('degree');var radius = block.getFieldValue('radius');var code = `await robot.arc(${dropdown_dirction}, ${angle_degree}, ${radius});\n`;return code;
};
  • Robot控制类
// eslint-disable-next-line no-unused-vars
class Robot {constructor() {this.isRun = false}init() {console.log("robot模块化程序初始化")this.isRun = true}stop() {console.log("robot模块运行结束")this.isRun = false}checkStatus() {if (!this.isRun) {throw '程序需要初始化模块'}}async move(distance) {this.checkStatus()// 模拟小车运动return new Promise(resolve => {let moveDis = 0let interval = setInterval(() => {if (moveDis < distance) {console.log(`move ${moveDis++}`)} else {clearInterval(interval)interval = undefinedresolve()}}, 100)})}async arc(direction, degree, distance) {this.checkStatus()// 模拟小车运动return new Promise(resolve => {let moveDis = 0let interval = setInterval(() => {if (moveDis < distance) {console.log(`direction: ${direction}, move:${moveDis++}, degree: ${degree}`)} else {clearInterval(interval)interval = undefinedresolve()}}, 100)})}
}
export default Robot

后续计划

  • 接入js-interpreter,步骤运行block块

开源项目GitHub链接

https://github.com/Wenbile/Child-Programming-Web

资源下载链接

  • Vue前端源码
  • ThinkJS后端源码

你的点赞是我继续编写的动力

Vue实现图形化积木式编程(十二)相关推荐

  1. Vue实现图形化积木式编程(十)

    Blockly自定义块 路由 下一篇 历史回顾 Babylon.js部分 Blockly部分 前言 最终实现效果 本文实现效果 完整代码 代码分解 0.代码块前置知识 0.1 汉化 0.2 预置块 0 ...

  2. Vue实现图形化积木式编程(十三)

    步骤运行代码块高亮 路由 历史回顾 Babylon.js部分 Blockly部分 前言 最终实现效果 本文内容 实现 1. 安装依赖 2. 简化语法 3. 引入js解析器 4. 运行代码 5. 加入高 ...

  3. Vue实现图形化积木式编程(十一)

    Blockly插件使用 路由 下一篇 历史回顾 Babylon.js部分 Blockly部分 前言 最终实现效果 本文内容 安装 使用 后续计划 开源项目GitHub链接 资源下载链接 你的点赞是我继 ...

  4. Vue实现图形化积木式编程(一)

    Babylon.js基础场景搭建 路由 前言 最终实现效果 本文实现效果 技术选型 1.前端 2.后端 完整代码 代码分解 0.npm安装相关依赖 1.引入模块 2.场景初始化 3.ArcRotate ...

  5. Vue实现图形化积木式编程(二)

    Babylon.js加载模型到场景中 路由 下一篇 历史回顾 前言 最终实现效果 本文实现效果 完整代码 操作分解(Babylon.js模型格式转换与导入) 0.在开源模型网上下载一个模型/自己制作一 ...

  6. 原创教程:下载和安装“图形化积木Python编程”海龟编辑器

    一.简介 Python编辑器是一款界面简单充满童趣的Python编程软件,该软件普遍适用于低龄化用户,帮助培养用户对代码编程的兴趣:众所周知编写代码是比较枯燥的事情,所以学起来更是如此,为解决这一难题 ...

  7. python积木式编程_TurnipBit—MicroPython开发板:从积木式编程语言开始学作小小创客...

    编程.建模.制做动画和游戏--这些当初咱们默认只有成年人玩得转的事情,如今早已经被无数小孩子给颠覆甚至玩出新境界了.热爱科技和动手的"创客"(Maker)如今在全世界都煊赫一时.今 ...

  8. 竞赛无人机搭积木式编程——以2022年TI电赛送货无人机一等奖复现为例学习(7月B题)

    在学习本教程前,请确保已经学习了前4讲中无人机相关坐标系知识.基础飞行控制函数.激光雷达SLAM定位条件下的室内定点控制.自动飞行支持函数.导航控制函数等入门阶段的先导教程. 同时用户在做二次开发自定 ...

  9. python积木式编程_【发现教育版亮点之美】3D One还能这么玩:“趣味编程”建模让你脑洞大开...

    原标题:[发现教育版亮点之美]3D One还能这么玩:"趣味编程"建模让你脑洞大开 "[有奖征文]发现3D One教育版亮点之美"教育版功能文章征集活动已经告一 ...

最新文章

  1. 9月Python开源项目Top10
  2. Linux内核分析——第五章 系统调用
  3. 1-4 多文档界面处理(3)
  4. 【剑指offer】矩形覆盖
  5. python for in list
  6. c++11-type_traits类型萃取
  7. Booting Android: bootloaders, fastboot and boot images
  8. leetcode1119. 删去字符串中的元音 小学难度
  9. mysql怎么分组查询所有数据库_Mysql-4 分组查询与子查询
  10. web中的classpath 和 classpath*
  11. 在32位windows 7下安装PyLucene
  12. 我用Python采集了班花的空间数据集,除了美照竟然再一次发现了她另外的秘密!
  13. RHEL7 -- 使用Chrony设置时间与时钟服务器同步
  14. Storm 多语言支持
  15. mysql 的下划线搜索转义
  16. android官方原生主题,原生Android可以更换系统主题吗?
  17. IE下载时中文文件名乱码解决
  18. 阿里云服务器购买配置、环境部署、搭建网站教程(转载)
  19. 公共关系计算机操作题,Access上机操作题库.doc
  20. php外语文献有哪些,外语论文参考文献

热门文章

  1. 【软件实现实验三:迷宫】
  2. 1.6.4- 四大名著案例
  3. Web server failed to start. Port 9080 was already in use报错解决
  4. iOS 自定义页面的切换动画与交互动画 By Swift
  5. 费控产品之易快报洞察解析
  6. Deferred Decal
  7. 19 | 耗电优化(下):耗电的优化方法与线上监控
  8. js 时间转东八区_js:固定与东八区服务器时间保持一致并且可选时间格式
  9. 4.1EF Core
  10. AVS2的GB帧与s帧