Skip to content

Commit de754dd

Browse files
committed
Added support for calling component methods to the mrc_call_python_function block.
Handle case where a component name collides with a variable name. Added getComponentNames method to the editor class so the mrc_call_python_function blocks can populate the component name dropdown fields. Added robot parameter to OpMode init method. Modified mrc_class_method_def so it generates the call to super().__init__() for __init__ methods. Save the component names and types in the files. Added blocks to component_samples_category.ts.
1 parent d3ec222 commit de754dd

File tree

8 files changed

+601
-296
lines changed

8 files changed

+601
-296
lines changed

src/blocks/mrc_call_python_function.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ import * as Blockly from 'blockly';
2424
import { Order } from 'blockly/python';
2525

2626
import * as pythonUtils from './utils/generated/python';
27+
import { createFieldDropdown } from '../fields/FieldDropdown';
2728
import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
2829
import { getAllowedTypesForSetCheck, getOutputCheck } from './utils/python';
2930
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
3031
import { MRC_STYLE_FUNCTIONS } from '../themes/styles'
3132
import { ClassMethodDefExtraState } from './mrc_class_method_def'
33+
import { Editor } from '../editor/editor';
3234

3335
// A block to call a python function.
3436

@@ -40,10 +42,13 @@ enum FunctionKind {
4042
CONSTRUCTOR = 'constructor',
4143
INSTANCE = 'instance',
4244
INSTANCE_WITHIN = 'instance_within',
45+
INSTANCE_COMPONENT = 'instance_component'
4346
}
4447

4548
const RETURN_TYPE_NONE = 'None';
4649

