From f5c6b602278be43d53210c302584d50d9ae41176 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 8 Apr 2025 20:59:44 -0400 Subject: [PATCH 01/23] Initial mechanisms and components --- src/blocks/mrc_component.ts | 126 +++++++++++++++++++ src/blocks/mrc_mechanism.ts | 16 +-- src/blocks/mrc_mechanism_component_holder.ts | 60 +++++++++ src/blocks/setup_custom_blocks.ts | 4 + src/modules/robot_start.json | 7 ++ src/themes/styles.ts | 34 +++-- src/toolbox/hardware_category.ts | 87 +++++++++++++ src/toolbox/mechanism_category.ts | 51 -------- src/toolbox/methods_category.ts | 4 + src/toolbox/toolbox.ts | 2 + 10 files changed, 323 insertions(+), 68 deletions(-) create mode 100644 src/blocks/mrc_component.ts create mode 100644 src/blocks/mrc_mechanism_component_holder.ts create mode 100644 src/toolbox/hardware_category.ts delete mode 100644 src/toolbox/mechanism_category.ts diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts new file mode 100644 index 00000000..b7632c40 --- /dev/null +++ b/src/blocks/mrc_component.ts @@ -0,0 +1,126 @@ +/** + * @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 Create a component with a name of a certain type + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly'; +import { Order } from 'blockly/python'; + +import { MRC_STYLE_COMPONENTS } from '../themes/styles' +import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; +import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; +import { getAllowedTypesForSetCheck } from './utils/python'; + +export const BLOCK_NAME = 'mrc_component'; +export const OUTPUT_NAME = 'mrc_component'; + +export type ConstructorArg = { + name: string, + type: string, +}; + +type ComponentExtraState = { + importModule?: string, + params?: ConstructorArg[], +} + +type ComponentBlock = Blockly.Block & ComponentMixin; +interface ComponentMixin extends ComponentMixinType { + mrcArgs: ConstructorArg[], + mrcImportModule: string, +} +type ComponentMixinType = typeof COMPONENT; + +const COMPONENT = { + /** + * Block initialization. + */ + init: function (this: ComponentBlock): void { + this.setStyle(MRC_STYLE_COMPONENTS); + this.appendDummyInput() + .appendField(new Blockly.FieldTextInput('my_mech'), 'NAME') + .appendField('of type') + .appendField(createFieldNonEditableText(''), 'TYPE'); + this.setPreviousStatement(false); + this.setNextStatement(false); + this.setOutput(true, OUTPUT_NAME); + }, + + /** + * Returns the state of this block as a JSON serializable object. + */ + saveExtraState: function (this: ComponentBlock): ComponentExtraState { + const extraState: ComponentExtraState = { + }; + extraState.params = []; + this.mrcArgs.forEach((arg) => { + extraState.params!.push({ + 'name': arg.name, + 'type': arg.type, + }); + }); + if (this.mrcImportModule) { + extraState.importModule = this.mrcImportModule; + } + return extraState; + }, + /** + * Applies the given state to this block. + */ + loadExtraState: function (this: ComponentBlock, extraState: ComponentExtraState): void { + this.mrcImportModule = extraState.importModule ? extraState.importModule : ''; + this.mrcArgs = []; + + if(extraState.params){ + extraState.params.forEach((arg) => { + this.mrcArgs.push({ + 'name': arg.name, + 'type': arg.type, + }); + }); + } + this.mrcArgs = extraState.params ? extraState.params : []; + this.updateBlock_(); + }, + /** + * Update the block to reflect the newly loaded extra state. + */ + updateBlock_: function(this: ComponentBlock): void { + // Add input sockets for the arguments. + for (let i = 0; i < this.mrcArgs.length; i++) { + const input = this.appendValueInput('ARG' + i) + .setAlign(Blockly.inputs.Align.RIGHT) + .appendField(this.mrcArgs[i].name); + if (this.mrcArgs[i].type) { + input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type)); + } + } + } +} + +export const setup = function () { + Blockly.Blocks[BLOCK_NAME] = COMPONENT; +} + +export const pythonFromBlock = function ( + componentBlock: ComponentBlock, + generator: ExtendedPythonGenerator, +) { + return ''; +} diff --git a/src/blocks/mrc_mechanism.ts b/src/blocks/mrc_mechanism.ts index 56d8c6ad..23f8a2ce 100644 --- a/src/blocks/mrc_mechanism.ts +++ b/src/blocks/mrc_mechanism.ts @@ -22,7 +22,7 @@ import * as Blockly from 'blockly'; import { Order } from 'blockly/python'; -import { MRC_STYLE_FUNCTIONS } from '../themes/styles' +import { MRC_STYLE_MECHANISMS } from '../themes/styles' import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import { getAllowedTypesForSetCheck } from './utils/python'; @@ -44,22 +44,22 @@ type MechanismBlock = Blockly.Block & MechanismMixin; interface MechanismMixin extends MechanismMixinType { mrcArgs: ConstructorArg[], mrcImportModule: string, - } -type MechanismMixinType = typeof MECHANISM_FUNCTION; +type MechanismMixinType = typeof MECHANISM; -const MECHANISM_FUNCTION = { +const MECHANISM = { /** * Block initialization. */ init: function (this: MechanismBlock): void { - this.setStyle(MRC_STYLE_FUNCTIONS); + this.setStyle(MRC_STYLE_MECHANISMS); this.appendDummyInput() .appendField(new Blockly.FieldTextInput('my_mech'), 'NAME') .appendField('of type') .appendField(createFieldNonEditableText(''), 'TYPE'); - this.setPreviousStatement(true); - this.setNextStatement(true); + this.setPreviousStatement(false); + this.setNextStatement(false); + this.setOutput(true, OUTPUT_NAME); }, /** @@ -115,7 +115,7 @@ const MECHANISM_FUNCTION = { } export const setup = function () { - Blockly.Blocks[BLOCK_NAME] = MECHANISM_FUNCTION; + Blockly.Blocks[BLOCK_NAME] = MECHANISM; } export const pythonFromBlock = function ( diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts new file mode 100644 index 00000000..5b4ef36c --- /dev/null +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -0,0 +1,60 @@ +/** + * @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 to hold mechanisms and containers + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly'; + +import { MRC_STYLE_MECHANISMS } from '../themes/styles'; +import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; +import {OUTPUT_NAME as MECHANISM_OUTPUT } from './mrc_mechanism'; +import {OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component'; + + +export const BLOCK_NAME = 'mrc_mechanism_component_holder'; + + +type MechanismComponentHolderBlock = Blockly.Block & MechanismComponentHolderMixin; +interface MechanismComponentHolderMixin extends MechanismComponentHolderMixinType { +} +type MechanismComponentHolderMixinType = typeof MECHANISM_COMPONENT_HOLDER; + +const MECHANISM_COMPONENT_HOLDER = { + /** + * Block initialization. + */ + init: function (this: MechanismComponentHolderBlock): void { + this.setInputsInline(false); + this.appendValueInput('MECHANISM_1').appendField('Mechanisms').setCheck(MECHANISM_OUTPUT); + this.appendValueInput('COMPONENT_1').appendField('Components').setCheck(COMPONENT_OUTPUT); + this.setOutput(false); + this.setStyle(MRC_STYLE_MECHANISMS); + }, +} + +export const setup = function () { + Blockly.Blocks[BLOCK_NAME] = MECHANISM_COMPONENT_HOLDER; +} + +export const pythonFromBlock = function ( + MechanismComponentHolderBlock: MechanismComponentHolderBlock, + generator: ExtendedPythonGenerator, +) { + return '' +} \ No newline at end of file diff --git a/src/blocks/setup_custom_blocks.ts b/src/blocks/setup_custom_blocks.ts index 0f40f6f5..983483c3 100644 --- a/src/blocks/setup_custom_blocks.ts +++ b/src/blocks/setup_custom_blocks.ts @@ -9,6 +9,8 @@ import * as MiscEvaluateButIgnoreResult from './mrc_misc_evaluate_but_ignore_res import * as SetPythonVariable from './mrc_set_python_variable'; import * as ClassMethodDef from './mrc_class_method_def'; import * as Mechanism from './mrc_mechanism'; +import * as Component from './mrc_component'; +import * as MechanismContainerHolder from './mrc_mechanism_component_holder'; const customBlocks = [ CallPythonFunction, @@ -21,6 +23,8 @@ const customBlocks = [ MiscEvaluateButIgnoreResult, SetPythonVariable, Mechanism, + Component, + MechanismContainerHolder, ]; export const setup = function(forBlock: any) { diff --git a/src/modules/robot_start.json b/src/modules/robot_start.json index 6452b44e..2135cd75 100644 --- a/src/modules/robot_start.json +++ b/src/modules/robot_start.json @@ -19,6 +19,13 @@ "fields": { "NAME": "init" } + }, + { + "type": "mrc_mechanism_component_holder", + "id": "ElbOAb{)s(,UprTW(|1C", + "x": 210, + "y": 10, + "deletable": false } ] } diff --git a/src/themes/styles.ts b/src/themes/styles.ts index cf750cbc..18c79feb 100644 --- a/src/themes/styles.ts +++ b/src/themes/styles.ts @@ -28,15 +28,17 @@ export const MRC_STYLE_COMMENTS = 'mrc_style_comments'; export const MRC_STYLE_MISC = 'mrc_style_misc'; export const MRC_STYLE_CLASS_BLOCKS = 'mrc_style_class_blocks'; export const MRC_CATEGORY_STYLE_METHODS = 'mrc_category_style_methods'; +export const MRC_STYLE_MECHANISMS = 'mrc_style_mechanisms'; +export const MRC_STYLE_COMPONENTS = 'mrc_style_components'; -export const add_mrc_styles = function(theme : Blockly.Theme) : Blockly.Theme { - theme.setBlockStyle(MRC_STYLE_FUNCTIONS,{ +export const add_mrc_styles = function (theme: Blockly.Theme): Blockly.Theme { + theme.setBlockStyle(MRC_STYLE_FUNCTIONS, { colourPrimary: "#805ba5", colourSecondary: "#e6deed", colourTertiary: "#664984", hat: "" }); - theme.setBlockStyle(MRC_STYLE_ENUM,{ + theme.setBlockStyle(MRC_STYLE_ENUM, { colourPrimary: "#5ba5a5", colourSecondary: "#deeded", colourTertiary: "#498484", @@ -46,13 +48,13 @@ export const add_mrc_styles = function(theme : Blockly.Theme) : Blockly.Theme { colourPrimary: "#5ba55b", colourSecondary: "#deedde", colourTertiary: "#498449", - hat:"" + hat: "" }); theme.setBlockStyle(MRC_STYLE_CLASS_BLOCKS, { colourPrimary: "#4a148c", - colourSecondary:"#AD7BE9", - colourTertiary:"#CDB6E9", - hat:"" + colourSecondary: "#AD7BE9", + colourTertiary: "#CDB6E9", + hat: "" }); theme.setCategoryStyle(MRC_CATEGORY_STYLE_METHODS, { colour: '#4A148C', @@ -62,13 +64,27 @@ export const add_mrc_styles = function(theme : Blockly.Theme) : Blockly.Theme { colourPrimary: "#5b5ba5", colourSecondary: "#dedeed", colourTertiary: "#494984", - hat:"" + hat: "" }); theme.setBlockStyle(MRC_STYLE_MISC, { colourPrimary: "#5b5ba5", colourSecondary: "#dedeed", colourTertiary: "#494984", - hat:"" + hat: "" + }); + theme.setBlockStyle(MRC_STYLE_MECHANISMS, { + "colourPrimary": "#5b61a5", + "colourSecondary": "#dedfed", + "colourTertiary": "#494e84", + hat: "" + }); + theme.setBlockStyle(MRC_STYLE_COMPONENTS, { + "colourPrimary": "#5b80a5", + "colourSecondary": "#dee6ed", + "colourTertiary": "#496684", + hat: "" + }); + return theme; } \ No newline at end of file diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts new file mode 100644 index 00000000..07098f25 --- /dev/null +++ b/src/toolbox/hardware_category.ts @@ -0,0 +1,87 @@ +export const category = +{ + kind: 'category', + name: 'Hardware', + contents: [ + { + kind: 'label', + text: 'Mechanisms', + }, + { + kind: 'block', + type: 'mrc_mechanism', + fields: { + NAME: 'my_drive', + TYPE: 'DriveMecanum' + }, + extraState: { + importModule : 'DriveMecanum', + params: [{name:'front_left_drive',type:'int'}, + {name:'front_right_drive',type:'int'}, + {name:'back_left_drive',type:'int'}, + {name:'back_right_drive',type:'int'}, + ] + }, + inputs: { + ARG0: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + ARG1: { + shadow: { + type: 'math_number', + fields: { + NUM: 2, + }, + }, + }, + ARG2: { + shadow: { + type: 'math_number', + fields: { + NUM: 3, + }, + }, + }, + ARG3: { + shadow: { + type: 'math_number', + fields: { + NUM: 4, + }, + }, + }, + } + }, + { + kind: 'label', + text: 'Components', + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_motor', + TYPE: 'DcMotor' + }, + extraState: { + importModule : 'DcMotor', + params: [{name:'port',type:'int'}] + }, + inputs: { + ARG0: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + } + }, + ], +} \ No newline at end of file diff --git a/src/toolbox/mechanism_category.ts b/src/toolbox/mechanism_category.ts deleted file mode 100644 index c87d184b..00000000 --- a/src/toolbox/mechanism_category.ts +++ /dev/null @@ -1,51 +0,0 @@ -export const category = -{ - kind: 'category', - name: 'Mechanisms', - contents: [ - { - kind: 'block', - type: 'mrc_mechanism', - fields: { - NAME: 'my_servo', - TYPE: 'Servo' - }, - extraState: { - importModule : 'Servo', - params: [{name:'port',type:'int'}] - }, - inputs: { - ARG0: { - shadow: { - type: 'math_number', - fields: { - NUM: 1, - }, - }, - }, - } - }, - { - kind: 'block', - type: 'mrc_mechanism', - fields: { - NAME: 'my_motor', - TYPE: 'DcMotor' - }, - extraState: { - importModule : 'DcMotor', - params: [{name:'port',type:'int'}] - }, - inputs: { - ARG0: { - shadow: { - type: 'math_number', - fields: { - NUM: 1, - }, - }, - }, - } - }, - ], -} \ No newline at end of file diff --git a/src/toolbox/methods_category.ts b/src/toolbox/methods_category.ts index d41ced35..30097f3a 100644 --- a/src/toolbox/methods_category.ts +++ b/src/toolbox/methods_category.ts @@ -87,6 +87,10 @@ export class MethodsCategory { // Add a block that lets the user define a new method. contents.push( + { + kind: 'block', + type: 'mrc_mechanism_container_holder', + }, { kind: 'label', text: 'Custom Methods', diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index ed17a591..3ffb69f1 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -29,6 +29,7 @@ import {category as listsCategory} from './lists_category'; import {category as miscCategory} from './misc_category'; import {category as methodsCategory} from './methods_category'; import {category as componentSampleCategory} from './component_samples_category'; +import {category as mechanismCategory} from './hardware_category'; export function getToolboxJSON( opt_includeExportedBlocksFromProject: toolboxItems.ContentsType[], @@ -73,6 +74,7 @@ export function getToolboxJSON( custom: 'VARIABLE', }, methodsCategory, + mechanismCategory, componentSampleCategory, ]); From ca4f7e44c41d3bb1ad7893f6ef6f61e1c0fffeb7 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Wed, 9 Apr 2025 20:45:32 -0400 Subject: [PATCH 02/23] Can now increase/decrease inputs on mechanisms and components --- src/blocks/mrc_mechanism_component_holder.ts | 171 ++++++++++++++++++- src/fields/field_minus.ts | 56 ++++++ src/fields/field_plus.ts | 59 +++++++ src/fields/serialization_helper.ts | 21 +++ src/modules/mechanism_start.json | 14 +- src/modules/robot_start.json | 4 +- 6 files changed, 316 insertions(+), 9 deletions(-) create mode 100644 src/fields/field_minus.ts create mode 100644 src/fields/field_plus.ts create mode 100644 src/fields/serialization_helper.ts diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index 5b4ef36c..5df69498 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -23,15 +23,27 @@ import * as Blockly from 'blockly'; import { MRC_STYLE_MECHANISMS } from '../themes/styles'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; -import {OUTPUT_NAME as MECHANISM_OUTPUT } from './mrc_mechanism'; -import {OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component'; - +import { OUTPUT_NAME as MECHANISM_OUTPUT } from './mrc_mechanism'; +import { OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component'; +import { createPlusField } from '../fields/field_plus'; +import { createMinusField } from '../fields/field_minus'; export const BLOCK_NAME = 'mrc_mechanism_component_holder'; +export const MECHANISM = 'mechanism'; +export const COMPONENT = 'component'; + +type MechanismComponentHolderExtraState = { + numMechanismInputs?: number, + numComponentInputs?: number, +} type MechanismComponentHolderBlock = Blockly.Block & MechanismComponentHolderMixin; interface MechanismComponentHolderMixin extends MechanismComponentHolderMixinType { + mrcNumMechanismInputs: number, + mrcNumComponentInputs: number, + lastMechanism: Blockly.Input, + lastComponent: Blockly.Input } type MechanismComponentHolderMixinType = typeof MECHANISM_COMPONENT_HOLDER; @@ -41,11 +53,160 @@ const MECHANISM_COMPONENT_HOLDER = { */ init: function (this: MechanismComponentHolderBlock): void { this.setInputsInline(false); - this.appendValueInput('MECHANISM_1').appendField('Mechanisms').setCheck(MECHANISM_OUTPUT); - this.appendValueInput('COMPONENT_1').appendField('Components').setCheck(COMPONENT_OUTPUT); + this.appendValueInput('MECHANISM_1') + .appendField('Mechanisms') + .appendField(createPlusField(MECHANISM), 'PLUS_MECHANISM') + .setCheck(MECHANISM_OUTPUT); + + this.appendValueInput('COMPONENT_1') + .appendField('Components') + .appendField(createPlusField(COMPONENT), 'PLUS_COMPONENT') + .setCheck(COMPONENT_OUTPUT); this.setOutput(false); this.setStyle(MRC_STYLE_MECHANISMS); + this.mrcNumComponentInputs = 1; + this.mrcNumMechanismInputs = 1; + }, + plus: function (this: MechanismComponentHolderBlock, type: string): void { + if (type == MECHANISM) { + this.addMechanismPart_(); + } else if (type == COMPONENT) { + this.addComponentPart_(); + } + }, + addMechanismPart_: function (this: MechanismComponentHolderBlock): void { + this.mrcNumMechanismInputs += 1; + let newName = 'MECHANISM_' + this.mrcNumMechanismInputs; + let lastLast = this.lastMechanism; + this.lastMechanism = this.appendValueInput(newName) + .setAlign(Blockly.inputs.Align.RIGHT) + .appendField(createMinusField(MECHANISM), 'MINUS_MECHANISM') + .setCheck(MECHANISM_OUTPUT); + this.moveInputBefore(newName, 'COMPONENT_1'); + if (lastLast) { + lastLast.removeField('MINUS_MECHANISM'); + } + }, + addComponentPart_: function (this: MechanismComponentHolderBlock): void { + this.mrcNumComponentInputs += 1; + let newName = 'COMPONENT_' + this.mrcNumComponentInputs; + let lastLast = this.lastComponent; + this.lastComponent = this.appendValueInput(newName) + .setAlign(Blockly.inputs.Align.RIGHT) + .appendField(createMinusField(COMPONENT), 'MINUS_COMPONENT') + .setCheck(COMPONENT_OUTPUT); + if (lastLast) { + lastLast.removeField('MINUS_COMPONENT'); + } + }, + minus: function (this: MechanismComponentHolderBlock, type: string): void { + if (type == MECHANISM) { + this.subtractMechanismPart_(); + } else if (type == COMPONENT) { + this.subtractComponentPart_(); + } }, + subtractMechanismPart_: function (this: MechanismComponentHolderBlock): void { + if (this.mrcNumMechanismInputs > 1) { + let name = 'MECHANISM_' + this.mrcNumMechanismInputs; + this.removeInput(name); + this.mrcNumMechanismInputs -= 1; + if (this.mrcNumMechanismInputs > 1) { + name = 'MECHANISM_' + this.mrcNumMechanismInputs; + let lastInput = this.getInput(name); + if (lastInput) { + this.lastMechanism = lastInput.appendField(createMinusField(MECHANISM), 'MINUS_MECHANISM'); + } + } + } + }, + subtractComponentPart_: function (this: MechanismComponentHolderBlock): void { + if (this.mrcNumComponentInputs > 1) { + let name = 'COMPONENT_' + this.mrcNumComponentInputs; + this.removeInput(name); + this.mrcNumComponentInputs -= 1; + if (this.mrcNumComponentInputs > 1) { + name = 'COMPONENT_' + this.mrcNumComponentInputs; + let lastInput = this.getInput(name); + if (lastInput) { + this.lastMechanism = lastInput.appendField(createMinusField(COMPONENT), 'MINUS_COMPONENT'); + } + } + } + }, + saveExtraState: function (this: MechanismComponentHolderBlock): MechanismComponentHolderExtraState { + const extraState: MechanismComponentHolderExtraState = { + }; + if (this.mrcNumComponentInputs != 1) { + extraState.numComponentInputs = this.mrcNumComponentInputs; + } + if (this.mrcNumMechanismInputs != 1) { + extraState.numMechanismInputs = this.mrcNumMechanismInputs; + } + return extraState; + }, + /** + * Applies the given state to this block. + */ + loadExtraState: function (this: MechanismComponentHolderBlock, extraState: MechanismComponentHolderExtraState): void { + this.mrcNumComponentInputs = (extraState.numComponentInputs == undefined) ? 1 : extraState.numComponentInputs; + this.mrcNumMechanismInputs = (extraState.numMechanismInputs == undefined) ? 1 : extraState.numMechanismInputs; + this.updateBlock_(); + }, + /** + * Update the block to reflect the newly loaded extra state. + */ + updateBlock_: function (this: MechanismComponentHolderBlock): void { + let number = 1; + while (this.getInput('MECHANISM_' + number)) { + this.removeInput('MECHANISM_' + number); + number += 1; + } + number = 1; + while (this.getInput('COMPONENT_' + number)) { + this.removeInput('COMPONENT_' + number); + number += 1; + } + + if (this.mrcNumMechanismInputs != 0) { + this.appendValueInput('MECHANISM_1') + .appendField('Mechanisms') + .appendField(createPlusField(MECHANISM), 'PLUS_MECHANISM') + .setCheck(MECHANISM_OUTPUT); + } + let remainingMechanisms = this.mrcNumMechanismInputs - 1; + number = 1; + while (remainingMechanisms > 0) { + number += 1; + let newName = 'MECHANISM_' + number; + this.lastMechanism = this.appendValueInput(newName) + .setAlign(Blockly.inputs.Align.RIGHT) + .setCheck(MECHANISM_OUTPUT); + if (remainingMechanisms == 1) { + this.lastMechanism.appendField(createMinusField(MECHANISM), 'MINUS_MECHANISM') + } + remainingMechanisms--; + } + + if (this.mrcNumComponentInputs != 0) { + this.appendValueInput('COMPONENT_1') + .appendField('Components') + .appendField(createPlusField(COMPONENT), 'PLUS_COMPONENT') + .setCheck(COMPONENT_OUTPUT); + } + let remainingComponents = this.mrcNumComponentInputs - 1; + number = 1; + while (remainingComponents > 0) { + let newName = 'COMPONENT_' + number; + this.lastComponent = this.appendValueInput(newName) + .setAlign(Blockly.inputs.Align.RIGHT) + .setCheck(COMPONENT_OUTPUT); + if (remainingComponents == 1) { + this.lastComponent.appendField(createMinusField(COMPONENT), 'MINUS_COMPONENT') + } + remainingComponents--; + } + } } export const setup = function () { diff --git a/src/fields/field_minus.ts b/src/fields/field_minus.ts new file mode 100644 index 00000000..f2d77681 --- /dev/null +++ b/src/fields/field_minus.ts @@ -0,0 +1,56 @@ +import * as Blockly from 'blockly/core'; +import {getExtraBlockState} from './serialization_helper'; + +/** + * Creates a minus image field used for mutation. + * @param type this is + * @returns {Blockly.FieldImage} The Plus field. + */ +export function createMinusField(type : string) { + const minus = new Blockly.FieldImage(minusImage, 15, 15, undefined, onClick_); + /** + * Untyped args passed to block.minus when the field is clicked. + * @type {?(Object|undefined)} + * @private + */ + (minus as any).type = type; + return minus; +} + +/** + * Calls block.minus(args) when the minus field is clicked. + * @param {!Blockly.FieldImage} minusField The field being clicked. + * @private + */ +function onClick_(minusField : Blockly.FieldImage) { + // TODO: This is a dupe of the mutator code, anyway to unify? + const block = minusField.getSourceBlock(); + + if (block!.isInFlyout) { + return; + } + + Blockly.Events.setGroup(true); + const oldExtraState = getExtraBlockState(block!); + (block as any).minus((minusField as any).type); + const newExtraState = getExtraBlockState(block!); + + if (oldExtraState != newExtraState) { + Blockly.Events.fire( + new Blockly.Events.BlockChange( + block!, + 'mutation', + null, + oldExtraState, + newExtraState, + ), + ); + } + Blockly.Events.setGroup(false); +} + +const minusImage = + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAw' + + 'MC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBkPS' + + 'JNMTggMTFoLTEyYy0xLjEwNCAwLTIgLjg5Ni0yIDJzLjg5NiAyIDIgMmgxMmMxLjEwNCAw' + + 'IDItLjg5NiAyLTJzLS44OTYtMi0yLTJ6IiBmaWxsPSJ3aGl0ZSIgLz48L3N2Zz4K'; \ No newline at end of file diff --git a/src/fields/field_plus.ts b/src/fields/field_plus.ts new file mode 100644 index 00000000..519f6a1e --- /dev/null +++ b/src/fields/field_plus.ts @@ -0,0 +1,59 @@ +import * as Blockly from 'blockly/core'; +import {getExtraBlockState} from './serialization_helper'; + +/** + * Creates a plus image field used for mutation. + * @param {Object=} args Untyped args passed to block.minus when the field + * is clicked. + * @returns {Blockly.FieldImage} The Plus field. + */ +export function createPlusField(type : string) { + const plus = new Blockly.FieldImage(plusImage, 15, 15, undefined, onClick_); + /** + * Untyped args passed to block.plus when the field is clicked. + * @type {?(Object|undefined)} + * @private + */ + (plus as any).type = type; + return plus; +} + +/** + * Calls block.plus(args) when the plus field is clicked. + * @param {!Blockly.FieldImage} plusField The field being clicked. + * @private + */ +function onClick_(plusField : Blockly.FieldImage) { + // TODO: This is a dupe of the mutator code, anyway to unify? + const block = plusField.getSourceBlock(); + + if (block!.isInFlyout) { + return; + } + + Blockly.Events.setGroup(true); + const oldExtraState = getExtraBlockState(block!); + (block as any).plus((plusField as any).type); + const newExtraState = getExtraBlockState(block!); + + if (oldExtraState != newExtraState) { + Blockly.Events.fire( + new Blockly.Events.BlockChange( + block!, + 'mutation', + null, + oldExtraState, + newExtraState, + ), + ); + } + Blockly.Events.setGroup(false); +} + +const plusImage = + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC' + + '9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBkPSJNMT' + + 'ggMTBoLTR2LTRjMC0xLjEwNC0uODk2LTItMi0ycy0yIC44OTYtMiAybC4wNzEgNGgtNC4wNz' + + 'FjLTEuMTA0IDAtMiAuODk2LTIgMnMuODk2IDIgMiAybDQuMDcxLS4wNzEtLjA3MSA0LjA3MW' + + 'MwIDEuMTA0Ljg5NiAyIDIgMnMyLS44OTYgMi0ydi00LjA3MWw0IC4wNzFjMS4xMDQgMCAyLS' + + '44OTYgMi0ycy0uODk2LTItMi0yeiIgZmlsbD0id2hpdGUiIC8+PC9zdmc+Cg=='; \ No newline at end of file diff --git a/src/fields/serialization_helper.ts b/src/fields/serialization_helper.ts new file mode 100644 index 00000000..0327ef12 --- /dev/null +++ b/src/fields/serialization_helper.ts @@ -0,0 +1,21 @@ +// (From blockly-samples/plugins/block-plus-minus) +import * as Blockly from 'blockly/core'; + +/** + * Returns the extra state of the given block (either as XML or a JSO, depending + * on the block's definition). + * @param {!Blockly.BlockSvg} block The block to get the extra state of. + * @returns {string} A stringified version of the extra state of the given + * block. + */ +export function getExtraBlockState(block : Blockly.Block) { + if (block.saveExtraState) { + const state = block.saveExtraState(); + return state ? JSON.stringify(state) : ''; + } else if (block.mutationToDom) { + const state = block.mutationToDom(); + return state ? Blockly.Xml.domToText(state) : ''; + } + return ''; +} + diff --git a/src/modules/mechanism_start.json b/src/modules/mechanism_start.json index fa26e3bb..e257e2ab 100644 --- a/src/modules/mechanism_start.json +++ b/src/modules/mechanism_start.json @@ -6,7 +6,7 @@ "type": "mrc_class_method_def", "id": "ElbONc{)s(,UprTW(|1C", "x": 10, - "y": 10, + "y": 210, "deletable": false, "extraState": { "canChangeSignature": false, @@ -24,7 +24,7 @@ "type": "mrc_class_method_def", "id": "wxFAh6eaR1|3fTuV:UAd", "x": 10, - "y": 200, + "y": 410, "deletable": false, "extraState": { "canChangeSignature": false, @@ -36,6 +36,16 @@ "fields": { "NAME": "update" } + }, + { + "type": "mrc_mechanism_component_holder", + "id": "ElbOAb{)s(,UprTW(|1C", + "x": 10, + "y": 10, + "deletable": false, + "extraState":{ + "numMechanismInputs": 0 + } } ] } diff --git a/src/modules/robot_start.json b/src/modules/robot_start.json index 2135cd75..1a54456b 100644 --- a/src/modules/robot_start.json +++ b/src/modules/robot_start.json @@ -6,7 +6,7 @@ "type": "mrc_class_method_def", "id": "ElbONc{)s(,UprTW(|1C", "x": 10, - "y": 10, + "y": 210, "deletable": false, "extraState": { "canChangeSignature": false, @@ -23,7 +23,7 @@ { "type": "mrc_mechanism_component_holder", "id": "ElbOAb{)s(,UprTW(|1C", - "x": 210, + "x": 10, "y": 10, "deletable": false } From 62286fa5080eef7409670bb704ed37ea48d794fd Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Wed, 9 Apr 2025 21:37:57 -0400 Subject: [PATCH 03/23] Generate Python for mechanisms and components --- src/blocks/mrc_component.ts | 17 +++++++-- src/blocks/mrc_mechanism.ts | 23 ++++++------- src/blocks/mrc_mechanism_component_holder.ts | 36 ++++++++++++++++++-- src/editor/extended_python_generator.ts | 8 ++--- src/editor/generator_context.ts | 12 +++---- 5 files changed, 69 insertions(+), 27 deletions(-) diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index b7632c40..e572669e 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -119,8 +119,21 @@ export const setup = function () { } export const pythonFromBlock = function ( - componentBlock: ComponentBlock, + block: ComponentBlock, generator: ExtendedPythonGenerator, ) { - return ''; + if(this.mrcImportModule){ + generator.addImport(this.mrcImportModule); + } + let code = 'self.' + this.getFieldValue('NAME') + ' = ' + this.getFieldValue('TYPE') + '('; + + for (let i = 0; i < block.mrcArgs.length; i++) { + const fieldName = 'ARG' + i; + if(i != 0){ + code += ', ' + } + code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); + } + code += ')'; + return [code, Order.ATOMIC]; } diff --git a/src/blocks/mrc_mechanism.ts b/src/blocks/mrc_mechanism.ts index 23f8a2ce..72cdf537 100644 --- a/src/blocks/mrc_mechanism.ts +++ b/src/blocks/mrc_mechanism.ts @@ -119,24 +119,21 @@ export const setup = function () { } export const pythonFromBlock = function ( - mechanismBlock: MechanismBlock, + block: MechanismBlock, generator: ExtendedPythonGenerator, ) { - if (mechanismBlock.mrcImportModule) { - generator.addImport(mechanismBlock.mrcImportModule); + if (block.mrcImportModule) { + generator.addImport(block.mrcImportModule); } - generator.setHasMechanism(); - let code = 'self.mechanisms["' + mechanismBlock.getFieldValue('NAME') + '"] = ' - + mechanismBlock.getFieldValue('TYPE') + '(' - for (let i = 0; i < mechanismBlock.mrcArgs.length; i++) { + let code = 'self.' + this.getFieldValue('NAME') + ' = ' + this.getFieldValue('TYPE') + '('; + + for (let i = 0; i < block.mrcArgs.length; i++) { const fieldName = 'ARG' + i; if(i != 0){ code += ', ' } - code += mechanismBlock.mrcArgs[i].name + ' = ' + generator.valueToCode(mechanismBlock, fieldName, Order.NONE); - } - - code += ')' + "\n"; - - return code + code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); + } + code += ')'; + return [code, Order.ATOMIC]; } diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index 5df69498..a76b9a10 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -27,6 +27,8 @@ import { OUTPUT_NAME as MECHANISM_OUTPUT } from './mrc_mechanism'; import { OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component'; import { createPlusField } from '../fields/field_plus'; import { createMinusField } from '../fields/field_minus'; +import { Order } from 'blockly/python'; + export const BLOCK_NAME = 'mrc_mechanism_component_holder'; @@ -214,8 +216,38 @@ export const setup = function () { } export const pythonFromBlock = function ( - MechanismComponentHolderBlock: MechanismComponentHolderBlock, + block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator, ) { - return '' + generator.setHasHardware(); + let code = 'def define_hardware(self):\n'; + + let body = ''; + for(let i = 1; i <= this.mrcNumMechanismInputs; i++){ + const name = 'MECHANISM_' + i; + if(block.getInput(name)){ + let mechanismCode = generator.valueToCode(block, name, Order.NONE) || ''; + if (mechanismCode != ''){ + body += generator.INDENT + mechanismCode + "\n"; + } + } + } + for(let i = 1; i <= this.mrcNumComponentInputs; i++){ + const name = 'COMPONENT_' + i; + if(block.getInput(name)){ + let componentCode = generator.valueToCode(block, name, Order.NONE) || ''; + if (componentCode != ''){ + body += generator.INDENT + componentCode + "\n"; + } + } + } + + if(body != ''){ + code += body; + }else{ + code += generator.INDENT + 'pass'; + } + + generator.addClassMethodDefinition('define_hardware', code); + return ''; } \ No newline at end of file diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index cba4c9d9..1468ab01 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -63,8 +63,8 @@ export class ExtendedPythonGenerator extends PythonGenerator { defineClassVariables() : string { let variableDefinitions = ''; - if (this.context?.getHasMechanisms()) { - variableDefinitions += this.INDENT + "self.mechanisms = []\n"; + if (this.context?.getHasHardware()) { + variableDefinitions += this.INDENT + "self.define_hardware()\n"; } return variableDefinitions; @@ -73,8 +73,8 @@ export class ExtendedPythonGenerator extends PythonGenerator { const varName = super.getVariableName(nameOrId); return "self." + varName; } - setHasMechanism() : void{ - this.context?.setHasMechanism(); + setHasHardware() : void{ + this.context?.setHasHardware(); } mrcWorkspaceToCode(workspace: Blockly.Workspace, context: GeneratorContext): string { diff --git a/src/editor/generator_context.ts b/src/editor/generator_context.ts index 4532e2cb..e6007ade 100644 --- a/src/editor/generator_context.ts +++ b/src/editor/generator_context.ts @@ -34,7 +34,7 @@ export class GeneratorContext { private exportedBlocks: Block[] = []; // Has mechanisms (ie, needs in init) - private hasMechanisms = false; + private hasHardware = false; setModule(module: commonStorage.Module | null) { this.module = module; @@ -43,15 +43,15 @@ export class GeneratorContext { clear(): void { this.clearExportedBlocks(); - this.hasMechanisms = false; + this.hasHardware= false; } - setHasMechanism():void{ - this.hasMechanisms = true; + setHasHardware():void{ + this.hasHardware = true; } - getHasMechanisms():boolean{ - return this.hasMechanisms; + getHasHardware():boolean{ + return this.hasHardware; } getClassName(): string { From 35eb4dd78d5ed2b2a47f0fcb84c3049b26f7c8cb Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Wed, 9 Apr 2025 21:41:10 -0400 Subject: [PATCH 04/23] Fix errors --- src/blocks/mrc_component.ts | 6 +++--- src/blocks/mrc_mechanism.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index e572669e..8680ae89 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -122,10 +122,10 @@ export const pythonFromBlock = function ( block: ComponentBlock, generator: ExtendedPythonGenerator, ) { - if(this.mrcImportModule){ - generator.addImport(this.mrcImportModule); + if(block.mrcImportModule){ + generator.addImport(block.mrcImportModule); } - let code = 'self.' + this.getFieldValue('NAME') + ' = ' + this.getFieldValue('TYPE') + '('; + let code = 'self.' + block.getFieldValue('NAME') + ' = ' + block.getFieldValue('TYPE') + '('; for (let i = 0; i < block.mrcArgs.length; i++) { const fieldName = 'ARG' + i; diff --git a/src/blocks/mrc_mechanism.ts b/src/blocks/mrc_mechanism.ts index 72cdf537..80ec8bdb 100644 --- a/src/blocks/mrc_mechanism.ts +++ b/src/blocks/mrc_mechanism.ts @@ -125,7 +125,7 @@ export const pythonFromBlock = function ( if (block.mrcImportModule) { generator.addImport(block.mrcImportModule); } - let code = 'self.' + this.getFieldValue('NAME') + ' = ' + this.getFieldValue('TYPE') + '('; + let code = 'self.' + block.getFieldValue('NAME') + ' = ' + block.getFieldValue('TYPE') + '('; for (let i = 0; i < block.mrcArgs.length; i++) { const fieldName = 'ARG' + i; From f9223259121d891b56163bbc804cf1247c1b4240 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Wed, 9 Apr 2025 21:47:30 -0400 Subject: [PATCH 05/23] Fixed where holder had to be before init to generate correctly --- src/blocks/mrc_mechanism_component_holder.ts | 5 ++--- src/editor/extended_python_generator.ts | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index a76b9a10..bba03f16 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -219,11 +219,10 @@ export const pythonFromBlock = function ( block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator, ) { - generator.setHasHardware(); let code = 'def define_hardware(self):\n'; let body = ''; - for(let i = 1; i <= this.mrcNumMechanismInputs; i++){ + for(let i = 1; i <= block.mrcNumMechanismInputs; i++){ const name = 'MECHANISM_' + i; if(block.getInput(name)){ let mechanismCode = generator.valueToCode(block, name, Order.NONE) || ''; @@ -232,7 +231,7 @@ export const pythonFromBlock = function ( } } } - for(let i = 1; i <= this.mrcNumComponentInputs; i++){ + for(let i = 1; i <= block.mrcNumComponentInputs; i++){ const name = 'COMPONENT_' + i; if(block.getInput(name)){ let componentCode = generator.valueToCode(block, name, Order.NONE) || ''; diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index 1468ab01..3d0d4b27 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -23,7 +23,8 @@ import * as Blockly from 'blockly/core'; import { PythonGenerator } from 'blockly/python'; import { GeneratorContext } from './generator_context'; import { Block } from '../toolbox/items'; -import { CallPythonFunctionBlock, FunctionArg } from '../blocks/mrc_call_python_function'; +import { FunctionArg } from '../blocks/mrc_call_python_function'; +import * as MechanismContainerHolder from '../blocks/mrc_mechanism_component_holder'; import * as commonStorage from '../storage/common_storage'; // Extends the python generator to collect some information about functions and @@ -82,6 +83,10 @@ export class ExtendedPythonGenerator extends PythonGenerator { this.context = context; this.context.clear(); + if (this.workspace.getBlocksByType(MechanismContainerHolder.BLOCK_NAME).length > 0){ + this.setHasHardware(); + } + const code = super.workspaceToCode(workspace); this.workspace = workspace; From beed3d9e306e8280525c87aa5cb0489f5ab8f49f Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Fri, 11 Apr 2025 19:05:21 -0400 Subject: [PATCH 06/23] Move to have components and mechanisms like statements --- src/blocks/mrc_component.ts | 10 +- src/blocks/mrc_mechanism.ts | 47 ++--- src/blocks/mrc_mechanism_component_holder.ts | 187 +++---------------- src/blocks/utils/connection_checker.ts | 80 ++++++++ src/modules/mechanism_start.json | 2 +- src/reactComponents/BlocklyComponent.tsx | 5 + 6 files changed, 141 insertions(+), 190 deletions(-) create mode 100644 src/blocks/utils/connection_checker.ts diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 8680ae89..8ac61657 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -57,9 +57,9 @@ const COMPONENT = { .appendField(new Blockly.FieldTextInput('my_mech'), 'NAME') .appendField('of type') .appendField(createFieldNonEditableText(''), 'TYPE'); - this.setPreviousStatement(false); - this.setNextStatement(false); - this.setOutput(true, OUTPUT_NAME); + this.setPreviousStatement(true, OUTPUT_NAME); + this.setNextStatement(true, OUTPUT_NAME); + // this.setOutput(true, OUTPUT_NAME); }, /** @@ -134,6 +134,6 @@ export const pythonFromBlock = function ( } code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); } - code += ')'; - return [code, Order.ATOMIC]; + code += ')\n' + "self.hardware.append(self." + block.getFieldValue('NAME') + ")\n"; + return code; } diff --git a/src/blocks/mrc_mechanism.ts b/src/blocks/mrc_mechanism.ts index 80ec8bdb..5d0c4387 100644 --- a/src/blocks/mrc_mechanism.ts +++ b/src/blocks/mrc_mechanism.ts @@ -57,9 +57,9 @@ const MECHANISM = { .appendField(new Blockly.FieldTextInput('my_mech'), 'NAME') .appendField('of type') .appendField(createFieldNonEditableText(''), 'TYPE'); - this.setPreviousStatement(false); - this.setNextStatement(false); - this.setOutput(true, OUTPUT_NAME); + this.setPreviousStatement(true, OUTPUT_NAME); + this.setNextStatement(true, OUTPUT_NAME); + //this.setOutput(true, OUTPUT_NAME); }, /** @@ -74,7 +74,7 @@ const MECHANISM = { 'name': arg.name, 'type': arg.type, }); - }); + }); if (this.mrcImportModule) { extraState.importModule = this.mrcImportModule; } @@ -87,7 +87,7 @@ const MECHANISM = { this.mrcImportModule = extraState.importModule ? extraState.importModule : ''; this.mrcArgs = []; - if(extraState.params){ + if (extraState.params) { extraState.params.forEach((arg) => { this.mrcArgs.push({ 'name': arg.name, @@ -101,17 +101,17 @@ const MECHANISM = { /** * Update the block to reflect the newly loaded extra state. */ - updateBlock_: function(this: MechanismBlock): void { - // Add input sockets for the arguments. - for (let i = 0; i < this.mrcArgs.length; i++) { - const input = this.appendValueInput('ARG' + i) - .setAlign(Blockly.inputs.Align.RIGHT) - .appendField(this.mrcArgs[i].name); - if (this.mrcArgs[i].type) { - input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type)); - } + updateBlock_: function (this: MechanismBlock): void { + // Add input sockets for the arguments. + for (let i = 0; i < this.mrcArgs.length; i++) { + const input = this.appendValueInput('ARG' + i) + .setAlign(Blockly.inputs.Align.RIGHT) + .appendField(this.mrcArgs[i].name); + if (this.mrcArgs[i].type) { + input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type)); } } + } } export const setup = function () { @@ -126,14 +126,15 @@ export const pythonFromBlock = function ( generator.addImport(block.mrcImportModule); } let code = 'self.' + block.getFieldValue('NAME') + ' = ' + block.getFieldValue('TYPE') + '('; - + for (let i = 0; i < block.mrcArgs.length; i++) { - const fieldName = 'ARG' + i; - if(i != 0){ - code += ', ' - } - code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); - } - code += ')'; - return [code, Order.ATOMIC]; + const fieldName = 'ARG' + i; + if (i != 0) { + code += ', ' + } + code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); + } + code += ')\n' + "self.hardware.append(self." + block.getFieldValue('NAME') + ")\n"; + + return code; } diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index bba03f16..5f3bdff9 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -25,10 +25,6 @@ import { MRC_STYLE_MECHANISMS } from '../themes/styles'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import { OUTPUT_NAME as MECHANISM_OUTPUT } from './mrc_mechanism'; import { OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component'; -import { createPlusField } from '../fields/field_plus'; -import { createMinusField } from '../fields/field_minus'; -import { Order } from 'blockly/python'; - export const BLOCK_NAME = 'mrc_mechanism_component_holder'; @@ -36,16 +32,12 @@ export const MECHANISM = 'mechanism'; export const COMPONENT = 'component'; type MechanismComponentHolderExtraState = { - numMechanismInputs?: number, - numComponentInputs?: number, + hideMechanims?: boolean; } type MechanismComponentHolderBlock = Blockly.Block & MechanismComponentHolderMixin; interface MechanismComponentHolderMixin extends MechanismComponentHolderMixinType { - mrcNumMechanismInputs: number, - mrcNumComponentInputs: number, - lastMechanism: Blockly.Input, - lastComponent: Blockly.Input + mrcHideMechanisms : boolean; } type MechanismComponentHolderMixinType = typeof MECHANISM_COMPONENT_HOLDER; @@ -55,95 +47,17 @@ const MECHANISM_COMPONENT_HOLDER = { */ init: function (this: MechanismComponentHolderBlock): void { this.setInputsInline(false); - this.appendValueInput('MECHANISM_1') - .appendField('Mechanisms') - .appendField(createPlusField(MECHANISM), 'PLUS_MECHANISM') - .setCheck(MECHANISM_OUTPUT); + this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms'); + this.appendStatementInput('COMPONENTS').setCheck(COMPONENT_OUTPUT).appendField('Components'); - this.appendValueInput('COMPONENT_1') - .appendField('Components') - .appendField(createPlusField(COMPONENT), 'PLUS_COMPONENT') - .setCheck(COMPONENT_OUTPUT); this.setOutput(false); this.setStyle(MRC_STYLE_MECHANISMS); - this.mrcNumComponentInputs = 1; - this.mrcNumMechanismInputs = 1; - }, - plus: function (this: MechanismComponentHolderBlock, type: string): void { - if (type == MECHANISM) { - this.addMechanismPart_(); - } else if (type == COMPONENT) { - this.addComponentPart_(); - } - }, - addMechanismPart_: function (this: MechanismComponentHolderBlock): void { - this.mrcNumMechanismInputs += 1; - let newName = 'MECHANISM_' + this.mrcNumMechanismInputs; - let lastLast = this.lastMechanism; - this.lastMechanism = this.appendValueInput(newName) - .setAlign(Blockly.inputs.Align.RIGHT) - .appendField(createMinusField(MECHANISM), 'MINUS_MECHANISM') - .setCheck(MECHANISM_OUTPUT); - this.moveInputBefore(newName, 'COMPONENT_1'); - if (lastLast) { - lastLast.removeField('MINUS_MECHANISM'); - } - }, - addComponentPart_: function (this: MechanismComponentHolderBlock): void { - this.mrcNumComponentInputs += 1; - let newName = 'COMPONENT_' + this.mrcNumComponentInputs; - let lastLast = this.lastComponent; - this.lastComponent = this.appendValueInput(newName) - .setAlign(Blockly.inputs.Align.RIGHT) - .appendField(createMinusField(COMPONENT), 'MINUS_COMPONENT') - .setCheck(COMPONENT_OUTPUT); - if (lastLast) { - lastLast.removeField('MINUS_COMPONENT'); - } - }, - minus: function (this: MechanismComponentHolderBlock, type: string): void { - if (type == MECHANISM) { - this.subtractMechanismPart_(); - } else if (type == COMPONENT) { - this.subtractComponentPart_(); - } - }, - subtractMechanismPart_: function (this: MechanismComponentHolderBlock): void { - if (this.mrcNumMechanismInputs > 1) { - let name = 'MECHANISM_' + this.mrcNumMechanismInputs; - this.removeInput(name); - this.mrcNumMechanismInputs -= 1; - if (this.mrcNumMechanismInputs > 1) { - name = 'MECHANISM_' + this.mrcNumMechanismInputs; - let lastInput = this.getInput(name); - if (lastInput) { - this.lastMechanism = lastInput.appendField(createMinusField(MECHANISM), 'MINUS_MECHANISM'); - } - } - } - }, - subtractComponentPart_: function (this: MechanismComponentHolderBlock): void { - if (this.mrcNumComponentInputs > 1) { - let name = 'COMPONENT_' + this.mrcNumComponentInputs; - this.removeInput(name); - this.mrcNumComponentInputs -= 1; - if (this.mrcNumComponentInputs > 1) { - name = 'COMPONENT_' + this.mrcNumComponentInputs; - let lastInput = this.getInput(name); - if (lastInput) { - this.lastMechanism = lastInput.appendField(createMinusField(COMPONENT), 'MINUS_COMPONENT'); - } - } - } }, saveExtraState: function (this: MechanismComponentHolderBlock): MechanismComponentHolderExtraState { const extraState: MechanismComponentHolderExtraState = { }; - if (this.mrcNumComponentInputs != 1) { - extraState.numComponentInputs = this.mrcNumComponentInputs; - } - if (this.mrcNumMechanismInputs != 1) { - extraState.numMechanismInputs = this.mrcNumMechanismInputs; + if (this.mrcHideMechanisms == true) { + extraState.hideMechanims = this.mrcHideMechanisms; } return extraState; }, @@ -151,62 +65,23 @@ const MECHANISM_COMPONENT_HOLDER = { * Applies the given state to this block. */ loadExtraState: function (this: MechanismComponentHolderBlock, extraState: MechanismComponentHolderExtraState): void { - this.mrcNumComponentInputs = (extraState.numComponentInputs == undefined) ? 1 : extraState.numComponentInputs; - this.mrcNumMechanismInputs = (extraState.numMechanismInputs == undefined) ? 1 : extraState.numMechanismInputs; + this.mrcHideMechanisms = (extraState.hideMechanims == undefined) ? 1 : extraState.hideMechanims; this.updateBlock_(); }, /** * Update the block to reflect the newly loaded extra state. */ updateBlock_: function (this: MechanismComponentHolderBlock): void { - let number = 1; - while (this.getInput('MECHANISM_' + number)) { - this.removeInput('MECHANISM_' + number); - number += 1; - } - number = 1; - while (this.getInput('COMPONENT_' + number)) { - this.removeInput('COMPONENT_' + number); - number += 1; - } - - if (this.mrcNumMechanismInputs != 0) { - this.appendValueInput('MECHANISM_1') - .appendField('Mechanisms') - .appendField(createPlusField(MECHANISM), 'PLUS_MECHANISM') - .setCheck(MECHANISM_OUTPUT); - } - let remainingMechanisms = this.mrcNumMechanismInputs - 1; - number = 1; - while (remainingMechanisms > 0) { - number += 1; - let newName = 'MECHANISM_' + number; - this.lastMechanism = this.appendValueInput(newName) - .setAlign(Blockly.inputs.Align.RIGHT) - .setCheck(MECHANISM_OUTPUT); - if (remainingMechanisms == 1) { - this.lastMechanism.appendField(createMinusField(MECHANISM), 'MINUS_MECHANISM') - } - remainingMechanisms--; - } - - if (this.mrcNumComponentInputs != 0) { - this.appendValueInput('COMPONENT_1') - .appendField('Components') - .appendField(createPlusField(COMPONENT), 'PLUS_COMPONENT') - .setCheck(COMPONENT_OUTPUT); - } - let remainingComponents = this.mrcNumComponentInputs - 1; - number = 1; - while (remainingComponents > 0) { - let newName = 'COMPONENT_' + number; - this.lastComponent = this.appendValueInput(newName) - .setAlign(Blockly.inputs.Align.RIGHT) - .setCheck(COMPONENT_OUTPUT); - if (remainingComponents == 1) { - this.lastComponent.appendField(createMinusField(COMPONENT), 'MINUS_COMPONENT') - } - remainingComponents--; + if(this.mrcHideMechanisms){ + if(this.getInput('MECHANISMS')){ + this.removeInput('MECHANISMS') + } + } + else{ + if(!this.getInput('MECHANISMS')){ + this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms'); + this.moveInputBefore('MECHANISMS', 'COMPONENTS') + } } } } @@ -219,28 +94,18 @@ export const pythonFromBlock = function ( block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator, ) { - let code = 'def define_hardware(self):\n'; + let code = 'def define_hardware(self):\n' + generator.INDENT + 'self.hardware = []\n'; - let body = ''; - for(let i = 1; i <= block.mrcNumMechanismInputs; i++){ - const name = 'MECHANISM_' + i; - if(block.getInput(name)){ - let mechanismCode = generator.valueToCode(block, name, Order.NONE) || ''; - if (mechanismCode != ''){ - body += generator.INDENT + mechanismCode + "\n"; - } - } + let mechanisms = ''; + let components = ''; + + if (block.getInput('MECHANISMS')) { + mechanisms = generator.statementToCode(block, 'MECHANISMS'); } - for(let i = 1; i <= block.mrcNumComponentInputs; i++){ - const name = 'COMPONENT_' + i; - if(block.getInput(name)){ - let componentCode = generator.valueToCode(block, name, Order.NONE) || ''; - if (componentCode != ''){ - body += generator.INDENT + componentCode + "\n"; - } - } + if (block.getInput('COMPONENTS')) { + components = generator.statementToCode(block, 'COMPONENTS'); } - + const body = mechanisms + components; if(body != ''){ code += body; }else{ diff --git a/src/blocks/utils/connection_checker.ts b/src/blocks/utils/connection_checker.ts new file mode 100644 index 00000000..cda8a768 --- /dev/null +++ b/src/blocks/utils/connection_checker.ts @@ -0,0 +1,80 @@ +/** + * @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 A Blockly plugin that makes connection type checks include methods + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly/core'; +import { OUTPUT_NAME as MECHANISM_OUTPUT } from '../mrc_mechanism'; +import { OUTPUT_NAME as COMPONENT_OUTPUT } from '../mrc_component'; + +export class MethodConnectionChecker extends Blockly.ConnectionChecker { + /** + * Constructor for the connection checker. + */ + constructor() { + super(); + } + + /** + * Type check arrays must either intersect or both be null. + * @override + */ + doTypeChecks(a : Blockly.Connection, b: Blockly.Connection) { + const checkArrayOne = a.getCheck(); + const checkArrayTwo = b.getCheck(); + + if (!checkArrayOne || !checkArrayTwo) { + // if either one has mrc_component, they must match + if((checkArrayOne && (checkArrayOne.indexOf(COMPONENT_OUTPUT) != -1)) || + (checkArrayTwo && (checkArrayTwo.indexOf(COMPONENT_OUTPUT) != -1))){ + return false; + } + // if either one has mrc_mechanism, they must match + if((checkArrayOne && (checkArrayOne.indexOf(MECHANISM_OUTPUT) != -1)) || + (checkArrayTwo && (checkArrayTwo.indexOf(MECHANISM_OUTPUT) != -1))){ + return false; + } + + return true; + } + + // Find any intersection in the check lists. + for (let i = 0; i < checkArrayOne.length; i++) { + if (checkArrayTwo.indexOf(checkArrayOne[i]) != -1) { + return true; + } + } + // No intersection. + + return false; + } +} + +export const registrationType = Blockly.registry.Type.CONNECTION_CHECKER; +export const registrationName = 'HardwareConnectionChecker'; + +Blockly.registry.register( + registrationType, + registrationName, + MethodConnectionChecker, +); + +export const pluginInfo = { + [registrationType as any]: registrationName, +}; \ No newline at end of file diff --git a/src/modules/mechanism_start.json b/src/modules/mechanism_start.json index e257e2ab..d3f6a04b 100644 --- a/src/modules/mechanism_start.json +++ b/src/modules/mechanism_start.json @@ -44,7 +44,7 @@ "y": 10, "deletable": false, "extraState":{ - "numMechanismInputs": 0 + "hideMechanisms" : true } } ] diff --git a/src/reactComponents/BlocklyComponent.tsx b/src/reactComponents/BlocklyComponent.tsx index cff5c310..f56e4ebe 100644 --- a/src/reactComponents/BlocklyComponent.tsx +++ b/src/reactComponents/BlocklyComponent.tsx @@ -3,6 +3,8 @@ import React from 'react'; import * as Blockly from 'blockly/core'; import * as locale from 'blockly/msg/en'; import * as MrcTheme from '../themes/mrc_theme_dark' +import {pluginInfo as HardwareConnectionsPluginInfo} from '../blocks/utils/connection_checker'; + import 'blockly/blocks'; // Includes standard blocks like controls_if, logic_compare, etc. @@ -48,6 +50,9 @@ const BlocklyComponent = React.forwardRef((_, ref) wheel: true }, oneBasedIndex: false, + plugins: { + ...HardwareConnectionsPluginInfo, + } }; // Set Blockly locale From 1bdb64290490de9f1f396d597f7f1942647d9157 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Fri, 11 Apr 2025 19:06:39 -0400 Subject: [PATCH 07/23] remove unneeded files --- src/fields/field_minus.ts | 56 ---------------------------- src/fields/field_plus.ts | 59 ------------------------------ src/fields/serialization_helper.ts | 21 ----------- 3 files changed, 136 deletions(-) delete mode 100644 src/fields/field_minus.ts delete mode 100644 src/fields/field_plus.ts delete mode 100644 src/fields/serialization_helper.ts diff --git a/src/fields/field_minus.ts b/src/fields/field_minus.ts deleted file mode 100644 index f2d77681..00000000 --- a/src/fields/field_minus.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as Blockly from 'blockly/core'; -import {getExtraBlockState} from './serialization_helper'; - -/** - * Creates a minus image field used for mutation. - * @param type this is - * @returns {Blockly.FieldImage} The Plus field. - */ -export function createMinusField(type : string) { - const minus = new Blockly.FieldImage(minusImage, 15, 15, undefined, onClick_); - /** - * Untyped args passed to block.minus when the field is clicked. - * @type {?(Object|undefined)} - * @private - */ - (minus as any).type = type; - return minus; -} - -/** - * Calls block.minus(args) when the minus field is clicked. - * @param {!Blockly.FieldImage} minusField The field being clicked. - * @private - */ -function onClick_(minusField : Blockly.FieldImage) { - // TODO: This is a dupe of the mutator code, anyway to unify? - const block = minusField.getSourceBlock(); - - if (block!.isInFlyout) { - return; - } - - Blockly.Events.setGroup(true); - const oldExtraState = getExtraBlockState(block!); - (block as any).minus((minusField as any).type); - const newExtraState = getExtraBlockState(block!); - - if (oldExtraState != newExtraState) { - Blockly.Events.fire( - new Blockly.Events.BlockChange( - block!, - 'mutation', - null, - oldExtraState, - newExtraState, - ), - ); - } - Blockly.Events.setGroup(false); -} - -const minusImage = - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAw' + - 'MC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBkPS' + - 'JNMTggMTFoLTEyYy0xLjEwNCAwLTIgLjg5Ni0yIDJzLjg5NiAyIDIgMmgxMmMxLjEwNCAw' + - 'IDItLjg5NiAyLTJzLS44OTYtMi0yLTJ6IiBmaWxsPSJ3aGl0ZSIgLz48L3N2Zz4K'; \ No newline at end of file diff --git a/src/fields/field_plus.ts b/src/fields/field_plus.ts deleted file mode 100644 index 519f6a1e..00000000 --- a/src/fields/field_plus.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as Blockly from 'blockly/core'; -import {getExtraBlockState} from './serialization_helper'; - -/** - * Creates a plus image field used for mutation. - * @param {Object=} args Untyped args passed to block.minus when the field - * is clicked. - * @returns {Blockly.FieldImage} The Plus field. - */ -export function createPlusField(type : string) { - const plus = new Blockly.FieldImage(plusImage, 15, 15, undefined, onClick_); - /** - * Untyped args passed to block.plus when the field is clicked. - * @type {?(Object|undefined)} - * @private - */ - (plus as any).type = type; - return plus; -} - -/** - * Calls block.plus(args) when the plus field is clicked. - * @param {!Blockly.FieldImage} plusField The field being clicked. - * @private - */ -function onClick_(plusField : Blockly.FieldImage) { - // TODO: This is a dupe of the mutator code, anyway to unify? - const block = plusField.getSourceBlock(); - - if (block!.isInFlyout) { - return; - } - - Blockly.Events.setGroup(true); - const oldExtraState = getExtraBlockState(block!); - (block as any).plus((plusField as any).type); - const newExtraState = getExtraBlockState(block!); - - if (oldExtraState != newExtraState) { - Blockly.Events.fire( - new Blockly.Events.BlockChange( - block!, - 'mutation', - null, - oldExtraState, - newExtraState, - ), - ); - } - Blockly.Events.setGroup(false); -} - -const plusImage = - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC' + - '9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBkPSJNMT' + - 'ggMTBoLTR2LTRjMC0xLjEwNC0uODk2LTItMi0ycy0yIC44OTYtMiAybC4wNzEgNGgtNC4wNz' + - 'FjLTEuMTA0IDAtMiAuODk2LTIgMnMuODk2IDIgMiAybDQuMDcxLS4wNzEtLjA3MSA0LjA3MW' + - 'MwIDEuMTA0Ljg5NiAyIDIgMnMyLS44OTYgMi0ydi00LjA3MWw0IC4wNzFjMS4xMDQgMCAyLS' + - '44OTYgMi0ycy0uODk2LTItMi0yeiIgZmlsbD0id2hpdGUiIC8+PC9zdmc+Cg=='; \ No newline at end of file diff --git a/src/fields/serialization_helper.ts b/src/fields/serialization_helper.ts deleted file mode 100644 index 0327ef12..00000000 --- a/src/fields/serialization_helper.ts +++ /dev/null @@ -1,21 +0,0 @@ -// (From blockly-samples/plugins/block-plus-minus) -import * as Blockly from 'blockly/core'; - -/** - * Returns the extra state of the given block (either as XML or a JSO, depending - * on the block's definition). - * @param {!Blockly.BlockSvg} block The block to get the extra state of. - * @returns {string} A stringified version of the extra state of the given - * block. - */ -export function getExtraBlockState(block : Blockly.Block) { - if (block.saveExtraState) { - const state = block.saveExtraState(); - return state ? JSON.stringify(state) : ''; - } else if (block.mutationToDom) { - const state = block.mutationToDom(); - return state ? Blockly.Xml.domToText(state) : ''; - } - return ''; -} - From 3a8c4ea1d7bc6aad88422d187343f982c0c333cd Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Fri, 11 Apr 2025 20:07:24 -0400 Subject: [PATCH 08/23] Fixes typos --- src/blocks/mrc_mechanism_component_holder.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index 5f3bdff9..acf96ab4 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -32,7 +32,7 @@ export const MECHANISM = 'mechanism'; export const COMPONENT = 'component'; type MechanismComponentHolderExtraState = { - hideMechanims?: boolean; + hideMechanisms?: boolean; } type MechanismComponentHolderBlock = Blockly.Block & MechanismComponentHolderMixin; @@ -57,7 +57,7 @@ const MECHANISM_COMPONENT_HOLDER = { const extraState: MechanismComponentHolderExtraState = { }; if (this.mrcHideMechanisms == true) { - extraState.hideMechanims = this.mrcHideMechanisms; + extraState.hideMechanisms = this.mrcHideMechanisms; } return extraState; }, @@ -65,7 +65,7 @@ const MECHANISM_COMPONENT_HOLDER = { * Applies the given state to this block. */ loadExtraState: function (this: MechanismComponentHolderBlock, extraState: MechanismComponentHolderExtraState): void { - this.mrcHideMechanisms = (extraState.hideMechanims == undefined) ? 1 : extraState.hideMechanims; + this.mrcHideMechanisms = (extraState.hideMechanisms == undefined) ? false : extraState.hideMechanisms; this.updateBlock_(); }, /** @@ -78,7 +78,7 @@ const MECHANISM_COMPONENT_HOLDER = { } } else{ - if(!this.getInput('MECHANISMS')){ + if(this.getInput('MECHANISMS') == null){ this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms'); this.moveInputBefore('MECHANISMS', 'COMPONENTS') } From bfbdd2254d3dbb95caae36ca789b2df6b8bdd692 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Fri, 11 Apr 2025 20:36:17 -0400 Subject: [PATCH 09/23] add more sample components to hardware category --- src/toolbox/hardware_category.ts | 50 ++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index 07098f25..a02841ac 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -66,11 +66,11 @@ export const category = type: 'mrc_component', fields: { NAME: 'my_motor', - TYPE: 'DcMotor' + TYPE: 'SmartMotor' }, extraState: { - importModule : 'DcMotor', - params: [{name:'port',type:'int'}] + importModule : 'smart_motor', + params: [{name:'Motor Port',type:'int'}] }, inputs: { ARG0: { @@ -83,5 +83,49 @@ export const category = }, } }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_color_range_sensor', + TYPE: 'ColorRangeSensor' + }, + extraState: { + importModule : 'color_range_sensor', + params: [{name:'I2C Port',type:'int'}] + }, + inputs: { + ARG0: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + } + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_touch_sensor', + TYPE: 'RevTouchSensor' + }, + extraState: { + importModule : 'rev_touch_sensor', + params: [{name:'SmartIO port',type:'int'}] + }, + inputs: { + ARG0: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + } + }, ], } \ No newline at end of file From d48aebe825ce1eff52775ac1c0b88d53d613cfee Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Fri, 25 Apr 2025 22:28:01 -0400 Subject: [PATCH 10/23] Changes to add ports, get things more like we want --- src/blocks/mrc_component.ts | 37 +++- src/blocks/mrc_port.ts | 66 ++++++ src/blocks/setup_custom_blocks.ts | 2 + src/modules/mechanism_start.json | 11 +- src/themes/styles.ts | 7 + src/toolbox/hardware_category.ts | 333 +++++++++++++++++++----------- src/toolbox/robot_category.ts | 134 ++++++++++++ src/toolbox/toolbox.ts | 2 + 8 files changed, 460 insertions(+), 132 deletions(-) create mode 100644 src/blocks/mrc_port.ts create mode 100644 src/toolbox/robot_category.ts diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 8ac61657..a42e8eb6 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -37,12 +37,14 @@ export type ConstructorArg = { type ComponentExtraState = { importModule?: string, + hideParams?: boolean, params?: ConstructorArg[], } type ComponentBlock = Blockly.Block & ComponentMixin; interface ComponentMixin extends ComponentMixinType { mrcArgs: ConstructorArg[], + hideParams: boolean, mrcImportModule: string, } type ComponentMixinType = typeof COMPONENT; @@ -59,7 +61,6 @@ const COMPONENT = { .appendField(createFieldNonEditableText(''), 'TYPE'); this.setPreviousStatement(true, OUTPUT_NAME); this.setNextStatement(true, OUTPUT_NAME); - // this.setOutput(true, OUTPUT_NAME); }, /** @@ -74,10 +75,13 @@ const COMPONENT = { 'name': arg.name, 'type': arg.type, }); - }); + }); if (this.mrcImportModule) { extraState.importModule = this.mrcImportModule; } + if (this.hideParams) { + extraState.hideParams = this.hideParams; + } return extraState; }, /** @@ -85,9 +89,10 @@ const COMPONENT = { */ loadExtraState: function (this: ComponentBlock, extraState: ComponentExtraState): void { this.mrcImportModule = extraState.importModule ? extraState.importModule : ''; + this.hideParams = extraState.hideParams ? extraState.hideParams : false; this.mrcArgs = []; - if(extraState.params){ + if (extraState.params) { extraState.params.forEach((arg) => { this.mrcArgs.push({ 'name': arg.name, @@ -101,17 +106,19 @@ const COMPONENT = { /** * Update the block to reflect the newly loaded extra state. */ - updateBlock_: function(this: ComponentBlock): void { + updateBlock_: function (this: ComponentBlock): void { + if (this.hideParams == false) { // Add input sockets for the arguments. for (let i = 0; i < this.mrcArgs.length; i++) { const input = this.appendValueInput('ARG' + i) - .setAlign(Blockly.inputs.Align.RIGHT) - .appendField(this.mrcArgs[i].name); + .setAlign(Blockly.inputs.Align.RIGHT) + .appendField(this.mrcArgs[i].name); if (this.mrcArgs[i].type) { input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type)); } } } + } } export const setup = function () { @@ -122,18 +129,26 @@ export const pythonFromBlock = function ( block: ComponentBlock, generator: ExtendedPythonGenerator, ) { - if(block.mrcImportModule){ + if (block.mrcImportModule) { generator.addImport(block.mrcImportModule); } let code = 'self.' + block.getFieldValue('NAME') + ' = ' + block.getFieldValue('TYPE') + '('; - + for (let i = 0; i < block.mrcArgs.length; i++) { - const fieldName = 'ARG' + i; + const fieldName = 'ARG' + i; + if (i != 0) { + code += ', ' + } + if(block.hideParams){ + let extension = ''; if(i != 0){ - code += ', ' + extension = '_' + (i + 1).toString(); } + code += block.mrcArgs[i].name + " = " + block.getFieldValue('NAME') + extension; + }else{ code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); - } + } + } code += ')\n' + "self.hardware.append(self." + block.getFieldValue('NAME') + ")\n"; return code; } diff --git a/src/blocks/mrc_port.ts b/src/blocks/mrc_port.ts new file mode 100644 index 00000000..93c41c95 --- /dev/null +++ b/src/blocks/mrc_port.ts @@ -0,0 +1,66 @@ +/** + * @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 Create a component with a name of a certain type + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly'; +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'; + + +type PortBlock = Blockly.Block & PortMixin; +interface PortMixin extends PortMixinType { +} +type PortMixinType = typeof COMPONENT; + +const COMPONENT = { + /** + * Block initialization. + */ + init: function (this: PortBlock): void { + this.setStyle(MRC_STYLE_PORTS); + this.appendDummyInput() + .appendField(createFieldNonEditableText(''), 'TYPE') + .appendField(new Blockly.FieldTextInput(''), 'PORT_NUM'); + + //this.setOutput(true, OUTPUT_NAME); + this.setOutput(true); + }, +} + +export const setup = function () { + Blockly.Blocks[BLOCK_NAME] = COMPONENT; +} + +export const pythonFromBlock = function ( + block: PortBlock, + generator: ExtendedPythonGenerator, +) { + let code = ""; + //TODO(Alan) - Make this real + + return [code, Order.ATOMIC]; +} diff --git a/src/blocks/setup_custom_blocks.ts b/src/blocks/setup_custom_blocks.ts index 983483c3..06ab7538 100644 --- a/src/blocks/setup_custom_blocks.ts +++ b/src/blocks/setup_custom_blocks.ts @@ -11,6 +11,7 @@ import * as ClassMethodDef from './mrc_class_method_def'; import * as Mechanism from './mrc_mechanism'; import * as Component from './mrc_component'; import * as MechanismContainerHolder from './mrc_mechanism_component_holder'; +import * as Port from './mrc_port'; const customBlocks = [ CallPythonFunction, @@ -25,6 +26,7 @@ const customBlocks = [ Mechanism, Component, MechanismContainerHolder, + Port, ]; export const setup = function(forBlock: any) { diff --git a/src/modules/mechanism_start.json b/src/modules/mechanism_start.json index d3f6a04b..c11a52f7 100644 --- a/src/modules/mechanism_start.json +++ b/src/modules/mechanism_start.json @@ -13,7 +13,16 @@ "canBeCalledWithinClass": false, "canBeCalledOutsideClass": false, "returnType": "None", - "params": [], + "params": [ + { + "name": "port_my_motor", + "type": "int" + }, + { + "name": "port_my_color_range_sensor", + "type": "int" + } + ], "pythonMethodName": "__init__" }, "fields": { diff --git a/src/themes/styles.ts b/src/themes/styles.ts index 18c79feb..ef86d0cc 100644 --- a/src/themes/styles.ts +++ b/src/themes/styles.ts @@ -30,6 +30,7 @@ export const MRC_STYLE_CLASS_BLOCKS = 'mrc_style_class_blocks'; export const MRC_CATEGORY_STYLE_METHODS = 'mrc_category_style_methods'; export const MRC_STYLE_MECHANISMS = 'mrc_style_mechanisms'; export const MRC_STYLE_COMPONENTS = 'mrc_style_components'; +export const MRC_STYLE_PORTS = 'mrc_style_ports'; export const add_mrc_styles = function (theme: Blockly.Theme): Blockly.Theme { theme.setBlockStyle(MRC_STYLE_FUNCTIONS, { @@ -85,6 +86,12 @@ export const add_mrc_styles = function (theme: Blockly.Theme): Blockly.Theme { "colourTertiary": "#496684", hat: "" }); + theme.setBlockStyle(MRC_STYLE_PORTS, { + colourPrimary: "#5ba55b", + colourSecondary: "#deedde", + colourTertiary: "#498449", + hat: "" + }); return theme; } \ No newline at end of file diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index a02841ac..3012db8c 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -1,131 +1,224 @@ -export const category = +const category_robot = { - kind: 'category', - name: 'Hardware', - contents: [ - { - kind: 'label', - text: 'Mechanisms', + kind: 'category', + name: 'Hardware', + contents: [ + { + kind: 'label', + text: 'Mechanisms', + }, + { + kind: 'block', + type: 'mrc_mechanism', + fields: { + NAME: 'my_drive', + TYPE: 'DriveMecanum' + }, + extraState: { + importModule: 'DriveMecanum', + params: [{ name: 'front_left_drive', type: 'int' }, + { name: 'front_right_drive', type: 'int' }, + { name: 'back_left_drive', type: 'int' }, + { name: 'back_right_drive', type: 'int' }, + ] + }, + inputs: { + ARG0: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 1 + }, + }, + }, + ARG1: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 2 + }, + }, + }, + ARG2: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 3 + }, + }, + }, + ARG3: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 4 + }, + }, + }, + } + }, + { + kind: 'block', + type: 'mrc_mechanism', + fields: { + NAME: 'my_claw', + TYPE: 'Claw' + }, + extraState: { + importModule: 'claw', + params: [{ name: 'port_gripper', type: 'int' }, + { name: 'port_piece_sensor', type: 'int' }, + ] + }, + inputs: { + ARG0: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 1 + }, + }, + }, + ARG1: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartIO', + PORT_NUM: 1 + }, + }, }, - { - kind: 'block', - type: 'mrc_mechanism', + } + }, + { + kind: 'label', + text: 'Components', + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_motor', + TYPE: 'SmartMotor' + }, + extraState: { + importModule: 'smart_motor', + params: [{ name: 'motorPort', type: 'int' }] + }, + inputs: { + ARG0: { + shadow: { + type: 'mrc_port', fields: { - NAME: 'my_drive', - TYPE: 'DriveMecanum' + TYPE: 'SmartMotor', + PORT_NUM: 1 }, - extraState: { - importModule : 'DriveMecanum', - params: [{name:'front_left_drive',type:'int'}, - {name:'front_right_drive',type:'int'}, - {name:'back_left_drive',type:'int'}, - {name:'back_right_drive',type:'int'}, - ] - }, - inputs: { - ARG0: { - shadow: { - type: 'math_number', - fields: { - NUM: 1, - }, - }, - }, - ARG1: { - shadow: { - type: 'math_number', - fields: { - NUM: 2, - }, - }, - }, - ARG2: { - shadow: { - type: 'math_number', - fields: { - NUM: 3, - }, - }, - }, - ARG3: { - shadow: { - type: 'math_number', - fields: { - NUM: 4, - }, - }, - }, - } + }, }, - { - kind: 'label', - text: 'Components', - }, - { - kind: 'block', - type: 'mrc_component', + } + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_color_range_sensor', + TYPE: 'ColorRangeSensor' + }, + extraState: { + importModule: 'color_range_sensor', + params: [{ name: 'I2CPort', type: 'int' }] + }, + inputs: { + ARG0: { + shadow: { + type: 'mrc_port', fields: { - NAME: 'my_motor', - TYPE: 'SmartMotor' + TYPE: 'I2C', + PORT_NUM: 1 }, - extraState: { - importModule : 'smart_motor', - params: [{name:'Motor Port',type:'int'}] - }, - inputs: { - ARG0: { - shadow: { - type: 'math_number', - fields: { - NUM: 1, - }, - }, - }, - } + }, }, - { - kind: 'block', - type: 'mrc_component', - fields: { - NAME: 'my_color_range_sensor', - TYPE: 'ColorRangeSensor' + } + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_touch_sensor', + TYPE: 'RevTouchSensor' + }, + extraState: { + importModule: 'rev_touch_sensor', + params: [{ name: 'SmartIOPort', type: 'int' }] + }, + inputs: { + ARG0: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartIO', + PORT_NUM: 1 + }, }, - extraState: { - importModule : 'color_range_sensor', - params: [{name:'I2C Port',type:'int'}] - }, - inputs: { - ARG0: { - shadow: { - type: 'math_number', - fields: { - NUM: 1, - }, - }, - }, - } - }, - { - kind: 'block', - type: 'mrc_component', - fields: { - NAME: 'my_touch_sensor', - TYPE: 'RevTouchSensor' }, - extraState: { - importModule : 'rev_touch_sensor', - params: [{name:'SmartIO port',type:'int'}] - }, - inputs: { - ARG0: { - shadow: { - type: 'math_number', - fields: { - NUM: 1, - }, - }, - }, - } + } + }, + ], +} + +const category_mechanism = +{ + kind: 'category', + name: 'Hardware', + contents: [ + { + kind: 'label', + text: 'Components', + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_motor', + TYPE: 'SmartMotor' + }, + extraState: { + importModule: 'smart_motor', + params: [{ name: 'motorPort', type: 'int' }], + hideParams: true + }, + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_color_range_sensor', + TYPE: 'ColorRangeSensor' + }, + extraState: { + importModule: 'color_range_sensor', + params: [{ name: 'I2CPort', type: 'int' }], + hideParams: true + }, + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_touch_sensor', + TYPE: 'RevTouchSensor' + }, + extraState: { + importModule: 'rev_touch_sensor', + params: [{ name: 'SmartIOPort', type: 'int' }], + hideParams: true + }, }, - ], -} \ No newline at end of file + ], +} +export const category = category_robot; \ No newline at end of file diff --git a/src/toolbox/robot_category.ts b/src/toolbox/robot_category.ts new file mode 100644 index 00000000..454f8752 --- /dev/null +++ b/src/toolbox/robot_category.ts @@ -0,0 +1,134 @@ +export const category = +{ + kind: 'category', + name: 'Robot', + contents: [ + { + kind: 'category', + name: 'my_drive', + contents: [ + { + kind: 'block', + type: 'mrc_call_python_function', + extraState: { + functionKind: 'instance_component', + returnType: '', + args: [ + { + name: 'forward_speed', + type: 'float', + }, + { + name: 'strafe_right_speed', + type: 'float', + }, + { + name: 'rotate_cw_speed', + type: 'float', + }, + ], + tooltip: 'Drive (robot relative)', + importModule: '', + componentClassName: 'rev.ColorRangeSensor', + componentName: 'robot.my_drive', + }, + fields: { + COMPONENT_NAME: 'robot.my_drive', + FUNC: 'drive_field_relative', + }, + inputs: {}, + }, + ] + }, + { + kind: 'category', + name: 'my_claw', + contents: [ + { + kind: 'category', + name: 'gripper', + contents: [ + + ], + }, + { + kind: 'category', + name: 'piece_sensor', + contents: [ + // def get_color_rgb(self) -> tuple[int, int, int]: + { + kind: 'block', + type: 'mrc_call_python_function', + extraState: { + functionKind: 'instance_component', + returnType: 'tuple[int, int, int]', + args: [], + tooltip: 'Get the color in rgb (red, green, blue).', + importModule: '', + componentClassName: 'rev.ColorRangeSensor', + componentName: 'colorSensor', + }, + fields: { + COMPONENT_NAME: 'robot.my_claw.piece_sensor', + FUNC: 'get_color_rgb', + }, + inputs: {}, + }, + // def get_color_hsv(self) -> tuple[int, int, int]: + { + kind: 'block', + type: 'mrc_call_python_function', + extraState: { + functionKind: 'instance_component', + returnType: 'tuple[int, int, int]', + args: [], + tooltip: 'Get the color in hsv (hue, saturation, value).', + importModule: '', + componentClassName: 'rev.ColorRangeSensor', + componentName: 'colorSensor', + }, + fields: { + COMPONENT_NAME: 'robot.my_claw.piece_sensor', + FUNC: 'get_color_hsv', + }, + inputs: {}, + }, + // def get_distance_mm(self) -> float: + { + kind: 'block', + type: 'mrc_call_python_function', + extraState: { + functionKind: 'instance_component', + returnType: 'float', + args: [], + tooltip: 'Get the distance of the object seen.', + importModule: '', + componentClassName: 'rev.ColorRangeSensor', + componentName: 'colorSensor', + }, + fields: { + COMPONENT_NAME: 'robot.my_claw.piece_sensor', + FUNC: 'get_distance_mm', + }, + inputs: {}, + }, + ], + }, + ] + }, + { + kind: 'category', + name: 'flywheel', + contents: [ + + ], + }, + { + kind: 'category', + name: 'shooter', + contents: [ + + ], + }, + ], +} \ No newline at end of file diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index 38fa9766..d1e7ee08 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -30,6 +30,7 @@ import {category as miscCategory} from './misc_category'; import {category as methodsCategory} from './methods_category'; import {category as componentSampleCategory} from './component_samples_category'; import {category as mechanismCategory} from './hardware_category'; +import {category as robotCategory} from './robot_category'; export function getToolboxJSON( opt_includeExportedBlocksFromProject: toolboxItems.ContentsType[], @@ -78,6 +79,7 @@ export function getToolboxJSON( }, methodsCategory, mechanismCategory, + robotCategory, componentSampleCategory, ]); From e73915210d858564801849a979f73f20d0b1ccb0 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sat, 26 Apr 2025 08:28:23 -0400 Subject: [PATCH 11/23] added update method --- src/modules/robot_start.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/modules/robot_start.json b/src/modules/robot_start.json index 1a54456b..cb06cbf8 100644 --- a/src/modules/robot_start.json +++ b/src/modules/robot_start.json @@ -20,6 +20,23 @@ "NAME": "init" } }, + { + "type": "mrc_class_method_def", + "id": "ElbONc{46f,UprTW(|1C", + "x": 210, + "y": 210, + "deletable": false, + "extraState": { + "canChangeSignature": false, + "canBeCalledWithinClass": false, + "canBeCalledOutsideClass": false, + "returnType": "None", + "params": [] + }, + "fields": { + "NAME": "update" + } + }, { "type": "mrc_mechanism_component_holder", "id": "ElbOAb{)s(,UprTW(|1C", From 7074b79b99f070efa35c2e835026a3132743b9f3 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sat, 26 Apr 2025 08:28:44 -0400 Subject: [PATCH 12/23] change orders and names of samples --- src/toolbox/hardware_category.ts | 80 ++++++++++++++++---------------- src/toolbox/robot_category.ts | 14 +++--- src/toolbox/toolbox.ts | 11 +++-- 3 files changed, 54 insertions(+), 51 deletions(-) diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index 3012db8c..45462527 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -11,15 +11,13 @@ const category_robot = kind: 'block', type: 'mrc_mechanism', fields: { - NAME: 'my_drive', - TYPE: 'DriveMecanum' + NAME: 'claw', + TYPE: 'Claw' }, extraState: { - importModule: 'DriveMecanum', - params: [{ name: 'front_left_drive', type: 'int' }, - { name: 'front_right_drive', type: 'int' }, - { name: 'back_left_drive', type: 'int' }, - { name: 'back_right_drive', type: 'int' }, + importModule: 'claw', + params: [{ name: 'gripper_port', type: 'int' }, + { name: 'piece_sensor_port', type: 'int' }, ] }, inputs: { @@ -36,26 +34,8 @@ const category_robot = shadow: { type: 'mrc_port', fields: { - TYPE: 'SmartMotor', - PORT_NUM: 2 - }, - }, - }, - ARG2: { - shadow: { - type: 'mrc_port', - fields: { - TYPE: 'SmartMotor', - PORT_NUM: 3 - }, - }, - }, - ARG3: { - shadow: { - type: 'mrc_port', - fields: { - TYPE: 'SmartMotor', - PORT_NUM: 4 + TYPE: 'SmartIO', + PORT_NUM: 1 }, }, }, @@ -65,13 +45,15 @@ const category_robot = kind: 'block', type: 'mrc_mechanism', fields: { - NAME: 'my_claw', - TYPE: 'Claw' + NAME: 'drive', + TYPE: 'DriveMecanum' }, extraState: { - importModule: 'claw', - params: [{ name: 'port_gripper', type: 'int' }, - { name: 'port_piece_sensor', type: 'int' }, + importModule: 'DriveMecanum', + params: [{ name: 'front_left_drive_port', type: 'int' }, + { name: 'front_right_drive_port', type: 'int' }, + { name: 'back_left_drive_port', type: 'int' }, + { name: 'back_right_drive_port', type: 'int' }, ] }, inputs: { @@ -88,8 +70,26 @@ const category_robot = shadow: { type: 'mrc_port', fields: { - TYPE: 'SmartIO', - PORT_NUM: 1 + TYPE: 'SmartMotor', + PORT_NUM: 2 + }, + }, + }, + ARG2: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 3 + }, + }, + }, + ARG3: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 4 }, }, }, @@ -108,7 +108,7 @@ const category_robot = }, extraState: { importModule: 'smart_motor', - params: [{ name: 'motorPort', type: 'int' }] + params: [{ name: 'motor_port', type: 'int' }] }, inputs: { ARG0: { @@ -131,7 +131,7 @@ const category_robot = }, extraState: { importModule: 'color_range_sensor', - params: [{ name: 'I2CPort', type: 'int' }] + params: [{ name: 'i2c_port', type: 'int' }] }, inputs: { ARG0: { @@ -154,7 +154,7 @@ const category_robot = }, extraState: { importModule: 'rev_touch_sensor', - params: [{ name: 'SmartIOPort', type: 'int' }] + params: [{ name: 'smartIO_port', type: 'int' }] }, inputs: { ARG0: { @@ -189,7 +189,7 @@ const category_mechanism = }, extraState: { importModule: 'smart_motor', - params: [{ name: 'motorPort', type: 'int' }], + params: [{ name: 'motor_port', type: 'int' }], hideParams: true }, }, @@ -202,7 +202,7 @@ const category_mechanism = }, extraState: { importModule: 'color_range_sensor', - params: [{ name: 'I2CPort', type: 'int' }], + params: [{ name: 'i2c_port', type: 'int' }], hideParams: true }, }, @@ -215,7 +215,7 @@ const category_mechanism = }, extraState: { importModule: 'rev_touch_sensor', - params: [{ name: 'SmartIOPort', type: 'int' }], + params: [{ name: 'smartIO_port', type: 'int' }], hideParams: true }, }, diff --git a/src/toolbox/robot_category.ts b/src/toolbox/robot_category.ts index 454f8752..e7bd2768 100644 --- a/src/toolbox/robot_category.ts +++ b/src/toolbox/robot_category.ts @@ -5,7 +5,7 @@ export const category = contents: [ { kind: 'category', - name: 'my_drive', + name: 'drive', contents: [ { kind: 'block', @@ -30,10 +30,10 @@ export const category = tooltip: 'Drive (robot relative)', importModule: '', componentClassName: 'rev.ColorRangeSensor', - componentName: 'robot.my_drive', + componentName: 'robot.drive', }, fields: { - COMPONENT_NAME: 'robot.my_drive', + COMPONENT_NAME: 'robot.drive', FUNC: 'drive_field_relative', }, inputs: {}, @@ -42,7 +42,7 @@ export const category = }, { kind: 'category', - name: 'my_claw', + name: 'claw', contents: [ { kind: 'category', @@ -69,7 +69,7 @@ export const category = componentName: 'colorSensor', }, fields: { - COMPONENT_NAME: 'robot.my_claw.piece_sensor', + COMPONENT_NAME: 'robot.claw.piece_sensor', FUNC: 'get_color_rgb', }, inputs: {}, @@ -88,7 +88,7 @@ export const category = componentName: 'colorSensor', }, fields: { - COMPONENT_NAME: 'robot.my_claw.piece_sensor', + COMPONENT_NAME: 'robot.claw.piece_sensor', FUNC: 'get_color_hsv', }, inputs: {}, @@ -107,7 +107,7 @@ export const category = componentName: 'colorSensor', }, fields: { - COMPONENT_NAME: 'robot.my_claw.piece_sensor', + COMPONENT_NAME: 'robot.claw.piece_sensor', FUNC: 'get_distance_mm', }, inputs: {}, diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index d1e7ee08..caaed92f 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -62,12 +62,16 @@ export function getToolboxJSON( { kind: 'sep', }, - miscCategory, + robotCategory, + { + kind: 'sep', + }, logicCategory, loopCategory, mathCategory, textCategory, listsCategory, + miscCategory, { kind: 'sep', }, @@ -78,9 +82,8 @@ export function getToolboxJSON( custom: 'VARIABLE', }, methodsCategory, - mechanismCategory, - robotCategory, - componentSampleCategory, + //mechanismCategory, + //componentSampleCategory, ]); return { From c104f18b91f66b9461a7708e0e4cbcd66c6e89ae Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sat, 26 Apr 2025 08:29:03 -0400 Subject: [PATCH 13/23] change defaults for mechanism_start (for screenshot) --- src/modules/mechanism_start.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/mechanism_start.json b/src/modules/mechanism_start.json index c11a52f7..c23344dc 100644 --- a/src/modules/mechanism_start.json +++ b/src/modules/mechanism_start.json @@ -15,11 +15,11 @@ "returnType": "None", "params": [ { - "name": "port_my_motor", + "name": "my_motor_port", "type": "int" }, { - "name": "port_my_color_range_sensor", + "name": "my_color_range_sensor_port", "type": "int" } ], From 57b47db159599b6820491c50c469da44a81d872e Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sat, 3 May 2025 15:42:20 -0400 Subject: [PATCH 14/23] Have ports being passed in --- src/blocks/mrc_component.ts | 4 +- src/blocks/mrc_mechanism_component_holder.ts | 43 +++++++++++++++----- src/blocks/mrc_port.ts | 6 +-- src/editor/extended_python_generator.ts | 27 +++++++++++- src/toolbox/hardware_category.ts | 2 +- src/toolbox/toolbox.ts | 4 +- 6 files changed, 67 insertions(+), 19 deletions(-) diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index a42e8eb6..374b411a 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -144,7 +144,9 @@ export const pythonFromBlock = function ( if(i != 0){ extension = '_' + (i + 1).toString(); } - code += block.mrcArgs[i].name + " = " + block.getFieldValue('NAME') + extension; + const newPort = block.getFieldValue('NAME') + extension + '_port'; + generator.addHardwarePort(newPort, block.mrcArgs[i].type); + code += block.mrcArgs[i].name + " = " + newPort; }else{ code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); } diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index acf96ab4..258c3383 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -90,28 +90,49 @@ export const setup = function () { Blockly.Blocks[BLOCK_NAME] = MECHANISM_COMPONENT_HOLDER; } -export const pythonFromBlock = function ( - block: MechanismComponentHolderBlock, - generator: ExtendedPythonGenerator, -) { +function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator){ let code = 'def define_hardware(self):\n' + generator.INDENT + 'self.hardware = []\n'; let mechanisms = ''; let components = ''; - if (block.getInput('MECHANISMS')) { - mechanisms = generator.statementToCode(block, 'MECHANISMS'); - } - if (block.getInput('COMPONENTS')) { - components = generator.statementToCode(block, 'COMPONENTS'); - } + mechanisms = generator.statementToCode(block, 'MECHANISMS'); + components = generator.statementToCode(block, 'COMPONENTS'); + const body = mechanisms + components; if(body != ''){ code += body; }else{ code += generator.INDENT + 'pass'; } + generator.addClassMethodDefinition('define_hardware', code); +} + +function pythonFromBlockInMechanism(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator){ + let components = ''; + + components = generator.statementToCode(block, 'COMPONENTS'); + let code = 'def define_hardware(self' + generator.getListOfPorts(false) + '):\n' + + generator.INDENT + 'self.hardware = []\n'; + + if(components != ''){ + code += components; + }else{ + code += generator.INDENT + 'pass'; + } generator.addClassMethodDefinition('define_hardware', code); - return ''; +} + +export const pythonFromBlock = function ( + block: MechanismComponentHolderBlock, + generator: ExtendedPythonGenerator, +) { + if(block.getInput('MECHANISMS')){ + pythonFromBlockInRobot(block, generator); + } + else{ + pythonFromBlockInMechanism(block, generator); + } + return '' } \ No newline at end of file diff --git a/src/blocks/mrc_port.ts b/src/blocks/mrc_port.ts index 93c41c95..2d0ce0f8 100644 --- a/src/blocks/mrc_port.ts +++ b/src/blocks/mrc_port.ts @@ -59,8 +59,8 @@ export const pythonFromBlock = function ( block: PortBlock, generator: ExtendedPythonGenerator, ) { - let code = ""; - //TODO(Alan) - Make this real + //TODO (Alan) : Specify the type here as well + let code = block.getFieldValue('PORT_NUM'); - return [code, Order.ATOMIC]; + return [code, Order.ATOMIC]; } diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index 3d0d4b27..c2f6cf28 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -35,6 +35,7 @@ export class ExtendedPythonGenerator extends PythonGenerator { private context: GeneratorContext | null = null; private classMethods: {[key: string]: string} = Object.create(null); + private ports: {[key: string]: string} = Object.create(null); constructor() { super('Python'); @@ -65,7 +66,9 @@ export class ExtendedPythonGenerator extends PythonGenerator { let variableDefinitions = ''; if (this.context?.getHasHardware()) { - variableDefinitions += this.INDENT + "self.define_hardware()\n"; + variableDefinitions += this.INDENT + "self.define_hardware("; + variableDefinitions += this.getListOfPorts(true); + variableDefinitions += ')'; } return variableDefinitions; @@ -108,6 +111,28 @@ export class ExtendedPythonGenerator extends PythonGenerator { this.classMethods[methodName] = code; } + /** + * Add a Hardware Port + */ + addHardwarePort(portName: string, type: string): void{ + this.ports[portName] = type; + } + + getListOfPorts(startWithFirst: boolean): string{ + let returnString = '' + let firstPort = startWithFirst; + for (const port in this.ports) { + if(!firstPort){ + returnString += ', '; + } + else{ + firstPort = false; + } + returnString += port; + } + return returnString; + } + finish(code: string): string { if (this.context && this.workspace) { const className = this.context.getClassName(); diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index 45462527..5895810c 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -221,4 +221,4 @@ const category_mechanism = }, ], } -export const category = category_robot; \ No newline at end of file +export const category = category_mechanism; \ No newline at end of file diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index caaed92f..023c6a34 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -29,7 +29,7 @@ import {category as listsCategory} from './lists_category'; import {category as miscCategory} from './misc_category'; import {category as methodsCategory} from './methods_category'; import {category as componentSampleCategory} from './component_samples_category'; -import {category as mechanismCategory} from './hardware_category'; +import {category as hardwareCategory} from './hardware_category'; import {category as robotCategory} from './robot_category'; export function getToolboxJSON( @@ -82,7 +82,7 @@ export function getToolboxJSON( custom: 'VARIABLE', }, methodsCategory, - //mechanismCategory, + hardwareCategory, //componentSampleCategory, ]); From 70e76c53b197510baebeb0826144eff33d131ad1 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sat, 3 May 2025 15:48:32 -0400 Subject: [PATCH 15/23] add list of ports needed for mechanism --- src/blocks/mrc_class_method_def.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/blocks/mrc_class_method_def.ts b/src/blocks/mrc_class_method_def.ts index 9b081f96..e3b556e0 100644 --- a/src/blocks/mrc_class_method_def.ts +++ b/src/blocks/mrc_class_method_def.ts @@ -458,7 +458,7 @@ export const pythonFromBlock = function ( xfix2 = xfix1; } if(block.mrcPythonMethodName == '__init__'){ - branch = generator.INDENT + 'super().__init__(robot)\n' + + branch = generator.INDENT + 'super().__init__()\n' + generator.defineClassVariables() + branch; } if (returnValue) { @@ -469,6 +469,10 @@ export const pythonFromBlock = function ( let params = block.mrcParameters; let paramString = "self"; + if (block.mrcPythonMethodName == '__init__') { + paramString += generator.getListOfPorts(false); + } + if (params.length != 0) { block.mrcParameters.forEach((param) => { paramString += ', ' + param.name; From 624db2b87f32fed9d972a153aba9b7d67ca70091 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sat, 3 May 2025 15:51:01 -0400 Subject: [PATCH 16/23] Clear the list of ports in finish --- src/editor/extended_python_generator.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index c2f6cf28..a9d8a2d3 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -144,6 +144,7 @@ export class ExtendedPythonGenerator extends PythonGenerator { classMethods.push(this.classMethods[name]) } this.classMethods = Object.create(null); + this.ports = Object.create(null); code = classDef + this.prefixLines(classMethods.join('\n\n'), this.INDENT); this.context.setExportedBlocks(this.produceExportedBlocks(this.workspace)); From b9b53c7a72ba39d2c4e08c93f82af0bf28b8658e Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sat, 3 May 2025 15:52:11 -0400 Subject: [PATCH 17/23] Remove the temporary init fields --- src/modules/mechanism_start.json | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/modules/mechanism_start.json b/src/modules/mechanism_start.json index c23344dc..d3f6a04b 100644 --- a/src/modules/mechanism_start.json +++ b/src/modules/mechanism_start.json @@ -13,16 +13,7 @@ "canBeCalledWithinClass": false, "canBeCalledOutsideClass": false, "returnType": "None", - "params": [ - { - "name": "my_motor_port", - "type": "int" - }, - { - "name": "my_color_range_sensor_port", - "type": "int" - } - ], + "params": [], "pythonMethodName": "__init__" }, "fields": { From 8fa56124a4f42bcf4829e1318c7d75ba1a9ebc7c Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sat, 3 May 2025 16:36:39 -0400 Subject: [PATCH 18/23] No longer allow multiple components or mechanisms named the same Fixes #115 --- src/blocks/mrc_mechanism_component_holder.ts | 71 +++++++++++++++----- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index 258c3383..6f4c838d 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -22,8 +22,13 @@ import * as Blockly from 'blockly'; import { MRC_STYLE_MECHANISMS } from '../themes/styles'; +import * as ChangeFramework from './utils/change_framework'; +import { getLegalName } from './utils/python'; 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'; export const BLOCK_NAME = 'mrc_mechanism_component_holder'; @@ -37,10 +42,25 @@ type MechanismComponentHolderExtraState = { type MechanismComponentHolderBlock = Blockly.Block & MechanismComponentHolderMixin; interface MechanismComponentHolderMixin extends MechanismComponentHolderMixinType { - mrcHideMechanisms : boolean; + mrcHideMechanisms: boolean; } type MechanismComponentHolderMixinType = typeof MECHANISM_COMPONENT_HOLDER; +function setName(block: Blockly.BlockSvg){ + const parentBlock = ChangeFramework.getParentOfType(block, BLOCK_NAME); + if (parentBlock) { + const variableBlocks = parentBlock!.getDescendants(true) + const otherNames: string[] = [] + variableBlocks?.forEach(function (variableBlock) { + if (variableBlock != block) { + otherNames.push(variableBlock.getFieldValue('NAME')); + } + }); + const currentName = block.getFieldValue('NAME'); + block.setFieldValue(getLegalName(currentName, otherNames), 'NAME'); + } +} + const MECHANISM_COMPONENT_HOLDER = { /** * Block initialization. @@ -52,6 +72,9 @@ const MECHANISM_COMPONENT_HOLDER = { 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); + }, saveExtraState: function (this: MechanismComponentHolderBlock): MechanismComponentHolderExtraState { const extraState: MechanismComponentHolderExtraState = { @@ -72,25 +95,39 @@ const MECHANISM_COMPONENT_HOLDER = { * Update the block to reflect the newly loaded extra state. */ updateBlock_: function (this: MechanismComponentHolderBlock): void { - if(this.mrcHideMechanisms){ - if(this.getInput('MECHANISMS')){ + if (this.mrcHideMechanisms) { + if (this.getInput('MECHANISMS')) { this.removeInput('MECHANISMS') - } + } } - else{ - if(this.getInput('MECHANISMS') == null){ + else { + if (this.getInput('MECHANISMS') == null) { this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms'); this.moveInputBefore('MECHANISMS', 'COMPONENTS') } } - } + }, + onBlockChanged: function (block: Blockly.BlockSvg, blockEvent: Blockly.Events.BlockBase) { + if (blockEvent.type == Blockly.Events.BLOCK_MOVE) { + let blockMoveEvent = blockEvent as Blockly.Events.BlockMove; + if (blockMoveEvent.reason?.includes('connect')) { + setName(block); + } + } + else { + if (blockEvent.type == Blockly.Events.BLOCK_CHANGE) { + setName(block); + } + } + }, + } export const setup = function () { Blockly.Blocks[BLOCK_NAME] = MECHANISM_COMPONENT_HOLDER; } -function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator){ +function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator) { let code = 'def define_hardware(self):\n' + generator.INDENT + 'self.hardware = []\n'; let mechanisms = ''; @@ -100,25 +137,25 @@ function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator: components = generator.statementToCode(block, 'COMPONENTS'); const body = mechanisms + components; - if(body != ''){ + if (body != '') { code += body; - }else{ + } else { code += generator.INDENT + 'pass'; } generator.addClassMethodDefinition('define_hardware', code); } -function pythonFromBlockInMechanism(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator){ +function pythonFromBlockInMechanism(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator) { let components = ''; components = generator.statementToCode(block, 'COMPONENTS'); - let code = 'def define_hardware(self' + generator.getListOfPorts(false) + '):\n' + - generator.INDENT + 'self.hardware = []\n'; + let code = 'def define_hardware(self' + generator.getListOfPorts(false) + '):\n' + + generator.INDENT + 'self.hardware = []\n'; - if(components != ''){ + if (components != '') { code += components; - }else{ + } else { code += generator.INDENT + 'pass'; } generator.addClassMethodDefinition('define_hardware', code); @@ -128,10 +165,10 @@ export const pythonFromBlock = function ( block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator, ) { - if(block.getInput('MECHANISMS')){ + if (block.getInput('MECHANISMS')) { pythonFromBlockInRobot(block, generator); } - else{ + else { pythonFromBlockInMechanism(block, generator); } return '' From d3fe869364bdd725cdc28a0c07feb4e6b61f6c69 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sat, 3 May 2025 16:54:35 -0400 Subject: [PATCH 19/23] Change leftover cut/paste error --- src/blocks/mrc_port.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blocks/mrc_port.ts b/src/blocks/mrc_port.ts index 2d0ce0f8..6be68cf7 100644 --- a/src/blocks/mrc_port.ts +++ b/src/blocks/mrc_port.ts @@ -34,9 +34,9 @@ export const OUTPUT_NAME = 'mrc_port'; type PortBlock = Blockly.Block & PortMixin; interface PortMixin extends PortMixinType { } -type PortMixinType = typeof COMPONENT; +type PortMixinType = typeof PORT; -const COMPONENT = { +const PORT = { /** * Block initialization. */ @@ -52,7 +52,7 @@ const COMPONENT = { } export const setup = function () { - Blockly.Blocks[BLOCK_NAME] = COMPONENT; + Blockly.Blocks[BLOCK_NAME] = PORT; } export const pythonFromBlock = function ( From 828c76dc0f000c80e859093c491bd5402e03cd72 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sun, 4 May 2025 16:13:49 -0400 Subject: [PATCH 20/23] Add details to OpMode Fixes #90 Fixe #91 --- src/blocks/mrc_opmode_details.ts | 99 +++++++++++++++++++++++++ src/blocks/setup_custom_blocks.ts | 2 + src/editor/extended_python_generator.ts | 33 ++++++++- src/modules/opmode_start.json | 17 ++++- 4 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 src/blocks/mrc_opmode_details.ts diff --git a/src/blocks/mrc_opmode_details.ts b/src/blocks/mrc_opmode_details.ts new file mode 100644 index 00000000..d1e7ba85 --- /dev/null +++ b/src/blocks/mrc_opmode_details.ts @@ -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 Block for Opmode details + * @author alan@porpoiseful.com (Alan Smith) + */ + +/** + * @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 Create a component with a name of a certain type + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly'; + +import { ExtendedPythonGenerator, OpModeDetails } from '../editor/extended_python_generator'; +import { createFieldDropdown } from '../fields/FieldDropdown'; +import { MRC_STYLE_CLASS_BLOCKS } from '../themes/styles'; + +export const BLOCK_NAME = 'mrc_opmode_details'; + +type OpmodeDetailsBlock = Blockly.Block & OpmodeDetailsMixin; +interface OpmodeDetailsMixin extends OpmodeDetailsMixinType { +} +type OpmodeDetailsMixinType = typeof OPMODE_DETAILS; + +const OPMODE_DETAILS = { + /** + * Block initialization. + */ + init: function (this: OpmodeDetailsBlock): void { + this.setStyle(MRC_STYLE_CLASS_BLOCKS); + this.appendDummyInput() + .appendField('Type') + .appendField(createFieldDropdown(['Auto', 'Teleop', 'Test']), 'TYPE') + .appendField(' ') + .appendField('Enabled') + .appendField(new Blockly.FieldCheckbox(true), 'ENABLED'); + + this.appendDummyInput() + .appendField('Display Name') + .appendField(new Blockly.FieldTextInput(''), 'NAME') + this.appendDummyInput() + .appendField('Display Group') + .appendField(new Blockly.FieldTextInput(''), 'GROUP'); + + this.getField('TYPE')?.setTooltip('What sort of OpMode this is'); + this.getField('ENABLED')?.setTooltip('Whether the OpMode is shown on Driver Station'); + this.getField('NAME')?.setTooltip('The name shown on the Driver Station. If blank will use the class name.'); + this.getField('GROUP')?.setTooltip('An optional group to group OpModes on Driver Station'); + }, +} + +export const setup = function () { + Blockly.Blocks[BLOCK_NAME] = OPMODE_DETAILS; +} + +export const pythonFromBlock = function ( + block: OpmodeDetailsBlock, + generator: ExtendedPythonGenerator, +) { + generator.setOpModeDetails(new OpModeDetails( + block.getFieldValue('NAME'), + block.getFieldValue('GROUP'), + block.getFieldValue('ENABLED') == 'TRUE', + block.getFieldValue('TYPE') + )); + return ''; +} diff --git a/src/blocks/setup_custom_blocks.ts b/src/blocks/setup_custom_blocks.ts index 06ab7538..645e888c 100644 --- a/src/blocks/setup_custom_blocks.ts +++ b/src/blocks/setup_custom_blocks.ts @@ -12,6 +12,7 @@ import * as Mechanism from './mrc_mechanism'; 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'; const customBlocks = [ CallPythonFunction, @@ -27,6 +28,7 @@ const customBlocks = [ Component, MechanismContainerHolder, Port, + OpModeDetails ]; export const setup = function(forBlock: any) { diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index a9d8a2d3..4e150482 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -27,6 +27,24 @@ import { FunctionArg } from '../blocks/mrc_call_python_function'; import * as MechanismContainerHolder from '../blocks/mrc_mechanism_component_holder'; import * as commonStorage from '../storage/common_storage'; +export class OpModeDetails { + constructor(private name: string, private group : string, private enabled : boolean, private type : string) {} + annotations() : string{ + let code = ''; + + if(this.enabled){ + code += '@' + this.type + "\n"; + if(this.name){ + code += '@name("' + this.name + '")\n'; + } + if(this.group){ + code += '@group("' + this.group + '")\n'; + } + } + return code; + } +} + // Extends the python generator to collect some information about functions and // variables that have been defined so they can be used in other modules. @@ -36,7 +54,9 @@ export class ExtendedPythonGenerator extends PythonGenerator { private classMethods: {[key: string]: string} = Object.create(null); private ports: {[key: string]: string} = Object.create(null); - + // Opmode details + private details : OpModeDetails | null = null; + constructor() { super('Python'); } @@ -137,7 +157,9 @@ export class ExtendedPythonGenerator extends PythonGenerator { if (this.context && this.workspace) { const className = this.context.getClassName(); const classParent = this.context.getClassParent(); + const annotations = this.details?.annotations(); this.addImport(classParent); + const classDef = 'class ' + className + '(' + classParent + '):\n'; const classMethods = []; for (const name in this.classMethods) { @@ -145,7 +167,7 @@ export class ExtendedPythonGenerator extends PythonGenerator { } this.classMethods = Object.create(null); this.ports = Object.create(null); - code = classDef + this.prefixLines(classMethods.join('\n\n'), this.INDENT); + code = annotations + classDef + this.prefixLines(classMethods.join('\n\n'), this.INDENT); this.context.setExportedBlocks(this.produceExportedBlocks(this.workspace)); } @@ -262,6 +284,13 @@ export class ExtendedPythonGenerator extends PythonGenerator { } return exportedBlocks; } + setOpModeDetails(details : OpModeDetails) { + this.details = details; + } + + getOpModeDetails() : OpModeDetails | null{ + return this.details; + } } export const extendedPythonGenerator = new ExtendedPythonGenerator(); diff --git a/src/modules/opmode_start.json b/src/modules/opmode_start.json index 55b5f11b..66e40497 100644 --- a/src/modules/opmode_start.json +++ b/src/modules/opmode_start.json @@ -2,11 +2,24 @@ "blocks": { "languageVersion": 0, "blocks": [ + { + "type": "mrc_opmode_details", + "id": "ElbONc{)gh,UprTW(|1C", + "x": 10, + "y": 10, + "deletable": false, + "fields": { + "NAME": "", + "GROUP": "", + "TYPE": "Auto", + "ENABLED": true + } + }, { "type": "mrc_class_method_def", "id": "ElbONc{)s(,UprTW(|1C", "x": 10, - "y": 10, + "y": 120, "deletable": false, "extraState": { "canChangeSignature": false, @@ -29,7 +42,7 @@ "type": "mrc_class_method_def", "id": "wxFAh6eaR1|3fTuV:UAd", "x": 10, - "y": 200, + "y": 300, "deletable": false, "extraState": { "canChangeSignature": false, From d363b109245d870b0736f1173b6b77ec5ec76636 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sun, 4 May 2025 16:19:35 -0400 Subject: [PATCH 21/23] Fixed output for non-opmodes --- src/editor/extended_python_generator.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index 4e150482..e3f23414 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -167,7 +167,11 @@ export class ExtendedPythonGenerator extends PythonGenerator { } this.classMethods = Object.create(null); this.ports = Object.create(null); - code = annotations + classDef + this.prefixLines(classMethods.join('\n\n'), this.INDENT); + code = classDef + this.prefixLines(classMethods.join('\n\n'), this.INDENT); + if(annotations){ + code = annotations + code; + } + this.details = null; this.context.setExportedBlocks(this.produceExportedBlocks(this.workspace)); } From b495146e4f307d2bac4af627ce0d0bbd92a429cf Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sun, 4 May 2025 16:25:23 -0400 Subject: [PATCH 22/23] Fix passing the correct thing to superclass init --- src/blocks/mrc_class_method_def.ts | 3 ++- src/editor/extended_python_generator.ts | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/blocks/mrc_class_method_def.ts b/src/blocks/mrc_class_method_def.ts index e3b556e0..b4696dfc 100644 --- a/src/blocks/mrc_class_method_def.ts +++ b/src/blocks/mrc_class_method_def.ts @@ -458,7 +458,8 @@ export const pythonFromBlock = function ( xfix2 = xfix1; } if(block.mrcPythonMethodName == '__init__'){ - branch = generator.INDENT + 'super().__init__()\n' + + let class_specific = generator.getClassSpecificForInit(); + branch = generator.INDENT + 'super().__init__(' + class_specific + ')\n' + generator.defineClassVariables() + branch; } if (returnValue) { diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index e3f23414..887aaf9e 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -291,9 +291,12 @@ export class ExtendedPythonGenerator extends PythonGenerator { setOpModeDetails(details : OpModeDetails) { this.details = details; } - - getOpModeDetails() : OpModeDetails | null{ - return this.details; + getClassSpecificForInit() : string{ + let classParent = this.context?.getClassParent(); + if (classParent == 'OpMode'){ + return 'robot' + } + return '' } } From 0f89813c05fdd1ee1e18f23d62f3db0b1dddc4ed Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sun, 4 May 2025 16:29:41 -0400 Subject: [PATCH 23/23] Removed some unnecessary code --- src/blocks/mrc_mechanism_component_holder.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index 6f4c838d..b72b1810 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -139,9 +139,8 @@ function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator: const body = mechanisms + components; if (body != '') { code += body; - } else { - code += generator.INDENT + 'pass'; - } + } + generator.addClassMethodDefinition('define_hardware', code); } @@ -155,9 +154,8 @@ function pythonFromBlockInMechanism(block: MechanismComponentHolderBlock, genera if (components != '') { code += components; - } else { - code += generator.INDENT + 'pass'; - } + } + generator.addClassMethodDefinition('define_hardware', code); }