diff --git a/src/blocks/mrc_call_python_function.ts b/src/blocks/mrc_call_python_function.ts index f233c01d..33da9e08 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -49,6 +49,7 @@ enum FunctionKind { INSTANCE_WITHIN = 'instance_within', INSTANCE_COMPONENT = 'instance_component', INSTANCE_ROBOT = 'instance_robot', + INSTANCE_MECHANISM = 'instance_mechanism', EVENT = 'event', } @@ -58,6 +59,7 @@ const FIELD_MODULE_OR_CLASS_NAME = 'MODULE_OR_CLASS'; const FIELD_FUNCTION_NAME = 'FUNC'; const FIELD_EVENT_NAME = 'EVENT'; const FIELD_COMPONENT_NAME = 'COMPONENT_NAME'; +const FIELD_MECHANISM_NAME = 'MECHANISM_NAME'; type FunctionArg = { name: string, @@ -77,6 +79,8 @@ interface CallPythonFunctionMixin extends CallPythonFunctionMixinType { mrcOtherBlockId: string, mrcComponentClassName: string, mrcOriginalComponentName: string, + mrcMechanismClassName: string, + mrcMechanismBlockId: string, } type CallPythonFunctionMixinType = typeof CALL_PYTHON_FUNCTION; @@ -113,7 +117,8 @@ type CallPythonFunctionExtraState = { actualFunctionName?: string, /** * The id of the block that defines the method, component, or event. - * For INSTANCE_ROBOT or INSTANCE_WITHIN, this is the id of an mrc_class_method_def block. + * For INSTANCE_WITHIN, INSTANCE_ROBOT, or INSTANCE_MECHANISM, this is the id of an + * mrc_class_method_def block. * For INSTANCE_COMPONENT, this is the id of an mrc_component block. * For EVENT, this is the id of an mrc_event block. */ @@ -126,6 +131,15 @@ type CallPythonFunctionExtraState = { * The component class name. Specified only if the function kind is INSTANCE_COMPONENT. */ componentClassName?: string, + /** + * The mechanism class name. Specified only if the function kind is INSTANCE_MECHANISM. + */ + mechanismClassName?: string, + /** + * The id of the mrc_mechanism block that adds the mechanism to the robot. + * Specified only if the function kind is INSTANCE_MECHANISM. + */ + mechanismBlockId?: string, }; const CALL_PYTHON_FUNCTION = { @@ -187,6 +201,13 @@ const CALL_PYTHON_FUNCTION = { tooltip = 'Calls the robot method ' + functionName + '.'; break; } + case FunctionKind.INSTANCE_MECHANISM: { + const className = this.mrcMechanismClassName; + const functionName = this.getFieldValue(FIELD_FUNCTION_NAME); + tooltip = 'Calls the instance method ' + className + '.' + functionName + + ' on the mechanism named ' + this.getFieldValue(FIELD_MECHANISM_NAME) + '.'; + break; + } default: throw new Error('mrcFunctionKind has unexpected value: ' + this.mrcFunctionKind) } @@ -246,6 +267,12 @@ const CALL_PYTHON_FUNCTION = { } } } + if (this.mrcMechanismClassName) { + extraState.mechanismClassName = this.mrcMechanismClassName; + } + if (this.mrcMechanismBlockId) { + extraState.mechanismBlockId = this.mrcMechanismBlockId; + } return extraState; }, /** @@ -286,6 +313,10 @@ const CALL_PYTHON_FUNCTION = { ? extraState.componentClassName : ''; this.mrcOriginalComponentName = extraState.componentName ? extraState.componentName : ''; + this.mrcMechanismClassName = extraState.mechanismClassName + ? extraState.mechanismClassName : ''; + this.mrcMechanismBlockId = extraState.mechanismBlockId + ? extraState.mechanismBlockId : ''; this.updateBlock_(); }, /** @@ -382,6 +413,14 @@ const CALL_PYTHON_FUNCTION = { .appendField(createFieldNonEditableText(''), FIELD_FUNCTION_NAME); break; } + case FunctionKind.INSTANCE_MECHANISM: { + this.appendDummyInput('TITLE') + .appendField('call') + .appendField(createFieldNonEditableText(''), FIELD_MECHANISM_NAME) + .appendField('.') + .appendField(createFieldNonEditableText(''), FIELD_FUNCTION_NAME); + break; + } default: throw new Error('mrcFunctionKind has unexpected value: ' + this.mrcFunctionKind) } @@ -415,13 +454,44 @@ const CALL_PYTHON_FUNCTION = { this.removeInput('ARG' + i); } }, - renameMethodCaller: function(this: CallPythonFunctionBlock, newName: string): void { - // renameMethodCaller is called when the method or event definition block in the same module is modified. - if (this.mrcFunctionKind == FunctionKind.EVENT) { - this.setFieldValue(newName, FIELD_EVENT_NAME); - } else if (this.mrcFunctionKind == FunctionKind.INSTANCE_WITHIN) { - this.setFieldValue(newName, FIELD_FUNCTION_NAME); - // mrcActualFunctionName does not need to be updated because it is not used for INSTANCE_WITHIN. + renameMethodCaller: function(this: CallPythonFunctionBlock, blockId: string, newName: string): void { + // renameMethodCaller is called when a component, mechanism, event, or + // method block in the same module is modified. + switch (this.mrcFunctionKind) { + case FunctionKind.INSTANCE_WITHIN: + if (blockId === this.mrcOtherBlockId) { + // For INSTANCE_WITHIN this.mrcOtherBlockId is the id of an mrc_class_method_def block. + this.setFieldValue(newName, FIELD_FUNCTION_NAME); + // mrcActualFunctionName does not need to be updated because it is not used for INSTANCE_WITHIN. + } + break; + case FunctionKind.INSTANCE_COMPONENT: + if (blockId === this.mrcOtherBlockId) { + // For INSTANCE_COMPONENT this.mrcOtherBlockId is the id of an mrc_component block. + this.setFieldValue(newName, FIELD_COMPONENT_NAME); + } + break; + case FunctionKind.INSTANCE_ROBOT: + if (blockId === this.mrcOtherBlockId) { + // For INSTANCE_ROBOT this.mrcOtherBlockId is the id of an mrc_class_method_def block. + this.setFieldValue(newName, FIELD_FUNCTION_NAME); + } + break; + case FunctionKind.INSTANCE_MECHANISM: + if (blockId === this.mrcOtherBlockId) { + // For INSTANCE_MECHANISM this.mrcOtherBlockId is the id of an mrc_class_method_def block. + this.setFieldValue(newName, FIELD_FUNCTION_NAME); + } else if (blockId === this.mrcMechanismBlockId) { + // For INSTANCE_MECHANISM this.mrcMechanismBlockId is the id of an mrc_mechanism block. + this.setFieldValue(newName, FIELD_MECHANISM_NAME); + } + break; + case FunctionKind.EVENT: + if (blockId === this.mrcOtherBlockId) { + // For EVENT, this.mrcOtherBlockId is the id of an mrc_event block. + this.setFieldValue(newName, FIELD_EVENT_NAME); + } + break; } }, mutateMethodCaller: function( @@ -467,10 +537,14 @@ const CALL_PYTHON_FUNCTION = { }, mrcOnLoad: function(this: CallPythonFunctionBlock): void { // mrcOnLoad is called for each CallPythonFunctionBlock when the blocks are loaded in the blockly workspace. + const editor = Editor.getEditorForBlocklyWorkspace(this.workspace); + if (!editor) { + return; + } const warnings: string[] = []; - // If this block is calling a component method, check that the component - // still exists and hasn't been changed. + // If this block is calling a component method, check whether the component + // still exists and whether it has been changed. // If the component doesn't exist, put a visible warning on this block. // If the component has changed, update the block if possible or put a // visible warning on it. @@ -478,8 +552,7 @@ const CALL_PYTHON_FUNCTION = { let foundComponent = false; const componentsInScope: commonStorage.Component[] = []; componentsInScope.push(...this.getComponentsFromRobot()); - const editor = Editor.getEditorForBlocklyWorkspace(this.workspace); - if (editor && editor.getCurrentModuleType() === commonStorage.MODULE_TYPE_MECHANISM) { + if (editor.getCurrentModuleType() === commonStorage.MODULE_TYPE_MECHANISM) { componentsInScope.push(...editor.getComponentsFromWorkspace()); } for (const component of componentsInScope) { @@ -520,20 +593,20 @@ const CALL_PYTHON_FUNCTION = { // TODO(lizlooney): Could the component's method have change or been deleted? } - // If this block is calling a robot method, check that the robot method - // still exists and hasn't been changed. + // If this block is calling a robot method, check whether the robot method + // still exists and whether it has been changed. // If the robot method doesn't exist, put a visible warning on this block. // If the robot method has changed, update the block if possible or put a // visible warning on it. if (this.mrcFunctionKind === FunctionKind.INSTANCE_ROBOT) { - let foundRobotMethod = false; - const editor = Editor.getEditorForBlocklyWorkspace(this.workspace); - if (editor) { + if (editor.getCurrentModuleType() === commonStorage.MODULE_TYPE_MECHANISM) { + warnings.push('This block is not allowed to be used inside a mechanism.'); + } else { + let foundRobotMethod = false; const robotMethods = editor.getMethodsFromRobot(); for (const robotMethod of robotMethods) { if (robotMethod.blockId === this.mrcOtherBlockId) { foundRobotMethod = true; - if (this.mrcActualFunctionName !== robotMethod.pythonName) { this.mrcActualFunctionName = robotMethod.pythonName; } @@ -562,6 +635,72 @@ const CALL_PYTHON_FUNCTION = { } } + // If this block is calling a mechanism method, check whether the mechanism + // still exists, whether it has been changed, whether the method still + // exists, and whether the method has been changed. + // If the mechanism doesn't exist, put a visible warning on this block. + // If the mechanism has changed, update the block if possible or put a + // visible warning on it. + // If the method doesn't exist, put a visible warning on this block. + // If the method has changed, update the block if possible or put a + // visible warning on it. + if (this.mrcFunctionKind === FunctionKind.INSTANCE_MECHANISM) { + if (editor.getCurrentModuleType() === commonStorage.MODULE_TYPE_MECHANISM) { + warnings.push('This block is not allowed to be used inside a mechanism.'); + } else { + let foundMechanism = false; + const mechanismsInRobot = editor.getMechanismsFromRobot(); + for (const mechanismInRobot of mechanismsInRobot) { + if (mechanismInRobot.blockId === this.mrcMechanismBlockId) { + foundMechanism = true; + + // If the mechanism name has changed, we can handle that. + if (this.getFieldValue(FIELD_MECHANISM_NAME) !== mechanismInRobot.name) { + this.setFieldValue(mechanismInRobot.name, FIELD_MECHANISM_NAME); + } + + let foundMechanismMethod = false; + const mechanism = editor.getMechanism(mechanismInRobot); + const mechanismMethods: commonStorage.Method[] = mechanism + ? editor.getMethodsFromMechanism(mechanism) : []; + for (const mechanismMethod of mechanismMethods) { + if (mechanismMethod.blockId === this.mrcOtherBlockId) { + foundMechanismMethod = true; + if (this.mrcActualFunctionName !== mechanismMethod.pythonName) { + this.mrcActualFunctionName = mechanismMethod.pythonName; + } + if (this.getFieldValue(FIELD_FUNCTION_NAME) !== mechanismMethod.visibleName) { + this.setFieldValue(mechanismMethod.visibleName, FIELD_FUNCTION_NAME); + } + this.mrcReturnType = mechanismMethod.returnType; + this.mrcArgs = []; + // We don't include the arg for the self argument because we don't need a socket for it. + for (let i = 1; i < mechanismMethod.args.length; i++) { + this.mrcArgs.push({ + name: mechanismMethod.args[i].name, + type: mechanismMethod.args[i].type, + }); + } + this.updateBlock_(); + + // Since we found the mechanism method, we can break out of the loop. + break; + } + } + if (!foundMechanismMethod) { + warnings.push('This block calls a method that no longer exists.'); + } + + // Since we found the mechanism, we can break out of the loop. + break; + } + } + if (!foundMechanism) { + warnings.push('This block calls a method on a mechanism that no longer exists.'); + } + } + } + if (warnings.length) { // Add a warnings to the block. const warningText = warnings.join('\n\n'); @@ -586,7 +725,7 @@ export const pythonFromBlock = function( if (callPythonFunctionBlock.mrcImportModule) { generator.addImport(callPythonFunctionBlock.mrcImportModule); } - let code; + let code = ''; let needOpenParen = true; let delimiterBeforeArgs = ''; let argStartIndex = 0; @@ -655,7 +794,6 @@ export const pythonFromBlock = function( code = 'self.'; break; case commonStorage.MODULE_TYPE_OPMODE: - default: code = 'self.robot.'; break; } @@ -669,6 +807,28 @@ export const pythonFromBlock = function( code = 'self.robot.' + functionName; break; } + case FunctionKind.INSTANCE_MECHANISM: { + const mechanismName = block.getFieldValue(FIELD_MECHANISM_NAME); + const functionName = callPythonFunctionBlock.mrcActualFunctionName + ? callPythonFunctionBlock.mrcActualFunctionName + : block.getFieldValue(FIELD_FUNCTION_NAME); + // Generate the correct code depending on the module type. + switch (generator.getModuleType()) { + case commonStorage.MODULE_TYPE_ROBOT: + code = 'self.' + mechanismName; + break; + case commonStorage.MODULE_TYPE_OPMODE: + code = 'self.robot.' + mechanismName; + break; + case commonStorage.MODULE_TYPE_MECHANISM: + // The INSTANCE_MECHANISM version should not be used in a mechanism. + // TODO(lizlooney): What if the user copies a block from an robot or opmode and pastes + // it into a mechanism? + break; + } + code += '.' + functionName; + break; + } default: throw new Error('mrcFunctionKind has unexpected value: ' + callPythonFunctionBlock.mrcFunctionKind) } @@ -708,13 +868,15 @@ function generateCodeForArguments( function getMethodCallers(workspace: Blockly.Workspace, otherBlockId: string): Blockly.Block[] { return workspace.getBlocksByType(BLOCK_NAME).filter((block) => { - return (block as CallPythonFunctionBlock).mrcOtherBlockId === otherBlockId; + return ( + (block as CallPythonFunctionBlock).mrcOtherBlockId === otherBlockId || + (block as CallPythonFunctionBlock).mrcMechanismBlockId === otherBlockId); }); } export function renameMethodCallers(workspace: Blockly.Workspace, otherBlockId: string, newName: string): void { getMethodCallers(workspace, otherBlockId).forEach(block => { - (block as CallPythonFunctionBlock).renameMethodCaller(newName); + (block as CallPythonFunctionBlock).renameMethodCaller(otherBlockId, newName); }); } @@ -1024,6 +1186,47 @@ function createInstanceRobotBlock(method: commonStorage.Method): toolboxItems.Bl return createBlock(extraState, fields, inputs); } +export function addInstanceMechanismBlocks( + mechanismInRobot: commonStorage.MechanismInRobot, + methods: commonStorage.Method[], + contents: toolboxItems.ContentsType[]) { + methods.forEach(method => { + contents.push(createInstanceMechanismBlock(mechanismInRobot, method)); + }); +} + +function createInstanceMechanismBlock( + mechanismInRobot: commonStorage.MechanismInRobot, + method: commonStorage.Method): toolboxItems.Block { + const extraState: CallPythonFunctionExtraState = { + functionKind: FunctionKind.INSTANCE_MECHANISM, + returnType: method.returnType, + actualFunctionName: method.pythonName, + args: [], + otherBlockId: method.blockId, + mechanismClassName: mechanismInRobot.className, + mechanismBlockId: mechanismInRobot.blockId, + }; + const fields: {[key: string]: any} = {}; + fields[FIELD_MECHANISM_NAME] = mechanismInRobot.name; + fields[FIELD_FUNCTION_NAME] = method.visibleName; + const inputs: {[key: string]: any} = {}; + // Convert method.args from commonStorage.MethodArg[] to ArgData[]. + const args: ArgData[] = []; + // For INSTANCE_MECHANISM functions, the 0 argument is 'self', but + // self is represented by the FIELD_MECHANISM_NAME field. + // We don't include the arg for the self argument because we don't need a socket for it. + for (let i = 1; i < method.args.length; i++) { + args.push({ + name: method.args[i].name, + type: method.args[i].type, + defaultValue: '', + }); + } + processArgs(args, extraState, inputs); + return createBlock(extraState, fields, inputs); +} + export function addFireEventBlocks( events: commonStorage.Event[], contents: toolboxItems.ContentsType[]) { diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 5f5a8b0a..c56444e8 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -24,6 +24,7 @@ import { Order } from 'blockly/python'; import { MRC_STYLE_COMPONENTS } from '../themes/styles' import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; +import { Editor } from '../editor/editor'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import { getAllowedTypesForSetCheck, getClassData, getModuleData, getSubclassNames } from './utils/python'; import * as toolboxItems from '../toolbox/items'; @@ -31,6 +32,7 @@ import * as commonStorage from '../storage/common_storage'; import { createPortShadow } from './mrc_port'; import { createNumberShadowValue } from './utils/value'; import { ClassData, FunctionData } from './utils/python_json_types'; +import { renameMethodCallers } from './mrc_call_python_function' export const BLOCK_NAME = 'mrc_component'; @@ -48,14 +50,12 @@ type ComponentExtraState = { importModule?: string, // If staticFunctionName is not present, generate the constructor. staticFunctionName?: string, - hideParams?: boolean, params?: ConstructorArg[], } export type ComponentBlock = Blockly.Block & ComponentMixin; interface ComponentMixin extends ComponentMixinType { mrcArgs: ConstructorArg[], - hideParams: boolean, mrcImportModule: string, mrcStaticFunctionName: string, } @@ -67,8 +67,10 @@ const COMPONENT = { */ init: function (this: ComponentBlock): void { this.setStyle(MRC_STYLE_COMPONENTS); + const nameField = new Blockly.FieldTextInput('') + nameField.setValidator(this.mrcNameFieldValidator.bind(this, nameField)); this.appendDummyInput() - .appendField(new Blockly.FieldTextInput(''), FIELD_NAME) + .appendField(nameField, FIELD_NAME) .appendField(Blockly.Msg.OF_TYPE) .appendField(createFieldNonEditableText(''), FIELD_TYPE); this.setPreviousStatement(true, OUTPUT_NAME); @@ -96,9 +98,6 @@ const COMPONENT = { if (this.mrcStaticFunctionName) { extraState.staticFunctionName = this.mrcStaticFunctionName; } - if (this.hideParams) { - extraState.hideParams = this.hideParams; - } return extraState; }, /** @@ -107,7 +106,6 @@ const COMPONENT = { loadExtraState: function (this: ComponentBlock, extraState: ComponentExtraState): void { this.mrcImportModule = extraState.importModule ? extraState.importModule : ''; this.mrcStaticFunctionName = extraState.staticFunctionName ? extraState.staticFunctionName : ''; - this.hideParams = extraState.hideParams ? extraState.hideParams : false; this.mrcArgs = []; if (extraState.params) { @@ -125,7 +123,8 @@ const COMPONENT = { * Update the block to reflect the newly loaded extra state. */ updateBlock_: function (this: ComponentBlock): void { - if (this.hideParams == false) { + const editor = Editor.getEditorForBlocklyWorkspace(this.workspace); + if (editor && editor.getCurrentModuleType() === commonStorage.MODULE_TYPE_ROBOT) { // Add input sockets for the arguments. for (let i = 0; i < this.mrcArgs.length; i++) { const input = this.appendValueInput('ARG' + i) @@ -137,6 +136,18 @@ const COMPONENT = { } } }, + mrcNameFieldValidator(this: ComponentBlock, nameField: Blockly.FieldTextInput, name: string): string { + // Strip leading and trailing whitespace. + name = name.trim(); + + const legalName = name; + const oldName = nameField.getValue(); + if (oldName && oldName !== name && oldName !== legalName) { + // Rename any callers. + renameMethodCallers(this.workspace, this.id, legalName); + } + return legalName; + }, getComponent: function (this: ComponentBlock): commonStorage.Component | null { const componentName = this.getFieldValue(FIELD_NAME); const componentType = this.getFieldValue(FIELD_TYPE); @@ -149,19 +160,14 @@ const COMPONENT = { ports: ports, }; }, - getNewPort: function (this: ComponentBlock, i: number): string { - let extension = ''; - if (i != 0) { - extension = '_' + (i + 1).toString(); - } - return this.getFieldValue(FIELD_NAME) + extension + '_port'; + getArgName: function (this: ComponentBlock, i: number): string { + return this.getFieldValue(FIELD_NAME) + '__' + this.mrcArgs[i].name; }, - getComponentPorts: function (this: ComponentBlock, ports: {[key: string]: string}): void { + getComponentPorts: function (this: ComponentBlock, ports: {[argName: string]: string}): void { // Collect the ports for this component block. for (let i = 0; i < this.mrcArgs.length; i++) { - const newPort = this.getNewPort(i); - // The key is the port, the value is the type. - ports[newPort] = this.mrcArgs[i].type; + const argName = this.getArgName(i); + ports[argName] = this.mrcArgs[i].type; } }, } @@ -184,14 +190,13 @@ export const pythonFromBlock = function ( code += '('; for (let i = 0; i < block.mrcArgs.length; i++) { - const fieldName = 'ARG' + i; if (i != 0) { - code += ', ' + code += ', '; } - if (block.hideParams) { - code += block.mrcArgs[i].name + ' = ' + block.getNewPort(i); + if (generator.getModuleType() === commonStorage.MODULE_TYPE_ROBOT) { + code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, 'ARG' + i, Order.NONE); } else { - code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); + code += block.mrcArgs[i].name + ' = ' + block.getArgName(i); } } code += ')\n' + 'self.hardware.append(self.' + block.getFieldValue(FIELD_NAME) + ')\n'; @@ -230,7 +235,6 @@ function createComponentBlock( importModule: classData.moduleName, staticFunctionName: staticFunctionData.functionName, params: [], - hideParams: (moduleType == commonStorage.MODULE_TYPE_MECHANISM), }; const fields: {[key: string]: any} = {}; fields[FIELD_NAME] = componentName; diff --git a/src/blocks/mrc_mechanism.ts b/src/blocks/mrc_mechanism.ts index 72e9d40f..90e77f80 100644 --- a/src/blocks/mrc_mechanism.ts +++ b/src/blocks/mrc_mechanism.ts @@ -30,6 +30,7 @@ import { getAllowedTypesForSetCheck } from './utils/python'; import * as toolboxItems from '../toolbox/items'; import * as commonStorage from '../storage/common_storage'; import * as value from './utils/value'; +import { renameMethodCallers } from './mrc_call_python_function' export const BLOCK_NAME = 'mrc_mechanism'; export const OUTPUT_NAME = 'mrc_mechansim'; @@ -62,13 +63,14 @@ const MECHANISM = { */ init: function (this: MechanismBlock): void { this.setStyle(MRC_STYLE_MECHANISMS); + const nameField = new Blockly.FieldTextInput('') + nameField.setValidator(this.mrcNameFieldValidator.bind(this, nameField)); this.appendDummyInput() - .appendField(new Blockly.FieldTextInput('my_mech'), FIELD_NAME) + .appendField(nameField, FIELD_NAME) .appendField(Blockly.Msg.OF_TYPE) .appendField(createFieldNonEditableText(''), FIELD_TYPE); this.setPreviousStatement(true, OUTPUT_NAME); this.setNextStatement(true, OUTPUT_NAME); - //this.setOutput(true, OUTPUT_NAME); }, /** @@ -137,6 +139,18 @@ const MECHANISM = { this.removeInput('ARG' + i); } }, + mrcNameFieldValidator(this: MechanismBlock, nameField: Blockly.FieldTextInput, name: string): string { + // Strip leading and trailing whitespace. + name = name.trim(); + + const legalName = name; + const oldName = nameField.getValue(); + if (oldName && oldName !== name && oldName !== legalName) { + // Rename any callers. + renameMethodCallers(this.workspace, this.id, legalName); + } + return legalName; + }, getMechanism: function (this: MechanismBlock): commonStorage.MechanismInRobot | null { const mechanismName = this.getFieldValue(FIELD_NAME); const mechanismType = this.mrcImportModule + '.' + this.getFieldValue(FIELD_TYPE); diff --git a/src/editor/editor.ts b/src/editor/editor.ts index cdd14186..6db3fae2 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -138,8 +138,7 @@ export class Editor { this.clearBlocklyWorkspace(); if (this.currentModule && this.currentProject) { - // Fetch the content for the current module and the robot. - // TODO: Also fetch the content for the mechanisms? + // Fetch the content for the current module, the robot, and the mechanisms. const promises: { [modulePath: string]: Promise } = {}; // value is promise of module content. promises[this.modulePath] = this.storage.fetchModuleContentText(this.modulePath); if (this.robotPath !== this.modulePath) { @@ -377,6 +376,20 @@ export class Editor { return this.currentProject ? this.currentProject.mechanisms : []; } + /** + * Returns the Mechanism matching the given MechanismInRobot. + */ + public getMechanism(mechanismInRobot: commonStorage.MechanismInRobot): commonStorage.Mechanism | null { + if (this.currentProject) { + for (const mechanism of this.currentProject.mechanisms) { + if (mechanism.moduleName + '.' + mechanism.className === mechanismInRobot.className) { + return mechanism; + } + } + } + return null; + } + /** * Returns the components defined in the given mechanism. */ diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 63546973..bc993876 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -34,7 +34,7 @@ export type Module = { projectName: string, moduleName: string, dateModifiedMillis: number, - className: string, + className: string, // Does not include the module name. }; export type Robot = Module; @@ -65,13 +65,13 @@ export type Method = { export type MechanismInRobot = { blockId: string, // ID of the mrc_mechanism block that adds the mechanism to the robot. name: string, - className: string, + className: string, // Includes the module name, for example 'game_piece_shooter.GamePieceShooter'. } export type Component = { blockId: string, // ID of the mrc_component block that adds the component to the robot or to a mechanism. name: string, - className: string, + className: string, // Includes the module name, for example 'smart_motor.SmartMotor'. ports: {[port: string]: string}, // The value is the type. } diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index 5885fc31..0017fd36 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -24,7 +24,10 @@ import * as commonStorage from '../storage/common_storage'; import * as toolboxItems from './items'; import { createMechanismBlock } from '../blocks/mrc_mechanism'; import { getAllPossibleComponents } from '../blocks/mrc_component'; -import { getInstanceComponentBlocks, addInstanceRobotBlocks } from '../blocks/mrc_call_python_function'; +import { + getInstanceComponentBlocks, + addInstanceRobotBlocks, + addInstanceMechanismBlocks } from '../blocks/mrc_call_python_function'; import { addRobotEventHandlerBlocks } from '../blocks/mrc_event_handler'; import { Editor } from '../editor/editor'; @@ -88,142 +91,23 @@ function getRobotMechanismsCategory(currentModule: commonStorage.Module): toolbo } if (editor) { - editor.getMechanismsFromRobot().forEach(mechanism => { + editor.getMechanismsFromRobot().forEach(mechanismInRobot => { const mechanismBlocks: toolboxItems.Block[] = []; - // TODO(lizlooney): add the blocks for mechanism methods. - contents.push({ - kind: 'category', - name: mechanism.name, - contents: mechanismBlocks, - }); - }); - } - /* // Uncomment this fake code for testing purposes only. - contents.push( - { - kind: 'category', - name: 'drive', - contents: [ - { - kind: 'block', - type: 'mrc_call_python_function', - extraState: { - functionKind: 'instance_component', - returnType: '', - args: [ - { - name: 'forward_speed', - type: 'float', - }, - { - name: 'strafe_right_speed', - type: 'float', - }, - { - name: 'rotate_cw_speed', - type: 'float', - }, - ], - tooltip: 'Drive (robot relative)', - importModule: '', - componentClassName: 'DriveRobotRelative', - componentName: 'robot.drive', - }, - fields: { - COMPONENT_NAME: 'robot.drive', - FUNC: 'drive_field_relative', - }, - inputs: {}, - }, - ] - }, - { - kind: 'category', - name: 'claw', - contents: [ - { - kind: 'category', - name: 'gripper', - contents: [], - }, - { + // Add the blocks for this mechanism's methods. + const mechanism = editor.getMechanism(mechanismInRobot); + if (mechanism) { + const methodsFromMechanism = editor.getMethodsFromMechanism(mechanism); + addInstanceMechanismBlocks(mechanismInRobot, methodsFromMechanism, mechanismBlocks); + + contents.push({ kind: 'category', - name: 'piece_sensor', - contents: [ - // def get_color_rgb(self) -> tuple[int, int, int]: - { - kind: 'block', - type: 'mrc_call_python_function', - extraState: { - functionKind: 'instance_component', - returnType: 'tuple[int, int, int]', - args: [], - tooltip: 'Get the color in rgb (red, green, blue).', - importModule: '', - componentClassName: 'color_range_sensor.ColorRangeSensor', - componentName: 'colorSensor', - }, - fields: { - COMPONENT_NAME: 'robot.claw.piece_sensor', - FUNC: 'get_color_rgb', - }, - inputs: {}, - }, - // def get_color_hsv(self) -> tuple[int, int, int]: - { - kind: 'block', - type: 'mrc_call_python_function', - extraState: { - functionKind: 'instance_component', - returnType: 'tuple[int, int, int]', - args: [], - tooltip: 'Get the color in hsv (hue, saturation, value).', - importModule: '', - componentClassName: 'color_range_sensor.ColorRangeSensor', - componentName: 'colorSensor', - }, - fields: { - COMPONENT_NAME: 'robot.claw.piece_sensor', - FUNC: 'get_color_hsv', - }, - inputs: {}, - }, - // def get_distance_mm(self) -> float: - { - kind: 'block', - type: 'mrc_call_python_function', - extraState: { - functionKind: 'instance_component', - returnType: 'float', - args: [], - tooltip: 'Get the distance of the object seen.', - importModule: '', - componentClassName: 'color_range_sensor.ColorRangeSensor', - componentName: 'colorSensor', - }, - fields: { - COMPONENT_NAME: 'robot.claw.piece_sensor', - FUNC: 'get_distance_mm', - }, - inputs: {}, - }, - ], - }, - ] - }, - { - kind: 'category', - name: 'flywheel', - contents: [], - }, - { - kind: 'category', - name: 'shooter', - contents: [], - } - ); - */ + name: mechanismInRobot.name, + contents: mechanismBlocks, + }); + } + }); + } return { kind: 'category', diff --git a/src/toolbox/methods_category.ts b/src/toolbox/methods_category.ts index 258e1912..1d24df9f 100644 --- a/src/toolbox/methods_category.ts +++ b/src/toolbox/methods_category.ts @@ -69,7 +69,6 @@ export class MethodsCategory { 'define_hardware', 'fire_event', 'register_event_handler', - 'register_event_handler', 'unregister_event_handler', ]; // Add the methods for a Robot. @@ -141,5 +140,4 @@ export class MethodsCategory { } } } - }