50+
const FIELD_COMPONENT_NAME = 'COMPONENT_NAME';
51+
4752
export type FunctionArg = {
4853
name: string,
4954
type: string,
@@ -58,6 +63,8 @@ interface CallPythonFunctionMixin extends CallPythonFunctionMixinType {
5863
mrcImportModule: string,
5964
mrcActualFunctionName: string,
6065
mrcExportedFunction: boolean,
66+
mrcComponentClassName: string,
67+
mrcComponentName: string, // Do not access directly. Call getComponentName.
6168
renameMethod(this: CallPythonFunctionBlock, newName: string): void;
6269
mutateMethod(this: CallPythonFunctionBlock, defBlockExtraState: ClassMethodDefExtraState): void;
6370
}
@@ -99,6 +106,14 @@ type CallPythonFunctionExtraState = {
99106
* user's Project).
100107
*/
101108
exportedFunction: boolean,
109+
/**
110+
* The component name. Specified only if the function kind is INSTANCE_COMPONENT.
111+
*/
112+
componentName?: string,
113+
/**
114+
* The component class name. Specified only if the function kind is INSTANCE_COMPONENT.
115+
*/
116+
componentClassName?: string,
102117
};
103118

104119
const CALL_PYTHON_FUNCTION = {
@@ -138,6 +153,12 @@ const CALL_PYTHON_FUNCTION = {
138153
tooltip = 'Calls the method ' + functionName + '.';
139154
break;
140155
}
156+
case FunctionKind.INSTANCE_COMPONENT: {
157+
const className = this.mrcComponentClassName;
158+
const functionName = this.getFieldValue(pythonUtils.FIELD_FUNCTION_NAME);
159+
tooltip = 'Calls the function ' + className + '.' + functionName + '.';
160+
break;
161+
}
141162
default:
142163
throw new Error('mrcFunctionKind has unexpected value: ' + this.mrcFunctionKind)
143164
}
@@ -174,6 +195,12 @@ const CALL_PYTHON_FUNCTION = {
174195
if (this.mrcActualFunctionName) {
175196
extraState.actualFunctionName = this.mrcActualFunctionName;
176197
}
198+
if (this.mrcComponentClassName) {
199+
extraState.componentClassName = this.mrcComponentClassName;
200+
}
201+
if (this.getField(FIELD_COMPONENT_NAME)) {
202+
extraState.componentName = this.getComponentName();
203+
}
177204
return extraState;
178205
},
179206
/**
@@ -199,6 +226,10 @@ const CALL_PYTHON_FUNCTION = {
199226
? extraState.actualFunctionName : '';
200227
this.mrcExportedFunction = extraState.exportedFunction
201228
? extraState.exportedFunction : false;
229+
this.mrcComponentClassName = extraState.componentClassName
230+
? extraState.componentClassName : '';
231+
this.mrcComponentName = extraState.componentName
232+
? extraState.componentName : '';
202233
this.updateBlock_();
203234
},
204235
/**
@@ -260,6 +291,19 @@ const CALL_PYTHON_FUNCTION = {
260291
}
261292
break;
262293
}
294+
case FunctionKind.INSTANCE_COMPONENT: {
295+
const componentNames = Editor.getComponentNames(this.workspace, this.mrcComponentClassName);
296+
const componentName = this.getComponentName();
297+
if (!componentNames.includes(componentName)) {
298+
componentNames.push(componentName);
299+
}
300+
this.appendDummyInput('TITLE')
301+
.appendField('call')
302+
.appendField(createFieldDropdown(componentNames), FIELD_COMPONENT_NAME)
303+
.appendField('.')
304+
.appendField(createFieldNonEditableText(''), pythonUtils.FIELD_FUNCTION_NAME);
305+
break;
306+
}
263307
default:
264308
throw new Error('mrcFunctionKind has unexpected value: ' + this.mrcFunctionKind)
265309
}
@@ -293,6 +337,15 @@ const CALL_PYTHON_FUNCTION = {
293337
this.removeInput('ARG' + i);
294338
}
295339
},
340+
getComponentName(this: CallPythonFunctionBlock): string {
341+
// If the COMPONENT_NAME field has been created, get the field value, which the user may have changed.
342+
// If the COMPONENT_NAME field has not been created, get the component name from this.mrcComponentName.
343+
return (this.getField(FIELD_COMPONENT_NAME))
344+
? this.getFieldValue(FIELD_COMPONENT_NAME) : this.mrcComponentName;
345+
},
346+
getComponentClassName(this: CallPythonFunctionBlock): string {
347+
return this.mrcComponentClassName;
348+
},
296349
renameMethod: function(this: CallPythonFunctionBlock, newName: string): void {
297350
this.setFieldValue(newName, pythonUtils.FIELD_FUNCTION_NAME);
298351
},
@@ -365,6 +418,15 @@ export const pythonFromBlock = function(
365418
code = 'self.' + functionName;
366419
break;
367420
}
421+
case FunctionKind.INSTANCE_COMPONENT: {
422+
const componentName = callPythonFunctionBlock.getComponentName();
423+
const varName = generator.componentNameToVarName(componentName);
424+
const functionName = callPythonFunctionBlock.mrcActualFunctionName
425+
? callPythonFunctionBlock.mrcActualFunctionName
426+
: block.getFieldValue(pythonUtils.FIELD_FUNCTION_NAME);
427+
code = 'self.' + varName + '.' + functionName;
428+
break;
429+
}
368430
default:
369431
throw new Error('mrcFunctionKind has unexpected value: ' + callPythonFunctionBlock.mrcFunctionKind)
370432
}

src/blocks/mrc_class_method_def.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,8 @@ export const pythonFromBlock = function (
458458
xfix2 = xfix1;
459459
}
460460
if(block.mrcPythonMethodName == '__init__'){
461-
branch = generator.defineClassVariables() + branch;
461+
branch = generator.INDENT + 'super().__init__()\n' +
462+
generator.defineClassVariables() + branch;
462463
}
463464
if (returnValue) {
464465
returnValue = generator.INDENT + 'return ' + returnValue + '\n';

src/editor/editor.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const EMPTY_TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = {
3434
};
3535

3636
export class Editor {
37+
private static workspaceIdToEditor: {[key: string]: Editor} = {};
38+
3739
private blocklyWorkspace: Blockly.WorkspaceSvg;
3840
private generatorContext: GeneratorContext;
3941
private storage: commonStorage.Storage;
@@ -47,6 +49,7 @@ export class Editor {
4749
private toolbox: Blockly.utils.toolbox.ToolboxDefinition = EMPTY_TOOLBOX;
4850

4951
constructor(blocklyWorkspace: Blockly.WorkspaceSvg, generatorContext: GeneratorContext, storage: commonStorage.Storage) {
52+
Editor.workspaceIdToEditor[blocklyWorkspace.id] = this;
5053
this.blocklyWorkspace = blocklyWorkspace;
5154
this.generatorContext = generatorContext;
5255
this.storage = storage;
@@ -219,8 +222,17 @@ export class Editor {
219222
const exportedBlocks = JSON.stringify(this.generatorContext.getExportedBlocks());
220223
const blocksContent = JSON.stringify(
221224
Blockly.serialization.workspaces.save(this.blocklyWorkspace));
225+
const componentsContent = JSON.stringify(this.getComponents());
222226
return commonStorage.makeModuleContent(
223-
this.currentModule, pythonCode, exportedBlocks, blocksContent);
227+
this.currentModule, pythonCode, blocksContent, exportedBlocks, componentsContent);
228+
}
229+
230+
private getComponents(): commonStorage.Component[] {
231+
const components: commonStorage.Component[] = [];
232+
if (this.currentModule?.moduleType === commonStorage.MODULE_TYPE_PROJECT) {
233+
// TODO(lizlooney): Fill the components array.
234+
}
235+
return components;
224236
}
225237

226238
public async saveBlocks() {
@@ -233,4 +245,45 @@ export class Editor {
233245
}
234246
}
235247

248+
private getComponentNamesImpl(componentClassName: string): string[] {
249+
if (!this.projectContent) {
250+
throw new Error('getComponentNames: this.projectContent is null.');
251+
}
252+
const components = commonStorage.extractComponents(this.projectContent);
253+
254+
// TODO(lizlooney): Remove this fake code after getComponents (above) has been implemented.
255+
components.push({name: 'frontTouch', className: 'wpilib.RevTouchSensor'});
256+
components.push({name: 'backTouch', className: 'wpilib.RevTouchSensor'});
257+
components.push({name: 'leftMotor', className: 'wpilib.SmartMotor'});
258+
components.push({name: 'rightMotor', className: 'wpilib.SmartMotor'});
259+
components.push({name: 'claw', className: 'wpilib.Servo'});
260+
components.push({name: 'colorSensor', className: 'wpilib.ColorRangeSensor'});
261+
components.push({name: 'ledStick', className: 'wpilib.SparkFunLEDStick'});
262+
// End of fake code
263+
264+
const componentNames: string[] = [];
265+
components.forEach((component) => {
266+
if (component.className === componentClassName) {
267+
componentNames.push(component.name);
268+
}
269+
});
270+
return componentNames;
271+
}
272+
273+
public static getComponentNames(
274+
workspace: Blockly.Workspace, componentClassName: string): string[] {
275+
276+
let editor: Editor | null = null;
277+
if (workspace.id in Editor.workspaceIdToEditor) {
278+
editor = Editor.workspaceIdToEditor[workspace.id];
279+
} else {
280+
// If the workspace id was not found, it might be because the workspace is associated with the
281+
// toolbox flyout, not a real workspace. In that case, use the first editor.
282+
const allEditors = Object.values(Editor.workspaceIdToEditor);
283+
if (allEditors.length) {
284+
editor = allEditors[0];
285+
}
286+
}
287+
return editor ? editor.getComponentNamesImpl(componentClassName) : [];
288+
}
236289
}

src/editor/extended_python_generator.ts

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import * as Blockly from 'blockly/core';
2323
import { PythonGenerator } from 'blockly/python';
2424
import { GeneratorContext } from './generator_context';
2525
import { Block } from '../toolbox/items';
26-
import { FunctionArg } from '../blocks/mrc_call_python_function';
26+
import { CallPythonFunctionBlock, FunctionArg } from '../blocks/mrc_call_python_function';
2727
import * as commonStorage from '../storage/common_storage';
2828

2929
// Extends the python generator to collect some information about functions and
@@ -34,16 +34,18 @@ export class ExtendedPythonGenerator extends PythonGenerator {
3434
private context: GeneratorContext | null = null;
3535

3636
private classMethods: {[key: string]: string} = Object.create(null);
37+
private mapComponentNameToVarName: {[key: string]: string} = Object.create(null);
3738

3839
constructor() {
3940
super('Python');
4041
}
4142

4243
init(workspace: Blockly.Workspace){
4344
super.init(workspace);
44-
// This will have all variables in the definition 'variables' so we will need to make it contain only the developer variables
45-
delete this.definitions_['variables'];
4645

46+
// super.init will have put all variables in this.definitions_['variables'] but we need to make
47+
// it contain only the developer variables.
48+
delete this.definitions_['variables'];
4749
const defvars = [];
4850
// Add developer variables (not created or named by the user).
4951
const devVarList = Blockly.Variables.allDeveloperVariables(workspace);
@@ -52,20 +54,37 @@ export class ExtendedPythonGenerator extends PythonGenerator {
5254
this.nameDB_!.getName(devVarList[i], Blockly.Names.DEVELOPER_VARIABLE_TYPE) + ' = None',
5355
);
5456
}
55-
this.definitions_['variables'] = defvars.join('\n');
57+
this.definitions_['variables'] = defvars.join('\n');
58+
59+
// Collect the components used in all mrc_call_python_function blocks.
60+
workspace.getBlocksByType('mrc_call_python_function', false).forEach((block) => {
61+
const callPythonFunctionBlock = block as CallPythonFunctionBlock;
62+
const componentName = callPythonFunctionBlock.getComponentName();
63+
if (componentName) {
64+
if (!(componentName in this.mapComponentNameToVarName)) {
65+
const varName = this.nameDB_!.getDistinctName(componentName, 'VARIABLE');
66+
this.mapComponentNameToVarName[componentName] = varName;
67+
}
68+
}
69+
});
5670
}
5771

58-
/*
72+
/*
5973
* This is called from the python generator for the mrc_class_method_def for the
6074
* init method
6175
*/
6276
defineClassVariables() : string {
63-
let variableDefinitions = '';
77+
let variableDefinitions = '';
6478

65-
if (this.context?.getHasMechanisms()) {
66-
variableDefinitions += this.INDENT + "self.mechanisms = []\n";
67-
}
68-
return variableDefinitions;
79+
if (this.context?.getHasMechanisms()) {
80+
variableDefinitions += this.INDENT + "self.mechanisms = []\n";
81+
}
82+
83+
Object.entries(this.mapComponentNameToVarName).forEach(([componentName, varName]) => {
84+
variableDefinitions += this.INDENT + 'self.' + varName + ' = robot.' + componentName + '\n';
85+
});
86+
87+
return variableDefinitions;
6988
}
7089
getVariableName(nameOrId: string): string {
7190
const varName = super.getVariableName(nameOrId);
@@ -74,7 +93,7 @@ export class ExtendedPythonGenerator extends PythonGenerator {
7493
setHasMechanism() : void{
7594
this.context?.setHasMechanism();
7695
}
77-
96+
7897
mrcWorkspaceToCode(workspace: Blockly.Workspace, context: GeneratorContext): string {
7998
this.workspace = workspace;
8099
this.context = context;
@@ -101,14 +120,21 @@ export class ExtendedPythonGenerator extends PythonGenerator {
101120
this.classMethods[methodName] = code;
102121
}
103122

123+
/**
124+
* Get the variable name to use for a component.
125+
*/
126+
componentNameToVarName(componentName: string): string {
127+
return this.mapComponentNameToVarName[componentName];
128+
}
129+
104130
finish(code: string): string {
105131
if (this.context && this.workspace) {
106132
const className = this.context.getClassName();
107133
const classParent = this.context.getClassParent();
108134
this.addImport(classParent);
109135
const classDef = 'class ' + className + '(' + classParent + '):\n';
110136
const classMethods = [];
111-
for (let name in this.classMethods) {
137+
for (const name in this.classMethods) {
112138
classMethods.push(this.classMethods[name])
113139
}
114140
this.classMethods = Object.create(null);
@@ -117,6 +143,8 @@ export class ExtendedPythonGenerator extends PythonGenerator {
117143
this.context.setExportedBlocks(this.produceExportedBlocks(this.workspace));
118144
}
119145

146+
this.mapComponentNameToVarName = Object.create(null);
147+
120148
return super.finish(code);
121149
}
122150

src/modules/opmode_start.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
"canBeCalledWithinClass": false,
1414
"canBeCalledOutsideClass": false,
1515
"returnType": "None",
16-
"params": [],
16+
"params": [
17+
{
18+
"name": "robot",
19+
"type": "Robot"
20+
}
21+
],
1722
"pythonMethodName": "__init__"
1823
},
1924
"fields": {

src/storage/client_side_storage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ class ClientSideStorage implements commonStorage.Storage {
574574
};
575575
const modulesObjectStore = transaction.objectStore('modules');
576576

577-
for (let moduleName in moduleTypes) {
577+
for (const moduleName in moduleTypes) {
578578
const moduleType = moduleTypes[moduleName];
579579
const moduleContent = moduleContents[moduleName];
580580
const modulePath = commonStorage.makeModulePath(projectName, moduleName);

0 commit comments

Comments
 (0)