Skip to content

Commit 3af13c1

Browse files
authored
Generate the blocks for the +Components toolbox category dynamically. (#146)
* 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. * Removed component files from src/toolbox/hardware_components. Fixed problem with + Components category for mechanism (where hideParams is true). * Fixed i2c port (not smart io!). * 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.
1 parent 833b509 commit 3af13c1

18 files changed

+313
-358
lines changed

external_samples/color_range_sensor.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# @author [email protected] (Alan Smith)
1818

1919
from component import Component, PortType, InvalidPortException
20-
from typing import Protocol
20+
from typing import Protocol, Self
2121

2222
class DistanceCallable(Protocol):
2323
def __call__(self, distance : float) -> None:
@@ -52,7 +52,13 @@ def get_connection_port_type(self) -> list[PortType]:
5252
def periodic(self) -> None:
5353
pass
5454

55+
# Alternative constructor to create an instance from an i2c port
56+
@classmethod
57+
def from_i2c_port(cls: type[Self], i2c_port: int) -> Self:
58+
return cls([(PortType.I2C_PORT, i2c_port)])
59+
5560
# Component specific methods
61+
5662
def get_color_rgb(self) -> tuple[int, int, int]:
5763
'''gets the color in rgb (red, green, blue)'''
5864
pass

external_samples/rev_touch_sensor.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# @fileoverview This is a sample for a touch sensor
1717
# @author [email protected] (Alan Smith)
1818

19+
from typing import Self
1920
from component import Component, PortType, InvalidPortException, EmptyCallable
2021

2122
class RevTouchSensor(Component):
@@ -51,6 +52,14 @@ def periodic(self) -> None:
5152
self.pressed_callback()
5253
elif old and self.released_callback:
5354
self.released_callback()
55+
56+
# Alternative constructor to create an instance from a smart io port
57+
@classmethod
58+
def from_smart_io_port(cls: type[Self], smart_io_port: int) -> Self:
59+
return cls([(PortType.SMART_IO_PORT, smart_io_port)])
60+
61+
# Component specific methods
62+
5463
def _read_hardware(self):
5564
# here read hardware to get the current value of the sensor and set self.is_pressed
5665
pass
@@ -67,4 +76,4 @@ def register_when_pressed(self, callback: EmptyCallable) -> None:
6776

6877
def register_when_released(self, callback: EmptyCallable) -> None:
6978
'''Event when touch sensor is released (after being pressed)'''
70-
self.released_callback = callback
79+
self.released_callback = callback

external_samples/servo.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# @fileoverview This is a sample for a servo
1717
# @author [email protected] (Alan Smith)
1818

19+
from typing import Self
1920
from component import Component, PortType, InvalidPortException
2021

2122
class Servo(Component):
@@ -44,14 +45,17 @@ def get_connection_port_type(self) -> list[PortType]:
4445
def periodic(self) -> None:
4546
pass
4647

48+
# Alternative constructor to create an instance from an servo port
49+
@classmethod
50+
def from_servo_port(cls: type[Self], servo_port: int) -> Self:
51+
return cls([(PortType.SERVO_PORT, servo_port)])
52+
4753
# Component specific methods
54+
4855
def set_position(self, pos: float) -> None:
4956
'''Set the servo to a position between 0 and 1'''
5057
# sends to the hardware the position of the servo
5158
pass
5259
def set_angle_degrees(self, angle: float) -> None:
5360
'''Set the servo to an angle between 0 and 270'''
5461
self.set_position(angle / 270.0)
55-
56-
57-

external_samples/smart_motor.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515

1616
# @fileoverview This is a sample for a smart motor
1717
# @author [email protected] (Alan Smith)
18+
19+
from typing import Self
1820
from component import Component, PortType, InvalidPortException
1921

20-
class SmartMotor(Component):
22+
class SmartMotor(Component):
2123
def __init__(self, ports : list[tuple[PortType, int]]):
2224
portType, port = ports[0]
2325
if portType != PortType.SMART_MOTOR_PORT:
@@ -43,7 +45,13 @@ def get_connection_port_type(self) -> list[PortType]:
4345
def periodic(self) -> None:
4446
pass
4547

48+
# Alternative constructor to create an instance from a smart motor port
49+
@classmethod
50+
def from_smart_motor_port(cls: type[Self], smart_motor_port: int) -> Self:
51+
return cls([(PortType.SMART_MOTOR_PORT, smart_motor_port)])
52+
4653
# Component specific methods
54+
4755
def set_speed(self, speed: float) -> None:
4856
'''Set the motor to a speed between -1 and 1'''
4957
# TODO: send to the hardware the speed of the motor
@@ -64,4 +72,3 @@ def get_angle_degrees(self) -> float:
6472
def reset_relative_encoder(self) -> None:
6573
'''Reset the relative encoder value to 0'''
6674
pass
67-

external_samples/spark_mini.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
__author__ = "[email protected] (Liz Looney)"
2222

23+
from typing import Self
2324
from component import Component, PortType, InvalidPortException
2425
import wpilib
2526
import wpimath
@@ -32,8 +33,6 @@ def __init__(self, ports : list[tuple[PortType, int]]):
3233
raise InvalidPortException
3334
self.spark_mini = wpilib.SparkMini(port)
3435

35-
# Component methods
36-
3736
def get_manufacturer(self) -> str:
3837
return "REV Robotics"
3938

@@ -62,6 +61,11 @@ def get_connection_port_type(self) -> list[PortType]:
6261
def periodic(self) -> None:
6362
pass
6463

64+
# Alternative constructor to create an instance from a smart motor port
65+
@classmethod
66+
def from_smart_motor_port(cls: type[Self], smart_motor_port: int) -> Self:
67+
return cls([(PortType.SMART_MOTOR_PORT, smart_motor_port)])
68+
6569
# Component specific methods
6670

6771
# Methods from wpilib.PWMMotorController

external_samples/sparkfun_led_stick.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
__author__ = "[email protected] (Liz Looney)"
1616

17+
from typing import Self
1718
from component import Component, PortType, InvalidPortException
1819
import wpilib
1920

@@ -24,8 +25,6 @@ def __init__(self, ports : list[tuple[PortType, int]]):
2425
raise InvalidPortException
2526
self.port = port
2627

27-
# Component methods
28-
2928
def get_manufacturer(self) -> str:
3029
return "SparkFun"
3130

@@ -53,6 +52,11 @@ def get_connection_port_type(self) -> list[PortType]:
5352
def periodic(self) -> None:
5453
pass
5554

55+
# Alternative constructor to create an instance from an i2c port
56+
@classmethod
57+
def from_i2c_port(cls: type[Self], i2c_port: int) -> Self:
58+
return cls([(PortType.I2C_PORT, i2c_port)])
59+
5660
# SparkFunLEDStick methods
5761

5862
def set_color(self, position: int, color: wpilib.Color) -> None:

src/blocks/mrc_call_python_function.ts

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ import { Order } from 'blockly/python';
2626
import { ClassMethodDefExtraState } from './mrc_class_method_def'
2727
import { getClassData, getAllowedTypesForSetCheck, getOutputCheck } from './utils/python';
2828
import { FunctionData, findSuperFunctionData } from './utils/python_json_types';
29-
import * as value from './utils/value';
30-
import * as variable from './utils/variable';
29+
import * as Value from './utils/value';
30+
import * as Variable from './utils/variable';
3131
import { Editor } from '../editor/editor';
3232
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
3333
import { createFieldDropdown } from '../fields/FieldDropdown';
3434
import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
3535
import { MRC_STYLE_FUNCTIONS } from '../themes/styles'
36-
import * as toolboxItems from '../toolbox/items';
37-
import * as commonStorage from '../storage/common_storage';
36+
import * as ToolboxItems from '../toolbox/items';
37+
import * as CommonStorage from '../storage/common_storage';
3838

3939

4040
// A block to call a python function.
@@ -67,7 +67,7 @@ export type FunctionArg = {
6767
export function addModuleFunctionBlocks(
6868
moduleName: string,
6969
functions: FunctionData[],
70-
contents: toolboxItems.ContentsType[]) {
70+
contents: ToolboxItems.ContentsType[]) {
7171
for (const functionData of functions) {
7272
const block = createModuleFunctionOrStaticMethodBlock(
7373
FunctionKind.MODULE, moduleName, moduleName, functionData);
@@ -78,7 +78,7 @@ export function addModuleFunctionBlocks(
7878
export function addStaticMethodBlocks(
7979
importModule: string,
8080
functions: FunctionData[],
81-
contents: toolboxItems.ContentsType[]) {
81+
contents: ToolboxItems.ContentsType[]) {
8282
for (const functionData of functions) {
8383
if (functionData.declaringClassName) {
8484
const block = createModuleFunctionOrStaticMethodBlock(
@@ -92,7 +92,7 @@ function createModuleFunctionOrStaticMethodBlock(
9292
functionKind: FunctionKind,
9393
importModule: string,
9494
moduleOrClassName: string,
95-
functionData: FunctionData): toolboxItems.Block {
95+
functionData: FunctionData): ToolboxItems.Block {
9696
const extraState: CallPythonFunctionExtraState = {
9797
functionKind: functionKind,
9898
returnType: functionData.returnType,
@@ -111,16 +111,16 @@ function createModuleFunctionOrStaticMethodBlock(
111111
'type': argData.type,
112112
});
113113
// Check if we should plug a variable getter block into the argument input socket.
114-
const input = value.valueForFunctionArgInput(argData.type, argData.defaultValue);
114+
const input = Value.valueForFunctionArgInput(argData.type, argData.defaultValue);
115115
if (input) {
116116
inputs['ARG' + i] = input;
117117
}
118118
}
119-
let block = new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
119+
let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
120120
if (functionData.returnType && functionData.returnType != 'None') {
121-
const varName = variable.varNameForType(functionData.returnType);
121+
const varName = Variable.varNameForType(functionData.returnType);
122122
if (varName) {
123-
block = variable.createVariableSetterBlock(varName, block);
123+
block = Variable.createVariableSetterBlock(varName, block);
124124
}
125125
}
126126
return block;
@@ -129,7 +129,7 @@ function createModuleFunctionOrStaticMethodBlock(
129129
export function addConstructorBlocks(
130130
importModule: string,
131131
functions: FunctionData[],
132-
contents: toolboxItems.ContentsType[]) {
132+
contents: ToolboxItems.ContentsType[]) {
133133
for (const functionData of functions) {
134134
const block = createConstructorBlock(importModule, functionData);
135135
contents.push(block);
@@ -138,7 +138,7 @@ export function addConstructorBlocks(
138138

139139
function createConstructorBlock(
140140
importModule: string,
141-
functionData: FunctionData): toolboxItems.Block {
141+
functionData: FunctionData): ToolboxItems.Block {
142142
const extraState: CallPythonFunctionExtraState = {
143143
functionKind: FunctionKind.CONSTRUCTOR,
144144
returnType: functionData.returnType,
@@ -156,32 +156,32 @@ function createConstructorBlock(
156156
'type': argData.type,
157157
});
158158
// Check if we should plug a variable getter block into the argument input socket.
159-
const input = value.valueForFunctionArgInput(argData.type, argData.defaultValue);
159+
const input = Value.valueForFunctionArgInput(argData.type, argData.defaultValue);
160160
if (input) {
161161
inputs['ARG' + i] = input;
162162
}
163163
}
164-
let block = new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
164+
let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
165165
if (functionData.returnType && functionData.returnType != 'None') {
166-
const varName = variable.varNameForType(functionData.returnType);
166+
const varName = Variable.varNameForType(functionData.returnType);
167167
if (varName) {
168-
block = variable.createVariableSetterBlock(varName, block);
168+
block = Variable.createVariableSetterBlock(varName, block);
169169
}
170170
}
171171
return block;
172172
}
173173

174174
export function addInstanceMethodBlocks(
175175
functions: FunctionData[],
176-
contents: toolboxItems.ContentsType[]) {
176+
contents: ToolboxItems.ContentsType[]) {
177177
for (const functionData of functions) {
178178
const block = createInstanceMethodBlock(functionData);
179179
contents.push(block);
180180
}
181181
}
182182

183183
function createInstanceMethodBlock(
184-
functionData: FunctionData): toolboxItems.Block {
184+
functionData: FunctionData): ToolboxItems.Block {
185185
const extraState: CallPythonFunctionExtraState = {
186186
functionKind: FunctionKind.INSTANCE,
187187
returnType: functionData.returnType,
@@ -196,32 +196,32 @@ function createInstanceMethodBlock(
196196
const argData = functionData.args[i];
197197
let argName = argData.name;
198198
if (i === 0 && argName === 'self' && functionData.declaringClassName) {
199-
argName = variable.getSelfArgName(functionData.declaringClassName);
199+
argName = Variable.getSelfArgName(functionData.declaringClassName);
200200
}
201201
extraState.args.push({
202202
'name': argName,
203203
'type': argData.type,
204204
});
205205
// Check if we should plug a variable getter block into the argument input socket.
206-
const input = value.valueForFunctionArgInput(argData.type, argData.defaultValue);
206+
const input = Value.valueForFunctionArgInput(argData.type, argData.defaultValue);
207207
if (input) {
208208
inputs['ARG' + i] = input;
209209
}
210210
}
211-
let block = new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
211+
let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
212212
if (functionData.returnType && functionData.returnType != 'None') {
213-
const varName = variable.varNameForType(functionData.returnType);
213+
const varName = Variable.varNameForType(functionData.returnType);
214214
if (varName) {
215-
block = variable.createVariableSetterBlock(varName, block);
215+
block = Variable.createVariableSetterBlock(varName, block);
216216
}
217217
}
218218
return block;
219219
}
220220

221-
export function addInstanceComponentBlocks(
221+
export function getInstanceComponentBlocks(
222222
componentType: string,
223-
componentName: string,
224-
contents: toolboxItems.ContentsType[]) {
223+
componentName: string) {
224+
const contents: ToolboxItems.ContentsType[] = [];
225225

226226
const classData = getClassData(componentType);
227227
if (!classData) {
@@ -243,10 +243,12 @@ export function addInstanceComponentBlocks(
243243
const block = createInstanceComponentBlock(componentName, functionData);
244244
contents.push(block);
245245
}
246+
247+
return contents;
246248
}
247249

248250
function createInstanceComponentBlock(
249-
componentName: string, functionData: FunctionData): toolboxItems.Block {
251+
componentName: string, functionData: FunctionData): ToolboxItems.Block {
250252
const extraState: CallPythonFunctionExtraState = {
251253
functionKind: FunctionKind.INSTANCE_COMPONENT,
252254
returnType: functionData.returnType,
@@ -271,17 +273,17 @@ function createInstanceComponentBlock(
271273
'type': argData.type,
272274
});
273275
// Check if we should plug a variable getter block into the argument input socket.
274-
const input = value.valueForFunctionArgInput(argData.type, argData.defaultValue);
276+
const input = Value.valueForFunctionArgInput(argData.type, argData.defaultValue);
275277
if (input) {
276278
// Because we skipped the self argument, use i - 1 when filling the inputs array.
277279
inputs['ARG' + (i - 1)] = input;
278280
}
279281
}
280-
let block = new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
282+
let block = new ToolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
281283
if (functionData.returnType && functionData.returnType != 'None') {
282-
const varName = variable.varNameForType(functionData.returnType);
284+
const varName = Variable.varNameForType(functionData.returnType);
283285
if (varName) {
284-
block = variable.createVariableSetterBlock(varName, block);
286+
block = Variable.createVariableSetterBlock(varName, block);
285287
}
286288
}
287289
return block;
@@ -688,11 +690,11 @@ export const pythonFromBlock = function(
688690
: block.getFieldValue(FIELD_FUNCTION_NAME);
689691
// Generate the correct code depending on the module type.
690692
switch (generator.getModuleType()) {
691-
case commonStorage.MODULE_TYPE_ROBOT:
692-
case commonStorage.MODULE_TYPE_MECHANISM:
693+
case CommonStorage.MODULE_TYPE_ROBOT:
694+
case CommonStorage.MODULE_TYPE_MECHANISM:
693695
code = 'self.';
694696
break;
695-
case commonStorage.MODULE_TYPE_OPMODE:
697+
case CommonStorage.MODULE_TYPE_OPMODE:
696698
default:
697699
code = 'self.robot.';
698700
break;

0 commit comments

Comments
 (0)