Blockly之工具Block模块的加入流程

一.Block小模块的加入过程:

在Blockly中侧边栏中的ToolBox是的加入经过assets目录下的json文件数据进行转化成Block的小模块。json中的数据标签通过BlockDefinition类解析一个个的具有属性Block模块,之后把Block模块加入到BlockFactory的工厂中。在需要的地方绘制到Fragment中去。



二.添加自定义的模块的流程:

1.在DefaultBlocks.java中添加要加入模块的json路径如:

添加路径的常量值:

public static final String WANGYONGYAO_BLOCKS_PATH = "default/wangyongyao_blocks.json";
在getAllBlockDefinitions方法中添加定义的路径常量值,在这里加入是通过一系列的方法进行json数据的解析成Block中的属性值:


2.在assets目录中的toolbox.xml中添加自己定义的block的category种类:

<toolbox>
    <category name="王永耀" colour="200">
        <block type="wangyongyao_type"></block>
    </category>
</toolbox>

3.自己定义的json文件wangyongyao.json格式如下,如是图片转化成 base64码的网站

[
  {
    "type": "wangyongyao_type",     //自定的type模式在toolbox.xml中进行定义
    "message0": "%1 王永耀 %2",      //%后的是type数量值如有两个就是这种模式,如是三个就在后面加上%3以此类推
    "args0": [
      {
        "type": "field_image",      //添加图片模式,src后添加的是图片转成的base64的值
        "src": "data:image/gif;base64,R0lGODlhDwAPAMQfAF9h4RYVnZeQJC0u0lRQU42R6P/7Fv74L05NrRkZxi4tW52XXv71D8nAIWxnjnRxr3NuMJKOluXbBe7kCa2x7UFD1vPoB77D8Jqe6n6B5tvTUr62BMrP8lJPh1xbuv///yH5BAEAAB8ALAAAAAAPAA8AAAWD4CeOWQKMaDpESepi3tFlLgpExlK9RT9ohkYi08N8KhWP8nEwMBwIDyJRSTgO2CaDYcBOCAlMgtDYmhmTDSFQ+HAqgbLZIlAMLqiKw7m1EAYuFQsGEhITEwItKBc/EgIEAhINAUYkCBIQAQMBEGonIwAKa21iCgo7IxQDFRQjF1VtHyEAOw==",
        "width": 15,
        "height": 15,
        "alt": "*"
      },
      {
        "type": "input_value",
        "name": "yuan_text",
        "check": "String"
      }
    ],
    "colour": 330,
    "tooltip": "",
    "helpUrl": ""
  }
]
这样就简单添加了一个自定义模块的block。


三.json及xml数据加入的源码分析:

1.在assets资产目录下的json文件,通过AbstractBlockActivity抽象类中的抽象方法getBlockDedinitionJsonPaths():

private static final List<String> BLOCK_DEFINITIONS = DefaultBlocks.getAllBlockDefinitions();
@NonNull
@Override
protected List<String> getBlockDefinitionsJsonPaths() {             //获取模块中默认assets的json数据
    return BLOCK_DEFINITIONS;
}

2.接下来传给BlockActivityHelper类的resetBlockFactory():

protected void resetBlockFactory() {
    mBlocklyActivityHelper.resetBlockFactory(               //将获取到的assets中的路径信息交给BlockActivityHelper处理
            getBlockDefinitionsJsonPaths());

    configureBlockExtensions();
    configureMutators();
    configureCategoryFactories();

    // Reload the toolbox?
}

3.在resetBlockFactort()方法中获取到DefaltBlock中定义的json的路径常量。

public void resetBlockFactory(
        @Nullable List<String> blockDefinitionsJsonPaths) {
    AssetManager assets = mActivity.getAssets();
    BlockFactory factory = mController.getBlockFactory();
    factory.clear();

    String assetPath = null;
    try {
        if (blockDefinitionsJsonPaths != null) {
            Log.i("everb","blockDefinitionsJsonPaths:"+blockDefinitionsJsonPaths);
            for (String path : blockDefinitionsJsonPaths) {
                assetPath = path;
                factory.addJsonDefinitions(assets.open(path));  //传给BlockFactory工厂进行Block成型的处理
            }
        }
    } catch (IOException | BlockLoadingException e) {
        throw new IllegalStateException(
                "Failed to load block definition asset file: " + assetPath, e);
    }
}

