Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/blocks/mrc_call_python_function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ export const pythonFromBlock = function(
const functionName = callPythonFunctionBlock.mrcActualFunctionName
? callPythonFunctionBlock.mrcActualFunctionName
: block.getFieldValue(FIELD_FUNCTION_NAME);
code = 'self.robot.' + componentName + '.' + functionName;
code = 'self.' + componentName + '.' + functionName;
break;
}
default:
Expand Down
6 changes: 4 additions & 2 deletions src/blocks/mrc_class_method_def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ const CLASS_METHOD_DEF = {
this.setStyle(MRC_STYLE_CLASS_BLOCKS);
this.appendStatementInput('STACK').appendField('');
this.mrcParameters = [];
this.setPreviousStatement(false);
this.setNextStatement(false);
},
/**
* Returns the state of this block as a JSON serializable object.
Expand Down Expand Up @@ -353,12 +355,12 @@ export const pythonFromBlock = function (
}
if (block.mrcPythonMethodName == '__init__') {
let class_specific = generator.getClassSpecificForInit();
branch = generator.INDENT + 'super().__init__(' + class_specific + ')\n' +
branch = generator.INDENT + 'super.__init__(' + class_specific + ')\n' +
generator.defineClassVariables() + branch;
}
else if (funcName == 'update'){
// Special case for update, to also call the update method of the base class
branch = generator.INDENT + 'self.update()\n' + branch;
branch = generator.INDENT + 'super.update()\n' + branch;
}
if (returnValue) {
returnValue = generator.INDENT + 'return ' + returnValue + '\n';
Expand Down
222 changes: 222 additions & 0 deletions src/blocks/mrc_event_handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/**
* @license
* Copyright 2025 Porpoiseful LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @fileoverview Blocks for event handlers
* @author [email protected] (Alan Smith)
*/

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';

export const BLOCK_NAME = 'mrc_event_handler';

export enum SenderType {
ROBOT = 'robot',
MECHANISM = 'mechanism',
COMPONENT = 'component'
}

export interface Parameter {
name: string;
type?: string;
}

export type EventHandlerBlock = Blockly.Block & EventHandlerMixin & Blockly.BlockSvg;

interface EventHandlerMixin extends EventHandlerMixinType {
mrcPathOfSender: string;
mrcTypeOfSender: SenderType;
mrcParameters: Parameter[];
}

type EventHandlerMixinType = typeof EVENT_HANDLER;

/** Extra state for serialising event handler blocks. */
export interface EventHandlerExtraState {
pathOfSender: string;
typeOfSender: SenderType;
/** The parameters of the event handler. */
params: Parameter[];
}

const EVENT_HANDLER = {
/**
* Block initialization.
*/
init(this: EventHandlerBlock): void {
this.appendDummyInput('TITLE')
.appendField('When')
.appendField(createFieldNonEditableText('sender'), 'SENDER')
.appendField(createFieldNonEditableText('eventName'), 'EVENT_NAME');
this.appendDummyInput('PARAMS')
.appendField('with');
this.setOutput(false);
this.setStyle(MRC_STYLE_EVENT_HANDLER);
this.appendStatementInput('STACK').appendField('');
this.mrcParameters = [];
this.setPreviousStatement(false);
this.setNextStatement(false);
},

/**
* Returns the state of this block as a JSON serializable object.
*/
saveExtraState(this: EventHandlerBlock): EventHandlerExtraState {
const extraState: EventHandlerExtraState = {
pathOfSender: this.mrcPathOfSender,
typeOfSender: this.mrcTypeOfSender,
params: [],
};

this.mrcParameters.forEach((param) => {
extraState.params.push({
name: param.name,
type: param.type,
});
});

return extraState;
},

/**
* Applies the given state to this block.
*/
loadExtraState(this: EventHandlerBlock, extraState: EventHandlerExtraState): void {
this.mrcParameters = [];
this.mrcPathOfSender = extraState.pathOfSender;
this.mrcTypeOfSender = extraState.typeOfSender;

extraState.params.forEach((param) => {
this.mrcParameters.push({
name: param.name,
type: param.type,
});
});
this.mrcUpdateParams();
},

/**
* Update the block to reflect the newly loaded extra state.
*/
mrcUpdateParams(this: EventHandlerBlock): void {
if (this.mrcParameters.length > 0) {
const input = this.getInput('PARAMS');
if (input) {
this.removeParameterFields(input);
this.mrcParameters.forEach((param) => {
const paramName = `PARAM_${param.name}`;
input.appendField(createFieldFlydown(param.name, false), paramName);
});
}
} else {
this.removeInput('PARAMS', true);
}
},

/**
* Removes parameter fields from the given input.
*/
removeParameterFields(input: Blockly.Input): void {
const fieldsToRemove = input.fieldRow
.filter(field => field.name?.startsWith('PARAM_'))
.map(field => field.name!);

fieldsToRemove.forEach(fieldName => {
input.removeField(fieldName);
});
},
};

export function setup(): void {
Blockly.Blocks[BLOCK_NAME] = EVENT_HANDLER;
}

export function pythonFromBlock(
block: EventHandlerBlock,
generator: ExtendedPythonGenerator,
): string {
const blocklyName = `${block.getFieldValue('SENDER')}_${block.getFieldValue('EVENT_NAME')}`;
const funcName = generator.getProcedureName(blocklyName);

let xfix1 = '';
if (generator.STATEMENT_PREFIX) {
xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block);
}
if (generator.STATEMENT_SUFFIX) {
xfix1 += generator.injectId(generator.STATEMENT_SUFFIX, block);
}
if (xfix1) {
xfix1 = generator.prefixLines(xfix1, generator.INDENT);
}

