From 6bcace64b1b6f182e4fc09597e53b2eb70b195b7 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Wed, 16 Jul 2025 23:42:35 -0700 Subject: [PATCH 1/2] In common_storage, added Event type. In editor, added getEventsFromWorkspace function. In mrc_call_python_funcction: Added addFireEventBlocks and createFireEventBlock functions. Replace classMethodDefBlockId and componentBlockId with otherBlockId. Replace mrcClassMethodDefBlockId and componentBlockId with otherBlockId. Make renameMethodCallers and mutateMethodCallers work for fire event blocks. Added function getComponentsFromRobot. Removed mrcComponents. Make fire event block use a field called 'EVENT' instead of 'FUNC'. In mrc_component, added getComponent function. In mrc_class_method_def: Remove mrcMethod. Added function getMethod. Added mrcFuncName which is set during python code generation. Pass commonStorage.Method (instead of ClassMethodDefExtraState) to mutateMethodCallers. In mrc_event: Added mrcNameFieldValidator and getEvent functions. Call mutateMethodCallers and renameMethodCallers user changes parameters or name. In mrc_mechanism_component_holder, added getEvents function. In event_category, call editor.getEventsFromWorkspace and addFireEventBlocks instead of using the mrc_event and mrc_call_python_function implementation details here. --- src/blocks/mrc_call_python_function.ts | 311 +++++++++++-------- src/blocks/mrc_class_method_def.ts | 74 +++-- src/blocks/mrc_component.ts | 27 +- src/blocks/mrc_event.ts | 42 ++- src/blocks/mrc_mechanism_component_holder.ts | 37 ++- src/editor/editor.ts | 19 +- src/editor/extended_python_generator.ts | 1 + src/storage/common_storage.ts | 6 + src/toolbox/event_category.ts | 43 +-- src/toolbox/methods_category.ts | 4 +- 10 files changed, 345 insertions(+), 219 deletions(-) diff --git a/src/blocks/mrc_call_python_function.ts b/src/blocks/mrc_call_python_function.ts index 9944a601..bf8073bf 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -23,25 +23,24 @@ import * as Blockly from 'blockly'; import { Order } from 'blockly/python'; -import { ClassMethodDefExtraState } from './mrc_class_method_def' import { getClassData, getAllowedTypesForSetCheck, getOutputCheck } from './utils/python'; import { ArgData, FunctionData, findSuperFunctionData } from './utils/python_json_types'; -import * as Value from './utils/value'; -import * as Variable from './utils/variable'; +import * as value from './utils/value'; +import * as variable from './utils/variable'; import { Editor } from '../editor/editor'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import { createFieldDropdown } from '../fields/FieldDropdown'; import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; import { MRC_STYLE_FUNCTIONS } from '../themes/styles' -import * as ToolboxItems from '../toolbox/items'; -import * as CommonStorage from '../storage/common_storage'; +import * as toolboxItems from '../toolbox/items'; +import * as commonStorage from '../storage/common_storage'; // A block to call a python function. export const BLOCK_NAME = 'mrc_call_python_function'; -export enum FunctionKind { +enum FunctionKind { BUILT_IN = 'built-in', MODULE = 'module', STATIC = 'static', @@ -53,13 +52,14 @@ export enum FunctionKind { EVENT = 'event', } -export const RETURN_TYPE_NONE = 'None'; +const RETURN_TYPE_NONE = 'None'; 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'; -export type FunctionArg = { +type FunctionArg = { name: string, type: string, }; @@ -70,17 +70,13 @@ export type CallPythonFunctionBlock = Blockly.Block & CallPythonFunctionMixin & interface CallPythonFunctionMixin extends CallPythonFunctionMixinType { mrcFunctionKind: FunctionKind, mrcReturnType: string, - mrcArgs: FunctionArg[], + mrcArgs: FunctionArg[], // Include the self arg only if there is a socket for it. mrcTooltip: string, mrcImportModule: string, mrcActualFunctionName: string, - mrcClassMethodDefBlockId: string, + mrcOtherBlockId: string, mrcComponentClassName: string, - mrcComponents: CommonStorage.Component[], - mrcComponentName: string, - mrcComponentBlockId: string, - renameMethod(this: CallPythonFunctionBlock, newName: string): void; - mutateMethod(this: CallPythonFunctionBlock, defBlockExtraState: ClassMethodDefExtraState): void; + mrcOriginalComponentName: string, } type CallPythonFunctionMixinType = typeof CALL_PYTHON_FUNCTION; @@ -116,9 +112,12 @@ type CallPythonFunctionExtraState = { */ actualFunctionName?: string, /** - * The id of the mrc_class_method_def type that defines the method. Specified only if the function kind is INSTANCE_ROBOT. + * 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_COMPONENT, this is the id of an mrc_component block. + * For EVENT, this is the id of an mrc_event block. */ - classMethodDefBlockId?: string, + otherBlockId?: string, /** * The component name. Specified only if the function kind is INSTANCE_COMPONENT. */ @@ -127,10 +126,6 @@ type CallPythonFunctionExtraState = { * The component class name. Specified only if the function kind is INSTANCE_COMPONENT. */ componentClassName?: string, - /** - * The id of the mrc_component type that defines the component. Specified only if the function kind is INSTANCE_COMPONENT. - */ - componentBlockId?: string, }; const CALL_PYTHON_FUNCTION = { @@ -176,8 +171,8 @@ const CALL_PYTHON_FUNCTION = { break; } case FunctionKind.EVENT: { - const functionName = this.getFieldValue(FIELD_FUNCTION_NAME); - tooltip = 'Fires the event ' + functionName + '.'; + const eventName = this.getFieldValue(FIELD_EVENT_NAME); + tooltip = 'Fires the event ' + eventName + '.'; break; } case FunctionKind.INSTANCE_COMPONENT: { @@ -215,8 +210,8 @@ const CALL_PYTHON_FUNCTION = { if (this.mrcArgs){ this.mrcArgs.forEach((arg) => { extraState.args.push({ - 'name': arg.name, - 'type': arg.type, + name: arg.name, + type: arg.type, }); }); } @@ -229,18 +224,25 @@ const CALL_PYTHON_FUNCTION = { if (this.mrcActualFunctionName) { extraState.actualFunctionName = this.mrcActualFunctionName; } - if (this.mrcClassMethodDefBlockId) { - extraState.classMethodDefBlockId = this.mrcClassMethodDefBlockId; + if (this.mrcOtherBlockId) { + extraState.otherBlockId = this.mrcOtherBlockId; } if (this.mrcComponentClassName) { extraState.componentClassName = this.mrcComponentClassName; } if (this.getField(FIELD_COMPONENT_NAME)) { extraState.componentName = this.getFieldValue(FIELD_COMPONENT_NAME); - for (const component of this.mrcComponents) { - if (component.name == extraState.componentName) { - extraState.componentBlockId = component.blockId; - break; + // The component name field is a drop down where the user can choose between different + // components of the same type. For example, they can easily switch from a motor component + // name "left_motor" to a motor component named "right_motor". + if (extraState.componentName !== this.mrcOriginalComponentName) { + // The user has chosen a different component name. We need to get the block id of the + // mrc_component block that defines the component that the user has chosen. + for (const component of this.getComponentsFromRobot()) { + if (component.name == extraState.componentName) { + extraState.otherBlockId = component.blockId; + break; + } } } } @@ -258,8 +260,8 @@ const CALL_PYTHON_FUNCTION = { this.mrcArgs = []; extraState.args.forEach((arg) => { this.mrcArgs.push({ - 'name': arg.name, - 'type': arg.type, + name: arg.name, + type: arg.type, }); }); this.mrcTooltip = extraState.tooltip ? extraState.tooltip : ''; @@ -267,25 +269,23 @@ const CALL_PYTHON_FUNCTION = { ? extraState.importModule : ''; this.mrcActualFunctionName = extraState.actualFunctionName ? extraState.actualFunctionName : ''; - this.mrcClassMethodDefBlockId = extraState.classMethodDefBlockId - ? extraState.classMethodDefBlockId : ''; + this.mrcOtherBlockId = extraState.otherBlockId + ? extraState.otherBlockId : ''; + // TODO(lizlooney: Remove this if statement after Alan and I have updated our projects. + if (!this.mrcOtherBlockId) { + const oldExtraState = extraState as any; + if (oldExtraState.classMethodDefBlockId) { + this.mrcOtherBlockId = oldExtraState.classMethodDefBlockId; + } else if (oldExtraState.componentBlockId) { + this.mrcOtherBlockId = oldExtraState.componentBlockId; + } else if (oldExtraState.eventBlockId) { + this.mrcOtherBlockId = oldExtraState.eventBlockId; + } + } this.mrcComponentClassName = extraState.componentClassName ? extraState.componentClassName : ''; - // Get the list of components whose type matches this.mrcComponentClassName so we can - // create a dropdown that has the appropriate component name choices. - this.mrcComponents = []; - const editor = Editor.getEditorForBlocklyWorkspace(this.workspace); - if (editor) { - editor.getComponentsFromRobot().forEach(component => { - if (component.className === this.mrcComponentClassName) { - this.mrcComponents.push(component); - } - }); - } - this.mrcComponentName = extraState.componentName + this.mrcOriginalComponentName = extraState.componentName ? extraState.componentName : ''; - this.mrcComponentBlockId = extraState.componentBlockId - ? extraState.componentBlockId : ''; this.updateBlock_(); }, /** @@ -357,15 +357,15 @@ const CALL_PYTHON_FUNCTION = { if (!input) { this.appendDummyInput('TITLE') .appendField('fire') - .appendField(createFieldNonEditableText(''), FIELD_FUNCTION_NAME); + .appendField(createFieldNonEditableText(''), FIELD_EVENT_NAME); } break; } case FunctionKind.INSTANCE_COMPONENT: { const componentNameChoices : string[] = []; - this.mrcComponents.forEach(component => componentNameChoices.push(component.name)); - if (!componentNameChoices.includes(this.mrcComponentName)) { - componentNameChoices.push(this.mrcComponentName); + this.getComponentsFromRobot().forEach(component => componentNameChoices.push(component.name)); + if (!componentNameChoices.includes(this.mrcOriginalComponentName)) { + componentNameChoices.push(this.mrcOriginalComponentName); } this.appendDummyInput('TITLE') .appendField('call') @@ -415,23 +415,54 @@ const CALL_PYTHON_FUNCTION = { this.removeInput('ARG' + i); } }, - renameMethod: function(this: CallPythonFunctionBlock, newName: string): void { - this.setFieldValue(newName, FIELD_FUNCTION_NAME); + renameMethodCaller: function(this: CallPythonFunctionBlock, newName: string): void { + if (this.mrcFunctionKind == FunctionKind.EVENT) { + this.setFieldValue(newName, FIELD_EVENT_NAME); + } else if (this.mrcFunctionKind == FunctionKind.INSTANCE_WITHIN) { + // TODO(lizlooney): What about this.mrcActualFunctionName? Does it need to be updated? + this.setFieldValue(newName, FIELD_FUNCTION_NAME); + } }, - mutateMethod: function( + mutateMethodCaller: function( this: CallPythonFunctionBlock, - defBlockExtraState: ClassMethodDefExtraState + methodOrEvent: commonStorage.Method | commonStorage.Event ): void { - this.mrcReturnType = defBlockExtraState.returnType; - this.mrcArgs = []; - defBlockExtraState.params.forEach((param) => { - this.mrcArgs.push({ - 'name': param.name, - 'type': param.type ?? '', + if (this.mrcFunctionKind == FunctionKind.EVENT) { + const event = methodOrEvent as commonStorage.Event; + this.mrcArgs = []; + event.args.forEach((arg) => { + this.mrcArgs.push({ + name: arg.name, + type: arg.type, + }); }); - }); + } else if (this.mrcFunctionKind == FunctionKind.INSTANCE_WITHIN) { + const method = methodOrEvent as commonStorage.Method; + this.mrcReturnType = method.returnType; + this.mrcArgs = []; + // We don't include the arg for the self argument because we don't want a socket for it. + for (let i = 1; i < method.args.length; i++) { + this.mrcArgs.push({ + name: method.args[i].name, + type: method.args[i].type, + }); + } + } this.updateBlock_(); }, + getComponentsFromRobot: function(this: CallPythonFunctionBlock): commonStorage.Component[] { + // Get the list of components whose type matches this.mrcComponentClassName. + const components: commonStorage.Component[] = []; + const editor = Editor.getEditorForBlocklyWorkspace(this.workspace); + if (editor) { + editor.getComponentsFromRobot().forEach(component => { + if (component.className === this.mrcComponentClassName) { + components.push(component); + } + }); + } + return components; + }, onLoad: function(this: CallPythonFunctionBlock): void { const warnings: string[] = []; @@ -442,8 +473,9 @@ const CALL_PYTHON_FUNCTION = { // visible warning on it. if (this.mrcFunctionKind === FunctionKind.INSTANCE_COMPONENT) { let foundComponent = false; - for (const component of this.mrcComponents) { - if (component.blockId === this.mrcComponentBlockId) { + const components = this.getComponentsFromRobot(); + for (const component of components) { + if (component.blockId === this.mrcOtherBlockId) { foundComponent = true; // If the component name has changed, we can handle that. @@ -460,7 +492,7 @@ const CALL_PYTHON_FUNCTION = { } if (indexOfComponentName != -1) { const componentNameChoices : string[] = []; - this.mrcComponents.forEach(component => componentNameChoices.push(component.name)); + components.forEach(component => componentNameChoices.push(component.name)); titleInput.removeField(FIELD_COMPONENT_NAME); titleInput.insertFieldAt(indexOfComponentName, createFieldDropdown(componentNameChoices), FIELD_COMPONENT_NAME); @@ -491,7 +523,7 @@ const CALL_PYTHON_FUNCTION = { if (editor) { const robotMethods = editor.getMethodsFromRobot(); for (const robotMethod of robotMethods) { - if (robotMethod.blockId === this.mrcClassMethodDefBlockId) { + if (robotMethod.blockId === this.mrcOtherBlockId) { foundRobotMethod = true; // If the function name has changed, we can fix this block. @@ -604,9 +636,10 @@ export const pythonFromBlock = function( break; } case FunctionKind.EVENT: { - const blocklyName = block.getFieldValue(FIELD_FUNCTION_NAME); - const functionName = generator.getProcedureName(blocklyName); - code = 'if self.events.get("' + functionName + '", None):\n' + generator.INDENT + 'self.events["' + functionName + '"]'; + const eventName = block.getFieldValue(FIELD_EVENT_NAME); + code = + 'if self.events.get("' + eventName + '", None):\n' + + generator.INDENT + 'self.events["' + eventName + '"]'; break; } case FunctionKind.INSTANCE_COMPONENT: { @@ -616,11 +649,11 @@ export const pythonFromBlock = function( : block.getFieldValue(FIELD_FUNCTION_NAME); // Generate the correct code depending on the module type. switch (generator.getModuleType()) { - case CommonStorage.MODULE_TYPE_ROBOT: - case CommonStorage.MODULE_TYPE_MECHANISM: + case commonStorage.MODULE_TYPE_ROBOT: + case commonStorage.MODULE_TYPE_MECHANISM: code = 'self.'; break; - case CommonStorage.MODULE_TYPE_OPMODE: + case commonStorage.MODULE_TYPE_OPMODE: default: code = 'self.robot.'; break; @@ -652,45 +685,42 @@ function generateCodeForArguments( startIndex: number) { let code = ''; if (block.mrcArgs.length - startIndex === 1) { + // If there is only one input, put it on the same line as the function name. code += generator.valueToCode(block, 'ARG' + startIndex, Order.NONE) || 'None'; } else { - let delimiter = '\n' + generator.INDENT + generator.INDENT; + let delimiter = ''; for (let i = startIndex; i < block.mrcArgs.length; i++) { - code += delimiter; + code += delimiter + '\n' + generator.INDENT + generator.INDENT; code += generator.valueToCode(block, 'ARG' + i, Order.NONE) || 'None'; - delimiter = ',\n' + generator.INDENT + generator.INDENT; + delimiter = ','; } } return code; } -function getMethodCallers(workspace: Blockly.Workspace, name: string): Blockly.Block[] { +function getMethodCallers(workspace: Blockly.Workspace, otherBlockId: string): Blockly.Block[] { return workspace.getBlocksByType('mrc_call_python_function').filter((block) => { - const callBlock = block as CallPythonFunctionBlock; - return ( - callBlock.mrcFunctionKind === FunctionKind.INSTANCE_WITHIN && - callBlock.getFieldValue(FIELD_FUNCTION_NAME) === name - ); + return (block as CallPythonFunctionBlock).mrcOtherBlockId === otherBlockId; }); } -export function renameMethodCallers(workspace: Blockly.Workspace, oldName: string, newName: string): void { - for (const block of getMethodCallers(workspace, oldName)) { - (block as CallPythonFunctionBlock).renameMethod(newName); +export function renameMethodCallers(workspace: Blockly.Workspace, otherBlockId: string, newName: string): void { + for (const block of getMethodCallers(workspace, otherBlockId)) { + (block as CallPythonFunctionBlock).renameMethodCaller(newName); } } export function mutateMethodCallers( - workspace: Blockly.Workspace, methodName: string, defBlockExtraState: ClassMethodDefExtraState) { + workspace: Blockly.Workspace, otherBlockId: string, methodOrEvent: commonStorage.Method | commonStorage.Event) { const oldRecordUndo = Blockly.Events.getRecordUndo(); - for (const block of getMethodCallers(workspace, methodName)) { + for (const block of getMethodCallers(workspace, otherBlockId)) { const callBlock = block as CallPythonFunctionBlock; // Get the extra state before changing the call block. const oldExtraState = callBlock.saveExtraState(); // Apply the changes. - callBlock.mutateMethod(defBlockExtraState); + callBlock.mutateMethodCaller(methodOrEvent); // Get the extra state after changing the call block. const newExtraState = callBlock.saveExtraState(); @@ -715,14 +745,14 @@ export function mutateMethodCallers( export function addBuiltInFunctionBlocks( functions: FunctionData[], - contents: ToolboxItems.ContentsType[]) { + contents: toolboxItems.ContentsType[]) { functions.forEach(functionData => { contents.push(createBuiltInMethodBlock(functionData)); }); } function createBuiltInMethodBlock( - functionData: FunctionData): ToolboxItems.Block { + functionData: FunctionData): toolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: FunctionKind.BUILT_IN, returnType: functionData.returnType, @@ -744,14 +774,14 @@ function processArgs( for (let i = 0; i < args.length; i++) { let argName = args[i].name; if (i === 0 && argName === 'self' && declaringClassName) { - argName = Variable.getSelfArgName(declaringClassName); + argName = variable.getSelfArgName(declaringClassName); } extraState.args.push({ - 'name': argName, - 'type': args[i].type, + name: argName, + type: args[i].type, }); // Check if we should plug a variable getter block into the argument input socket. - const input = Value.valueForFunctionArgInput(args[i].type, args[i].defaultValue); + const input = value.valueForFunctionArgInput(args[i].type, args[i].defaultValue); if (input) { inputs['ARG' + i] = input; } @@ -761,12 +791,12 @@ function processArgs( function createBlock( extraState: CallPythonFunctionExtraState, fields: {[key: string]: any}, - inputs: {[key: string]: any}): ToolboxItems.Block { - let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); - if (extraState.returnType && extraState.returnType != 'None') { - const varName = Variable.varNameForType(extraState.returnType); + inputs: {[key: string]: any}): toolboxItems.Block { + let block = new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); + if (extraState.returnType && extraState.returnType != RETURN_TYPE_NONE) { + const varName = variable.varNameForType(extraState.returnType); if (varName) { - block = Variable.createVariableSetterBlock(varName, block); + block = variable.createVariableSetterBlock(varName, block); } } return block; @@ -775,7 +805,7 @@ function createBlock( export function addModuleFunctionBlocks( moduleName: string, functions: FunctionData[], - contents: ToolboxItems.ContentsType[]) { + contents: toolboxItems.ContentsType[]) { functions.forEach(functionData => { const block = createModuleFunctionOrStaticMethodBlock( FunctionKind.MODULE, moduleName, moduleName, functionData); @@ -786,7 +816,7 @@ export function addModuleFunctionBlocks( export function addStaticMethodBlocks( importModule: string, functions: FunctionData[], - contents: ToolboxItems.ContentsType[]) { + contents: toolboxItems.ContentsType[]) { functions.forEach(functionData => { if (functionData.declaringClassName) { const block = createModuleFunctionOrStaticMethodBlock( @@ -800,7 +830,7 @@ function createModuleFunctionOrStaticMethodBlock( functionKind: FunctionKind, importModule: string, moduleOrClassName: string, - functionData: FunctionData): ToolboxItems.Block { + functionData: FunctionData): toolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: functionKind, returnType: functionData.returnType, @@ -819,7 +849,7 @@ function createModuleFunctionOrStaticMethodBlock( export function addConstructorBlocks( importModule: string, functions: FunctionData[], - contents: ToolboxItems.ContentsType[]) { + contents: toolboxItems.ContentsType[]) { functions.forEach(functionData => { contents.push(createConstructorBlock(importModule, functionData)); }); @@ -827,7 +857,7 @@ export function addConstructorBlocks( function createConstructorBlock( importModule: string, - functionData: FunctionData): ToolboxItems.Block { + functionData: FunctionData): toolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: FunctionKind.CONSTRUCTOR, returnType: functionData.returnType, @@ -844,14 +874,14 @@ function createConstructorBlock( export function addInstanceMethodBlocks( functions: FunctionData[], - contents: ToolboxItems.ContentsType[]) { + contents: toolboxItems.ContentsType[]) { functions.forEach(functionData => { contents.push(createInstanceMethodBlock(functionData)); }); } function createInstanceMethodBlock( - functionData: FunctionData): ToolboxItems.Block { + functionData: FunctionData): toolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: FunctionKind.INSTANCE, returnType: functionData.returnType, @@ -862,32 +892,34 @@ function createInstanceMethodBlock( fields[FIELD_MODULE_OR_CLASS_NAME] = functionData.declaringClassName; fields[FIELD_FUNCTION_NAME] = functionData.functionName; const inputs: {[key: string]: any} = {}; + // We include the arg for the self argument for INSTANCE methods because we want a socket that + // the user can plug the object into. processArgs(functionData.args, extraState, inputs, functionData.declaringClassName); return createBlock(extraState, fields, inputs); } export function addInstanceWithinBlocks( - methods: CommonStorage.Method[], - contents: ToolboxItems.ContentsType[]) { + methods: commonStorage.Method[], + contents: toolboxItems.ContentsType[]) { methods.forEach(method => { contents.push(createInstanceWithinBlock(method)); }); } -function createInstanceWithinBlock(method: CommonStorage.Method): ToolboxItems.Block { +function createInstanceWithinBlock(method: commonStorage.Method): toolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: FunctionKind.INSTANCE_WITHIN, returnType: method.returnType, actualFunctionName: method.pythonName, args: [], - classMethodDefBlockId: method.blockId, + otherBlockId: method.blockId, }; const fields: {[key: string]: any} = {}; fields[FIELD_FUNCTION_NAME] = method.visibleName; const inputs: {[key: string]: any} = {}; - // Convert method.args from CommonStorage.MethodArg[] to ArgData[]. + // Convert method.args from commonStorage.MethodArg[] to ArgData[]. const args: ArgData[] = []; - // We don't include the arg for the self argument. + // 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, @@ -900,8 +932,8 @@ function createInstanceWithinBlock(method: CommonStorage.Method): ToolboxItems.B } export function getInstanceComponentBlocks( - component: CommonStorage.Component): ToolboxItems.ContentsType[] { - const contents: ToolboxItems.ContentsType[] = []; + component: commonStorage.Component): toolboxItems.ContentsType[] { + const contents: toolboxItems.ContentsType[] = []; const classData = getClassData(component.className); if (!classData) { @@ -928,7 +960,7 @@ export function getInstanceComponentBlocks( } function createInstanceComponentBlock( - component: CommonStorage.Component, functionData: FunctionData): ToolboxItems.Block { + component: commonStorage.Component, functionData: FunctionData): toolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: FunctionKind.INSTANCE_COMPONENT, returnType: functionData.returnType, @@ -937,7 +969,7 @@ function createInstanceComponentBlock( importModule: '', componentClassName: component.className, componentName: component.name, - componentBlockId: component.blockId, + otherBlockId: component.blockId, }; const fields: {[key: string]: any} = {}; fields[FIELD_COMPONENT_NAME] = component.name; @@ -945,34 +977,34 @@ function createInstanceComponentBlock( const inputs: {[key: string]: any} = {}; // For INSTANCE_COMPONENT functions, the 0 argument is 'self', but // self is represented by the FIELD_COMPONENT_NAME field. - // We don't include the arg for self. + // We don't include the arg for the self argument because we don't need a socket for it. const argsWithoutSelf = functionData.args.slice(1); processArgs(argsWithoutSelf, extraState, inputs); return createBlock(extraState, fields, inputs); } export function addInstanceRobotBlocks( - methods: CommonStorage.Method[], - contents: ToolboxItems.ContentsType[]) { + methods: commonStorage.Method[], + contents: toolboxItems.ContentsType[]) { methods.forEach(method => { contents.push(createInstanceRobotBlock(method)); }); } -function createInstanceRobotBlock(method: CommonStorage.Method): ToolboxItems.Block { +function createInstanceRobotBlock(method: commonStorage.Method): toolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: FunctionKind.INSTANCE_ROBOT, returnType: method.returnType, actualFunctionName: method.pythonName, args: [], - classMethodDefBlockId: method.blockId, + otherBlockId: method.blockId, }; const fields: {[key: string]: any} = {}; fields[FIELD_FUNCTION_NAME] = method.visibleName; const inputs: {[key: string]: any} = {}; - // Convert method.args from CommonStorage.MethodArg[] to ArgData[]. + // Convert method.args from commonStorage.MethodArg[] to ArgData[]. const args: ArgData[] = []; - // We don't include the arg for the self argument. + // 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, @@ -983,3 +1015,34 @@ function createInstanceRobotBlock(method: CommonStorage.Method): ToolboxItems.Bl processArgs(args, extraState, inputs); return createBlock(extraState, fields, inputs); } + +export function addFireEventBlocks( + events: commonStorage.Event[], + contents: toolboxItems.ContentsType[]) { + events.forEach(event => { + contents.push(createFireEventBlock(event)); + }); +} + +function createFireEventBlock(event: commonStorage.Event): toolboxItems.Block { + const extraState: CallPythonFunctionExtraState = { + functionKind: FunctionKind.EVENT, + returnType: RETURN_TYPE_NONE, + args: [], + otherBlockId: event.blockId, + }; + const fields: {[key: string]: any} = {}; + fields[FIELD_EVENT_NAME] = event.name; + const inputs: {[key: string]: any} = {}; + // Convert event.args from commonStorage.MethodArg[] to ArgData[]. + const args: ArgData[] = []; + event.args.forEach(methodArg => { + args.push({ + name: methodArg.name, + type: methodArg.type, + defaultValue: '', + }); + }); + processArgs(args, extraState, inputs); + return createBlock(extraState, fields, inputs); +} diff --git a/src/blocks/mrc_class_method_def.ts b/src/blocks/mrc_class_method_def.ts index 14de03f5..4eb0a389 100644 --- a/src/blocks/mrc_class_method_def.ts +++ b/src/blocks/mrc_class_method_def.ts @@ -38,7 +38,7 @@ export const BLOCK_NAME = 'mrc_class_method_def'; const FIELD_METHOD_NAME = 'NAME'; -export type Parameter = { +type Parameter = { name: string, type?: string, }; @@ -51,12 +51,12 @@ interface ClassMethodDefMixin extends ClassMethodDefMixinType { mrcReturnType: string, mrcParameters: Parameter[], mrcPythonMethodName: string, - mrcMethod: commonStorage.Method | null, + mrcFuncName: string | null, } type ClassMethodDefMixinType = typeof CLASS_METHOD_DEF; /** Extra state for serialising call_python_* blocks. */ -export type ClassMethodDefExtraState = { +type ClassMethodDefExtraState = { /** * Can change name and parameters and return type */ @@ -137,10 +137,9 @@ const CLASS_METHOD_DEF = { this.mrcCanBeCalledWithinClass = extraState.canBeCalledWithinClass; this.mrcCanBeCalledOutsideClass = extraState.canBeCalledOutsideClass; this.mrcPythonMethodName = extraState.pythonMethodName ? extraState.pythonMethodName : ''; + this.mrcFuncName = null; // Set during python code generation. this.mrcReturnType = extraState.returnType; this.mrcParameters = []; - this.mrcMethod = null; - extraState.params.forEach((param) => { this.mrcParameters.push({ 'name': param.name, @@ -148,7 +147,6 @@ const CLASS_METHOD_DEF = { }); }); this.updateBlock_(); - mutateMethodCallers(this.workspace, this.getFieldValue(FIELD_METHOD_NAME), this.saveExtraState()); }, /** * Update the block to reflect the newly loaded extra state. @@ -194,7 +192,9 @@ const CLASS_METHOD_DEF = { paramBlock.nextConnection && paramBlock.nextConnection.targetBlock(); } this.mrcUpdateParams(); - mutateMethodCallers(this.workspace, this.getFieldValue(FIELD_METHOD_NAME), this.saveExtraState()); + if (this.mrcCanBeCalledWithinClass) { + mutateMethodCallers(this.workspace, this.id, this.getMethodForWithin()); + } }, decompose: function (this: ClassMethodDefBlock, workspace: Blockly.Workspace) { // This is a special sub-block that only gets created in the mutator UI. @@ -259,15 +259,43 @@ const CLASS_METHOD_DEF = { const oldName = nameField.getValue(); if (oldName && oldName !== name && oldName !== legalName) { // Rename any callers. - renameMethodCallers(this.workspace, oldName, legalName); + renameMethodCallers(this.workspace, this.id, legalName); } return legalName; }, + getMethod: function (this: ClassMethodDefBlock): commonStorage.Method | null { + const method = { + blockId: this.id, + visibleName: this.getFieldValue(FIELD_METHOD_NAME), + pythonName: this.mrcFuncName, + returnType: this.mrcReturnType, + args: [{ + name: 'self', + type: '', + }], + }; + if (!method.pythonName) { + method.pythonName = method.visibleName; + } + this.mrcParameters.forEach(param => { + method.args.push({ + name: param.name, + type: param.type ? param.type : '', + }); + }); + return method; + }, getMethodForWithin: function (this: ClassMethodDefBlock): commonStorage.Method | null { - return this.mrcCanBeCalledWithinClass ? this.mrcMethod : null; + if (this.mrcCanBeCalledWithinClass) { + return this.getMethod(); + } + return null; }, getMethodForOutside: function (this: ClassMethodDefBlock): commonStorage.Method | null { - return this.mrcCanBeCalledOutsideClass ? this.mrcMethod : null; + if (this.mrcCanBeCalledOutsideClass) { + return this.getMethod(); + } + return null; }, canChangeSignature: function (this: ClassMethodDefBlock): boolean { return this.mrcCanChangeSignature; @@ -342,6 +370,7 @@ export const pythonFromBlock = function ( ) { const blocklyName = block.mrcPythonMethodName ? block.mrcPythonMethodName : block.getFieldValue(FIELD_METHOD_NAME); + // Call generator.getProcedureName so our function name is not a reserved word such as "while". const funcName = generator.getProcedureName(blocklyName); let xfix1 = ''; @@ -380,7 +409,8 @@ export const pythonFromBlock = function ( generator.defineClassVariables() + branch; } else if (generator.inBaseClassMethod(blocklyName)){ - // Special case for update, to also call the update method of the base class + // Special case for methods inherited from the based class: generate the + // call to the method in the base class. branch = generator.INDENT + 'super().' + blocklyName + '()\n' + branch; } if (returnValue) { @@ -416,25 +446,9 @@ export const pythonFromBlock = function ( code = generator.scrub_(block, code); generator.addClassMethodDefinition(funcName, code); - if (block.mrcCanBeCalledWithinClass || block.mrcCanBeCalledOutsideClass) { - // Update the mrcMethod. - block.mrcMethod = { - blockId: block.id, - visibleName: block.getFieldValue(FIELD_METHOD_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 : '', - }); - }); - } + // Save the name of the function we just generated so we can use it to create the commonStorage.Method. + // in the getMethod function. + block.mrcFuncName = funcName; return ''; } diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 5812ccea..0c19c032 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -26,8 +26,8 @@ import { MRC_STYLE_COMPONENTS } from '../themes/styles' import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import { getAllowedTypesForSetCheck, getClassData, getModuleData, getSubclassNames } from './utils/python'; -import * as ToolboxItems from '../toolbox/items'; -import * as CommonStorage from '../storage/common_storage'; +import * as toolboxItems from '../toolbox/items'; +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'; @@ -52,7 +52,7 @@ type ComponentExtraState = { params?: ConstructorArg[], } -type ComponentBlock = Blockly.Block & ComponentMixin; +export type ComponentBlock = Blockly.Block & ComponentMixin; interface ComponentMixin extends ComponentMixinType { mrcArgs: ConstructorArg[], hideParams: boolean, @@ -136,7 +136,16 @@ const COMPONENT = { } } } - } + }, + getComponent: function (this: ComponentBlock): commonStorage.Component | null { + const componentName = this.getFieldValue(FIELD_NAME); + const componentType = this.getFieldValue(FIELD_TYPE); + return { + blockId: this.id, + name: componentName, + className: componentType, + }; + }, } export const setup = function () { @@ -177,8 +186,8 @@ export const pythonFromBlock = function ( return code; } -export function getAllPossibleComponents(hideParams: boolean): ToolboxItems.ContentsType[] { - const contents: ToolboxItems.ContentsType[] = []; +export function getAllPossibleComponents(hideParams: boolean): toolboxItems.ContentsType[] { + const contents: toolboxItems.ContentsType[] = []; // Iterate through all the components subclasses and add definition blocks. const componentTypes = getSubclassNames('component.Component'); @@ -190,7 +199,7 @@ export function getAllPossibleComponents(hideParams: boolean): ToolboxItems.Cont const componentName = ( 'my_' + - CommonStorage.pascalCaseToSnakeCase( + commonStorage.pascalCaseToSnakeCase( componentType.substring(componentType.lastIndexOf('.') + 1))); classData.staticMethods.forEach(staticFunctionData => { @@ -204,7 +213,7 @@ export function getAllPossibleComponents(hideParams: boolean): ToolboxItems.Cont } function createComponentBlock( - componentName: string, classData: ClassData, staticFunctionData: FunctionData, hideParams: boolean): ToolboxItems.Block { + componentName: string, classData: ClassData, staticFunctionData: FunctionData, hideParams: boolean): toolboxItems.Block { const extraState: ComponentExtraState = { importModule: classData.moduleName, staticFunctionName: staticFunctionData.functionName, @@ -232,7 +241,7 @@ function createComponentBlock( } } } - return new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); + return new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); } function getPortTypeForArgument(argName: string): string | null { diff --git a/src/blocks/mrc_event.ts b/src/blocks/mrc_event.ts index c16c78c3..423b4582 100644 --- a/src/blocks/mrc_event.ts +++ b/src/blocks/mrc_event.ts @@ -20,7 +20,6 @@ * @author alan@porpoiseful.com (Alan Smith) */ import * as Blockly from 'blockly'; -import { Order } from 'blockly/python'; import { MRC_STYLE_EVENTS } from '../themes/styles' import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; @@ -28,13 +27,15 @@ import { MUTATOR_BLOCK_NAME, PARAM_CONTAINER_BLOCK_NAME, MethodMutatorArgBlock } import * as ChangeFramework from './utils/change_framework'; import { BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER } from './mrc_mechanism_component_holder'; import * as toolboxItems from '../toolbox/items'; +import * as commonStorage from '../storage/common_storage'; +import { renameMethodCallers, mutateMethodCallers } from './mrc_call_python_function' export const BLOCK_NAME = 'mrc_event'; export const OUTPUT_NAME = 'mrc_event'; const FIELD_EVENT_NAME = 'NAME'; -export type Parameter = { +type Parameter = { name: string, type?: string, }; @@ -112,7 +113,7 @@ const EVENT = { const nameField = new Blockly.FieldTextInput(name); input.insertFieldAt(0, nameField, FIELD_EVENT_NAME); this.setMutator(new Blockly.icons.MutatorIcon([MUTATOR_BLOCK_NAME], this)); - // nameField.setValidator(this.mrcNameFieldValidator.bind(this, nameField)); + nameField.setValidator(this.mrcNameFieldValidator.bind(this, nameField)); this.mrcUpdateParams(); }, @@ -135,7 +136,7 @@ const EVENT = { paramBlock.nextConnection && paramBlock.nextConnection.targetBlock(); } this.mrcUpdateParams(); - //mutateMethodCallers(this.workspace, this.getFieldValue(FIELD_EVENT_NAME), this.saveExtraState()); + mutateMethodCallers(this.workspace, this.id, this.getEvent()); }, decompose: function (this: EventBlock, workspace: Blockly.Workspace) { // This is a special sub-block that only gets created in the mutator UI. @@ -180,6 +181,18 @@ const EVENT = { input.removeField(fieldName); }); }, + mrcNameFieldValidator(this: EventBlock, 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; + }, onBlockChanged(block: Blockly.BlockSvg, blockEvent: Blockly.Events.BlockBase): void { const blockBlock = block as Blockly.Block; @@ -193,9 +206,23 @@ const EVENT = { } // If we end up here it shouldn't be allowed block.unplug(true); - blockBlock.setWarningText('Events can only go in the events block'); + blockBlock.setWarningText('Events can only go in the events section of the robot or mechanism'); } }, + getEvent: function (this: EventBlock): commonStorage.Event { + const event: commonStorage.Event = { + blockId: this.id, + name: this.getFieldValue(FIELD_EVENT_NAME), + args: [], + }; + this.mrcParameters.forEach(param => { + event.args.push({ + name: param.name, + type: param.type ? param.type : '', + }); + }); + return event; + }, } export const setup = function () { @@ -204,9 +231,8 @@ export const setup = function () { export const pythonFromBlock = function ( block: EventBlock, - generator: ExtendedPythonGenerator, -) { - //TODO (Alan): What should this do here?? + generator: ExtendedPythonGenerator) { + // TODO (Alan): What should this do here?? return ''; } diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index 7e3cf584..310fd49c 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -29,10 +29,11 @@ import * as commonStorage from '../storage/common_storage'; import { OUTPUT_NAME as MECHANISM_OUTPUT } from './mrc_mechanism'; import { BLOCK_NAME as MRC_MECHANISM_NAME } from './mrc_mechanism'; import { BLOCK_NAME as MRC_COMPONENT_NAME } from './mrc_component'; -import * as Component from './mrc_component'; import { OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component'; +import { ComponentBlock } from './mrc_component'; 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'; @@ -139,15 +140,9 @@ const MECHANISM_COMPONENT_HOLDER = { let componentBlock = componentsInput.connection.targetBlock(); while (componentBlock) { if (componentBlock.type === MRC_COMPONENT_NAME) { - const componentName = componentBlock.getFieldValue(Component.FIELD_NAME); - const componentType = componentBlock.getFieldValue(Component.FIELD_TYPE); - - if (componentName && componentType) { - components.push({ - blockId: componentBlock.id, - name: componentName, - className: componentType, - }); + const component = (componentBlock as ComponentBlock).getComponent(); + if (component) { + components.push(component); } } // Move to the next block in the chain @@ -157,6 +152,28 @@ const MECHANISM_COMPONENT_HOLDER = { return components; }, + getEvents: function (this: MechanismComponentHolderBlock): commonStorage.Event[] { + const events: commonStorage.Event[] = [] + + // Get event blocks from the EVENTS input + const eventsInput = this.getInput('EVENTS'); + if (eventsInput && eventsInput.connection) { + // Walk through all connected event blocks. + let eventBlock = eventsInput.connection.targetBlock(); + while (eventBlock) { + if (eventBlock.type === MRC_EVENT_NAME) { + const event = (eventBlock as EventBlock).getEvent(); + if (event) { + events.push(event); + } + } + // Move to the next block in the chain + eventBlock = eventBlock.getNextBlock(); + } + } + + return events; + }, } let toolboxUpdateTimeout: NodeJS.Timeout | null = null; diff --git a/src/editor/editor.ts b/src/editor/editor.ts index b581e912..b790dab9 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -225,9 +225,7 @@ export class Editor { holderBlocks.forEach(holderBlock => { const componentsFromHolder: commonStorage.Component[] = (holderBlock as mechanismComponentHolder.MechanismComponentHolderBlock).getComponents(); - componentsFromHolder.forEach(component => { - components.push(component); - }); + components.push(...componentsFromHolder); }); } return components; @@ -280,6 +278,21 @@ export class Editor { 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); + }); + } + return events; + } + public async saveBlocks() { const moduleContent = this.getModuleContent(); try { diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index 1bb16c3e..207bd553 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -249,6 +249,7 @@ export class ExtendedPythonGenerator extends PythonGenerator { * @returns list of method names */ getBaseClassMethods() : string[] { + // TODO(lizlooney): the names of base class methods should not be hard coded. let classParent = this.context?.getClassParent(); if (classParent == 'OpMode'){ return ['start', 'loop', 'stop']; diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 8650fa5c..a6729d1d 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -72,6 +72,12 @@ export type Component = { className: string, } +export type Event = { + blockId: string, // ID of the mrc_event block that defines the event. + name: string, + args: MethodArg[], +}; + export const MODULE_TYPE_UNKNOWN = 'unknown'; export const MODULE_TYPE_ROBOT = 'robot'; export const MODULE_TYPE_MECHANISM = 'mechanism'; diff --git a/src/toolbox/event_category.ts b/src/toolbox/event_category.ts index f388e17e..c2deea18 100644 --- a/src/toolbox/event_category.ts +++ b/src/toolbox/event_category.ts @@ -23,10 +23,10 @@ import * as Blockly from 'blockly/core'; import * as toolboxItems from './items'; import * as commonStorage from '../storage/common_storage'; -import { EventBlock } from '../blocks/mrc_event'; import { MRC_CATEGORY_STYLE_METHODS } from '../themes/styles'; -import { RETURN_TYPE_NONE, FunctionKind } from '../blocks/mrc_call_python_function'; import { createCustomEventBlock } from '../blocks/mrc_event'; +import { addFireEventBlocks } from '../blocks/mrc_call_python_function'; +import { Editor } from '../editor/editor'; const CUSTOM_CATEGORY_EVENTS = 'EVENTS'; @@ -57,38 +57,15 @@ export class EventsCategory { kind: 'label', text: 'Custom Events', }, - createCustomEventBlock()); + createCustomEventBlock() + ); - // For each mrc_class_method_def block in the blockly workspace, check if it - // can be called from within the class, and if so, add a - // mrc_call_python_function block. - workspace.getBlocksByType('mrc_event', false).forEach((block) => { - const eventBlock = block as EventBlock; - const callPythonFunctionBlock: toolboxItems.Block = { - kind: 'block', - type: 'mrc_call_python_function', - extraState: { - functionKind: FunctionKind.EVENT, - returnType: RETURN_TYPE_NONE, - args: [], - importModule: '', - }, - fields: { - FUNC: block.getFieldValue('NAME'), - }, - }; - // Add the parameters from the event block to the callPythonFunctionBlock. - eventBlock.mrcParameters.forEach((param) => { - if (callPythonFunctionBlock.extraState) { - callPythonFunctionBlock.extraState.args.push( - { - name: param.name, - type: param.type ?? '', - }); - } - }); - contents.push(callPythonFunctionBlock); - }); + // Get blocks for firing methods defined in the current workspace. + const editor = Editor.getEditorForBlocklyWorkspace(workspace); + if (editor) { + const eventsFromWorkspace = editor.getEventsFromWorkspace(); + addFireEventBlocks(eventsFromWorkspace, contents); + } const toolboxInfo = { contents: contents, diff --git a/src/toolbox/methods_category.ts b/src/toolbox/methods_category.ts index d39c4487..8da4fbcb 100644 --- a/src/toolbox/methods_category.ts +++ b/src/toolbox/methods_category.ts @@ -23,8 +23,8 @@ import * as Blockly from 'blockly/core'; import * as toolboxItems from './items'; import * as commonStorage from '../storage/common_storage'; -import { MRC_CATEGORY_STYLE_METHODS } from '../themes/styles' -import { addInstanceWithinBlocks } from '../blocks/mrc_call_python_function' +import { MRC_CATEGORY_STYLE_METHODS } from '../themes/styles'; +import { addInstanceWithinBlocks } from '../blocks/mrc_call_python_function'; import { createCustomMethodBlock, getBaseClassBlocks } from '../blocks/mrc_class_method_def'; import { Editor } from '../editor/editor'; From 50d2508d67be545ea8fa3302f930414e8919fe90 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Thu, 17 Jul 2025 18:04:35 -0700 Subject: [PATCH 2/2] Fixed typescript problems. --- src/blocks/mrc_class_method_def.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/blocks/mrc_class_method_def.ts b/src/blocks/mrc_class_method_def.ts index 4eb0a389..979feb90 100644 --- a/src/blocks/mrc_class_method_def.ts +++ b/src/blocks/mrc_class_method_def.ts @@ -193,7 +193,10 @@ const CLASS_METHOD_DEF = { } this.mrcUpdateParams(); if (this.mrcCanBeCalledWithinClass) { - mutateMethodCallers(this.workspace, this.id, this.getMethodForWithin()); + const methodForWithin = this.getMethodForWithin(); + if (methodForWithin) { + mutateMethodCallers(this.workspace, this.id, methodForWithin); + } } }, decompose: function (this: ClassMethodDefBlock, workspace: Blockly.Workspace) { @@ -264,10 +267,10 @@ const CLASS_METHOD_DEF = { return legalName; }, getMethod: function (this: ClassMethodDefBlock): commonStorage.Method | null { - const method = { + const method: commonStorage.Method = { blockId: this.id, visibleName: this.getFieldValue(FIELD_METHOD_NAME), - pythonName: this.mrcFuncName, + pythonName: this.mrcFuncName ? this.mrcFuncName : '', returnType: this.mrcReturnType, args: [{ name: 'self',