4.之后交给BlockFactory进行InputStream输入流的的处理:

public int addJsonDefinitions(InputStream jsonStream)
        throws IOException, BlockLoadingException {
    // Read stream as single string.
    String inString = new Scanner( jsonStream ).useDelimiter("\\A").next();
    Log.i("everb","inString:"+inString);
    return addJsonDefinitions(inString);//返回block的数量
}
public void addDefinition(BlockDefinition definition) {
    String typeName = definition.getTypeName();
    if (mDefinitions.containsKey(typeName)) {
        throw new IllegalArgumentException(
                "Definition \"" + typeName + "\" already defined. Prior must remove first.");
    }
    mDefinitions.put(typeName, definition);    //添加到mDefinitions的map中放在BlockFatoryTest去检测是否合法
}

5.在BlockDefinition类中解析在assets中的json文件,通过CreateInputList()方法生成Input的集合提供给block类调用生成一个模块:Field的bean类包涵着json数据中的所有type对应的标签.BlockDefinition对块block之间的输入输出关系进行了解析和check检查。Input是每个在工具栏中能拖动的block块的数据块,在Input中包涵着Filed最基础的模块,相当于一条语句的模块如:一个if语句就代表着一个Filed。

一个json块对等的关系:

Filed对等于json中的:

"args0": [
  {
    "type": "field_image",      //添加图片模式,src后添加的是图片转成的base64的值
    "src": "data:image/gif;base64,R0lGODlhDwAPAMQfAF9h4RYVnZeQJC0u0lRQU42R6P/7Fv74L05NrRkZxi4tW52XXv71D8nAIWxnjnRxr3NuMJKOluXbBe7kCa2x7UFD1vPoB77D8Jqe6n6B5tvTUr62BMrP8lJPh1xbuv///yH5BAEAAB8ALAAAAAAPAA8AAAWD4CeOWQKMaDpESepi3tFlLgpExlK9RT9ohkYi08N8KhWP8nEwMBwIDyJRSTgO2CaDYcBOCAlMgtDYmhmTDSFQ+HAqgbLZIlAMLqiKw7m1EAYuFQsGEhITEwItKBc/EgIEAhINAUYkCBIQAQMBEGonIwAKa21iCgo7IxQDFRQjF1VtHyEAOw==",
    "width": 15,
    "height": 15,
    "alt": "*"
  },
  {
    "type": "input_value",
    "name": "yuan_text",
    "check": "String"
  }
],
Input对等于json中的:

{
  "type": "wangyongyao_type",     //自定的type模式在toolbox.xml中进行定义
  "message0": "%1 王永耀 %2",      //%后的是type数量值如有两个就是这种模式,如是三个就在后面加上%3以此类推
  "args0": [
    {
      "type": "field_image",      //添加图片模式,src后添加的是图片转成的base64的值
      "src": "data:image/gif;base64,R0lGODlhDwAPAMQfAF9h4RYVnZeQJC0u0lRQU42R6P/7Fv74L05NrRkZxi4tW52XXv71D8nAIWxnjnRxr3NuMJKOluXbBe7kCa2x7UFD1vPoB77D8Jqe6n6B5tvTUr62BMrP8lJPh1xbuv///yH5BAEAAB8ALAAAAAAPAA8AAAWD4CeOWQKMaDpESepi3tFlLgpExlK9RT9ohkYi08N8KhWP8nEwMBwIDyJRSTgO2CaDYcBOCAlMgtDYmhmTDSFQ+HAqgbLZIlAMLqiKw7m1EAYuFQsGEhITEwItKBc/EgIEAhINAUYkCBIQAQMBEGonIwAKa21iCgo7IxQDFRQjF1VtHyEAOw==",
      "width": 15,
      "height": 15,
      "alt": "*"
    },
    {
      "type": "input_value",
      "name": "yuan_text",
      "check": "String"
    }
  ],
  "colour": 330,
  "tooltip": "",
  "helpUrl": ""
}
以下是createInputList方法将所有的json文件数据转成Input的过程:

/**
 * @return A new list of {@link Input} objects for a new block of this type, complete with
 *         fields.
 */