let loopTrap = '';
if (generator.INFINITE_LOOP_TRAP) {
loopTrap = generator.prefixLines(
generator.injectId(generator.INFINITE_LOOP_TRAP, block),
generator.INDENT,
);
}

let branch = '';
if (block.getInput('STACK')) {
branch = generator.statementToCode(block, 'STACK');
}

let returnValue = '';
if (block.getInput('RETURN')) {
returnValue = generator.valueToCode(block, 'RETURN', Order.NONE) || '';
}

let xfix2 = '';
if (branch && returnValue) {
// After executing the function body, revisit this block for the return.
xfix2 = xfix1;
}

if (returnValue) {
returnValue = `${generator.INDENT}return ${returnValue}\n`;
} else if (!branch) {
branch = generator.PASS;
}

const params = block.mrcParameters;
let paramString = 'self';

if (params.length !== 0) {
block.mrcParameters.forEach((param) => {
paramString += `, ${param.name}`;
});
}

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);

return '';
}
18 changes: 14 additions & 4 deletions src/blocks/mrc_get_parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {ExtendedPythonGenerator} from '../editor/extended_python_generator';
import {createFieldNonEditableText} from '../fields/FieldNonEditableText';
import {MRC_STYLE_VARIABLES} from '../themes/styles';
import {BLOCK_NAME as MRC_CLASS_METHOD_DEF, ClassMethodDefBlock} from './mrc_class_method_def';
import {BLOCK_NAME as MRC_EVENT_HANDLER, EventHandlerBlock} from './mrc_event_handler';
import * as ChangeFramework from './utils/change_framework';


Expand All @@ -51,7 +52,7 @@ const GET_PARAMETER_BLOCK = {
.appendField('parameter')
.appendField(createFieldNonEditableText('parameter'), 'PARAMETER_NAME');

this.setOutput(true, [OUTPUT_NAME, this.parameterType]);
this.setOutput(true, this.parameterType);
ChangeFramework.registerCallback(BLOCK_NAME, [Blockly.Events.BLOCK_MOVE], this.onBlockChanged);
},
setNameAndType: function(this: GetParameterBlock, name: string, type: string): void {
Expand All @@ -64,9 +65,8 @@ const GET_PARAMETER_BLOCK = {
const blockBlock = block as Blockly.Block;

if (blockEvent.type === Blockly.Events.BLOCK_MOVE) {
const parent = ChangeFramework.getParentOfType(block, MRC_CLASS_METHOD_DEF);

if (parent) {
let parent = blockBlock.getRootBlock();
if( parent.type === MRC_CLASS_METHOD_DEF) {
// It is a class method definition, so we see if this variable is in it.
const classMethodDefBlock = parent as ClassMethodDefBlock;
for (const parameter of classMethodDefBlock.mrcParameters) {
Expand All @@ -77,6 +77,16 @@ const GET_PARAMETER_BLOCK = {
}
}
}
else if (parent.type === MRC_EVENT_HANDLER) {
const classMethodDefBlock = parent as ClassMethodDefBlock;
for (const parameter of classMethodDefBlock.mrcParameters) {
if (parameter.name === blockBlock.getFieldValue('PARAMETER_NAME')) {
// If it is, we allow it to stay.
blockBlock.setWarningText(null);
return;
}
}
}
// If we end up here it shouldn't be allowed
block.unplug(true);
blockBlock.setWarningText('Parameters can only go in their method\'s block.');
Expand Down
4 changes: 3 additions & 1 deletion src/blocks/setup_custom_blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as OpModeDetails from './mrc_opmode_details';
import * as Event from './mrc_event';
import * as GetParameter from './mrc_get_parameter';
import * as ParameterMutator from './mrc_param_container'
import * as EventHandler from './mrc_event_handler';

const customBlocks = [
CallPythonFunction,
Expand All @@ -34,7 +35,8 @@ const customBlocks = [
OpModeDetails,
Event,
GetParameter,
ParameterMutator
ParameterMutator,
EventHandler,
];

export const setup = function(forBlock: any) {
Expand Down
5 changes: 5 additions & 0 deletions src/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import * as toolboxRobot from '../toolbox/toolbox_robot';
//import { testAllBlocksInToolbox } from '../toolbox/toolbox_tests';
import { MethodsCategory} from '../toolbox/methods_category';
import { EventsCategory} from '../toolbox/event_category';
import { ComponentsCategory } from '../toolbox/components_category';


const EMPTY_TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = {
Expand All @@ -46,6 +47,7 @@ export class Editor {
private storage: commonStorage.Storage;
private methodsCategory: MethodsCategory;
private eventsCategory: EventsCategory;
private componentsCategory: ComponentsCategory;
private currentModule: commonStorage.Module | null = null;
private modulePath: string = '';
private projectPath: string = '';
Expand All @@ -61,6 +63,7 @@ export class Editor {
this.storage = storage;
this.methodsCategory = new MethodsCategory(blocklyWorkspace);
this.eventsCategory = new EventsCategory(blocklyWorkspace);
this.componentsCategory = new ComponentsCategory(blocklyWorkspace);
}

private onChangeWhileLoading(event: Blockly.Events.Abstract) {
Expand Down Expand Up @@ -124,6 +127,8 @@ export class Editor {
this.currentModule = currentModule;
this.methodsCategory.setCurrentModule(currentModule);
this.eventsCategory.setCurrentModule(currentModule);
this.componentsCategory.setCurrentModule(currentModule);

if (currentModule) {
this.modulePath = currentModule.modulePath;
this.projectPath = commonStorage.makeProjectPath(currentModule.projectName);
Expand Down
Loading