diff --git a/src/blocks/mrc_call_python_function.ts b/src/blocks/mrc_call_python_function.ts index 08876d1c..fd2b86e8 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -48,6 +48,7 @@ export enum FunctionKind { INSTANCE = 'instance', INSTANCE_WITHIN = 'instance_within', INSTANCE_COMPONENT = 'instance_component', + INSTANCE_ROBOT = 'instance_robot', EVENT = 'event', } @@ -220,7 +221,7 @@ function createInstanceMethodBlock( export function getInstanceComponentBlocks( componentType: string, - componentName: string) { + componentName: string): ToolboxItems.ContentsType[] { const contents: ToolboxItems.ContentsType[] = []; const classData = getClassData(componentType); @@ -267,7 +268,7 @@ function createInstanceComponentBlock( // We don't include the arg or input for self. for (let i = 1; i < functionData.args.length; i++) { const argData = functionData.args[i]; - let argName = argData.name; + const argName = argData.name; extraState.args.push({ 'name': argName, 'type': argData.type, @@ -289,6 +290,51 @@ function createInstanceComponentBlock( return block; } +export function getInstanceRobotBlocks(methods: CommonStorage.Method[]): ToolboxItems.ContentsType[] { + const contents: ToolboxItems.ContentsType[] = []; + + for (const method of methods) { + const block = createInstanceRobotBlock(method); + contents.push(block); + } + + return contents; +} + +function createInstanceRobotBlock(method: CommonStorage.Method): ToolboxItems.Block { + const extraState: CallPythonFunctionExtraState = { + functionKind: FunctionKind.INSTANCE_ROBOT, + returnType: method.returnType, + actualFunctionName: method.pythonName, + args: [], + }; + const fields: {[key: string]: any} = {}; + fields[FIELD_FUNCTION_NAME] = method.visibleName; + const inputs: {[key: string]: any} = {}; + // We don't include the arg or input for the self argument. + for (let i = 1; i < method.args.length; i++) { + const arg = method.args[i]; + extraState.args.push({ + 'name': arg.name, + 'type': arg.type, + }); + // Check if we should plug a variable getter block into the argument input socket. + const input = Value.valueForFunctionArgInput(arg.type, ''); + if (input) { + // Because we skipped the self argument, use i - 1 when filling the inputs array. + inputs['ARG' + (i - 1)] = input; + } + } + let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); + if (method.returnType && method.returnType != 'None') { + const varName = Variable.varNameForType(method.returnType); + if (varName) { + block = Variable.createVariableSetterBlock(varName, block); + } + } + return block; +} + //.............................................................................. export type CallPythonFunctionBlock = Blockly.Block & CallPythonFunctionMixin; @@ -401,6 +447,11 @@ const CALL_PYTHON_FUNCTION = { ' on the component named ' + this.getComponentName() + '.'; break; } + case FunctionKind.INSTANCE_ROBOT: { + const functionName = this.getFieldValue(FIELD_FUNCTION_NAME); + tooltip = 'Calls the robot method ' + functionName + '.'; + break; + } default: throw new Error('mrcFunctionKind has unexpected value: ' + this.mrcFunctionKind) } @@ -568,6 +619,14 @@ const CALL_PYTHON_FUNCTION = { .appendField(createFieldNonEditableText(''), FIELD_FUNCTION_NAME); break; } + case FunctionKind.INSTANCE_ROBOT: { + this.appendDummyInput('TITLE') + .appendField('call') + .appendField(createFieldNonEditableText('robot')) + .appendField('.') + .appendField(createFieldNonEditableText(''), FIELD_FUNCTION_NAME); + break; + } default: throw new Error('mrcFunctionKind has unexpected value: ' + this.mrcFunctionKind) } @@ -707,6 +766,13 @@ export const pythonFromBlock = function( code += componentName + '.' + functionName; break; } + case FunctionKind.INSTANCE_ROBOT: { + const functionName = callPythonFunctionBlock.mrcActualFunctionName + ? callPythonFunctionBlock.mrcActualFunctionName + : block.getFieldValue(FIELD_FUNCTION_NAME); + code = 'self.robot.' + functionName; + break; + } default: throw new Error('mrcFunctionKind has unexpected value: ' + callPythonFunctionBlock.mrcFunctionKind) } diff --git a/src/blocks/mrc_class_method_def.ts b/src/blocks/mrc_class_method_def.ts index a8a8cfd9..a14c7f32 100644 --- a/src/blocks/mrc_class_method_def.ts +++ b/src/blocks/mrc_class_method_def.ts @@ -16,7 +16,7 @@ */ /** - * @fileoverview Blocks for class method defintion + * @fileoverview Blocks for class method definition * @author alan@porpoiseful.com (Alan Smith) */ import * as Blockly from 'blockly'; @@ -25,6 +25,7 @@ import { createFieldNonEditableText } from '../fields/FieldNonEditableText' import { createFieldFlydown } from '../fields/field_flydown'; import { Order } from 'blockly/python'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; +import * as commonStorage from '../storage/common_storage'; import { renameMethodCallers, mutateMethodCallers } from './mrc_call_python_function' import { findConnectedBlocksOfType } from './utils/find_connected_blocks'; import { BLOCK_NAME as MRC_GET_PARAMETER_BLOCK_NAME } from './mrc_get_parameter'; @@ -45,6 +46,7 @@ interface ClassMethodDefMixin extends ClassMethodDefMixinType { mrcReturnType: string, mrcParameters: Parameter[], mrcPythonMethodName: string, + mrcMethod: commonStorage.Method | null, } type ClassMethodDefMixinType = typeof CLASS_METHOD_DEF; @@ -131,6 +133,7 @@ const CLASS_METHOD_DEF = { this.mrcPythonMethodName = extraState.pythonMethodName ? extraState.pythonMethodName : ''; this.mrcReturnType = extraState.returnType; this.mrcParameters = []; + this.mrcMethod = null; extraState.params.forEach((param) => { this.mrcParameters.push({ @@ -254,6 +257,9 @@ const CLASS_METHOD_DEF = { } return legalName; }, + getMethod: function (this: ClassMethodDefBlock): commonStorage.Method | null { + return this.mrcMethod; + } }; /** @@ -395,5 +401,24 @@ export const pythonFromBlock = function ( code = generator.scrub_(block, code); generator.addClassMethodDefinition(funcName, code); + if (block.mrcCanBeCalledOutsideClass) { + // Update the mrcMethod. + block.mrcMethod = { + visibleName: block.getFieldValue('NAME'), + pythonName: funcName, + returnType: block.mrcReturnType, + args: [{ + name: 'self', + type: '', + }], + }; + block.mrcParameters.forEach(param => { + block.mrcMethod!.args.push({ + name: param.name, + type: param.type ? param.type : '', + }); + }); + } + return ''; } diff --git a/src/editor/editor.ts b/src/editor/editor.ts index 67aa746b..1d6375a7 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -25,6 +25,7 @@ import { extendedPythonGenerator } from './extended_python_generator'; import { GeneratorContext } from './generator_context'; import * as commonStorage from '../storage/common_storage'; import * as mechanismComponentHolder from '../blocks/mrc_mechanism_component_holder'; +import * as classMethodDef from '../blocks/mrc_class_method_def'; //import { testAllBlocksInToolbox } from '../toolbox/toolbox_tests'; import { MethodsCategory } from '../toolbox/methods_category'; import { EventsCategory } from '../toolbox/event_category'; @@ -71,38 +72,15 @@ export class Editor { return; } - // TODO(lizlooney): As blocks are loaded, determine whether any blocks - // are accessing variable or calling functions thar are defined in another - // blocks file (like the Robot) and check whether the variable or function - // definition has changed. This might happen if the user defines a variable - // or function in the Robot, uses the variable or function in the - // OpMode, and then removes or changes the variable or function in the - // Robot. + // TODO(lizlooney): Look at blocks with type 'mrc_call_python_function' that + // are calling methods defined in the Robot and check that the Robot method + // still exists and hasn't been changed. If it has changed, update the block + // if possible or put a visible warning on it. - // TODO(lizlooney): We will need a way to identify which variable or - // function, other than by the variable name or function name, because the - // user might change the name. This will take some thought and I should - // write up a design doc and discuss it with others to make sure we have a - // good solution. - - // TODO(lizlooney): Look at blocks with type 'mrc_get_python_variable' or - // 'mrc_set_python_variable', and where block.mrcExportedVariable === true. - // Look at block.mrcImportModule and get the exported blocks for that module. - // It could be from the Robot (or a Mechanism?) and we already have the Robot content. - // Check whether block.mrcActualVariableName matches any exportedBlock's - // extraState.actualVariableName. If there is no match, put a warning on the - // block. - - // TODO(lizlooney): Look at blocks with type 'mrc_call_python_function' and - // where block.mrcExportedFunction === true. - // Look at block.mrcImportModule and get the exported blocks for that module. - // It could be from the Robot (or a Mechanism?) and we already have the Robot content. - // Check whether block.mrcActualFunctionName matches any exportedBlock's - // extraState.actualFunctionName. If there is no match, put a warning on the block. - // If there is a match, check whether - // block.mrcArgs.length === exportedBlock.extraState.args.length and - // block.mrcArgs[i].name === exportedBlock.extraState.args[i].name for all args. - // If there is any differences, put a warning on the block. + // TODO(lizlooney): Look at blocks with type 'mrc_call_python_function' that + // are calling methods on components defined in the Robot and check that the + // component and the method still exists and hasn't been changed. If it has + // changed, update the block if possible or put a visible warning on it. } private onChangeAfterLoading(event: Blockly.Events.Abstract) { @@ -221,12 +199,12 @@ export class Editor { } const pythonCode = extendedPythonGenerator.mrcWorkspaceToCode( this.blocklyWorkspace, this.generatorContext); - const exportedBlocks = JSON.stringify(this.generatorContext.getExportedBlocks()); const blocksContent = JSON.stringify( Blockly.serialization.workspaces.save(this.blocklyWorkspace)); + const methodsContent = JSON.stringify(this.getMethodsFromWorkspace()); const componentsContent = JSON.stringify(this.getComponentsFromWorkspace()); return commonStorage.makeModuleContent( - this.currentModule, pythonCode, blocksContent, exportedBlocks, componentsContent); + this.currentModule, pythonCode, blocksContent, methodsContent, componentsContent); } public getComponentsFromWorkspace(): commonStorage.Component[] { @@ -246,6 +224,22 @@ export class Editor { return components; } + public getMethodsFromWorkspace(): commonStorage.Method[] { + const methods: commonStorage.Method[] = []; + if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT || + this.currentModule?.moduleType === commonStorage.MODULE_TYPE_MECHANISM) { + // Get the class method definition blocks. + const methodDefBlocks = this.blocklyWorkspace.getBlocksByType(classMethodDef.BLOCK_NAME); + methodDefBlocks.forEach(methodDefBlock => { + const method = (methodDefBlock as classMethodDef.ClassMethodDefBlock).getMethod(); + if (method) { + methods.push(method); + } + }); + } + return methods; + } + public async saveBlocks() { const moduleContent = this.getModuleContent(); try { @@ -260,8 +254,6 @@ export class Editor { * Returns the components defined in the robot. */ public getComponentsFromRobot(): commonStorage.Component[] { - let components: commonStorage.Component[]; - if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT) { return this.getComponentsFromWorkspace(); } @@ -271,6 +263,19 @@ export class Editor { return commonStorage.extractComponents(this.robotContent); } + /** + * Returns the methods defined in the robot. + */ + public getMethodsFromRobot(): commonStorage.Method[] { + if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT) { + return this.getMethodsFromWorkspace(); + } + if (!this.robotContent) { + throw new Error('getMethodsFromRobot: this.robotContent is null.'); + } + return commonStorage.extractMethods(this.robotContent); + } + public static getEditorForBlocklyWorkspace(workspace: Blockly.Workspace): Editor | null { if (workspace.id in Editor.workspaceIdToEditor) { return Editor.workspaceIdToEditor[workspace.id]; diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index 1a65a2c3..4233d87c 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -22,10 +22,7 @@ import * as Blockly from 'blockly/core'; import { PythonGenerator } from 'blockly/python'; import { GeneratorContext } from './generator_context'; -import { Block } from '../toolbox/items'; -import { FunctionArg } from '../blocks/mrc_call_python_function'; import * as MechanismContainerHolder from '../blocks/mrc_mechanism_component_holder'; -import * as commonStorage from '../storage/common_storage'; export class OpModeDetails { constructor(private name: string, private group : string, private enabled : boolean, private type : string) {} @@ -199,125 +196,15 @@ export class ExtendedPythonGenerator extends PythonGenerator { code = annotations + code; } this.details = null; - - this.context.setExportedBlocks(this.produceExportedBlocks(this.workspace)); } return super.finish(code); } - private produceExportedBlocks(workspace: Blockly.Workspace): Block[] { - // The exported blocks produced here have the extraState.importModule and fields.MODULE values - // set to the MODULE_NAME_PLACEHOLDER. This is so blocks modules can be renamed and copied - // without having to change the contents of the modules. - // The placeholders will be replaced with the actual module name before they are added to the - // toolbox. - - const exportedBlocks = []; - - // All functions are exported. - // TODO(lizlooney): instead of looking at procedure blocks, this code needs - // to look at mrc_class_method_def blocks. - const allProcedures = Blockly.Procedures.allProcedures(workspace); - const procedureTuples = allProcedures[0].concat(allProcedures[1]); - for (const procedureTuple of procedureTuples) { - const functionName = procedureTuple[0]; - const blockDefinition = Blockly.Procedures.getDefinition(functionName, workspace); - if (!blockDefinition || !blockDefinition.isEnabled()) { - continue; - } - const actualFunctionName = super.getProcedureName(functionName); - const hasReturnValue = procedureTuple[2]; - const args: FunctionArg[] = []; - const parameterNames = procedureTuple[1]; - parameterNames.forEach((parameterName) => { - args.push({ - 'name': parameterName, - 'type': '', - }) - }); - const callFunctionBlock: Block = { - 'kind': 'block', - 'type': 'mrc_call_python_function', - 'extraState': { - 'functionKind': 'module', - 'returnType': hasReturnValue ? '' : 'None', - 'args': args, - 'importModule': commonStorage.MODULE_NAME_PLACEHOLDER, - 'actualFunctionName': actualFunctionName, - 'exportedFunction': true, - }, - 'fields': { - 'MODULE_OR_CLASS': commonStorage.MODULE_NAME_PLACEHOLDER, - 'FUNC': functionName, - }, - }; - exportedBlocks.push(callFunctionBlock); - } - - const allVariables = workspace.getVariableMap().getAllVariables(); - for (const variableModel of allVariables) { - // Only variables that are used outside of functions are exported. (I'm not sure if this is - // the right choice, since all blockly variables are global variables.) - let exported = false; - const variableUsesById = workspace.getVariableUsesById(variableModel.getId()) - if (variableUsesById.length === 0) { - continue; - } - variableUsesById.forEach((block) => { - if (block.type === 'variables_get' || - block.type === 'variables_set' || - block.type === 'math_change' || - block.type === 'text_append') { - const rootBlock = block.getRootBlock(); - if (rootBlock.type !== 'procedures_defnoreturn' && - rootBlock.type !== 'procedures_defreturn') { - exported = true; - } - } - }); - if (exported) { - const variableName = variableModel.getName(); - const actualVariableName = super.getVariableName(variableModel.getId()); - const getPythonModuleVariableBlock = { - 'kind': 'block', - 'type': 'mrc_get_python_variable', - 'extraState': { - 'varKind': 'module', - 'moduleOrClassName': commonStorage.MODULE_NAME_PLACEHOLDER, - 'importModule': commonStorage.MODULE_NAME_PLACEHOLDER, - 'actualVariableName': actualVariableName, - 'exportedVariable': true, - }, - 'fields': { - 'MODULE_OR_CLASS': commonStorage.MODULE_NAME_PLACEHOLDER, - 'VAR': variableName, - }, - }; - exportedBlocks.push(getPythonModuleVariableBlock); - const setPythonModuleVariableBlock = { - 'kind': 'block', - 'type': 'mrc_set_python_variable', - 'extraState': { - 'varKind': 'module', - 'moduleOrClassName': commonStorage.MODULE_NAME_PLACEHOLDER, - 'importModule': commonStorage.MODULE_NAME_PLACEHOLDER, - 'actualVariableName': actualVariableName, - 'exportedVariable': true, - }, - 'fields': { - 'MODULE_OR_CLASS': commonStorage.MODULE_NAME_PLACEHOLDER, - 'VAR': variableName, - }, - }; - exportedBlocks.push(setPythonModuleVariableBlock); - } - } - return exportedBlocks; - } setOpModeDetails(details : OpModeDetails) { this.details = details; } + getClassSpecificForInit() : string{ let classParent = this.context?.getClassParent(); if (classParent == 'OpMode'){ diff --git a/src/editor/generator_context.ts b/src/editor/generator_context.ts index 4dc1d5a5..86c4c1a2 100644 --- a/src/editor/generator_context.ts +++ b/src/editor/generator_context.ts @@ -19,7 +19,6 @@ * @author lizlooney@google.com (Liz Looney) */ -import { Block } from "../toolbox/items"; import * as commonStorage from '../storage/common_storage'; @@ -30,9 +29,6 @@ export function createGeneratorContext(): GeneratorContext { export class GeneratorContext { private module: commonStorage.Module | null = null; - // The exported blocks for the current module. - private exportedBlocks: Block[] = []; - // Has mechanisms (ie, needs in init) private hasHardware = false; @@ -49,7 +45,6 @@ export class GeneratorContext { } clear(): void { - this.clearExportedBlocks(); this.hasHardware= false; } @@ -83,17 +78,4 @@ export class GeneratorContext { } return ''; } - - clearExportedBlocks() { - this.exportedBlocks.length = 0; - } - - setExportedBlocks(exportedBlocks: Block[]) { - this.exportedBlocks.length = 0; - this.exportedBlocks.push(...exportedBlocks); - } - - getExportedBlocks(): Block[] { - return this.exportedBlocks; - } } diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 7efcb617..97adfba3 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -23,7 +23,6 @@ import JSZip from 'jszip'; import * as Blockly from 'blockly/core'; -import { Block } from '../toolbox/items'; import startingOpModeBlocks from '../modules/opmode_start.json'; import startingMechanismBlocks from '../modules/mechanism_start.json'; import startingRobotBlocks from '../modules/robot_start.json'; @@ -54,6 +53,18 @@ export type Project = { opModes: OpMode[], }; +export type MethodArg = { + name: string, + type: string, // '' for an untyped arg. +}; + +export type Method = { + visibleName: string, + pythonName: string, + returnType: string, // 'None' for no return value, '' for an untyped return value. + args: MethodArg[], +}; + export type Component = { name: string, className: string, @@ -66,15 +77,13 @@ export const MODULE_TYPE_OPMODE = 'opmode'; export const ROBOT_CLASS_NAME = 'Robot'; -export const MODULE_NAME_PLACEHOLDER = '%module_name%'; - const DELIMITER_PREFIX = 'BlocksContent'; const MARKER_BLOCKS_CONTENT = 'blocksContent: '; -const MARKER_EXPORTED_BLOCKS = 'exportedBlocks: '; +const MARKER_METHODS = 'methods: '; const MARKER_MODULE_TYPE = 'moduleType: '; const MARKER_COMPONENTS = 'components: '; const PARTS_INDEX_BLOCKS_CONTENT = 0; -const PARTS_INDEX_EXPORTED_BLOCKS = 1; +const PARTS_INDEX_METHODS = 1; const PARTS_INDEX_MODULE_TYPE = 2; const PARTS_INDEX_COMPONENTS = 3; const NUMBER_OF_PARTS = 4; @@ -490,12 +499,12 @@ function startingBlocksToModuleContent( const pythonCode = extendedPythonGenerator.mrcWorkspaceToCode( headlessBlocklyWorkspace, generatorContext); - const exportedBlocks = JSON.stringify(generatorContext.getExportedBlocks()); const blocksContent = JSON.stringify( Blockly.serialization.workspaces.save(headlessBlocklyWorkspace)); - const components = '[]'; + const methodsContent = '[]'; + const componentsContent = '[]'; return makeModuleContent( - module, pythonCode, blocksContent, exportedBlocks, components); + module, pythonCode, blocksContent, methodsContent, componentsContent); } /** @@ -553,10 +562,13 @@ export function makeModuleContent( module: Module, pythonCode: string, blocksContent: string, - exportedBlocks: string, - components: string): string { + methodsContent: string, + componentsContent: string): string { let delimiter = DELIMITER_PREFIX; - while (blocksContent.includes(delimiter) || exportedBlocks.includes(delimiter) || module.moduleType.includes(delimiter)) { + while (module.moduleType.includes(delimiter) + || blocksContent.includes(delimiter) + || methodsContent.includes(delimiter) + || componentsContent.includes(delimiter)) { delimiter += '.'; } return ( @@ -564,11 +576,11 @@ export function makeModuleContent( pythonCode + '\n\n\n' + '"""\n' + delimiter + '\n' + - MARKER_COMPONENTS + components + '\n' + + MARKER_COMPONENTS + componentsContent + '\n' + delimiter + '\n' + MARKER_MODULE_TYPE + module.moduleType + '\n' + delimiter + '\n' + - MARKER_EXPORTED_BLOCKS + exportedBlocks + '\n' + + MARKER_METHODS + methodsContent + '\n' + delimiter + '\n' + MARKER_BLOCKS_CONTENT + blocksContent + '\n' + delimiter + '\n' + @@ -602,8 +614,12 @@ function getParts(moduleContent: string): string[] { parts.push(s.trim()); } } - if (parts.length <= PARTS_INDEX_EXPORTED_BLOCKS) { - parts.push('[]'); + if (parts.length <= PARTS_INDEX_METHODS) { + // This module was saved without methods. + parts.push(MARKER_METHODS + '[]'); + } else if (!parts[PARTS_INDEX_METHODS].startsWith(MARKER_METHODS)) { + // Older modules were saved with exported blocks instead of methods. + parts[PARTS_INDEX_METHODS] = MARKER_METHODS + '[]' } if (parts.length <= PARTS_INDEX_MODULE_TYPE) { // This module was saved without the module type. @@ -612,7 +628,7 @@ function getParts(moduleContent: string): string[] { } if (parts.length <= PARTS_INDEX_COMPONENTS) { // This module was saved without components. - parts.push(''); + parts.push(MARKER_COMPONENTS + '[]'); } return parts; } @@ -630,25 +646,16 @@ export function extractBlocksContent(moduleContent: string): string { } /** - * Extract the exportedBlocks from the given module content. + * Extract the methods from the given module content. */ -export function extractExportedBlocks(moduleName: string, moduleContent: string): Block[] { +export function extractMethods(moduleContent: string): Method[] { const parts = getParts(moduleContent); - let exportedBlocksContent = parts[PARTS_INDEX_EXPORTED_BLOCKS]; - if (exportedBlocksContent.startsWith(MARKER_EXPORTED_BLOCKS)) { - exportedBlocksContent = exportedBlocksContent.substring(MARKER_EXPORTED_BLOCKS.length); + let methodsContent = parts[PARTS_INDEX_METHODS]; + if (methodsContent.startsWith(MARKER_METHODS)) { + methodsContent = methodsContent.substring(MARKER_METHODS.length); } - - const exportedBlocks: Block[] = JSON.parse(exportedBlocksContent); - exportedBlocks.forEach((block) => { - if (block.extraState?.importModule === MODULE_NAME_PLACEHOLDER) { - block.extraState.importModule = moduleName; - } - if (block.fields?.MODULE_OR_CLASS === MODULE_NAME_PLACEHOLDER) { - block.fields.MODULE_OR_CLASS = moduleName; - } - }); - return exportedBlocks; + const methods: Method[] = JSON.parse(methodsContent); + return methods; } /** @@ -713,6 +720,14 @@ function _processModuleContentForDownload( if (moduleType.startsWith(MARKER_MODULE_TYPE)) { moduleType = moduleType.substring(MARKER_MODULE_TYPE.length); } + let methodsContent = parts[PARTS_INDEX_METHODS]; + if (methodsContent.startsWith(MARKER_METHODS)) { + methodsContent = methodsContent.substring(MARKER_METHODS.length); + } + let componentsContent = parts[PARTS_INDEX_COMPONENTS]; + if (componentsContent.startsWith(MARKER_COMPONENTS)) { + componentsContent = componentsContent.substring(MARKER_COMPONENTS.length); + } const module: Module = { modulePath: makeModulePath(projectName, moduleName), @@ -723,12 +738,10 @@ function _processModuleContentForDownload( className: getClassNameForModule(moduleType, moduleName), }; - // Clear out the python content and exported blocks. + // Clear out the python content. const pythonCode = ''; - const exportedBlocks = '[]'; - const components = '[]'; return makeModuleContent( - module, pythonCode, blocksContent, exportedBlocks, components); + module, pythonCode, blocksContent, methodsContent, componentsContent); } /** @@ -812,6 +825,14 @@ export function _processUploadedModule( if (moduleType.startsWith(MARKER_MODULE_TYPE)) { moduleType = moduleType.substring(MARKER_MODULE_TYPE.length); } + let methodsContent = parts[PARTS_INDEX_METHODS]; + if (methodsContent.startsWith(MARKER_METHODS)) { + methodsContent = methodsContent.substring(MARKER_METHODS.length); + } + let componentsContent = parts[PARTS_INDEX_COMPONENTS]; + if (componentsContent.startsWith(MARKER_COMPONENTS)) { + componentsContent = componentsContent.substring(MARKER_COMPONENTS.length); + } const moduleName = (moduleType === MODULE_TYPE_ROBOT) ? projectName : filename; @@ -825,7 +846,7 @@ export function _processUploadedModule( className: snakeCaseToPascalCase(moduleName), }; - // Generate the python content and exported blocks. + // Generate the python content. // Create a headless blockly workspace. const headlessBlocklyWorkspace = new Blockly.Workspace(); headlessBlocklyWorkspace.options.oneBasedIndex = false; @@ -837,9 +858,8 @@ export function _processUploadedModule( const pythonCode = extendedPythonGenerator.mrcWorkspaceToCode( headlessBlocklyWorkspace, generatorContext); - const exportedBlocks = JSON.stringify(generatorContext.getExportedBlocks()); - const components = '[]'; + const moduleContent = makeModuleContent( - module, pythonCode, blocksContent, exportedBlocks, components); + module, pythonCode, blocksContent, methodsContent, componentsContent); return [moduleName, moduleType, moduleContent]; } diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index a7c73b91..c12dfd58 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -21,24 +21,25 @@ import * as Blockly from 'blockly/core'; import * as commonStorage from '../storage/common_storage'; +import * as toolboxItems from './items'; import { getAllPossibleMechanisms } from './blocks_mechanisms'; import * as Component from '../blocks/mrc_component'; -import { getInstanceComponentBlocks } from '../blocks/mrc_call_python_function'; +import { getInstanceComponentBlocks, getInstanceRobotBlocks } from '../blocks/mrc_call_python_function'; import { Editor } from '../editor/editor'; -export function getHardwareCategory(currentModule: commonStorage.Module) { +export function getHardwareCategory(currentModule: commonStorage.Module): toolboxItems.Category { if (currentModule.moduleType === commonStorage.MODULE_TYPE_ROBOT) { return { kind: 'category', name: 'Hardware', contents: [ getRobotMechanismsBlocks(currentModule), - getComponentsBlocks(currentModule, false), + getComponentsBlocks(false), ] }; } if (currentModule.moduleType === commonStorage.MODULE_TYPE_MECHANISM) { - return getComponentsBlocks(currentModule, true); + return getComponentsBlocks(true); } if (currentModule.moduleType === commonStorage.MODULE_TYPE_OPMODE) { return { @@ -46,22 +47,22 @@ export function getHardwareCategory(currentModule: commonStorage.Module) { name: 'Robot', contents: [ getRobotMechanismsBlocks(currentModule), - getRobotComponentsBlocks(currentModule), - getRobotMethodsBlocks(currentModule), + getRobotComponentsBlocks(), + getRobotMethodsBlocks(), ] }; } throw new Error('currentModule.moduleType has unexpected value: ' + currentModule.moduleType) } -function getRobotMechanismsBlocks(currentModule: commonStorage.Module) { +function getRobotMechanismsBlocks(currentModule: commonStorage.Module): toolboxItems.Category { // getRobotMechanismsBlocks is called when the user is editing the robot or an opmode. // If the user is editing the robot, it allows the user to add a mechanism to // the robot or use an existing mechanism. // If the user is editing an opmode, it allows the user to use a mechanism that // was previously added to the Robot. - const contents = []; + const contents: toolboxItems.ContentsType[] = []; // Include the "+ Mechanism" category if the user it editing the robot. if (currentModule.moduleType === commonStorage.MODULE_TYPE_ROBOT) { @@ -208,13 +209,14 @@ function getRobotMechanismsBlocks(currentModule: commonStorage.Module) { }; } -function getRobotComponentsBlocks(currentModule: commonStorage.Module) { +function getRobotComponentsBlocks(): toolboxItems.Category { // getRobotComponentsBlocks is called when the user is editing an opmode. // It allows the user to use a component that was previously added to the Robot. - const contents = []; + const contents: toolboxItems.ContentsType[] = []; - // Get the list of components from the roobt. + // Get the list of components from the robot and add the blocks for calling the + // component functions. const workspace = Blockly.getMainWorkspace(); if (workspace) { const editor = Editor.getEditorForBlocklyWorkspace(workspace); @@ -238,11 +240,22 @@ function getRobotComponentsBlocks(currentModule: commonStorage.Module) { }; } -function getRobotMethodsBlocks(currentModule: commonStorage.Module) { - const contents = []; +function getRobotMethodsBlocks(): toolboxItems.Category { + // getRobotMethodsBlocks is called when the user is editing an opmode. + // It allows the user to use methods there previously defined in the Robot. - // TODO: Get the list of public methods from the robot and add the blocks for - // calling the robot functions to the toolbox. + const contents: toolboxItems.ContentsType[] = []; + + // Get the list of methods from the robot and add the blocks for calling the + // robot functions. + const workspace = Blockly.getMainWorkspace(); + if (workspace) { + const editor = Editor.getEditorForBlocklyWorkspace(workspace); + if (editor) { + const methodsFromRobot = editor.getMethodsFromRobot(); + contents.push(...getInstanceRobotBlocks(methodsFromRobot)); + } + } return { kind: 'category', @@ -251,11 +264,11 @@ function getRobotMethodsBlocks(currentModule: commonStorage.Module) { }; } -function getComponentsBlocks(currentModule: commonStorage.Module, hideParams : boolean) { +function getComponentsBlocks(hideParams : boolean): toolboxItems.Category { // getComponentsBlocks is called when the user is editing the robot or a // mechanism. It allows the user to add a component or use an existing component. - const contents = []; + const contents: toolboxItems.ContentsType[] = []; // Add the "+ Component" category contents.push({