protected ArrayList<Input> createInputList(BlockFactory factory) throws BlockLoadingException {
    ArrayList<Input> inputs = new ArrayList<>();
    ArrayList<Field> fields = new ArrayList<>();
    for (int i = 0; ; i++) {
        String messageKey = "message" + i;
        String argsKey = "args" + i;
        String lastDummyAlignKey = "lastDummyAlign" + i;
        if (!mJson.has(messageKey)) {
            break;
        }
        String message = mJson.optString(messageKey);
        JSONArray args = mJson.optJSONArray(argsKey);
        if (args == null) {
            // If there's no args for this message use an empty array.
            args = new JSONArray();
        }

        if (message.matches("^%[a-zA-Z][a-zA-Z_0-9]*$")) {
            // TODO(#83): load the message from resources.
        }
        // Split on all argument indices of the form "%N" where N is a number from 1 to
        // the number of args. Arguments indices are returned as "%N" strings.
        List<String> tokens = Block.tokenizeMessage(message);     //将json数据中的"message+index"标签后的属性进行切割取出,如:“if %1 do %2”把if和do取出赋值给tokens
        // Indices start at 1, make the array 1 bigger so we don't have to offset things
        boolean[] seenIndices = new boolean[args.length() + 1];

        for (String token : tokens) {
            // Check if this token is an argument index of the form "%N"
            Log.i("everb","token:"+token);
            if (token.matches("^%\\d+$")) {
                int index = Integer.parseInt(token.substring(1));
                if (index < 1 || index > args.length()) {
                    throw new BlockLoadingException("Message index " + index
                            + " is out of range.");
                }
                if (seenIndices[index]) {
                    throw new BlockLoadingException(("Message index " + index
                            + " is duplicated"));
                }
                seenIndices[index] = true;

                JSONObject element;
                try {
                    element = args.getJSONObject(index - 1);      //取出每个Input中的参数args相对于Field
                    Log.i("everb","element:"+element);
                } catch (JSONException e) {
                    throw new BlockLoadingException("Error reading arg %" + index, e);
                }
                while (element != null) {
                    String elementType = element.optString("type");
                    if (TextUtils.isEmpty(elementType)) {
                        throw new BlockLoadingException("No type for arg %" + index);
                    }

                    if (Field.isFieldType(elementType)) {       //判断Filed中的type类型是否为空
                        fields.add(factory.loadFieldFromJson(mTypeName, element));
                        break;
                    } else if (Input.isInputType(elementType)) {
                        Input input = Input.fromJson(element, fields);
                        fields.clear();
                        inputs.add(input);
                        break;
                    } else {
                        // Try getting the fallback block if it exists
                        Log.w(TAG, "Unknown element type: " + elementType);
                        element = element.optJSONObject("alt");
                    }
                }
            } else {
                token = token.replace("%%", "%").trim();
                if (!TextUtils.isEmpty(token)) {
                    fields.add(new FieldLabel(null, token));  //添加Filed的Label标签名
                }
            }
        }

        // Verify every argument was used
        for (int j = 1; j < seenIndices.length; j++) {
            if (!seenIndices[j]) {
                throw new BlockLoadingException("Argument " + j + " was never used.");
            }
        }
        // If there were leftover fields we need to add a dummy input to hold them.
        if (fields.size() != 0) {
            String align = mJson.optString(lastDummyAlignKey, Input.ALIGN_LEFT_STRING);
            Input input = new Input.InputDummy(null, fields, align);
            inputs.add(input);
            fields.clear();
        }
        Log.i("everb","input:"+inputs.get(i).getType());
    }

    return  inputs;
}

6.BlockFactory中的ObtainBlockFrom(BlockTemplate template)方法里获取BlockTemlate获取对应的样板:

public Block obtainBlockFrom(BlockTemplate template) throws BlockLoadingException {
    if (mController == null) {
        throw new IllegalStateException("Must set BlockController before creating block.");
    }

    String id = getCheckedId(template.mId);

    // Existing instance not found. Constructing a new Block.
    BlockDefinition definition;
    boolean isShadow = (template.mIsShadow == null) ? false : template.mIsShadow;
    Block block;
    if (template.mCopySource != null) {
        try {
            // TODO: Improve copy overhead. Template from copy to avoid XML I/O?
            String xml = BlocklyXmlHelper.writeBlockToXml(template.mCopySource,       //通过BlockTemplate获取对应的样板Block  
                    IOOptions.WRITE_ROOT_ONLY_WITHOUT_ID);
            String escapedId = BlocklyXmlHelper.escape(id);
            xml = xml.replace("<block", "<block id=\"" + escapedId + "\"");
            block = BlocklyXmlHelper.loadOneBlockFromXml(xml, this);
        } catch (BlocklySerializerException e) {
            throw new BlockLoadingException(
                    "Failed to serialize original " + template.mCopySource, e);
        }
    } else {
        // Start a new block from a block definition.
        if (template.mDefinition != null) {
            if (template.mTypeName != null
                    && !template.mTypeName.equals(template.mDefinition.getTypeName())) {
                throw new BlockLoadingException("Conflicting block definitions referenced.");
            }
            definition = template.mDefinition;
        } else if (template.mTypeName != null) {
            definition = mDefinitions.get(template.mTypeName.trim());
            if (definition == null) {
                throw new BlockLoadingException("Block definition named \""
                        + template.mTypeName + "\" not found.");
            }
        } else {
            throw new BlockLoadingException(template.toString() + " missing block definition.");
        }

        block = new Block(mController, this, definition, id, isShadow);
    }

    // Apply mutable state last.
    template.applyMutableState(block);
    mBlockRefs.put(block.getId(), new WeakReference<>(block));

    return block;
}

7.BlocklyCategory提供解析出toolbox.xml文件的种类及子种类。给BlockActivityHelper中的reloadToolbox调用:

调用过程是:将AbstractBlocklyAcitity获取到的toolbox的文件路径一步步地交给BlockActivityHelper->BlocklyController->Workspace->BlockXmlHelper进行解析XML中的数据类型及属性。

在BlockActivityHelper中:

public void reloadToolbox(String toolboxContentsXmlPath) {
    AssetManager assetManager = mActivity.getAssets();
    BlocklyController controller = getController();
    try {
        controller.loadToolboxContents(assetManager.open(toolboxContentsXmlPath));          //加载出toolbox.xml文件中的BlocklyCategory的种类
    } catch (IOException | BlockLoadingException e) {
        // compile time assets such as assets are assumed to be good.
        throw new IllegalStateException("Failed to load toolbox XML.", e);
    }
}

在BlocklyController中:

public void loadToolboxContents(InputStream toolboxJsonStream)
        throws IOException, BlockLoadingException {
    mWorkspace.loadToolboxContents(toolboxJsonStream);
    updateToolbox();
}
在Workspace中:

public void loadToolboxContents(InputStream source) throws BlockLoadingException {
    mFlyoutCategory = BlocklyXmlHelper.loadToolboxFromXml(source, mBlockFactory, BlocklyEvent.WORKSPACE_ID_TOOLBOX);
}
在BlockXmlHelper中:

public static BlocklyCategory loadToolboxFromXml(InputStream is, BlockFactory blockFactory,
                                                 String workspaceId)
        throws BlockLoadingException {
    try {
        XmlPullParser parser = PARSER_FACTORY.newPullParser();
        parser.setInput(is, null);
        return BlocklyCategory.fromXml(parser, blockFactory, workspaceId);//BlocklyCategory进行toolbox.xml的种类解析

    } catch (XmlPullParserException e) {
        throw new BlockLoadingException(e);
    }
}

四、FlyoutFragment中的Block的加入的流程:

1.AbstractBlocklyAcitity中onCreate()方法中初始化来自assets下的json文件之后,在进行Category种类的的设置:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    onCreateActivityRootView();
    mBlocklyActivityHelper = onCreateActivityHelper();
    if (mBlocklyActivityHelper == null) {
        throw new IllegalStateException("BlocklyActivityHelper is null. "
                + "onCreateActivityHelper must return a instance.");
    }
    resetBlockFactory();  // Initial load of block definitions, extensions, and mutators.
    configureCategoryFactories();  // After BlockFactory; before Toolbox
    reloadToolbox();

    // Load the workspace.
    boolean loadedPriorInstance = checkAllowRestoreBlocklyState(savedInstanceState)
            && (getController().onRestoreSnapshot(savedInstanceState) || onAutoload());
    if (!loadedPriorInstance) {
        onLoadInitialWorkspace();
    }
}
2.AbstractBlocklyAcitity交给BlockActivityHelper中的configureCategoryFactories方法里对默认的category种类获取及登记:

protected void configureCategoryFactories() {
    mBlocklyActivityHelper.configureCategoryFactories();
}
public void configureCategoryFactories() {
    Map <String, CustomCategory> factoryMap =
            DefaultBlocks.getToolboxCustomCategories(mController);  //在默认的DefaultBlocks获取到toolbox中Categories种类
    for (String key : factoryMap.keySet()) {
        mController.registerCategoryFactory(key, factoryMap.get(key));//在BlocklyController对toolbox中Categories种类进行登记
    }
}
3.接下来DefaultBlocks中对block参数及种类进行对应的生成:

public static Map<String, CustomCategory> getToolboxCustomCategories(
        BlocklyController controller) {
    // Don't store this map, because of the reference to the controller.
    Map<String, CustomCategory> map = new ArrayMap<>(2);
    map.put(VARIABLE_CATEGORY_NAME, new VariableCustomCategory(controller));//参数Category种类的添加
    map.put(PROCEDURE_CATEGORY_NAME, new ProcedureCustomCategory(controller));//block的Category种类在ProcedureCustomCategory生成器中生成
    return Collections.unmodifiableMap(map);
}
4.ProcedurCustomCategory种类生成器初始化时对BlockTemlate检查和categorry的item的数据设置:
public void initializeCategory(BlocklyCategory category) throws BlockLoadingException {
    checkRequiredBlocksAreDefined();  //检查Block的定义是否在BlockFactory对BlockTemplate进行了生成
    rebuildItems(category);         //把category种类的item通过一系列的回调中添加最终设置到FlyoutFragment布局RecyclerView的adapter中

private void rebuildItems(BlocklyCategory category) throws BlockLoadingException {
    category.clear();

    Block block = mBlockFactory.obtainBlockFrom(DEFINE_NO_RETURN_BLOCK_TEMPLATE);//从BlockFactory工厂中获取ProcedureManager中的block的模板
    ((FieldInput)block.getFieldByName(NAME_FIELD)).setText(mDefaultProcedureName);//设置名字和do something
    category.addItem(new BlocklyCategory.BlockItem(block));//添加到BlocklyCategory中的item中

    block = mBlockFactory.obtainBlockFrom(DEFINE_WITH_RETURN_BLOCK_TEMPLATE);
    ((FieldInput)block.getFieldByName(NAME_FIELD)).setText(mDefaultProcedureName);
    category.addItem(new BlocklyCategory.BlockItem(block));

    if (!mProcedureManager.hasProcedureDefinitionWithReturn()) {
        block = mBlockFactory.obtainBlockFrom(IF_RETURN_TEMPLATE);
        category.addItem(new BlocklyCategory.BlockItem(block));
    }

    // Create a call block for each definition.
    final Map<String, Block> definitions = mProcedureManager.getDefinitionBlocks();
    SortedSet<String> sortedProcNames = new TreeSet<>(new Comparator<String>() {
        @Override
        public int compare(String procName1, String procName2) {
            Block def1 = definitions.get(procName1);
            Block def2 = definitions.get(procName2);
            String type1 = def1.getType();
            String type2 = def2.getType();

            // procedures_defnoreturn < procedures_defreturn
            int typeComp = type1.compareTo(type2);
            if (typeComp != 0) {
                return typeComp;
            }
            // Otherwise sort by procedure name, alphabetically
            int nameComp = procName1.compareToIgnoreCase(procName2);
            if (nameComp != 0) {
                return nameComp;
            }
            return def1.getId().compareTo(def2.getId()); // Last resort, by block id
        }
    });
    sortedProcNames.addAll(definitions.keySet());
    for (String procName : sortedProcNames) {
        Block defBlock = definitions.get(procName);
        ProcedureInfo procedureInfo = ((AbstractProcedureMutator) defBlock.getMutator())
                .getProcedureInfo();
        BlockTemplate callBlockTemplate;
        if (defBlock.getType().equals(ProcedureManager.DEFINE_NO_RETURN_BLOCK_TYPE)) {
            callBlockTemplate = CALL_NO_RETURN_BLOCK_TEMPLATE;  // without return value
        } else {
            callBlockTemplate = CALL_WITH_RETURN_BLOCK_TEMPLATE;  // with return value
        }
        Block callBlock = mBlockFactory.obtainBlockFrom(callBlockTemplate);
        ((ProcedureCallMutator) callBlock.getMutator()).mutate(procedureInfo);
        category.addItem(new BlocklyCategory.BlockItem(callBlock));
    }
}

5.BlocklyCateory的addItem方法把item通过回调通知刷新的方式添加到BlcokRecyclerViewHelper的adapter中,并最终的设置到Recycler中。实现了FlyoutFragment中关于toolbox所有的block的显示:

/**
 * Add a {@link Block} to the blocks displayed in this category.
 *
 * @param item The {@link Block} to add.
 */
public void addItem(CategoryItem item) {
    mItems.add(item);
    if (mCallback != null) {
        mCallback.onItemAdded(mItems.size() - 1, item);//通过Callback回调的形式把item添加到BlockRecyclerViewHelper中的adapter中去
    }
}

6.BlcokRecyclerViewHelper中的设置CategoryCallback回调来监听item的事件变化:

protected class CategoryCallback extends BlocklyCategory.Callback {

    @Override
    public void onItemAdded(int index, BlocklyCategory.CategoryItem item) {
        mAdapter.notifyItemInserted(index);
    }

    @Override
    public void onItemRemoved(int index, BlocklyCategory.CategoryItem block) {
        mAdapter.notifyItemRemoved(index);
    }

    @Override
    public void onCategoryCleared() {
        mAdapter.notifyDataSetChanged();
    }
}

<think> 我们正在讨论Aelos Blockly EDU的代码模块生成方法。Aelos Blockly EDU是一个基于Blockly的可视化编程工具,用于教育机器人编程。用户可以通过拖放代码块来创建程序,然后将其转换为机器人可以执行的代码。 根据用户的问题,我们需要解释Aelos Blockly EDU中代码模块的生成方法。这可能包括: 1. 如何创建自定义代码块(模块)。 2. 如何将Blockly代码块转换为目标代码(例如C++或Python)。 3. Blockly工作区的使用和代码生成流程。 由于这是一个特定的工具,我将根据一般的Blockly开发流程进行解释,并结合Aelos Blockly EDU的特点。 ### Aelos Blockly EDU代码模块生成方法 #### 1. 定义自定义代码块 在Blockly中,代码块通过XML或JSON定义。每个块包括: - 块类型(type):唯一标识 - 输入字段(fields):文本标签、下拉菜单等 - 值输入(value inputs):连接其他块的输入 - 语句连接(statement inputs):连接语句块 - 工具提示(tooltip)和帮助URL(help url) **示例代码块定义(JSON格式)**: ```json { "type": "aelos_move_forward", "message0": "前进 %1 秒", "args0": [ { "type": "input_value", "name": "DURATION", "check": "Number" } ], "previousStatement": null, "nextStatement": null, "tooltip": "控制机器人向前移动指定时间", "helpUrl": "" } ``` #### 2. 为代码块生成代码 每个代码块需要定义生成目标代码的方法(通常为JavaScript、Python、C++等)。在Blockly中,通过给块添加生成器函数实现。 **示例生成器函数(JavaScript)**: ```javascript Blockly.JavaScript['aelos_move_forward'] = function(block) { const duration = Blockly.JavaScript.valueToCode(block, 'DURATION', Blockly.JavaScript.ORDER_ATOMIC); return `aelos.moveForward(${duration});\n`; }; ``` #### 3. 在Blockly工作区中使用 用户将自定义块添加到Blockly工具箱后,就可以在编辑器中拖放使用。当用户完成程序搭建,点击“生成代码”按钮时,Blockly调用生成器函数将所有块转换为目标代码。 #### 4. 代码生成流程 - **步骤1**:用户拖放块构建程序 - **步骤2**:调用`Blockly.Generator.workspaceToCode(workspace)`生成代码 - **步骤3**:将生成的代码发送给机器人或保存为文件 #### 5. Aelos Blockly EDU特定实现 Aelos Blockly EDU可能已经内置了大量机器人控制块(如移动、转向、传感器读取等)。其代码生成器会将Blockly块转换为Aelos机器人固件支持的C/C++代码。 **示例生成C++代码**: ```cpp void loop() { aelos.moveForward(2); // 前进2秒 aelos.turnLeft(90); // 左转90度 } ``` ### 如何扩展自定义模块 1. **定义新块**:在Blockly块定义文件中添加新块 2. **实现生成器**:为新块编写生成目标代码的函数 3. **更新工具箱**:将新块添加到工具箱配置XML中 ### 注意事项 - 需要确保生成的代码符合Aelos机器人的API - 对于复杂逻辑,可以使用Blockly提供的逻辑块和循环块 §§相关问题§§ 1. Aelos Blockly EDU支持哪些类型的代码块? 2. 如何将自定义Blockly块集成到Aelos Blockly EDU中? 3. Aelos机器人的API文档在哪里可以找到? 4. Blockly代码生成器的工作原理是什么? 以上内容基于Blockly的通用开发流程。具体到Aelos Blockly EDU,可能需要参考其官方文档或开发手册。 </think> ### Aelos Blockly EDU代码模块生成方法解析 Aelos Blockly EDU是一个基于Google Blockly框架的可视化编程工具,用于教育机器人编程。其代码模块生成方法遵循以下核心原理和流程: #### **1. 模块定义原理** - **视觉块与代码映射** 每个视觉模块对应一个JSON格式的定义文件,包含: ```json { "type": "aelos_move", // 模块唯一标识 "message": "前进 %1 秒", // 用户可见文本 "args": [{ "type": "input_value", // 参数类型 "name": "duration", // 参数名 "check": "Number" // 参数校验类型 }], "output": null, // 输出类型 "colour": 120, // 模块颜色 "tooltip": "控制机器人前进时间" // 悬浮提示 } ``` 该定义实现模块外观与功能的解耦[^1]。 - **类型系统** 通过`check`字段实现强类型校验: ``` Blockly.Blocks['aelos_move'] = { init: function() { this.appendValueInput("duration") .setCheck("Number"); // 限制输入必须为数字 ... } }; ``` #### **2. 代码生成机制** - **生成器函数** 每个模块需注册JavaScript生成器: ```javascript Blockly.JavaScript['aelos_move'] = function(block) { const duration = Blockly.JavaScript.valueToCode( block, 'duration', Blockly.JavaScript.ORDER_ATOMIC ); return `aelos.moveForward(${duration});\n`; // 生成目标代码 }; ``` 该函数将视觉块转换为可执行代码[^2]。 - **多语言支持** 支持同时生成Python/C++等语言: ```python # Python生成器示例 Blockly.Python['aelos_move'] = lambda block: f"aelos.move_forward({block.getFieldValue('duration')})" ``` #### **3. 模块开发流程** 1. **定义模块** - 创建JSON描述文件 - 设计SVG图标(尺寸:40×40 px) 2. **实现功能** ```javascript // 自定义传感器模块示例 Blockly.Blocks['aelos_read_ir'] = { init() { this.appendDummyInput() .appendField("读取红外传感器"); this.setOutput(true, "Number"); } }; Blockly.JavaScript['aelos_read_ir'] = () => ['aelos.readIR()', Blockly.JavaScript.ORDER_FUNCTION_CALL]; ``` 3. **集成到IDE** - 添加至工具箱配置文件 `toolbox.xml`: ```xml <category name="传感器"> <block type="aelos_read_ir"></block> </category> ``` #### **4. 实时编译流程** ```mermaid graph LR A[用户拖拽模块] --> B(Blockly工作区) B --> C{编译事件} C --> D[调用生成器函数] D --> E[生成目标代码] E --> F[上传至Aelos机器人] F --> G[实时执行] ``` #### **5. 调试技巧** - **代码映射**:使用`blockToCode()`方法查看中间表示 - **断点设置**: ```javascript Blockly.JavaScript['aelos_move'] = function(block) { debugger; // 调试断点 // ...生成代码 }; ``` - **错误追踪**:通过`Blockly.Events`监听编译错误
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值