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..4a62b9d1 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_smart_io_port(cls: type[Self], smart_io_port: int) -> Self: + return cls([(PortType.SMART_IO_PORT, smart_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..9dd0e496 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 smart motor port + @classmethod + 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 + 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..8922f29f 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 smart motor port + @classmethod + 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 # 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 07ee5c5b..496aa3e5 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -26,15 +26,15 @@ 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 { Editor } from '../editor/editor'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import { createFieldDropdown } from '../fields/FieldDropdown'; import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; import { MRC_STYLE_FUNCTIONS } from '../themes/styles' -import * as toolboxItems from '../toolbox/items'; -import * as commonStorage from '../storage/common_storage'; +import * as ToolboxItems from '../toolbox/items'; +import * as CommonStorage from '../storage/common_storage'; // A block to call a python function. @@ -67,7 +67,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); @@ -78,7 +78,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( @@ -92,7 +92,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, @@ -111,16 +111,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; @@ -129,7 +129,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); @@ -138,7 +138,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, @@ -156,16 +156,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; @@ -173,7 +173,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); @@ -181,7 +181,7 @@ export function addInstanceMethodBlocks( } function createInstanceMethodBlock( - functionData: FunctionData): toolboxItems.Block { + functionData: FunctionData): ToolboxItems.Block { const extraState: CallPythonFunctionExtraState = { functionKind: FunctionKind.INSTANCE, returnType: functionData.returnType, @@ -196,32 +196,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) { @@ -243,10 +243,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, @@ -271,17 +273,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; @@ -688,11 +690,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..785df886 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -25,11 +25,18 @@ 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'; +export const FIELD_NAME = 'NAME'; +export const FIELD_TYPE = 'TYPE'; + export type ConstructorArg = { name: string, type: string, @@ -37,6 +44,8 @@ export type ConstructorArg = { type ComponentExtraState = { importModule?: string, + // If staticFunctionName is not present, generate the constructor. + staticFunctionName?: string, hideParams?: boolean, params?: ConstructorArg[], } @@ -46,19 +55,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 +89,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 +118,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 +146,103 @@ 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 (!hideParams) { + 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 { + // 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/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({ 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..cd32927d 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": "smart_io_port", + "type": "int" + } + ], + "declaringClassName": "rev_touch_sensor.RevTouchSensor", + "functionName": "from_smart_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": "smart_motor_port", + "type": "int" + } + ], + "declaringClassName": "smart_motor.SmartMotor", + "functionName": "from_smart_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": "smart_motor_port", + "type": "int" + } + ], + "declaringClassName": "spark_mini.SparkMiniComponent", + "functionName": "from_smart_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 df274082..ba4ed221 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -28,8 +28,9 @@ 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 * as mechanismComponentHolder from '../blocks/mrc_mechanism_component_holder'; +import * as Component 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) { if (currentModule.moduleType === commonStorage.MODULE_TYPE_OPMODE) { @@ -220,7 +221,7 @@ function getRobotComponentsBlocks(currentModule: commonStorage.Module) { contents.push({ kind: 'category', name: '+ Component', - contents: getAllPossibleComponents(false) + contents: Component.getAllPossibleComponents(false) }); } @@ -248,14 +249,14 @@ 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 const workspace = Blockly.getMainWorkspace(); if (workspace) { // Get the holder block and ask it for the components. - const holderBlocks = workspace.getBlocksByType(mechanismComponentHolder.BLOCK_NAME); + const holderBlocks = workspace.getBlocksByType(MechanismComponentHolder.BLOCK_NAME); holderBlocks.forEach(holderBlock => { const componentsFromHolder: commonStorage.Component[] = holderBlock.getComponents(); @@ -264,7 +265,7 @@ function getComponentsBlocks(currentModule: commonStorage.Module, hideParams : b contents.push({ kind: 'category', name: component.name, - contents: getBlocks(component.className, component.name), + contents: getInstanceComponentBlocks(component.className, component.name), }); }); }); 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, - }, - }, - }, - }, - }), - }; -}