From a2e8db7f468a11bafcae7ad46ad05e591da3d094 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Fri, 4 Jul 2025 22:48:14 -0700 Subject: [PATCH 1/4] Added @classmethod pseudo-constructors (static functions) to python components. Changed mrc_component to use static functions when instantiating components. Generate the blocks for the +Components toolbox category dynamically. --- external_samples/color_range_sensor.py | 8 +- external_samples/rev_touch_sensor.py | 11 +- external_samples/servo.py | 10 +- external_samples/smart_motor.py | 11 +- external_samples/spark_mini.py | 8 +- external_samples/sparkfun_led_stick.py | 8 +- src/blocks/mrc_call_python_function.ts | 72 +++++----- src/blocks/mrc_component.ts | 129 +++++++++++++++--- src/blocks/mrc_port.ts | 12 ++ .../generated/external_samples_data.json | 93 ++++++++++++- src/blocks/utils/python.ts | 24 ++-- src/toolbox/blocks_components.ts | 42 ------ src/toolbox/hardware_category.ts | 5 +- 13 files changed, 310 insertions(+), 123 deletions(-) delete mode 100644 src/toolbox/blocks_components.ts diff --git a/external_samples/color_range_sensor.py b/external_samples/color_range_sensor.py index 9b422bda..e22d66b2 100644 --- a/external_samples/color_range_sensor.py +++ b/external_samples/color_range_sensor.py @@ -17,7 +17,7 @@ # @author alan@porpoiseful.com (Alan Smith) from component import Component, PortType, InvalidPortException -from typing import Protocol +from typing import Protocol, Self class DistanceCallable(Protocol): def __call__(self, distance : float) -> None: @@ -52,7 +52,13 @@ def get_connection_port_type(self) -> list[PortType]: def periodic(self) -> None: pass + # Alternative constructor to create an instance from an i2c port + @classmethod + def from_i2c_port(cls: type[Self], i2c_port: int) -> Self: + return cls([(PortType.I2C_PORT, i2c_port)]) + # Component specific methods + def get_color_rgb(self) -> tuple[int, int, int]: '''gets the color in rgb (red, green, blue)''' pass diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index bf3a2343..8fabdb04 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -16,6 +16,7 @@ # @fileoverview This is a sample for a touch sensor # @author alan@porpoiseful.com (Alan Smith) +from typing import Self from component import Component, PortType, InvalidPortException, EmptyCallable class RevTouchSensor(Component): @@ -51,6 +52,14 @@ def periodic(self) -> None: self.pressed_callback() elif old and self.released_callback: self.released_callback() + + # Alternative constructor to create an instance from a smart io port + @classmethod + def from_io_port(cls: type[Self], io_port: int) -> Self: + return cls([(PortType.SMART_IO_PORT, io_port)]) + + # Component specific methods + def _read_hardware(self): # here read hardware to get the current value of the sensor and set self.is_pressed pass @@ -67,4 +76,4 @@ def register_when_pressed(self, callback: EmptyCallable) -> None: def register_when_released(self, callback: EmptyCallable) -> None: '''Event when touch sensor is released (after being pressed)''' - self.released_callback = callback \ No newline at end of file + self.released_callback = callback diff --git a/external_samples/servo.py b/external_samples/servo.py index 2324b82d..3c2f70ef 100644 --- a/external_samples/servo.py +++ b/external_samples/servo.py @@ -16,6 +16,7 @@ # @fileoverview This is a sample for a servo # @author alan@porpoiseful.com (Alan Smith) +from typing import Self from component import Component, PortType, InvalidPortException class Servo(Component): @@ -44,7 +45,13 @@ def get_connection_port_type(self) -> list[PortType]: def periodic(self) -> None: pass + # Alternative constructor to create an instance from an servo port + @classmethod + def from_servo_port(cls: type[Self], servo_port: int) -> Self: + return cls([(PortType.SERVO_PORT, servo_port)]) + # Component specific methods + def set_position(self, pos: float) -> None: '''Set the servo to a position between 0 and 1''' # sends to the hardware the position of the servo @@ -52,6 +59,3 @@ def set_position(self, pos: float) -> None: def set_angle_degrees(self, angle: float) -> None: '''Set the servo to an angle between 0 and 270''' self.set_position(angle / 270.0) - - - \ No newline at end of file diff --git a/external_samples/smart_motor.py b/external_samples/smart_motor.py index f3964ee4..aee2f4cf 100644 --- a/external_samples/smart_motor.py +++ b/external_samples/smart_motor.py @@ -15,9 +15,11 @@ # @fileoverview This is a sample for a smart motor # @author alan@porpoiseful.com (Alan Smith) + +from typing import Self from component import Component, PortType, InvalidPortException -class SmartMotor(Component): +class SmartMotor(Component): def __init__(self, ports : list[tuple[PortType, int]]): portType, port = ports[0] if portType != PortType.SMART_MOTOR_PORT: @@ -43,7 +45,13 @@ def get_connection_port_type(self) -> list[PortType]: def periodic(self) -> None: pass + # Alternative constructor to create an instance from a motor port + @classmethod + def from_motor_port(cls: type[Self], motor_port: int) -> Self: + return cls([(PortType.SMART_MOTOR_PORT, motor_port)]) + # Component specific methods + def set_speed(self, speed: float) -> None: '''Set the motor to a speed between -1 and 1''' # TODO: send to the hardware the speed of the motor @@ -64,4 +72,3 @@ def get_angle_degrees(self) -> float: def reset_relative_encoder(self) -> None: '''Reset the relative encoder value to 0''' pass - \ No newline at end of file diff --git a/external_samples/spark_mini.py b/external_samples/spark_mini.py index 838524b1..50999eb9 100644 --- a/external_samples/spark_mini.py +++ b/external_samples/spark_mini.py @@ -20,6 +20,7 @@ __author__ = "lizlooney@google.com (Liz Looney)" +from typing import Self from component import Component, PortType, InvalidPortException import wpilib import wpimath @@ -32,8 +33,6 @@ def __init__(self, ports : list[tuple[PortType, int]]): raise InvalidPortException self.spark_mini = wpilib.SparkMini(port) - # Component methods - def get_manufacturer(self) -> str: return "REV Robotics" @@ -62,6 +61,11 @@ def get_connection_port_type(self) -> list[PortType]: def periodic(self) -> None: pass + # Alternative constructor to create an instance from a motor port + @classmethod + def from_motor_port(cls: type[Self], motor_port: int) -> Self: + return cls([(PortType.SMART_MOTOR_PORT, motor_port)]) + # Component specific methods # Methods from wpilib.PWMMotorController diff --git a/external_samples/sparkfun_led_stick.py b/external_samples/sparkfun_led_stick.py index 6fc0b6c0..27f1b39e 100644 --- a/external_samples/sparkfun_led_stick.py +++ b/external_samples/sparkfun_led_stick.py @@ -14,6 +14,7 @@ __author__ = "lizlooney@google.com (Liz Looney)" +from typing import Self from component import Component, PortType, InvalidPortException import wpilib @@ -24,8 +25,6 @@ def __init__(self, ports : list[tuple[PortType, int]]): raise InvalidPortException self.port = port - # Component methods - def get_manufacturer(self) -> str: return "SparkFun" @@ -53,6 +52,11 @@ def get_connection_port_type(self) -> list[PortType]: def periodic(self) -> None: pass + # Alternative constructor to create an instance from an i2c port + @classmethod + def from_i2c_port(cls: type[Self], i2c_port: int) -> Self: + return cls([(PortType.I2C_PORT, i2c_port)]) + # SparkFunLEDStick methods def set_color(self, position: int, color: wpilib.Color) -> None: diff --git a/src/blocks/mrc_call_python_function.ts b/src/blocks/mrc_call_python_function.ts index c82f6cc0..282ffd10 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -26,14 +26,14 @@ import { Order } from 'blockly/python'; import { ClassMethodDefExtraState } from './mrc_class_method_def' import { getClassData, getAllowedTypesForSetCheck, getOutputCheck } from './utils/python'; import { FunctionData, findSuperFunctionData } from './utils/python_json_types'; -import * as value from './utils/value'; -import * as variable from './utils/variable'; +import * as Value from './utils/value'; +import * as Variable from './utils/variable'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import { createFieldDropdown } from '../fields/FieldDropdown'; import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; import { MRC_STYLE_FUNCTIONS } from '../themes/styles' -import * as toolboxItems from '../toolbox/items'; -import * as commonStorage from '../storage/common_storage'; +import * as ToolboxItems from '../toolbox/items'; +import * as CommonStorage from '../storage/common_storage'; // A block to call a python function. @@ -66,7 +66,7 @@ export type FunctionArg = { export function addModuleFunctionBlocks( moduleName: string, functions: FunctionData[], - contents: toolboxItems.ContentsType[]) { + contents: ToolboxItems.ContentsType[]) { for (const functionData of functions) { const block = createModuleFunctionOrStaticMethodBlock( FunctionKind.MODULE, moduleName, moduleName, functionData); @@ -77,7 +77,7 @@ export function addModuleFunctionBlocks( export function addStaticMethodBlocks( importModule: string, functions: FunctionData[], - contents: toolboxItems.ContentsType[]) { + contents: ToolboxItems.ContentsType[]) { for (const functionData of functions) { if (functionData.declaringClassName) { const block = createModuleFunctionOrStaticMethodBlock( @@ -91,7 +91,7 @@ function createModuleFunctionOrStaticMethodBlock( functionKind: FunctionKind, importModule: string, moduleOrClassName: string, - functionData: FunctionData): toolboxItems.Block { + functionData: FunctionData): ToolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: functionKind, returnType: functionData.returnType, @@ -110,16 +110,16 @@ function createModuleFunctionOrStaticMethodBlock( 'type': argData.type, }); // Check if we should plug a variable getter block into the argument input socket. - const input = value.valueForFunctionArgInput(argData.type, argData.defaultValue); + const input = Value.valueForFunctionArgInput(argData.type, argData.defaultValue); if (input) { inputs['ARG' + i] = input; } } - let block = new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); + let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); if (functionData.returnType && functionData.returnType != 'None') { - const varName = variable.varNameForType(functionData.returnType); + const varName = Variable.varNameForType(functionData.returnType); if (varName) { - block = variable.createVariableSetterBlock(varName, block); + block = Variable.createVariableSetterBlock(varName, block); } } return block; @@ -128,7 +128,7 @@ function createModuleFunctionOrStaticMethodBlock( export function addConstructorBlocks( importModule: string, functions: FunctionData[], - contents: toolboxItems.ContentsType[]) { + contents: ToolboxItems.ContentsType[]) { for (const functionData of functions) { const block = createConstructorBlock(importModule, functionData); contents.push(block); @@ -137,7 +137,7 @@ export function addConstructorBlocks( function createConstructorBlock( importModule: string, - functionData: FunctionData): toolboxItems.Block { + functionData: FunctionData): ToolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: FunctionKind.CONSTRUCTOR, returnType: functionData.returnType, @@ -155,16 +155,16 @@ function createConstructorBlock( 'type': argData.type, }); // Check if we should plug a variable getter block into the argument input socket. - const input = value.valueForFunctionArgInput(argData.type, argData.defaultValue); + const input = Value.valueForFunctionArgInput(argData.type, argData.defaultValue); if (input) { inputs['ARG' + i] = input; } } - let block = new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); + let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); if (functionData.returnType && functionData.returnType != 'None') { - const varName = variable.varNameForType(functionData.returnType); + const varName = Variable.varNameForType(functionData.returnType); if (varName) { - block = variable.createVariableSetterBlock(varName, block); + block = Variable.createVariableSetterBlock(varName, block); } } return block; @@ -172,7 +172,7 @@ function createConstructorBlock( export function addInstanceMethodBlocks( functions: FunctionData[], - contents: toolboxItems.ContentsType[]) { + contents: ToolboxItems.ContentsType[]) { for (const functionData of functions) { const block = createInstanceMethodBlock(functionData); contents.push(block); @@ -180,7 +180,7 @@ export function addInstanceMethodBlocks( } function createInstanceMethodBlock( - functionData: FunctionData): toolboxItems.Block { + functionData: FunctionData): ToolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: FunctionKind.INSTANCE, returnType: functionData.returnType, @@ -195,32 +195,32 @@ function createInstanceMethodBlock( const argData = functionData.args[i]; let argName = argData.name; if (i === 0 && argName === 'self' && functionData.declaringClassName) { - argName = variable.getSelfArgName(functionData.declaringClassName); + argName = Variable.getSelfArgName(functionData.declaringClassName); } extraState.args.push({ 'name': argName, 'type': argData.type, }); // Check if we should plug a variable getter block into the argument input socket. - const input = value.valueForFunctionArgInput(argData.type, argData.defaultValue); + const input = Value.valueForFunctionArgInput(argData.type, argData.defaultValue); if (input) { inputs['ARG' + i] = input; } } - let block = new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); + let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); if (functionData.returnType && functionData.returnType != 'None') { - const varName = variable.varNameForType(functionData.returnType); + const varName = Variable.varNameForType(functionData.returnType); if (varName) { - block = variable.createVariableSetterBlock(varName, block); + block = Variable.createVariableSetterBlock(varName, block); } } return block; } -export function addInstanceComponentBlocks( +export function getInstanceComponentBlocks( componentType: string, - componentName: string, - contents: toolboxItems.ContentsType[]) { + componentName: string) { + const contents: ToolboxItems.ContentsType[] = []; const classData = getClassData(componentType); if (!classData) { @@ -242,10 +242,12 @@ export function addInstanceComponentBlocks( const block = createInstanceComponentBlock(componentName, functionData); contents.push(block); } + + return contents; } function createInstanceComponentBlock( - componentName: string, functionData: FunctionData): toolboxItems.Block { + componentName: string, functionData: FunctionData): ToolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: FunctionKind.INSTANCE_COMPONENT, returnType: functionData.returnType, @@ -270,17 +272,17 @@ function createInstanceComponentBlock( 'type': argData.type, }); // Check if we should plug a variable getter block into the argument input socket. - const input = value.valueForFunctionArgInput(argData.type, argData.defaultValue); + const input = Value.valueForFunctionArgInput(argData.type, argData.defaultValue); if (input) { // Because we skipped the self argument, use i - 1 when filling the inputs array. inputs['ARG' + (i - 1)] = input; } } - let block = new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); + let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); if (functionData.returnType && functionData.returnType != 'None') { - const varName = variable.varNameForType(functionData.returnType); + const varName = Variable.varNameForType(functionData.returnType); if (varName) { - block = variable.createVariableSetterBlock(varName, block); + block = Variable.createVariableSetterBlock(varName, block); } } return block; @@ -683,11 +685,11 @@ export const pythonFromBlock = function( : block.getFieldValue(FIELD_FUNCTION_NAME); // Generate the correct code depending on the module type. switch (generator.getModuleType()) { - case commonStorage.MODULE_TYPE_ROBOT: - case commonStorage.MODULE_TYPE_MECHANISM: + case CommonStorage.MODULE_TYPE_ROBOT: + case CommonStorage.MODULE_TYPE_MECHANISM: code = 'self.'; break; - case commonStorage.MODULE_TYPE_OPMODE: + case CommonStorage.MODULE_TYPE_OPMODE: default: code = 'self.robot.'; break; diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 374b411a..49d890b9 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -25,11 +25,33 @@ 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'; +import { getAllowedTypesForSetCheck, getClassData, getSubclassNames } from './utils/python'; +import * as ToolboxItems from '../toolbox/items'; +import * as CommonStorage from '../storage/common_storage'; +import { createPortShadow } from './mrc_port'; +import { createNumberShadowValue } from './utils/value'; export const BLOCK_NAME = 'mrc_component'; export const OUTPUT_NAME = 'mrc_component'; +const FIELD_NAME = 'NAME'; +const FIELD_TYPE = 'TYPE'; + +// key is a regex pattern that matches a function argument name, value is type of mrc_port block to use. +// TODO: improve these regex pattern. +const RECOGNIZED_PORT_ARG_NAME_PATTERNS: { [key: string]: string } = { + 'can_port': 'SmartCan', + 'i2c_port': 'SmartIO', + 'io_port': 'SmartIO', + 'smartIO_port': 'SmartIO', + 'smart_io_port': 'SmartIO', + 'motor_port': 'SmartMotor', + 'servo_port': 'SmartIO', + 'smart_motor_port': 'SmartMotor', + 'usb_port': 'USBPort', +}; + + export type ConstructorArg = { name: string, type: string, @@ -37,6 +59,8 @@ export type ConstructorArg = { type ComponentExtraState = { importModule?: string, + // If staticFunctionName is not present, generate the constructor. + staticFunctionName?: string, hideParams?: boolean, params?: ConstructorArg[], } @@ -46,19 +70,20 @@ interface ComponentMixin extends ComponentMixinType { mrcArgs: ConstructorArg[], hideParams: boolean, mrcImportModule: string, + mrcStaticFunctionName: string, } type ComponentMixinType = typeof COMPONENT; const COMPONENT = { /** - * Block initialization. - */ + * Block initialization. + */ init: function (this: ComponentBlock): void { this.setStyle(MRC_STYLE_COMPONENTS); this.appendDummyInput() - .appendField(new Blockly.FieldTextInput('my_mech'), 'NAME') + .appendField(new Blockly.FieldTextInput(''), FIELD_NAME) .appendField('of type') - .appendField(createFieldNonEditableText(''), 'TYPE'); + .appendField(createFieldNonEditableText(''), FIELD_TYPE); this.setPreviousStatement(true, OUTPUT_NAME); this.setNextStatement(true, OUTPUT_NAME); }, @@ -79,16 +104,20 @@ const COMPONENT = { if (this.mrcImportModule) { extraState.importModule = this.mrcImportModule; } + if (this.mrcStaticFunctionName) { + extraState.staticFunctionName = this.mrcStaticFunctionName; + } if (this.hideParams) { extraState.hideParams = this.hideParams; } return extraState; }, /** - * Applies the given state to this block. - */ + * Applies the given state to this block. + */ loadExtraState: function (this: ComponentBlock, extraState: ComponentExtraState): void { this.mrcImportModule = extraState.importModule ? extraState.importModule : ''; + this.mrcStaticFunctionName = extraState.staticFunctionName ? extraState.staticFunctionName : ''; this.hideParams = extraState.hideParams ? extraState.hideParams : false; this.mrcArgs = []; @@ -104,8 +133,8 @@ const COMPONENT = { this.updateBlock_(); }, /** - * Update the block to reflect the newly loaded extra state. - */ + * Update the block to reflect the newly loaded extra state. + */ updateBlock_: function (this: ComponentBlock): void { if (this.hideParams == false) { // Add input sockets for the arguments. @@ -132,25 +161,93 @@ export const pythonFromBlock = function ( if (block.mrcImportModule) { generator.addImport(block.mrcImportModule); } - let code = 'self.' + block.getFieldValue('NAME') + ' = ' + block.getFieldValue('TYPE') + '('; + let code = 'self.' + block.getFieldValue(FIELD_NAME) + ' = ' + block.getFieldValue(FIELD_TYPE); + if (block.mrcStaticFunctionName) { + code += '.' + block.mrcStaticFunctionName; + } + code += '('; for (let i = 0; i < block.mrcArgs.length; i++) { const fieldName = 'ARG' + i; if (i != 0) { code += ', ' } - if(block.hideParams){ + if (block.hideParams) { let extension = ''; - if(i != 0){ + if (i != 0) { extension = '_' + (i + 1).toString(); } - const newPort = block.getFieldValue('NAME') + extension + '_port'; + const newPort = block.getFieldValue(FIELD_NAME) + extension + '_port'; generator.addHardwarePort(newPort, block.mrcArgs[i].type); - code += block.mrcArgs[i].name + " = " + newPort; - }else{ + code += block.mrcArgs[i].name + ' = ' + newPort; + } else { code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); } } - code += ')\n' + "self.hardware.append(self." + block.getFieldValue('NAME') + ")\n"; + code += ')\n' + 'self.hardware.append(self.' + block.getFieldValue(FIELD_NAME) + ')\n'; return code; } + +export function getAllPossibleComponents(hideParams: boolean): ToolboxItems.ContentsType[] { + const contents: ToolboxItems.ContentsType[] = []; + // Iterate through all the components subclasses and add definition blocks. + const componentTypes = getSubclassNames('component.Component'); + componentTypes.forEach(componentType => { + const classData = getClassData(componentType); + if (!classData) { + throw new Error('Could not find classData for ' + componentType); + } + + const componentName = ( + 'my_' + + CommonStorage.pascalCaseToSnakeCase( + componentType.substring(componentType.lastIndexOf('.') + 1))); + + classData.staticMethods.forEach(staticFunctionData => { + if (staticFunctionData.returnType === componentType) { + contents.push(createComponentBlock(componentName, classData, staticFunctionData, hideParams)); + } + }); + }); + + return contents; +} + +function createComponentBlock( + componentName: string, classData: ClassData, staticFunctionData: FunctionData, hideParams: boolean): ToolboxItems.Block { + const extraState: ComponentExtraState = { + importModule: classData.moduleName, + staticFunctionName: staticFunctionData.functionName, + params: [], + hideParams: hideParams, + }; + const fields: {[key: string]: any} = {}; + fields[FIELD_NAME] = componentName; + fields[FIELD_TYPE] = classData.className; + const inputs: {[key: string]: any} = {}; + for (let i = 0; i < staticFunctionData.args.length; i++) { + const argData = staticFunctionData.args[i]; + extraState.params.push({ + 'name': argData.name, + 'type': argData.type, + }); + if (argData.type === 'int') { + const portType = getPortTypeForArgument(argData.name); + if (portType) { + inputs['ARG' + i] = createPortShadow(portType, 1); + } else { + inputs['ARG' + i] = createNumberShadowValue(1); + } + } + } + return new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); +} + +function getPortTypeForArgument(argName: string): string | null { + for (const pattern in RECOGNIZED_PORT_ARG_NAME_PATTERNS) { + if (new RegExp(pattern, 'i').test(argName)) { + return RECOGNIZED_PORT_ARG_NAME_PATTERNS[pattern]; + } + } + return null; +} diff --git a/src/blocks/mrc_port.ts b/src/blocks/mrc_port.ts index 6be68cf7..49421d9a 100644 --- a/src/blocks/mrc_port.ts +++ b/src/blocks/mrc_port.ts @@ -64,3 +64,15 @@ export const pythonFromBlock = function ( return [code, Order.ATOMIC]; } + +export function createPortShadow(portType: string, portNum: int) { + return { + shadow: { + type: 'mrc_port', + fields: { + TYPE: portType, + PORT_NUM: portNum, + }, + }, + }; +} diff --git a/src/blocks/utils/generated/external_samples_data.json b/src/blocks/utils/generated/external_samples_data.json index c798a301..4e0dfab3 100644 --- a/src/blocks/utils/generated/external_samples_data.json +++ b/src/blocks/utils/generated/external_samples_data.json @@ -269,7 +269,21 @@ ], "instanceVariables": [], "moduleName": "color_range_sensor", - "staticMethods": [] + "staticMethods": [ + { + "args": [ + { + "defaultValue": null, + "name": "i2c_port", + "type": "int" + } + ], + "declaringClassName": "color_range_sensor.ColorRangeSensor", + "functionName": "from_i2c_port", + "returnType": "color_range_sensor.ColorRangeSensor", + "tooltip": "" + } + ] }, { "className": "color_range_sensor.DistanceCallable", @@ -698,7 +712,21 @@ ], "instanceVariables": [], "moduleName": "rev_touch_sensor", - "staticMethods": [] + "staticMethods": [ + { + "args": [ + { + "defaultValue": null, + "name": "io_port", + "type": "int" + } + ], + "declaringClassName": "rev_touch_sensor.RevTouchSensor", + "functionName": "from_io_port", + "returnType": "rev_touch_sensor.RevTouchSensor", + "tooltip": "" + } + ] }, { "className": "servo.Servo", @@ -876,7 +904,21 @@ ], "instanceVariables": [], "moduleName": "servo", - "staticMethods": [] + "staticMethods": [ + { + "args": [ + { + "defaultValue": null, + "name": "servo_port", + "type": "int" + } + ], + "declaringClassName": "servo.Servo", + "functionName": "from_servo_port", + "returnType": "servo.Servo", + "tooltip": "" + } + ] }, { "className": "smart_motor.SmartMotor", @@ -1093,7 +1135,21 @@ ], "instanceVariables": [], "moduleName": "smart_motor", - "staticMethods": [] + "staticMethods": [ + { + "args": [ + { + "defaultValue": null, + "name": "motor_port", + "type": "int" + } + ], + "declaringClassName": "smart_motor.SmartMotor", + "functionName": "from_motor_port", + "returnType": "smart_motor.SmartMotor", + "tooltip": "" + } + ] }, { "className": "spark_mini.SparkMiniComponent", @@ -1542,6 +1598,19 @@ "functionName": "check_motors", "returnType": "None", "tooltip": "Check the motors to see if any have timed out.\n\nThis static method is called periodically to poll all the motors and stop\nany that have timed out." + }, + { + "args": [ + { + "defaultValue": null, + "name": "motor_port", + "type": "int" + } + ], + "declaringClassName": "spark_mini.SparkMiniComponent", + "functionName": "from_motor_port", + "returnType": "spark_mini.SparkMiniComponent", + "tooltip": "" } ] }, @@ -1752,7 +1821,21 @@ ], "instanceVariables": [], "moduleName": "sparkfun_led_stick", - "staticMethods": [] + "staticMethods": [ + { + "args": [ + { + "defaultValue": null, + "name": "i2c_port", + "type": "int" + } + ], + "declaringClassName": "sparkfun_led_stick.SparkFunLEDStick", + "functionName": "from_i2c_port", + "returnType": "sparkfun_led_stick.SparkFunLEDStick", + "tooltip": "" + } + ] } ], "modules": [ diff --git a/src/blocks/utils/python.ts b/src/blocks/utils/python.ts index f0f895fb..fa6d59ff 100644 --- a/src/blocks/utils/python.ts +++ b/src/blocks/utils/python.ts @@ -23,9 +23,9 @@ import { ClassData, PythonData, organizeVarDataByType, VariableGettersAndSetters import { robotPyData } from './robotpy_data'; import { externalSamplesData } from './external_samples_data'; -import * as pythonEnum from "../mrc_get_python_enum_value"; -import * as getPythonVariable from "../mrc_get_python_variable"; -import * as setPythonVariable from "../mrc_set_python_variable"; +import * as PythonEnum from "../mrc_get_python_enum_value"; +import * as GetPythonVariable from "../mrc_get_python_variable"; +import * as SetPythonVariable from "../mrc_set_python_variable"; // Utilities related to blocks for python modules and classes, including those from RobotPy, external samples, etc. @@ -41,7 +41,7 @@ export function initialize() { for (const moduleData of pythonData.modules) { // Initialize enums. for (const enumData of moduleData.enums) { - pythonEnum.initializeEnum(enumData.enumClassName, enumData.enumValues, enumData.tooltip); + PythonEnum.initializeEnum(enumData.enumClassName, enumData.enumValues, enumData.tooltip); } // Initialize module variables. @@ -49,13 +49,13 @@ export function initialize() { organizeVarDataByType(moduleData.moduleVariables); for (const varType in varsByType) { const variableGettersAndSetters = varsByType[varType]; - getPythonVariable.initializeModuleVariableGetter( + GetPythonVariable.initializeModuleVariableGetter( moduleData.moduleName, varType, variableGettersAndSetters.varNamesForGetter, variableGettersAndSetters.tooltipsForGetter); if (variableGettersAndSetters.varNamesForSetter.length) { - setPythonVariable.initializeModuleVariableSetter( + SetPythonVariable.initializeModuleVariableSetter( moduleData.moduleName, varType, variableGettersAndSetters.varNamesForSetter, @@ -68,7 +68,7 @@ export function initialize() { for (const classData of pythonData.classes) { // Initialize enums. for (const enumData of classData.enums) { - pythonEnum.initializeEnum(enumData.enumClassName, enumData.enumValues, enumData.tooltip); + PythonEnum.initializeEnum(enumData.enumClassName, enumData.enumValues, enumData.tooltip); } // Initialize instance variables. @@ -77,13 +77,13 @@ export function initialize() { organizeVarDataByType(classData.instanceVariables); for (const varType in varsByType) { const variableGettersAndSetters = varsByType[varType]; - getPythonVariable.initializeInstanceVariableGetter( + GetPythonVariable.initializeInstanceVariableGetter( classData.className, varType, variableGettersAndSetters.varNamesForGetter, variableGettersAndSetters.tooltipsForGetter); if (variableGettersAndSetters.varNamesForSetter.length) { - setPythonVariable.initializeInstanceVariableSetter( + SetPythonVariable.initializeInstanceVariableSetter( classData.className, varType, variableGettersAndSetters.varNamesForSetter, @@ -98,13 +98,13 @@ export function initialize() { organizeVarDataByType(classData.classVariables); for (const varType in varsByType) { const variableGettersAndSetters = varsByType[varType]; - getPythonVariable.initializeClassVariableGetter( + GetPythonVariable.initializeClassVariableGetter( classData.className, varType, variableGettersAndSetters.varNamesForGetter, variableGettersAndSetters.tooltipsForGetter); if (variableGettersAndSetters.varNamesForSetter.length) { - setPythonVariable.initializeClassVariableSetter( + SetPythonVariable.initializeClassVariableSetter( classData.className, varType, variableGettersAndSetters.varNamesForSetter, @@ -145,7 +145,7 @@ export function getAlias(type: string): string | null { // Returns the list of subclass names for the given type. // For example, if type is 'wpilib.drive.RobotDriveBase', this function will // return ['wpilib.drive.DifferentialDrive', 'wpilib.drive.MecanumDrive']. -function getSubclassNames(type: string): string[] | null { +export function getSubclassNames(type: string): string[] | null { for (const pythonData of allPythonData) { for (const className in pythonData.subclasses) { if (type === className) { diff --git a/src/toolbox/blocks_components.ts b/src/toolbox/blocks_components.ts deleted file mode 100644 index f95becf6..00000000 --- a/src/toolbox/blocks_components.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @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 Component blocks for the toolbox. - */ - -import * as ToolboxItems from './items'; -import * as ColorSensor from './hardware_components/color_sensor'; -import * as SmartMotor from './hardware_components/smart_motor'; -import * as Servo from './hardware_components/servo'; -import * as TouchSensor from './hardware_components/touch_sensor'; -import { addInstanceComponentBlocks } from '../blocks/mrc_call_python_function'; - -export function getAllPossibleComponents(hideParams: boolean): ToolboxItems.ContentsType[] { - return [ - SmartMotor.getDefinitionBlock(hideParams), - TouchSensor.getDefinitionBlock(hideParams), - ColorSensor.getDefinitionBlock(hideParams), - Servo.getDefinitionBlock(hideParams), - ]; -} - -export function getBlocks(componentType: string, componentName: string): ToolboxItems.ContentsType[] { - const contents: ToolboxItems.ContentsType[] = []; - addInstanceComponentBlocks(componentType, componentName, contents); - return contents; -} diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index b9c0cb35..5b3810ed 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -28,7 +28,8 @@ import * as Blockly from 'blockly/core'; import * as commonStorage from '../storage/common_storage'; import { getAllPossibleMechanisms } from './blocks_mechanisms'; -import { getAllPossibleComponents, getBlocks } from './blocks_components'; +import { getAllPossibleComponents } from '../blocks/mrc_component'; +import { getInstanceComponentBlocks } from '../blocks/mrc_call_python_function'; import * as MechanismComponentHolder from '../blocks/mrc_mechanism_component_holder'; export function getHardwareCategory(currentModule: commonStorage.Module) { @@ -273,7 +274,7 @@ function getComponentsBlocks(currentModule: commonStorage.Module, hideParams : b contents.push({ kind: 'category', name: componentName, - contents: getBlocks(componentType, componentName), + contents: getInstanceComponentBlocks(componentType, componentName), }); } } From 4669c16775f4646c1bf19c6947d6134853e22968 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Fri, 4 Jul 2025 23:22:05 -0700 Subject: [PATCH 2/4] Removed component files from src/toolbox/hardware_components. Fixed problem with + Components category for mechanism (where hideParams is true). --- src/blocks/mrc_component.ts | 21 ++++--- src/toolbox/hardware_category.ts | 12 ++-- .../hardware_components/color_sensor.ts | 57 ------------------- src/toolbox/hardware_components/servo.ts | 57 ------------------- .../hardware_components/smart_motor.ts | 57 ------------------- .../hardware_components/touch_sensor.ts | 57 ------------------- 6 files changed, 18 insertions(+), 243 deletions(-) delete mode 100644 src/toolbox/hardware_components/color_sensor.ts delete mode 100644 src/toolbox/hardware_components/servo.ts delete mode 100644 src/toolbox/hardware_components/smart_motor.ts delete mode 100644 src/toolbox/hardware_components/touch_sensor.ts diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 49d890b9..2d079a91 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -34,11 +34,12 @@ import { createNumberShadowValue } from './utils/value'; export const BLOCK_NAME = 'mrc_component'; export const OUTPUT_NAME = 'mrc_component'; -const FIELD_NAME = 'NAME'; -const FIELD_TYPE = 'TYPE'; +export const FIELD_NAME = 'NAME'; +export const FIELD_TYPE = 'TYPE'; // key is a regex pattern that matches a function argument name, value is type of mrc_port block to use. -// TODO: improve these regex pattern. +// TODO: Improve these regex pattern. +// TODO: Should the types here match the PortType values in external_samples/component.py? const RECOGNIZED_PORT_ARG_NAME_PATTERNS: { [key: string]: string } = { 'can_port': 'SmartCan', 'i2c_port': 'SmartIO', @@ -231,12 +232,14 @@ function createComponentBlock( 'name': argData.name, 'type': argData.type, }); - if (argData.type === 'int') { - const portType = getPortTypeForArgument(argData.name); - if (portType) { - inputs['ARG' + i] = createPortShadow(portType, 1); - } else { - inputs['ARG' + i] = createNumberShadowValue(1); + if (!hideParams) { + if (argData.type === 'int') { + const portType = getPortTypeForArgument(argData.name); + if (portType) { + inputs['ARG' + i] = createPortShadow(portType, 1); + } else { + inputs['ARG' + i] = createNumberShadowValue(1); + } } } } diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index 5b3810ed..1b62ccc6 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -28,7 +28,7 @@ import * as Blockly from 'blockly/core'; import * as commonStorage from '../storage/common_storage'; import { getAllPossibleMechanisms } from './blocks_mechanisms'; -import { getAllPossibleComponents } from '../blocks/mrc_component'; +import * as Component from '../blocks/mrc_component'; import { getInstanceComponentBlocks } from '../blocks/mrc_call_python_function'; import * as MechanismComponentHolder from '../blocks/mrc_mechanism_component_holder'; @@ -221,7 +221,7 @@ function getRobotComponentsBlocks(currentModule: commonStorage.Module) { contents.push({ kind: 'category', name: '+ Component', - contents: getAllPossibleComponents(false) + contents: Component.getAllPossibleComponents(false) }); } @@ -249,7 +249,7 @@ function getComponentsBlocks(currentModule: commonStorage.Module, hideParams : b contents.push({ kind: 'category', name: '+ Component', - contents: getAllPossibleComponents(hideParams) + contents: Component.getAllPossibleComponents(hideParams) }); // Get components from the current workspace @@ -266,11 +266,11 @@ function getComponentsBlocks(currentModule: commonStorage.Module, hideParams : b // Walk through all connected component blocks while (componentBlock) { if (componentBlock.type === 'mrc_component') { - const componentName = componentBlock.getFieldValue('NAME'); - const componentType = componentBlock.getFieldValue('TYPE'); + const componentName = componentBlock.getFieldValue(Component.FIELD_NAME); + const componentType = componentBlock.getFieldValue(Component.FIELD_TYPE); if (componentName && componentType) { - // Get the blocks for this specific component + // Get the function blocks for this specific component. contents.push({ kind: 'category', name: componentName, diff --git a/src/toolbox/hardware_components/color_sensor.ts b/src/toolbox/hardware_components/color_sensor.ts deleted file mode 100644 index 4c36136c..00000000 --- a/src/toolbox/hardware_components/color_sensor.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @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 Color range sensor hardware component definitions. - * @author alan@porpoiseful.com (Alan Smith) - */ - -import * as ToolboxItems from '../items'; - -export const TYPE_NAME = 'color_range_sensor.ColorRangeSensor'; - -/** - * Returns a component definition block for a color range sensor. - */ -export function getDefinitionBlock(hideParams: boolean): ToolboxItems.ContentsType { - return { - kind: 'block', - type: 'mrc_component', - fields: { - NAME: 'my_color_range_sensor', - TYPE: TYPE_NAME, - }, - extraState: { - importModule: 'color_range_sensor', - params: [{name: 'smartIO_port', type: 'int'}], - hideParams, - }, - ...(hideParams ? {} : { - inputs: { - ARG0: { - shadow: { - type: 'mrc_port', - fields: { - TYPE: 'SmartIO', - PORT_NUM: 1, - }, - }, - }, - }, - }), - }; -} diff --git a/src/toolbox/hardware_components/servo.ts b/src/toolbox/hardware_components/servo.ts deleted file mode 100644 index df770cd4..00000000 --- a/src/toolbox/hardware_components/servo.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @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 Servo hardware component definitions. - * @author alan@porpoiseful.com (Alan Smith) - */ - -import * as ToolboxItems from '../items'; - -export const TYPE_NAME = 'servo.Servo'; - -/** - * Returns a component definition block for a servo. - */ -export function getDefinitionBlock(hideParams: boolean): ToolboxItems.ContentsType { - return { - kind: 'block', - type: 'mrc_component', - fields: { - NAME: 'my_servo', - TYPE: TYPE_NAME, - }, - extraState: { - importModule: 'servo', - params: [{name: 'servo_port', type: 'int'}], - hideParams, - }, - ...(hideParams ? {} : { - inputs: { - ARG0: { - shadow: { - type: 'mrc_port', - fields: { - TYPE: 'SmartIO', - PORT_NUM: 1, - }, - }, - }, - }, - }), - }; -} diff --git a/src/toolbox/hardware_components/smart_motor.ts b/src/toolbox/hardware_components/smart_motor.ts deleted file mode 100644 index f8d7f766..00000000 --- a/src/toolbox/hardware_components/smart_motor.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @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 SmartMotor hardware component definitions. - * @author alan@porpoiseful.com (Alan Smith) - */ - -import * as ToolboxItems from '../items'; - -export const TYPE_NAME = 'smart_motor.SmartMotor'; - -/** - * Returns a component definition block for a smart motor. - */ -export function getDefinitionBlock(hideParams: boolean): ToolboxItems.ContentsType { - return { - kind: 'block', - type: 'mrc_component', - fields: { - NAME: 'my_motor', - TYPE: TYPE_NAME, - }, - extraState: { - importModule: 'smart_motor', - params: [{name: 'motor_port', type: 'int'}], - hideParams, - }, - ...(hideParams ? {} : { - inputs: { - ARG0: { - shadow: { - type: 'mrc_port', - fields: { - TYPE: 'SmartMotor', - PORT_NUM: 1, - }, - }, - }, - }, - }), - }; -} diff --git a/src/toolbox/hardware_components/touch_sensor.ts b/src/toolbox/hardware_components/touch_sensor.ts deleted file mode 100644 index ce7f2d67..00000000 --- a/src/toolbox/hardware_components/touch_sensor.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @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 Touch Sensor hardware component definitions. - * @author alan@porpoiseful.com (Alan Smith) - */ - -import * as ToolboxItems from '../items'; - -export const TYPE_NAME = 'rev_touch_sensor.RevTouchSensor'; - -/** - * Returns a component definition block for a touch sensor. - */ -export function getDefinitionBlock(hideParams: boolean): ToolboxItems.ContentsType { - return { - kind: 'block', - type: 'mrc_component', - fields: { - NAME: 'my_touch_sensor', - TYPE: TYPE_NAME, - }, - extraState: { - importModule: 'rev_touch_sensor', - params: [{name: 'smartIO_port', type: 'int'}], - hideParams, - }, - ...(hideParams ? {} : { - inputs: { - ARG0: { - shadow: { - type: 'mrc_port', - fields: { - TYPE: 'SmartIO', - PORT_NUM: 1, - }, - }, - }, - }, - }), - }; -} From b8d5344913fe31aa6ef8e27b978c6bd3d06bdca0 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Sat, 5 Jul 2025 14:55:57 -0700 Subject: [PATCH 3/4] Fixed i2c port (not smart io!). --- src/blocks/mrc_component.ts | 20 ++++++++++---------- src/blocks/mrc_mechanism_component_holder.ts | 7 ++++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 2d079a91..8bfd9588 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -39,17 +39,17 @@ export const FIELD_TYPE = 'TYPE'; // key is a regex pattern that matches a function argument name, value is type of mrc_port block to use. // TODO: Improve these regex pattern. -// TODO: Should the types here match the PortType values in external_samples/component.py? +// The types here match the PortType values in external_samples/component.py. const RECOGNIZED_PORT_ARG_NAME_PATTERNS: { [key: string]: string } = { - 'can_port': 'SmartCan', - 'i2c_port': 'SmartIO', - 'io_port': 'SmartIO', - 'smartIO_port': 'SmartIO', - 'smart_io_port': 'SmartIO', - 'motor_port': 'SmartMotor', - 'servo_port': 'SmartIO', - 'smart_motor_port': 'SmartMotor', - 'usb_port': 'USBPort', + 'can_port': 'CAN_PORT', + 'io_port': 'SMART_IO_PORT', + 'smartIO_port': 'SMART_IO_PORT', + 'smart_io_port': 'SMART_IO_PORT', + 'motor_port': 'SMART_MOTOR_PORT', + 'smart_motor_port': 'SMART_MOTOR_PORT', + 'servo_port': 'SERVO_PORT', + 'i2c_port': 'I2C_PORT', + 'usb_port': 'USB_PORT', }; diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index 12b245a5..e864fe5a 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -29,6 +29,7 @@ import * as commonStorage from '../storage/common_storage'; import { OUTPUT_NAME as MECHANISM_OUTPUT } from './mrc_mechanism'; import { BLOCK_NAME as MRC_MECHANISM_NAME } from './mrc_mechanism'; import { BLOCK_NAME as MRC_COMPONENT_NAME } from './mrc_component'; +import * as Component from './mrc_component'; import { OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component'; import { BLOCK_NAME as MRC_EVENT_NAME } from './mrc_event'; import { OUTPUT_NAME as EVENT_OUTPUT } from './mrc_event'; @@ -137,9 +138,9 @@ const MECHANISM_COMPONENT_HOLDER = { // Walk through all connected component blocks. let componentBlock = componentsInput.connection.targetBlock(); while (componentBlock) { - if (componentBlock.type === 'mrc_component') { - const componentName = componentBlock.getFieldValue('NAME'); - const componentType = componentBlock.getFieldValue('TYPE'); + if (componentBlock.type === MRC_COMPONENT_NAME) { + const componentName = componentBlock.getFieldValue(Component.FIELD_NAME); + const componentType = componentBlock.getFieldValue(Component.FIELD_TYPE); if (componentName && componentType) { components.push({ From 1d5f1429ce17c13dfb93701927d14c908f4f0152 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Sat, 5 Jul 2025 15:30:02 -0700 Subject: [PATCH 4/4] Changed code that looks at port argument names in pseudo constructors to look for names that match PortType enum values (case insensitive). Updated python code so that port argument names match the PortType values. --- external_samples/rev_touch_sensor.py | 4 +-- external_samples/smart_motor.py | 6 ++-- external_samples/spark_mini.py | 6 ++-- src/blocks/mrc_component.ts | 32 +++++++------------ .../generated/external_samples_data.json | 12 +++---- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index 8fabdb04..4a62b9d1 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -55,8 +55,8 @@ def periodic(self) -> None: # Alternative constructor to create an instance from a smart io port @classmethod - def from_io_port(cls: type[Self], io_port: int) -> Self: - return cls([(PortType.SMART_IO_PORT, io_port)]) + def from_smart_io_port(cls: type[Self], smart_io_port: int) -> Self: + return cls([(PortType.SMART_IO_PORT, smart_io_port)]) # Component specific methods diff --git a/external_samples/smart_motor.py b/external_samples/smart_motor.py index aee2f4cf..9dd0e496 100644 --- a/external_samples/smart_motor.py +++ b/external_samples/smart_motor.py @@ -45,10 +45,10 @@ def get_connection_port_type(self) -> list[PortType]: def periodic(self) -> None: pass - # Alternative constructor to create an instance from a motor port + # Alternative constructor to create an instance from a smart motor port @classmethod - def from_motor_port(cls: type[Self], motor_port: int) -> Self: - return cls([(PortType.SMART_MOTOR_PORT, motor_port)]) + def from_smart_motor_port(cls: type[Self], smart_motor_port: int) -> Self: + return cls([(PortType.SMART_MOTOR_PORT, smart_motor_port)]) # Component specific methods diff --git a/external_samples/spark_mini.py b/external_samples/spark_mini.py index 50999eb9..8922f29f 100644 --- a/external_samples/spark_mini.py +++ b/external_samples/spark_mini.py @@ -61,10 +61,10 @@ def get_connection_port_type(self) -> list[PortType]: def periodic(self) -> None: pass - # Alternative constructor to create an instance from a motor port + # Alternative constructor to create an instance from a smart motor port @classmethod - def from_motor_port(cls: type[Self], motor_port: int) -> Self: - return cls([(PortType.SMART_MOTOR_PORT, motor_port)]) + def from_smart_motor_port(cls: type[Self], smart_motor_port: int) -> Self: + return cls([(PortType.SMART_MOTOR_PORT, smart_motor_port)]) # Component specific methods diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 8bfd9588..785df886 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -37,22 +37,6 @@ export const OUTPUT_NAME = 'mrc_component'; export const FIELD_NAME = 'NAME'; export const FIELD_TYPE = 'TYPE'; -// key is a regex pattern that matches a function argument name, value is type of mrc_port block to use. -// TODO: Improve these regex pattern. -// The types here match the PortType values in external_samples/component.py. -const RECOGNIZED_PORT_ARG_NAME_PATTERNS: { [key: string]: string } = { - 'can_port': 'CAN_PORT', - 'io_port': 'SMART_IO_PORT', - 'smartIO_port': 'SMART_IO_PORT', - 'smart_io_port': 'SMART_IO_PORT', - 'motor_port': 'SMART_MOTOR_PORT', - 'smart_motor_port': 'SMART_MOTOR_PORT', - 'servo_port': 'SERVO_PORT', - 'i2c_port': 'I2C_PORT', - 'usb_port': 'USB_PORT', -}; - - export type ConstructorArg = { name: string, type: string, @@ -247,10 +231,18 @@ function createComponentBlock( } function getPortTypeForArgument(argName: string): string | null { - for (const pattern in RECOGNIZED_PORT_ARG_NAME_PATTERNS) { - if (new RegExp(pattern, 'i').test(argName)) { - return RECOGNIZED_PORT_ARG_NAME_PATTERNS[pattern]; - } + // TODO(lizlooney): Currently the JSON for component.PortType is ClassData + // instead of EnumData. When it is fixed to be EnumData, this code will need + // to be updated. + + const argNameLower = argName.toLowerCase(); + const classData = getClassData('component.PortType'); + if (classData) { + for (const varData of classData.classVariables) { + if (argNameLower === varData.name.toLowerCase()) { + return varData.name; + } + } } return null; } diff --git a/src/blocks/utils/generated/external_samples_data.json b/src/blocks/utils/generated/external_samples_data.json index 4e0dfab3..cd32927d 100644 --- a/src/blocks/utils/generated/external_samples_data.json +++ b/src/blocks/utils/generated/external_samples_data.json @@ -717,12 +717,12 @@ "args": [ { "defaultValue": null, - "name": "io_port", + "name": "smart_io_port", "type": "int" } ], "declaringClassName": "rev_touch_sensor.RevTouchSensor", - "functionName": "from_io_port", + "functionName": "from_smart_io_port", "returnType": "rev_touch_sensor.RevTouchSensor", "tooltip": "" } @@ -1140,12 +1140,12 @@ "args": [ { "defaultValue": null, - "name": "motor_port", + "name": "smart_motor_port", "type": "int" } ], "declaringClassName": "smart_motor.SmartMotor", - "functionName": "from_motor_port", + "functionName": "from_smart_motor_port", "returnType": "smart_motor.SmartMotor", "tooltip": "" } @@ -1603,12 +1603,12 @@ "args": [ { "defaultValue": null, - "name": "motor_port", + "name": "smart_motor_port", "type": "int" } ], "declaringClassName": "spark_mini.SparkMiniComponent", - "functionName": "from_motor_port", + "functionName": "from_smart_motor_port", "returnType": "spark_mini.SparkMiniComponent", "tooltip": "" }