diff --git a/server_python_scripts/blocks_base_classes/__init__.py b/server_python_scripts/blocks_base_classes/__init__.py index d59688f0..d4bc5ad3 100644 --- a/server_python_scripts/blocks_base_classes/__init__.py +++ b/server_python_scripts/blocks_base_classes/__init__.py @@ -5,4 +5,4 @@ from .robot_base import RobotBase __all__ = ['OpMode', 'Teleop', 'Auto', 'Test', 'Name', 'Group' - 'Mechanism', 'RobotBase'] \ No newline at end of file + 'Mechanism', 'RobotBase'] diff --git a/server_python_scripts/blocks_base_classes/robot_base.py b/server_python_scripts/blocks_base_classes/robot_base.py index c63129ce..a7080d57 100644 --- a/server_python_scripts/blocks_base_classes/robot_base.py +++ b/server_python_scripts/blocks_base_classes/robot_base.py @@ -1,14 +1,39 @@ # This is the class all robots derive from +from typing import Callable + class RobotBase: def __init__(self): self.hardware = [] + # In self.event_handlers, the keys are the event names, the values are a list of handlers. + self.event_handlers = {} + + def register_event_handler(self, event_name: str, event_handler: Callable) -> None: + if event_name in self.event_handlers: + self.event_handlers[event_name].append(event_handler) + else: + self.event_handlers[event_name] = [event_handler] + + def unregister_event_handler(self, event_name: str, event_handler: Callable) -> None: + if event_name in self.event_handlers: + if event_handler in self.event_handlers[event_name]: + self.event_handlers[event_name].remove(event_handler) + if not self.event_handlers[event_name]: + del self.event_handlers[event_name] + + def fire_event(self, event_name: str, *args) -> None: + if event_name in self.event_handlers: + for event_handler in self.event_handlers[event_name]: + event_handler(*args) + def start(self) -> None: for hardware in self.hardware: hardware.start() + def update(self) -> None: for hardware in self.hardware: hardware.update() + def stop(self) -> None: for hardware in self.hardware: hardware.stop() diff --git a/src/blocks/mrc_call_python_function.ts b/src/blocks/mrc_call_python_function.ts index bf8073bf..cbbe39c3 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -588,6 +588,8 @@ export const pythonFromBlock = function( generator.addImport(callPythonFunctionBlock.mrcImportModule); } let code; + let needOpenParen = true; + let delimiterBeforeArgs = ''; let argStartIndex = 0; switch (callPythonFunctionBlock.mrcFunctionKind) { case FunctionKind.BUILT_IN: { @@ -637,9 +639,9 @@ export const pythonFromBlock = function( } case FunctionKind.EVENT: { const eventName = block.getFieldValue(FIELD_EVENT_NAME); - code = - 'if self.events.get("' + eventName + '", None):\n' + - generator.INDENT + 'self.events["' + eventName + '"]'; + code = 'self.fire_event("' + eventName + '"'; + needOpenParen = false; + delimiterBeforeArgs = ', '; break; } case FunctionKind.INSTANCE_COMPONENT: { @@ -671,7 +673,14 @@ export const pythonFromBlock = function( default: throw new Error('mrcFunctionKind has unexpected value: ' + callPythonFunctionBlock.mrcFunctionKind) } - code += '(' + generateCodeForArguments(callPythonFunctionBlock, generator, argStartIndex) + ')'; + if (needOpenParen) { + code += '('; + } + const codeForArgs = generateCodeForArguments(callPythonFunctionBlock, generator, argStartIndex); + if (codeForArgs) { + code += delimiterBeforeArgs + codeForArgs; + } + code += ')'; if (block.outputConnection) { return [code, Order.FUNCTION_CALL]; } else { @@ -699,22 +708,22 @@ function generateCodeForArguments( } function getMethodCallers(workspace: Blockly.Workspace, otherBlockId: string): Blockly.Block[] { - return workspace.getBlocksByType('mrc_call_python_function').filter((block) => { + return workspace.getBlocksByType(BLOCK_NAME).filter((block) => { return (block as CallPythonFunctionBlock).mrcOtherBlockId === otherBlockId; }); } export function renameMethodCallers(workspace: Blockly.Workspace, otherBlockId: string, newName: string): void { - for (const block of getMethodCallers(workspace, otherBlockId)) { + getMethodCallers(workspace, otherBlockId).forEach(block => { (block as CallPythonFunctionBlock).renameMethodCaller(newName); - } + }); } export function mutateMethodCallers( workspace: Blockly.Workspace, otherBlockId: string, methodOrEvent: commonStorage.Method | commonStorage.Event) { const oldRecordUndo = Blockly.Events.getRecordUndo(); - for (const block of getMethodCallers(workspace, otherBlockId)) { + getMethodCallers(workspace, otherBlockId).forEach(block => { const callBlock = block as CallPythonFunctionBlock; // Get the extra state before changing the call block. const oldExtraState = callBlock.saveExtraState(); @@ -738,7 +747,7 @@ export function mutateMethodCallers( ); Blockly.Events.setRecordUndo(oldRecordUndo); } - } + }); } // Functions used for creating blocks for the toolbox. diff --git a/src/blocks/mrc_class_method_def.ts b/src/blocks/mrc_class_method_def.ts index 979feb90..167f62c7 100644 --- a/src/blocks/mrc_class_method_def.ts +++ b/src/blocks/mrc_class_method_def.ts @@ -36,7 +36,7 @@ import { MUTATOR_BLOCK_NAME, PARAM_CONTAINER_BLOCK_NAME, MethodMutatorArgBlock } export const BLOCK_NAME = 'mrc_class_method_def'; -const FIELD_METHOD_NAME = 'NAME'; +export const FIELD_METHOD_NAME = 'NAME'; type Parameter = { name: string, @@ -347,7 +347,7 @@ function findLegalMethodName(name: string, block: ClassMethodDefBlock): string { function isMethodNameUsed( name: string, workspace: Blockly.Workspace, opt_exclude?: Blockly.Block): boolean { const nameLowerCase = name.toLowerCase(); - for (const block of workspace.getBlocksByType('mrc_class_method_def')) { + for (const block of workspace.getBlocksByType(BLOCK_NAME)) { if (block === opt_exclude) { continue; } @@ -409,7 +409,7 @@ export const pythonFromBlock = function ( if (block.mrcPythonMethodName == '__init__') { let class_specific = generator.getClassSpecificForInit(); branch = generator.INDENT + 'super().__init__(' + class_specific + ')\n' + - generator.defineClassVariables() + branch; + generator.generateInitStatements() + branch; } else if (generator.inBaseClassMethod(blocklyName)){ // Special case for methods inherited from the based class: generate the @@ -510,3 +510,40 @@ function createClassMethodDefBlock( fields[FIELD_METHOD_NAME] = functionData.functionName; return new toolboxItems.Block(BLOCK_NAME, extraState, fields, null); } + +// Misc + +export function getMethodsForWithin( + workspace: Blockly.Workspace, + methods: commonStorage.Method[]): void { + workspace.getBlocksByType(BLOCK_NAME).forEach(block => { + const method = (block as ClassMethodDefBlock).getMethodForWithin(); + if (method) { + methods.push(method); + } + }); +} + +export function getMethodsForOutside( + workspace: Blockly.Workspace, + methods: commonStorage.Method[]): void { + workspace.getBlocksByType(BLOCK_NAME).forEach(block => { + const method = (block as ClassMethodDefBlock).getMethodForOutside(); + if (method) { + methods.push(method); + } + }); +} + +export function getMethodNamesAlreadyOverriddenInWorkspace( + workspace: Blockly.Workspace, + methodNamesAlreadyOverridden: string[]): void { + workspace.getBlocksByType(BLOCK_NAME).forEach(block => { + const methodDefBlock = block as ClassMethodDefBlock; + // If the user cannot change the signature, it means the block defines a method that overrides a baseclass method. + // That's what we are looking for here. + if (!methodDefBlock.canChangeSignature()) { + methodNamesAlreadyOverridden.push(methodDefBlock.getMethodName()); + } + }); +} diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 0c19c032..717e8f81 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -39,7 +39,7 @@ export const OUTPUT_NAME = 'mrc_component'; export const FIELD_NAME = 'NAME'; export const FIELD_TYPE = 'TYPE'; -export type ConstructorArg = { +type ConstructorArg = { name: string, type: string, }; @@ -146,6 +146,23 @@ const COMPONENT = { className: componentType, }; }, + getNewPort: function (this: ComponentBlock, i: number): string { + let extension = ''; + if (i != 0) { + extension = '_' + (i + 1).toString(); + } + return this.getFieldValue(FIELD_NAME) + extension + '_port'; + }, + getHardwarePorts: function (this: ComponentBlock, ports: {[key: string]: string}): void { + // Collect the hardware ports for this component block that are needed to generate + // the define_hardware method. (The key is the port, the value is the type.) + if (this.hideParams) { + for (let i = 0; i < this.mrcArgs.length; i++) { + const newPort = this.getNewPort(i); + ports[newPort] = this.mrcArgs[i].type; + } + } + }, } export const setup = function () { @@ -171,13 +188,7 @@ export const pythonFromBlock = function ( code += ', ' } if (block.hideParams) { - let extension = ''; - if (i != 0) { - extension = '_' + (i + 1).toString(); - } - const newPort = block.getFieldValue(FIELD_NAME) + extension + '_port'; - generator.addHardwarePort(newPort, block.mrcArgs[i].type); - code += block.mrcArgs[i].name + ' = ' + newPort; + code += block.mrcArgs[i].name + ' = ' + block.getNewPort(i); } else { code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); } diff --git a/src/blocks/mrc_event.ts b/src/blocks/mrc_event.ts index 423b4582..4046a64d 100644 --- a/src/blocks/mrc_event.ts +++ b/src/blocks/mrc_event.ts @@ -230,8 +230,8 @@ export const setup = function () { } export const pythonFromBlock = function ( - block: EventBlock, - generator: ExtendedPythonGenerator) { + _block: EventBlock, + _generator: ExtendedPythonGenerator) { // TODO (Alan): What should this do here?? return ''; } diff --git a/src/blocks/mrc_event_handler.ts b/src/blocks/mrc_event_handler.ts index 14174e90..920957f1 100644 --- a/src/blocks/mrc_event_handler.ts +++ b/src/blocks/mrc_event_handler.ts @@ -23,13 +23,19 @@ import * as Blockly from 'blockly'; import {Order} from 'blockly/python'; -import {ExtendedPythonGenerator} from '../editor/extended_python_generator'; -import {createFieldFlydown} from '../fields/field_flydown'; -import {createFieldNonEditableText} from '../fields/FieldNonEditableText'; -import {MRC_STYLE_EVENT_HANDLER} from '../themes/styles'; +import { Editor } from '../editor/editor'; +import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; +import { createFieldFlydown } from '../fields/field_flydown'; +import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; +import { MRC_STYLE_EVENT_HANDLER } from '../themes/styles'; +import * as toolboxItems from '../toolbox/items'; +import * as commonStorage from '../storage/common_storage'; export const BLOCK_NAME = 'mrc_event_handler'; +const FIELD_SENDER = 'SENDER'; +const FIELD_EVENT_NAME = 'EVENT_NAME'; + export enum SenderType { ROBOT = 'robot', MECHANISM = 'mechanism', @@ -41,12 +47,15 @@ export interface Parameter { type?: string; } +const WARNING_ID_EVENT_CHANGED = 'event changed'; + export type EventHandlerBlock = Blockly.Block & EventHandlerMixin & Blockly.BlockSvg; interface EventHandlerMixin extends EventHandlerMixinType { mrcPathOfSender: string; mrcTypeOfSender: SenderType; mrcParameters: Parameter[]; + mrcOtherBlockId: string, } type EventHandlerMixinType = typeof EVENT_HANDLER; @@ -57,6 +66,8 @@ export interface EventHandlerExtraState { typeOfSender: SenderType; /** The parameters of the event handler. */ params: Parameter[]; + /** The id of the mrc_event block that defines the event. */ + otherBlockId: string, } const EVENT_HANDLER = { @@ -66,8 +77,8 @@ const EVENT_HANDLER = { init(this: EventHandlerBlock): void { this.appendDummyInput('TITLE') .appendField(Blockly.Msg.WHEN) - .appendField(createFieldNonEditableText('sender'), 'SENDER') - .appendField(createFieldNonEditableText('eventName'), 'EVENT_NAME'); + .appendField(createFieldNonEditableText('sender'), FIELD_SENDER) + .appendField(createFieldNonEditableText('eventName'), FIELD_EVENT_NAME); this.appendDummyInput('PARAMS') .appendField(Blockly.Msg.WITH); this.setOutput(false); @@ -86,8 +97,9 @@ const EVENT_HANDLER = { pathOfSender: this.mrcPathOfSender, typeOfSender: this.mrcTypeOfSender, params: [], + otherBlockId: this.mrcOtherBlockId, }; - + this.mrcParameters.forEach((param) => { extraState.params.push({ name: param.name, @@ -102,9 +114,10 @@ const EVENT_HANDLER = { * Applies the given state to this block. */ loadExtraState(this: EventHandlerBlock, extraState: EventHandlerExtraState): void { - this.mrcParameters = []; this.mrcPathOfSender = extraState.pathOfSender; this.mrcTypeOfSender = extraState.typeOfSender; + this.mrcParameters = []; + this.mrcOtherBlockId = extraState.otherBlockId; extraState.params.forEach((param) => { this.mrcParameters.push({ @@ -145,6 +158,58 @@ const EVENT_HANDLER = { input.removeField(fieldName); }); }, + onLoad: function(this: EventHandlerBlock): void { + // onLoad is called for each EventHandlerBlock when the blocks are loaded in the blockly workspace. + const warnings: string[] = []; + + // If this block is an event handler for a robot event, check that the robot event + // still exists and hasn't been changed. + // If the robot event doesn't exist, put a visible warning on this block. + // If the robot event has changed, update the block if possible or put a + // visible warning on it. + if (this.mrcTypeOfSender === SenderType.ROBOT) { + let foundRobotEvent = false; + const editor = Editor.getEditorForBlocklyWorkspace(this.workspace); + if (editor) { + const robotEvents = editor.getEventsFromRobot(); + for (const robotEvent of robotEvents) { + if (robotEvent.blockId === this.mrcOtherBlockId) { + foundRobotEvent = true; + + // If the event name has changed, we can fix this block. + if (this.getFieldValue(FIELD_EVENT_NAME) !== robotEvent.name) { + this.setFieldValue(robotEvent.name, FIELD_EVENT_NAME); + } + + this.mrcParameters = []; + robotEvent.args.forEach(arg => { + this.mrcParameters.push({ + name: arg.name, + type: arg.type, + }); + }); + this.mrcUpdateParams(); + + // Since we found the robot event, we can break out of the loop. + break; + } + } + if (!foundRobotEvent) { + warnings.push('This block is an event handler for an event that no longer exists.'); + } + } + } + + if (warnings.length) { + // Add a warnings to the block. + const warningText = warnings.join('\n\n'); + this.setWarningText(warningText, WARNING_ID_EVENT_CHANGED); + this.bringToFront(); + } else { + // Clear the existing warning on the block. + this.setWarningText(null, WARNING_ID_EVENT_CHANGED); + } + }, }; export function setup(): void { @@ -155,9 +220,15 @@ export function pythonFromBlock( block: EventHandlerBlock, generator: ExtendedPythonGenerator, ): string { - const blocklyName = `${block.getFieldValue('SENDER')}_${block.getFieldValue('EVENT_NAME')}`; + const sender = block.getFieldValue(FIELD_SENDER); + const eventName = block.getFieldValue(FIELD_EVENT_NAME); + + const blocklyName = `${sender}_${eventName}`; const funcName = generator.getProcedureName(blocklyName); + // TODO(lizlooney): if the user adds multiple event handlers for the same event + // name, we need to make the event handler function names unique. + let xfix1 = ''; if (generator.STATEMENT_PREFIX) { xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block); @@ -211,12 +282,49 @@ export function pythonFromBlock( let code = `def ${funcName}(${paramString}):\n`; code += xfix1 + loopTrap + branch + xfix2 + returnValue; code = generator.scrub_(block, code); - + generator.addClassMethodDefinition(funcName, code); - generator.addEventHandler( - block.getFieldValue('SENDER'), - block.getFieldValue('EVENT_NAME'), - funcName); + generator.addEventHandler(sender, eventName, funcName); return ''; -} \ No newline at end of file +} + +// Functions used for creating blocks for the toolbox. + +export function addRobotEventHandlerBlocks( + events: commonStorage.Event[], + contents: toolboxItems.ContentsType[]) { + events.forEach(event => { + contents.push(createRobotEventHandlerBlock(event)); + }); +} + +function createRobotEventHandlerBlock( + event: commonStorage.Event): toolboxItems.Block { + const extraState: EventHandlerExtraState = { + // TODO(lizlooney): ask Alan what pathOfSender is for. + pathOfSender: '', + typeOfSender: SenderType.ROBOT, + params: [], + otherBlockId: event.blockId, + }; + event.args.forEach(arg => { + extraState.params.push({ + name: arg.name, + type: arg.type, + }); + }); + const fields: {[key: string]: any} = {}; + fields[FIELD_SENDER] = 'robot'; + fields[FIELD_EVENT_NAME] = event.name; + const inputs: {[key: string]: any} = {}; + return new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); +} + +// Misc + +export function getHasEventHandler(workspace: Blockly.Workspace): boolean { + return workspace.getBlocksByType(BLOCK_NAME).filter(block => { + return block.isEnabled(); + }).length > 0; +} diff --git a/src/blocks/mrc_mechanism.ts b/src/blocks/mrc_mechanism.ts index 340a0de6..a80793cb 100644 --- a/src/blocks/mrc_mechanism.ts +++ b/src/blocks/mrc_mechanism.ts @@ -30,7 +30,7 @@ import { getAllowedTypesForSetCheck } from './utils/python'; export const BLOCK_NAME = 'mrc_mechanism'; export const OUTPUT_NAME = 'mrc_mechansim'; -export type ConstructorArg = { +type ConstructorArg = { name: string, type: string, }; @@ -40,7 +40,7 @@ type MechanismExtraState = { params?: ConstructorArg[], } -type MechanismBlock = Blockly.Block & MechanismMixin; +export type MechanismBlock = Blockly.Block & MechanismMixin; interface MechanismMixin extends MechanismMixinType { mrcArgs: ConstructorArg[], mrcImportModule: string, @@ -111,7 +111,11 @@ const MECHANISM = { input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type)); } } - } + }, + getHardwarePorts: function (this: MechanismBlock, _ports: {[key: string]: string}): void { + // TODO: Collect the hardware ports for this mechanism block that are needed to generate + // the robot's define_hardware method. (The key is the port, the value is the type.) + }, } export const setup = function () { diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index 310fd49c..bf98c6ad 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -31,14 +31,16 @@ import { BLOCK_NAME as MRC_MECHANISM_NAME } from './mrc_mechanism'; import { BLOCK_NAME as MRC_COMPONENT_NAME } from './mrc_component'; import { OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component'; import { ComponentBlock } from './mrc_component'; +import { MechanismBlock } from './mrc_mechanism'; import { BLOCK_NAME as MRC_EVENT_NAME } from './mrc_event'; import { OUTPUT_NAME as EVENT_OUTPUT } from './mrc_event'; import { EventBlock } from './mrc_event'; export const BLOCK_NAME = 'mrc_mechanism_component_holder'; -export const MECHANISM = 'mechanism'; -export const COMPONENT = 'component'; +const INPUT_MECHANISMS = 'MECHANISMS'; +const INPUT_COMPONENTS = 'COMPONENTS'; +const INPUT_EVENTS = 'EVENTS'; export const TOOLBOX_UPDATE_EVENT = 'toolbox-update-requested'; @@ -73,9 +75,9 @@ const MECHANISM_COMPONENT_HOLDER = { */ init: function (this: MechanismComponentHolderBlock): void { this.setInputsInline(false); - this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField(Blockly.Msg.MECHANISMS); - this.appendStatementInput('COMPONENTS').setCheck(COMPONENT_OUTPUT).appendField(Blockly.Msg.COMPONENTS); - this.appendStatementInput('EVENTS').setCheck(EVENT_OUTPUT).appendField(Blockly.Msg.EVENTS); + this.appendStatementInput(INPUT_MECHANISMS).setCheck(MECHANISM_OUTPUT).appendField(Blockly.Msg.MECHANISMS); + this.appendStatementInput(INPUT_COMPONENTS).setCheck(COMPONENT_OUTPUT).appendField(Blockly.Msg.COMPONENTS); + this.appendStatementInput(INPUT_EVENTS).setCheck(EVENT_OUTPUT).appendField(Blockly.Msg.EVENTS); this.setOutput(false); @@ -104,14 +106,14 @@ const MECHANISM_COMPONENT_HOLDER = { */ updateBlock_: function (this: MechanismComponentHolderBlock): void { if (this.mrcHideMechanisms) { - if (this.getInput('MECHANISMS')) { - this.removeInput('MECHANISMS') + if (this.getInput(INPUT_MECHANISMS)) { + this.removeInput(INPUT_MECHANISMS) } } else { - if (this.getInput('MECHANISMS') == null) { - this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms'); - this.moveInputBefore('MECHANISMS', 'COMPONENTS') + if (this.getInput(INPUT_MECHANISMS) == null) { + this.appendStatementInput(INPUT_MECHANISMS).setCheck(MECHANISM_OUTPUT).appendField('Mechanisms'); + this.moveInputBefore(INPUT_MECHANISMS, INPUT_COMPONENTS) } } }, @@ -134,7 +136,7 @@ const MECHANISM_COMPONENT_HOLDER = { const components: commonStorage.Component[] = [] // Get component blocks from the COMPONENTS input - const componentsInput = this.getInput('COMPONENTS'); + const componentsInput = this.getInput(INPUT_COMPONENTS); if (componentsInput && componentsInput.connection) { // Walk through all connected component blocks. let componentBlock = componentsInput.connection.targetBlock(); @@ -156,7 +158,7 @@ const MECHANISM_COMPONENT_HOLDER = { const events: commonStorage.Event[] = [] // Get event blocks from the EVENTS input - const eventsInput = this.getInput('EVENTS'); + const eventsInput = this.getInput(INPUT_EVENTS); if (eventsInput && eventsInput.connection) { // Walk through all connected event blocks. let eventBlock = eventsInput.connection.targetBlock(); @@ -197,11 +199,8 @@ export const setup = function () { function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator) { let code = 'def define_hardware(self):\n'; - let mechanisms = ''; - let components = ''; - - mechanisms = generator.statementToCode(block, 'MECHANISMS'); - components = generator.statementToCode(block, 'COMPONENTS'); + const mechanisms = generator.statementToCode(block, INPUT_MECHANISMS); + const components = generator.statementToCode(block, INPUT_COMPONENTS); const body = mechanisms + components; if (body != '') { @@ -213,7 +212,7 @@ function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator: function pythonFromBlockInMechanism(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator) { let components = ''; - components = generator.statementToCode(block, 'COMPONENTS'); + components = generator.statementToCode(block, INPUT_COMPONENTS); let code = 'def define_hardware(self' + generator.getListOfPorts(false) + '):\n'; @@ -227,7 +226,7 @@ export const pythonFromBlock = function ( block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator, ) { - if (block.getInput('MECHANISMS')) { + if (block.getInput(INPUT_MECHANISMS)) { pythonFromBlockInRobot(block, generator); } else { @@ -235,3 +234,65 @@ export const pythonFromBlock = function ( } return '' } + +// Misc + +/** + * Collects the ports for hardware (mechanisms and components). + * Returns true if the given workspace has a mrc_mechanism_component_holder + * block that contains at least one component or mechanism. + */ +export function getHardwarePorts(workspace: Blockly.Workspace, ports: {[key: string]: string}): boolean { + let hasHardware = false; + workspace.getBlocksByType(BLOCK_NAME).forEach( block => { + const mechanismsInput = block.getInput(INPUT_MECHANISMS); + if (mechanismsInput && mechanismsInput.connection) { + // Walk through all connected mechanism blocks. + let mechanismBlock = mechanismsInput.connection.targetBlock(); + while (mechanismBlock) { + if (mechanismBlock.type === MRC_MECHANISM_NAME && mechanismBlock.isEnabled()) { + hasHardware = true; + (mechanismBlock as MechanismBlock).getHardwarePorts(ports); + } + // Move to the next block in the chain + mechanismBlock = mechanismBlock.getNextBlock(); + } + } + const componentsInput = block.getInput(INPUT_COMPONENTS); + if (componentsInput && componentsInput.connection) { + // Walk through all connected component blocks. + let componentBlock = componentsInput.connection.targetBlock(); + while (componentBlock) { + if (componentBlock.type === MRC_COMPONENT_NAME && componentBlock.isEnabled()) { + hasHardware = true; + (componentBlock as ComponentBlock).getHardwarePorts(ports); + } + // Move to the next block in the chain + componentBlock = componentBlock.getNextBlock(); + } + } + }); + return hasHardware; +} + +export function getComponents( + workspace: Blockly.Workspace, + components: commonStorage.Component[]): void { + // Get the holder block and ask it for the components. + workspace.getBlocksByType(BLOCK_NAME).forEach(block => { + const componentsFromHolder: commonStorage.Component[] = + (block as MechanismComponentHolderBlock).getComponents(); + components.push(...componentsFromHolder); + }); +} + +export function getEvents( + workspace: Blockly.Workspace, + events: commonStorage.Event[]): void { + // Get the holder block and ask it for the events. + workspace.getBlocksByType(BLOCK_NAME).forEach(block => { + const eventsFromHolder: commonStorage.Event[] = + (block as MechanismComponentHolderBlock).getEvents(); + events.push(...eventsFromHolder); + }); +} diff --git a/src/blocks/mrc_port.ts b/src/blocks/mrc_port.ts index 69b748b7..b4dbeab8 100644 --- a/src/blocks/mrc_port.ts +++ b/src/blocks/mrc_port.ts @@ -25,7 +25,6 @@ import { Order } from 'blockly/python'; import { MRC_STYLE_PORTS } from '../themes/styles' import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; -import { createFieldDropdown } from '../fields/FieldDropdown'; export const BLOCK_NAME = 'mrc_port'; export const OUTPUT_NAME = 'mrc_port'; @@ -56,13 +55,12 @@ export const setup = function () { } export const pythonFromBlock = function ( - block: PortBlock, - generator: ExtendedPythonGenerator, -) { - //TODO (Alan) : Specify the type here as well - let code = block.getFieldValue('PORT_NUM'); - - return [code, Order.ATOMIC]; + block: PortBlock, + _generator: ExtendedPythonGenerator) { + // TODO (Alan) : Specify the type here as well + let code = block.getFieldValue('PORT_NUM'); + + return [code, Order.ATOMIC]; } export function createPortShadow(portType: string, portNum: Number) { diff --git a/src/blocks/utils/generated/server_python_scripts.json b/src/blocks/utils/generated/server_python_scripts.json index 7ca10aba..11f81383 100644 --- a/src/blocks/utils/generated/server_python_scripts.json +++ b/src/blocks/utils/generated/server_python_scripts.json @@ -137,6 +137,52 @@ ], "enums": [], "instanceMethods": [ + { + "args": [ + { + "defaultValue": "", + "name": "self", + "type": "blocks_base_classes.RobotBase" + }, + { + "defaultValue": "", + "name": "event_name", + "type": "str" + }, + { + "defaultValue": "", + "name": "args", + "type": "tuple" + } + ], + "declaringClassName": "blocks_base_classes.RobotBase", + "functionName": "fire_event", + "returnType": "None", + "tooltip": "" + }, + { + "args": [ + { + "defaultValue": "", + "name": "self", + "type": "blocks_base_classes.RobotBase" + }, + { + "defaultValue": "", + "name": "event_name", + "type": "str" + }, + { + "defaultValue": "", + "name": "event_handler", + "type": "typing.Callable" + } + ], + "declaringClassName": "blocks_base_classes.RobotBase", + "functionName": "register_event_handler", + "returnType": "None", + "tooltip": "" + }, { "args": [ { @@ -163,6 +209,29 @@ "returnType": "None", "tooltip": "" }, + { + "args": [ + { + "defaultValue": "", + "name": "self", + "type": "blocks_base_classes.RobotBase" + }, + { + "defaultValue": "", + "name": "event_name", + "type": "str" + }, + { + "defaultValue": "", + "name": "event_handler", + "type": "typing.Callable" + } + ], + "declaringClassName": "blocks_base_classes.RobotBase", + "functionName": "unregister_event_handler", + "returnType": "None", + "tooltip": "" + }, { "args": [ { diff --git a/src/editor/editor.ts b/src/editor/editor.ts index b790dab9..ae96ba73 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 callPythonFunction from '../blocks/mrc_call_python_function'; +import * as eventHandler from '../blocks/mrc_event_handler'; import * as classMethodDef from '../blocks/mrc_class_method_def'; import * as mechanismComponentHolder from '../blocks/mrc_mechanism_component_holder'; //import { testAllBlocksInToolbox } from '../toolbox/toolbox_tests'; @@ -80,8 +81,12 @@ export class Editor { if (blockCreateEvent.ids) { blockCreateEvent.ids.forEach(id => { const block = this.blocklyWorkspace.getBlockById(id); - if (block && block.type == callPythonFunction.BLOCK_NAME) { - (block as callPythonFunction.CallPythonFunctionBlock).onLoad(); + if (block) { + if (block.type == callPythonFunction.BLOCK_NAME) { + (block as callPythonFunction.CallPythonFunctionBlock).onLoad(); + } else if (block.type == eventHandler.BLOCK_NAME) { + (block as eventHandler.EventHandlerBlock).onLoad(); + } } }); } @@ -212,83 +217,45 @@ export class Editor { ? JSON.stringify(this.getMethodsForOutsideFromWorkspace()) : '[]'; const componentsContent = JSON.stringify(this.getComponentsFromWorkspace()); + const eventsContent = JSON.stringify(this.getEventsFromWorkspace()); return commonStorage.makeModuleContent( - this.currentModule, pythonCode, blocksContent, methodsContent, componentsContent); + this.currentModule, pythonCode, blocksContent, + methodsContent, componentsContent, eventsContent); } public getComponentsFromWorkspace(): commonStorage.Component[] { const components: commonStorage.Component[] = []; if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT || - this.currentModule?.moduleType === commonStorage.MODULE_TYPE_MECHANISM) { - // Get the holder block and ask it for the components. - const holderBlocks = this.blocklyWorkspace.getBlocksByType(mechanismComponentHolder.BLOCK_NAME); - holderBlocks.forEach(holderBlock => { - const componentsFromHolder: commonStorage.Component[] = - (holderBlock as mechanismComponentHolder.MechanismComponentHolderBlock).getComponents(); - components.push(...componentsFromHolder); - }); + this.currentModule?.moduleType === commonStorage.MODULE_TYPE_MECHANISM) { + mechanismComponentHolder.getComponents(this.blocklyWorkspace, components); } return components; } public getMethodsForWithinFromWorkspace(): commonStorage.Method[] { const methods: commonStorage.Method[] = []; - - // Get the class method definition blocks. - const methodDefBlocks = this.blocklyWorkspace.getBlocksByType(classMethodDef.BLOCK_NAME); - methodDefBlocks.forEach(methodDefBlock => { - const method = (methodDefBlock as classMethodDef.ClassMethodDefBlock).getMethodForWithin(); - if (method) { - methods.push(method); - } - }); - + classMethodDef.getMethodsForWithin(this.blocklyWorkspace, methods); return methods; } public getMethodsForOutsideFromWorkspace(): commonStorage.Method[] { const methods: commonStorage.Method[] = []; - - // Get the class method definition blocks. - const methodDefBlocks = this.blocklyWorkspace.getBlocksByType(classMethodDef.BLOCK_NAME); - methodDefBlocks.forEach(methodDefBlock => { - const method = (methodDefBlock as classMethodDef.ClassMethodDefBlock).getMethodForOutside(); - if (method) { - methods.push(method); - } - }); - + classMethodDef.getMethodsForOutside(this.blocklyWorkspace, methods); return methods; } public getMethodNamesAlreadyOverriddenInWorkspace(): string[] { const methodNamesAlreadyOverridden: string[] = []; - - // Get the class method definition blocks. - const methodDefBlocks = this.blocklyWorkspace.getBlocksByType(classMethodDef.BLOCK_NAME); - methodDefBlocks.forEach(block => { - const methodDefBlock = block as classMethodDef.ClassMethodDefBlock; - // If the user cannot change the signature, it means the block defines a method that overrides a baseclass method. - // That's what we are looking for here. - if (!methodDefBlock.canChangeSignature()) { - methodNamesAlreadyOverridden.push(methodDefBlock.getMethodName()); - } - }); - + classMethodDef.getMethodNamesAlreadyOverriddenInWorkspace( + this.blocklyWorkspace, methodNamesAlreadyOverridden); return methodNamesAlreadyOverridden; } public getEventsFromWorkspace(): commonStorage.Event[] { const events: commonStorage.Event[] = []; if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT || - this.currentModule?.moduleType === commonStorage.MODULE_TYPE_MECHANISM) { - // Get the holder block and ask it for the components. - const holderBlocks = this.blocklyWorkspace.getBlocksByType(mechanismComponentHolder.BLOCK_NAME); - holderBlocks.forEach(holderBlock => { - const eventsFromHolder: commonStorage.Event[] = - (holderBlock as mechanismComponentHolder.MechanismComponentHolderBlock).getEvents(); - events.push(...eventsFromHolder); - }); + this.currentModule?.moduleType === commonStorage.MODULE_TYPE_MECHANISM) { + mechanismComponentHolder.getEvents(this.blocklyWorkspace, events); } return events; } @@ -316,6 +283,19 @@ export class Editor { return commonStorage.extractComponents(this.robotContent); } + /** + * Returns the events defined in the robot. + */ + public getEventsFromRobot(): commonStorage.Event[] { + if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_ROBOT) { + return this.getEventsFromWorkspace(); + } + if (!this.robotContent) { + throw new Error('getEventsFromRobot: this.robotContent is null.'); + } + return commonStorage.extractEvents(this.robotContent); + } + /** * Returns the methods defined in the robot. */ diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index c7f4abdc..0be37b49 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -22,7 +22,8 @@ import * as Blockly from 'blockly/core'; import { PythonGenerator } from 'blockly/python'; import { GeneratorContext } from './generator_context'; -import * as MechanismContainerHolder from '../blocks/mrc_mechanism_component_holder'; +import * as mechanismContainerHolder from '../blocks/mrc_mechanism_component_holder'; +import * as eventHandler from '../blocks/mrc_event_handler'; import { MODULE_NAME_BLOCKS_BASE_CLASSES, CLASS_NAME_OPMODE, @@ -69,10 +70,17 @@ export class ExtendedPythonGenerator extends PythonGenerator { private workspace: Blockly.Workspace | null = null; private context: GeneratorContext | null = null; - private classMethods: {[key: string]: string} = Object.create(null); - private events: {[key: string]: {sender: string, eventName: string}} = Object.create(null); + // Has components or mechanisms (ie, needs to call self.define_hardware in __init__) + private hasHardware = false; private ports: {[key: string]: string} = Object.create(null); - // Opmode details + + // Has event handlers (ie, needs to call self.register_event_handlers in __init__) + private hasEventHandler = false; + + private classMethods: {[key: string]: string} = Object.create(null); + // For eventHandlers, the keys are the function name. + private eventHandlers: {[key: string]: {sender: string, eventName: string}} = Object.create(null); + // Opmode details private details : OpModeDetails | null = null; constructor() { @@ -100,38 +108,33 @@ export class ExtendedPythonGenerator extends PythonGenerator { * This is called from the python generator for the mrc_class_method_def for the * init method */ - defineClassVariables() : string { - let variableDefinitions = ''; - - if (this.context?.getHasHardware()) { - if ('define_hardware' in this.classMethods) { - variableDefinitions += this.INDENT + "self.define_hardware("; - variableDefinitions += this.getListOfPorts(true); - variableDefinitions += ')\n'; - } - if (this.events && Object.keys(this.events).length > 0){ - variableDefinitions += this.INDENT + "self.register_events()\n"; - } + generateInitStatements() : string { + let initStatements = ''; + + if (this.hasHardware) { + initStatements += this.INDENT + "self.define_hardware("; + initStatements += this.getListOfPorts(true); + initStatements += ')\n'; + } + if (this.hasEventHandler) { + initStatements += this.INDENT + "self.register_event_handlers()\n"; } - return variableDefinitions; + return initStatements; } + getVariableName(nameOrId: string): string { const varName = super.getVariableName(nameOrId); return "self." + varName; } - setHasHardware() : void{ - this.context?.setHasHardware(); - } mrcWorkspaceToCode(workspace: Blockly.Workspace, context: GeneratorContext): string { this.workspace = workspace; this.context = context; - this.context.clear(); - if (this.workspace.getBlocksByType(MechanismContainerHolder.BLOCK_NAME).length > 0){ - this.setHasHardware(); - } + this.ports = Object.create(null); + this.hasHardware = mechanismContainerHolder.getHardwarePorts(this.workspace, this.ports); + this.hasEventHandler = eventHandler.getHasEventHandler(this.workspace); const code = super.workspaceToCode(workspace); @@ -173,16 +176,10 @@ export class ExtendedPythonGenerator extends PythonGenerator { } addEventHandler(sender: string, eventName: string, funcName: string): void { - this.events[funcName] = { + this.eventHandlers[funcName] = { 'sender': sender, - 'eventName': eventName,} + 'eventName': eventName } - - /** - * Add a Hardware Port - */ - addHardwarePort(portName: string, type: string): void{ - this.ports[portName] = type; } getListOfPorts(startWithFirst: boolean): string{ @@ -219,18 +216,19 @@ export class ExtendedPythonGenerator extends PythonGenerator { const classDef = 'class ' + className + '(' + simpleBaseClassName + '):\n'; const classMethods = []; - if (this.events && Object.keys(this.events).length > 0) { - let code = 'def register_events(self):\n'; - for (const eventName in this.events) { - const event = this.events[eventName]; - code += this.INDENT + 'self.' + event.sender + '.register_event("' + event.eventName + '", self.' + eventName + ')\n'; + if (this.eventHandlers && Object.keys(this.eventHandlers).length > 0) { + let code = 'def register_event_handlers(self):\n'; + for (const funcName in this.eventHandlers) { + const event = this.eventHandlers[funcName]; + code += this.INDENT + 'self.' + event.sender + '.register_event_handler("' + + event.eventName + '", self.' + funcName + ')\n'; } classMethods.push(code); } for (const name in this.classMethods) { classMethods.push(this.classMethods[name]) } - this.events = Object.create(null); + this.eventHandlers = Object.create(null); this.classMethods = Object.create(null); this.ports = Object.create(null); code = classDef + this.prefixLines(classMethods.join('\n\n'), this.INDENT); diff --git a/src/editor/generator_context.ts b/src/editor/generator_context.ts index 32543a8c..ddd62cec 100644 --- a/src/editor/generator_context.ts +++ b/src/editor/generator_context.ts @@ -30,12 +30,8 @@ export function createGeneratorContext(): GeneratorContext { export class GeneratorContext { private module: commonStorage.Module | null = null; - // Has mechanisms (ie, needs in init) - private hasHardware = false; - setModule(module: commonStorage.Module | null) { this.module = module; - this.clear(); } getModuleType(): string | null { @@ -45,18 +41,6 @@ export class GeneratorContext { return null; } - clear(): void { - this.hasHardware= false; - } - - setHasHardware():void{ - this.hasHardware = true; - } - - getHasHardware():boolean{ - return this.hasHardware; - } - getClassName(): string { if (!this.module) { throw new Error('getClassName: this.module is null.'); diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 1afe4a22..10cf8d74 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -90,11 +90,14 @@ const MARKER_BLOCKS_CONTENT = 'blocksContent: '; const MARKER_METHODS = 'methods: '; const MARKER_MODULE_TYPE = 'moduleType: '; const MARKER_COMPONENTS = 'components: '; +const MARKER_EVENTS = 'events: '; + const PARTS_INDEX_BLOCKS_CONTENT = 0; const PARTS_INDEX_METHODS = 1; const PARTS_INDEX_MODULE_TYPE = 2; const PARTS_INDEX_COMPONENTS = 3; -const NUMBER_OF_PARTS = 4; +const PARTS_INDEX_EVENTS = 4; +const NUMBER_OF_PARTS = 5; export const UPLOAD_DOWNLOAD_FILE_EXTENSION = '.blocks'; @@ -512,8 +515,14 @@ function startingBlocksToModuleContent( Blockly.serialization.workspaces.save(headlessBlocklyWorkspace)); const methodsContent = '[]'; const componentsContent = '[]'; + const eventsContent = '[]'; return makeModuleContent( - module, pythonCode, blocksContent, methodsContent, componentsContent); + module, + pythonCode, + blocksContent, + methodsContent, + componentsContent, + eventsContent); } /** @@ -568,16 +577,18 @@ export function newOpModeContent(projectName: string, opModeName: string): strin * Make the module content from the given python code and blocks content. */ export function makeModuleContent( - module: Module, - pythonCode: string, - blocksContent: string, - methodsContent: string, - componentsContent: string): string { + module: Module, + pythonCode: string, + blocksContent: string, + methodsContent: string, + componentsContent: string, + eventsContent: string): string { let delimiter = DELIMITER_PREFIX; while (module.moduleType.includes(delimiter) || blocksContent.includes(delimiter) || methodsContent.includes(delimiter) - || componentsContent.includes(delimiter)) { + || componentsContent.includes(delimiter) + || eventsContent.includes(delimiter)) { delimiter += '.'; } return ( @@ -585,6 +596,8 @@ export function makeModuleContent( pythonCode + '\n\n\n' + '"""\n' + delimiter + '\n' + + MARKER_EVENTS + eventsContent + '\n' + + delimiter + '\n' + MARKER_COMPONENTS + componentsContent + '\n' + delimiter + '\n' + MARKER_MODULE_TYPE + module.moduleType + '\n' + @@ -639,6 +652,10 @@ function getParts(moduleContent: string): string[] { // This module was saved without components. parts.push(MARKER_COMPONENTS + '[]'); } + if (parts.length <= PARTS_INDEX_EVENTS) { + // This module was saved without events. + parts.push(MARKER_EVENTS + '[]'); + } return parts; } @@ -692,6 +709,19 @@ export function extractComponents(moduleContent: string): Component[] { return components; } +/** + * Extract the events from the given module content. + */ +export function extractEvents(moduleContent: string): Event[] { + const parts = getParts(moduleContent); + let eventsContent = parts[PARTS_INDEX_EVENTS]; + if (eventsContent.startsWith(MARKER_EVENTS)) { + eventsContent = eventsContent.substring(MARKER_EVENTS.length); + } + const events: Event[] = JSON.parse(eventsContent); + return events; +} + /** * Produce the blob for downloading a project. */ @@ -737,6 +767,10 @@ function _processModuleContentForDownload( if (componentsContent.startsWith(MARKER_COMPONENTS)) { componentsContent = componentsContent.substring(MARKER_COMPONENTS.length); } + let eventsContent = parts[PARTS_INDEX_EVENTS]; + if (eventsContent.startsWith(MARKER_EVENTS)) { + eventsContent = eventsContent.substring(MARKER_EVENTS.length); + } const module: Module = { modulePath: makeModulePath(projectName, moduleName), @@ -750,7 +784,12 @@ function _processModuleContentForDownload( // Clear out the python content. const pythonCode = ''; return makeModuleContent( - module, pythonCode, blocksContent, methodsContent, componentsContent); + module, + pythonCode, + blocksContent, + methodsContent, + componentsContent, + eventsContent); } /** @@ -842,6 +881,10 @@ export function _processUploadedModule( if (componentsContent.startsWith(MARKER_COMPONENTS)) { componentsContent = componentsContent.substring(MARKER_COMPONENTS.length); } + let eventsContent = parts[PARTS_INDEX_EVENTS]; + if (eventsContent.startsWith(MARKER_EVENTS)) { + eventsContent = eventsContent.substring(MARKER_EVENTS.length); + } const moduleName = (moduleType === MODULE_TYPE_ROBOT) ? projectName : filename; @@ -869,6 +912,11 @@ export function _processUploadedModule( headlessBlocklyWorkspace, generatorContext); const moduleContent = makeModuleContent( - module, pythonCode, blocksContent, methodsContent, componentsContent); + module, + pythonCode, + blocksContent, + methodsContent, + componentsContent, + eventsContent); return [moduleName, moduleType, moduleContent]; } diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index 12a5258d..f87bb3fc 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -25,6 +25,7 @@ import * as toolboxItems from './items'; import { getAllPossibleMechanisms } from './blocks_mechanisms'; import { getAllPossibleComponents } from '../blocks/mrc_component'; import { getInstanceComponentBlocks, addInstanceRobotBlocks } from '../blocks/mrc_call_python_function'; +import { addRobotEventHandlerBlocks } from '../blocks/mrc_event_handler'; import { Editor } from '../editor/editor'; export function getHardwareCategory(currentModule: commonStorage.Module): toolboxItems.Category { @@ -49,6 +50,7 @@ export function getHardwareCategory(currentModule: commonStorage.Module): toolbo getRobotMechanismsBlocks(currentModule), getRobotComponentsBlocks(), getRobotMethodsBlocks(), + getRobotEventsBlocks(), ] }; } @@ -300,3 +302,27 @@ function getComponentsBlocks(hideParams : boolean): toolboxItems.Category { contents, }; } + +function getRobotEventsBlocks(): toolboxItems.Category { + // getRobotEventsBlocks is called when the user is editing an opmode. + // It allows the user to create event handlers for events previously defined in the Robot. + + const contents: toolboxItems.ContentsType[] = []; + + // Get the list of events 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 eventsFromRobot = editor.getEventsFromRobot(); + addRobotEventHandlerBlocks(eventsFromRobot, contents); + } + } + + return { + kind: 'category', + name: Blockly.Msg['MRC_CATEGORY_EVENTS'], + contents, + }; +} diff --git a/src/toolbox/methods_category.ts b/src/toolbox/methods_category.ts index 8415c784..a7708920 100644 --- a/src/toolbox/methods_category.ts +++ b/src/toolbox/methods_category.ts @@ -26,7 +26,7 @@ import * as commonStorage from '../storage/common_storage'; import { MRC_CATEGORY_STYLE_METHODS } from '../themes/styles'; import { CLASS_NAME_ROBOT_BASE, CLASS_NAME_OPMODE, CLASS_NAME_MECHANISM } from '../blocks/utils/python'; import { addInstanceWithinBlocks } from '../blocks/mrc_call_python_function'; -import { createCustomMethodBlock, getBaseClassBlocks } from '../blocks/mrc_class_method_def'; +import { createCustomMethodBlock, getBaseClassBlocks, FIELD_METHOD_NAME } from '../blocks/mrc_class_method_def'; import { Editor } from '../editor/editor'; @@ -67,19 +67,27 @@ export class MethodsCategory { if (this.currentModule) { if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_ROBOT) { + // TODO(lizlooney): We need a way to mark a method in python as not overridable. + // For example, in RobotBase, register_event_handler, unregister_event_handler, + // and fire_event should not be overridden in a user's robot. + const methodNamesNotOverrideable: string[] = [ + 'register_event_handler', + 'unregister_event_handler', + 'fire_event', + ]; // Add the methods for a Robot. this.addClassBlocksForCurrentModule( - 'More Robot Methods', this.robotClassBlocks, + 'More Robot Methods', this.robotClassBlocks, methodNamesNotOverrideable, methodNamesAlreadyOverridden, contents); } else if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_MECHANISM) { // Add the methods for a Mechanism. this.addClassBlocksForCurrentModule( - 'More Mechanism Methods', this.mechanismClassBlocks, + 'More Mechanism Methods', this.mechanismClassBlocks, [], methodNamesAlreadyOverridden, contents); } else if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_OPMODE) { // Add the methods for an OpMode. this.addClassBlocksForCurrentModule( - 'More OpMode Methods', this.opmodeClassBlocks, + 'More OpMode Methods', this.opmodeClassBlocks, [], methodNamesAlreadyOverridden, contents); } } @@ -108,26 +116,31 @@ export class MethodsCategory { } private addClassBlocksForCurrentModule( - label: string, class_blocks: toolboxItems.Block[], + label: string, classBlocks: toolboxItems.Block[], + methodNamesNotOverrideable: string[], methodNamesAlreadyOverridden: string[], contents: toolboxItems.ContentsType[]) { let labelAdded = false; - class_blocks.forEach((blockInfo) => { + for (const blockInfo of classBlocks) { if (blockInfo.fields) { - const methodName = blockInfo.fields['NAME']; - if (!methodNamesAlreadyOverridden.includes(methodName)) { - if (!labelAdded) { - contents.push( - { - kind: 'label', - text: label, - }, - ); - labelAdded = true; - } - contents.push(blockInfo); + const methodName = blockInfo.fields[FIELD_METHOD_NAME]; + if (methodNamesNotOverrideable.includes(methodName)) { + continue; } + if (methodNamesAlreadyOverridden.includes(methodName)) { + continue; + } + if (!labelAdded) { + contents.push( + { + kind: 'label', + text: label, + }, + ); + labelAdded = true; + } + contents.push(blockInfo); } - }); + } } }