Skip to content
Merged
252 changes: 138 additions & 114 deletions src/blocks/mrc_class_method_def.ts

Large diffs are not rendered by default.

112 changes: 112 additions & 0 deletions src/blocks/mrc_event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* @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 Creates an event that can be fired
* @author [email protected] (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';
import { MUTATOR_BLOCK_NAME } from './mrc_class_method_def'

export const BLOCK_NAME = 'mrc_event';
export const OUTPUT_NAME = 'mrc_event';

export type Parameter = {
name: string,
type?: string,
};

type EventExtraState = {
params?: Parameter[],
}

type EventBlock = Blockly.Block & EventMixin & Blockly.BlockSvg;

interface EventMixin extends EventMixinType {
mrcParams: Parameter[],
}
type EventMixinType = typeof EVENT;

const EVENT = {
/**
* Block initialization.
*/
init: function (this: EventBlock): void {
this.setStyle(MRC_STYLE_EVENTS);
this.appendDummyInput()
.appendField(new Blockly.FieldTextInput('my_event'), 'NAME');
this.setPreviousStatement(true, OUTPUT_NAME);
this.setNextStatement(true, OUTPUT_NAME);
this.setMutator(new Blockly.icons.MutatorIcon([MUTATOR_BLOCK_NAME], this));
},

/**
* Returns the state of this block as a JSON serializable object.
*/
saveExtraState: function (this: EventBlock): EventExtraState {
const extraState: EventExtraState = {
};
extraState.params = [];
if (this.mrcParams) {
this.mrcParams.forEach((arg) => {
extraState.params!.push({
'name': arg.name,
'type': arg.type,
});
});
}
return extraState;
},
/**
* Applies the given state to this block.
*/
loadExtraState: function (this: EventBlock, extraState: EventExtraState): void {
this.mrcParams = [];

if (extraState.params) {
extraState.params.forEach((arg) => {
this.mrcParams.push({
'name': arg.name,
'type': arg.type,
});
});
}
this.mrcParams = extraState.params ? extraState.params : [];
this.updateBlock_();
},
/**
* Update the block to reflect the newly loaded extra state.
*/
updateBlock_: function (this: EventBlock): void {
}
}

export const setup = function () {
Blockly.Blocks[BLOCK_NAME] = EVENT;
}

export const pythonFromBlock = function (
block: EventBlock,
generator: ExtendedPythonGenerator,
) {
//TODO (Alan): What should this do here??
return '';
}
99 changes: 99 additions & 0 deletions src/blocks/mrc_get_parameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* @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 This is a block that allows your code to use a parameter
* that is passed to a method.
* @author [email protected] (Alan Smith)
*/
import * as Blockly from 'blockly';
import {Order} from 'blockly/python';

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 * as ChangeFramework from './utils/change_framework';


export const BLOCK_NAME = 'mrc_get_parameter';
export const OUTPUT_NAME = 'mrc_get_parameter_output';


type GetParameterBlock = Blockly.Block & Blockly.BlockSvg & GetParameterMixin;

interface GetParameterMixin extends GetParameterMixinType {}

type GetParameterMixinType = typeof GET_PARAMETER_BLOCK;

const GET_PARAMETER_BLOCK = {
parameterType: '', // Later this will be set to the type of the parameter, e.g. 'string', 'number', etc.
/**
* Block initialization.
*/
init: function(this: GetParameterBlock): void {
this.setStyle(MRC_STYLE_VARIABLES);
this.appendDummyInput()
.appendField('parameter')
.appendField(createFieldNonEditableText('parameter'), 'PARAMETER_NAME');

this.setOutput(true, [OUTPUT_NAME, this.parameterType]);
ChangeFramework.registerCallback(BLOCK_NAME, [Blockly.Events.BLOCK_MOVE], this.onBlockChanged);
},
setNameAndType: function(this: GetParameterBlock, name: string, type: string): void {
this.setFieldValue(name, 'PARAMETER_NAME');
this.parameterType = type;
this.setOutput(true, [OUTPUT_NAME, type]);
},

onBlockChanged(block: Blockly.BlockSvg, blockEvent: Blockly.Events.BlockBase): void {
const blockBlock = block as Blockly.Block;

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

if (parent) {
// 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) {
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.');
}
},
};

export const setup = function() {
Blockly.Blocks[BLOCK_NAME] = GET_PARAMETER_BLOCK;
};

export const pythonFromBlock = function(
block: GetParameterBlock,
_generator: ExtendedPythonGenerator,
) {
// TODO (Alan) : Specify the type here as well
const code = block.getFieldValue('PARAMETER_NAME');

return [code, Order.ATOMIC];
};
9 changes: 6 additions & 3 deletions src/blocks/mrc_mechanism_component_holder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
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 { OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component';
import { BLOCK_NAME as MRC_EVENT_NAME } from './mrc_event';
import { OUTPUT_NAME as EVENT_OUTPUT } from './mrc_event';

export const BLOCK_NAME = 'mrc_mechanism_component_holder';

Expand Down Expand Up @@ -69,12 +70,14 @@ const MECHANISM_COMPONENT_HOLDER = {
this.setInputsInline(false);
this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms');
this.appendStatementInput('COMPONENTS').setCheck(COMPONENT_OUTPUT).appendField('Components');
this.appendStatementInput('EVENTS').setCheck(EVENT_OUTPUT).appendField('Events');


this.setOutput(false);
this.setStyle(MRC_STYLE_MECHANISMS);
ChangeFramework.registerCallback(MRC_COMPONENT_NAME, [Blockly.Events.BLOCK_MOVE, Blockly.Events.BLOCK_CHANGE], this.onBlockChanged);
ChangeFramework.registerCallback(MRC_MECHANISM_NAME, [Blockly.Events.BLOCK_MOVE, Blockly.Events.BLOCK_CHANGE], this.onBlockChanged);

ChangeFramework.registerCallback(MRC_EVENT_NAME, [Blockly.Events.BLOCK_MOVE, Blockly.Events.BLOCK_CHANGE], this.onBlockChanged);
},
saveExtraState: function (this: MechanismComponentHolderBlock): MechanismComponentHolderExtraState {
const extraState: MechanismComponentHolderExtraState = {
Expand Down Expand Up @@ -140,7 +143,7 @@ function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator:
if (body != '') {
code += body;
}

generator.addClassMethodDefinition('define_hardware', code);
}

Expand Down
6 changes: 5 additions & 1 deletion src/blocks/setup_custom_blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import * as Component from './mrc_component';
import * as MechanismContainerHolder from './mrc_mechanism_component_holder';
import * as Port from './mrc_port';
import * as OpModeDetails from './mrc_opmode_details';
import * as Event from './mrc_event';
import * as GetParameter from './mrc_get_parameter';

const customBlocks = [
CallPythonFunction,
Expand All @@ -28,7 +30,9 @@ const customBlocks = [
Component,
MechanismContainerHolder,
Port,
OpModeDetails
OpModeDetails,
Event,
GetParameter
];

export const setup = function(forBlock: any) {
Expand Down
50 changes: 50 additions & 0 deletions src/blocks/utils/find_connected_blocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @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 Allow registering for specific callbacks
* @author [email protected] (Alan Smith)
*/
import * as Blockly from 'blockly';

export function findConnectedBlocksOfType(block: Blockly.Block, targetType: string): Blockly.Block[] {
const foundBlocks: Blockly.Block[] = [];
const visited = new Set<string>(); // Prevent infinite loops

function searchRecursive(currentBlock: Blockly.Block): void {
if (visited.has(currentBlock.id)) return;
visited.add(currentBlock.id);

// Check if current block matches target type
if (currentBlock.type === targetType) {
foundBlocks.push(currentBlock);
}

// Search through all inputs
currentBlock.inputList.forEach(input => {
if (input.connection && input.connection.isConnected()) {
const connectedBlock = input.connection.targetBlock();
if (connectedBlock) {
searchRecursive(connectedBlock);
}
}
});
}

searchRecursive(block);
return foundBlocks;
}
2 changes: 1 addition & 1 deletion src/fields/FieldNonEditableText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import * as Blockly from 'blockly/core';
class FieldNonEditableText extends Blockly.FieldTextInput {
constructor(value: string) {
super(value);
this.CURSOR = '';
this.EDITABLE = false; // This field is not editable
}

protected override showEditor_() {
Expand Down
Loading