Skip to content

Commit 3fe6f5c

Browse files
authored
Call the steps method after super.loop (#311)
* Let the user delete the loop method in an OpMode. Removed code from opmode.py that calls the steps method. In mrc_class_method_def.ts: Renamed classSpecific and getClassSpecificForInit to superInitParameters and getSuperInitParameters. In mrc_steps.ts: Added constant STEPS_METHOD_NAME and changed the steps method name to _steps. In python.ts: Added constant PERIODIC_METHOD_NAME. In extended_python_generator.ts: Changed finish method to add the code for calling the steps method to the end of the loop method. Renamed getClassSpecificForInit to getSuperInitParameters. * Don't generated python code that checks whether _steps method exists. * If the workspace doesn't have steps and doesn't overide the loop method, show a warning on the opmode details block. * Fixed misspelling. * Changed the value of the warning id so it doesn't seem like a string that needs to be translated.
1 parent af999b2 commit 3fe6f5c

File tree

13 files changed

+85
-17
lines changed

13 files changed

+85
-17
lines changed

docs/testing_before_pr_checklist.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ For each PR, make sure each of these works
1313
* [ ] Add an event to the Mechanism
1414
* [ ] In Robot, make sure you can see the event handler in the toolbox Robot -> Mechanisms -> my_arm -> Events
1515
* [ ] In Opmode, make sure you can see the event handler in the toolbox Robot -> Mechanisms -> my_arm -> Events
16-
# Mechanims
16+
# Mechanisms
1717
* [ ] Add a public component to the mechanism
1818
* [ ] Add a private component to the mechanism
1919
* [ ] Make sure that in the Robot you can see the public component (and not the private one) in the toolbox

server_python_scripts/blocks_base_classes/opmode.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ def __init__(self, robot: RobotBase):
77
def start(self) -> None:
88
self.robot.start()
99
def loop(self) -> None:
10-
# Call steps method if it exists in the derived class
11-
if hasattr(self, 'steps') and callable(self.steps):
12-
self.steps()
1310
self.robot.update()
1411
def stop(self) -> None:
1512
self.robot.stop()

src/blocks/mrc_class_method_def.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,8 @@ export const pythonFromBlock = function (
457457
xfix2 = xfix1;
458458
}
459459
if (block.mrcPythonMethodName === '__init__') {
460-
const classSpecific = generator.getClassSpecificForInit();
461-
branch = generator.INDENT + 'super().__init__(' + classSpecific + ')\n' +
460+
const superInitParameters = generator.getSuperInitParameters();
461+
branch = generator.INDENT + 'super().__init__(' + superInitParameters + ')\n' +
462462
generator.generateInitStatements() + branch;
463463
}
464464
else if (generator.inBaseClassMethod(blocklyName)) {

src/blocks/mrc_opmode_details.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,19 @@
4343
*/
4444
import * as Blockly from 'blockly';
4545

46+
import { PERIODIC_METHOD_NAME } from './utils/python';
47+
import { Editor } from '../editor/editor';
4648
import { ExtendedPythonGenerator, OpModeDetails } from '../editor/extended_python_generator';
4749
import { createFieldDropdown } from '../fields/FieldDropdown';
4850
import { MRC_STYLE_CLASS_BLOCKS } from '../themes/styles';
4951

5052
export const BLOCK_NAME = 'mrc_opmode_details';
5153

54+
const WARNING_ID_STEPS_OR_PERIODIC_REQUIRED = 'id_steps_or_periodic_required';
55+
5256
type OpmodeDetailsBlock = Blockly.Block & OpmodeDetailsMixin;
5357
interface OpmodeDetailsMixin extends OpmodeDetailsMixinType {
58+
mrcHasStepsOrPeriodicRequiredWarning: boolean,
5459
}
5560
type OpmodeDetailsMixinType = typeof OPMODE_DETAILS;
5661

@@ -59,6 +64,7 @@ const OPMODE_DETAILS = {
5964
* Block initialization.
6065
*/
6166
init: function (this: OpmodeDetailsBlock): void {
67+
this.mrcHasStepsOrPeriodicRequiredWarning = false;
6268
this.setStyle(MRC_STYLE_CLASS_BLOCKS);
6369
this.appendDummyInput()
6470
.appendField(Blockly.Msg.TYPE)
@@ -80,6 +86,24 @@ const OPMODE_DETAILS = {
8086
this.getField('NAME')?.setTooltip(Blockly.Msg.OPMODE_NAME_TOOLTIP);
8187
this.getField('GROUP')?.setTooltip(Blockly.Msg.OPMODE_GROUP_TOOLTIP);
8288
},
89+
checkOpMode(this: OpmodeDetailsBlock, editor: Editor): void {
90+
if (editor.isStepsInWorkspace() ||
91+
editor.getMethodNamesAlreadyOverriddenInWorkspace().includes(PERIODIC_METHOD_NAME)) {
92+
// Remove the previous warning.
93+
this.setWarningText(null, WARNING_ID_STEPS_OR_PERIODIC_REQUIRED);
94+
this.mrcHasStepsOrPeriodicRequiredWarning = false;
95+
} else {
96+
// Otherwise, add a warning to the block.
97+
if (!this.mrcHasStepsOrPeriodicRequiredWarning) {
98+
this.setWarningText(Blockly.Msg.WARNING_OPMODE_STEPS_OR_PERIODIC_REQUIRED, WARNING_ID_STEPS_OR_PERIODIC_REQUIRED);
99+
const icon = this.getIcon(Blockly.icons.IconType.WARNING);
100+
if (icon) {
101+
icon.setBubbleVisible(true);
102+
}
103+
this.mrcHasStepsOrPeriodicRequiredWarning = true;
104+
}
105+
}
106+
}
83107
}
84108

85109
export const setup = function () {
@@ -98,3 +122,11 @@ export const pythonFromBlock = function (
98122
));
99123
return '';
100124
}
125+
126+
// Misc
127+
128+
export function checkOpMode(workspace: Blockly.Workspace, editor: Editor) {
129+
workspace.getBlocksByType(BLOCK_NAME).forEach(block => {
130+
(block as OpmodeDetailsBlock).checkOpMode(editor);
131+
});
132+
}

src/blocks/mrc_steps.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import * as toolboxItems from '../toolbox/items';
3232

3333
export const BLOCK_NAME = 'mrc_steps';
3434

35+
export const STEPS_METHOD_NAME = '_steps';
36+
3537
const INPUT_CONDITION_PREFIX = 'CONDITION_';
3638
const INPUT_STATEMENT_PREFIX = 'STATEMENT_';
3739

@@ -219,7 +221,7 @@ export const pythonFromBlock = function (
219221
block: StepsBlock,
220222
generator: ExtendedPythonGenerator,
221223
) {
222-
let code = 'def steps(self):\n';
224+
let code = 'def ' + STEPS_METHOD_NAME + '(self):\n';
223225
code += generator.INDENT + 'if not hasattr(self, \'_initialized_steps\'):\n';
224226
code += generator.INDENT.repeat(2) + 'self._current_step = \'' + block.mrcStepNames[0] + '\'\n';
225227
code += generator.INDENT.repeat(2) + 'self._initialized_steps = True\n\n';
@@ -243,7 +245,7 @@ export const pythonFromBlock = function (
243245
}
244246
});
245247

246-
generator.addClassMethodDefinition('steps', code);
248+
generator.addClassMethodDefinition(STEPS_METHOD_NAME, code);
247249

248250
return ''
249251
}

src/blocks/tokens.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export function customTokens(t: (key: string) => string): typeof Blockly.Msg {
106106
WARNING_EVENT_NOT_IN_HOLDER: t('BLOCKLY.WARNING.EVENT_NOT_IN_HOLDER'),
107107
WARNING_COMPONENT_NOT_IN_HOLDER: t('BLOCKLY.WARNING.COMPONENT_NOT_IN_HOLDER'),
108108
WARNING_MECHANISM_NOT_IN_HOLDER: t('BLOCKLY.WARNING.MECHANISM_NOT_IN_HOLDER'),
109+
WARNING_OPMODE_STEPS_OR_PERIODIC_REQUIRED: t('BLOCKLY.WARNING.OPMODE_STEPS_OR_PERIODIC_REQUIRED'),
109110
MRC_CATEGORY_HARDWARE: t('BLOCKLY.CATEGORY.HARDWARE'),
110111
MRC_CATEGORY_ROBOT: t('BLOCKLY.CATEGORY.ROBOT'),
111112
MRC_CATEGORY_COMPONENTS: t('BLOCKLY.CATEGORY.COMPONENTS'),

src/blocks/utils/python.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@ import * as SetPythonVariable from "../mrc_set_python_variable";
3333

3434
export const MODULE_NAME_BLOCKS_BASE_CLASSES = 'blocks_base_classes';
3535
export const CLASS_NAME_ROBOT_BASE = MODULE_NAME_BLOCKS_BASE_CLASSES + '.RobotBase';
36-
export const CLASS_NAME_OPMODE = MODULE_NAME_BLOCKS_BASE_CLASSES + '.OpMode';
3736
export const CLASS_NAME_MECHANISM = MODULE_NAME_BLOCKS_BASE_CLASSES + '.Mechanism';
3837

38+
// TODO(lizlooney): what about PeriodicOpMode and LinearOpMode?
39+
export const CLASS_NAME_OPMODE = MODULE_NAME_BLOCKS_BASE_CLASSES + '.OpMode';
40+
// TODO(lizlooney): Make sure to update the value of PERIODIC_METHOD_NAME when we update wpilib.
41+
export const PERIODIC_METHOD_NAME = 'loop';
3942

4043
export const robotPyData = generatedRobotPyData as PythonData;
4144
const externalSamplesData = generatedExternalSamplesData as PythonData

src/editor/editor.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import * as storageNames from '../storage/names';
2929
import * as storageProject from '../storage/project';
3030
import * as eventHandler from '../blocks/mrc_event_handler';
3131
import * as classMethodDef from '../blocks/mrc_class_method_def';
32+
import * as opmodeDetails from '../blocks/mrc_opmode_details';
3233
import * as blockSteps from '../blocks/mrc_steps';
3334
import * as mechanismComponentHolder from '../blocks/mrc_mechanism_component_holder';
3435
import * as workspaces from '../blocks/utils/workspaces';
@@ -90,12 +91,7 @@ export class Editor {
9091
return;
9192
}
9293
if (event.type === Blockly.Events.FINISHED_LOADING) {
93-
// Remove the while-loading listener.
94-
this.blocklyWorkspace.removeChangeListener(this.bindedOnChange);
95-
96-
// Add the after-loading listener.
97-
this.bindedOnChange = this.onChangeAfterLoading.bind(this);
98-
this.blocklyWorkspace.addChangeListener(this.bindedOnChange);
94+
this.onFinishedLoading();
9995
return;
10096
}
10197

@@ -116,6 +112,19 @@ export class Editor {
116112
}
117113
}
118114

115+
private onFinishedLoading(): void {
116+
// Remove the while-loading listener.
117+
this.blocklyWorkspace.removeChangeListener(this.bindedOnChange);
118+
119+
// Add the after-loading listener.
120+
this.bindedOnChange = this.onChangeAfterLoading.bind(this);
121+
this.blocklyWorkspace.addChangeListener(this.bindedOnChange);
122+
123+
if (this.module.moduleType === storageModule.ModuleType.OPMODE) {
124+
opmodeDetails.checkOpMode(this.blocklyWorkspace, this);
125+
}
126+
}
127+
119128
private onChangeAfterLoading(event: Blockly.Events.Abstract) {
120129
if (!this.blocklyWorkspace.rendered) {
121130
// This editor has been abandoned.
@@ -169,6 +178,10 @@ export class Editor {
169178
}
170179
}
171180
}
181+
182+
if (this.module.moduleType === storageModule.ModuleType.OPMODE) {
183+
opmodeDetails.checkOpMode(this.blocklyWorkspace, this);
184+
}
172185
}
173186

174187
public makeCurrent(

src/editor/extended_python_generator.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ import { PythonGenerator } from 'blockly/python';
2424
import { createGeneratorContext, GeneratorContext } from './generator_context';
2525
import * as mechanismContainerHolder from '../blocks/mrc_mechanism_component_holder';
2626
import * as eventHandler from '../blocks/mrc_event_handler';
27+
import { STEPS_METHOD_NAME } from '../blocks/mrc_steps';
28+
2729
import {
2830
MODULE_NAME_BLOCKS_BASE_CLASSES,
2931
CLASS_NAME_OPMODE,
32+
PERIODIC_METHOD_NAME,
3033
getClassData,
3134
} from '../blocks/utils/python';
3235
import * as storageModule from '../storage/module';
@@ -248,6 +251,21 @@ export class ExtendedPythonGenerator extends PythonGenerator {
248251

249252
code = decorators + 'class ' + className + '(' + baseClassName + '):\n';
250253

254+
if (this.getModuleType() === storageModule.ModuleType.OPMODE) {
255+
// If the user has a steps method, we need to generate code to call it from the periodic method.
256+
if (STEPS_METHOD_NAME in this.classMethods) {
257+
let periodicCode: string;
258+
if (PERIODIC_METHOD_NAME in this.classMethods) {
259+
periodicCode = this.classMethods[PERIODIC_METHOD_NAME];
260+
} else {
261+
periodicCode = `def ${PERIODIC_METHOD_NAME}(self):\n`;
262+
periodicCode += this.INDENT + `super().${PERIODIC_METHOD_NAME}()\n`;
263+
}
264+
periodicCode += this.INDENT + `self.${STEPS_METHOD_NAME}()\n`;
265+
this.classMethods[PERIODIC_METHOD_NAME] = periodicCode;
266+
}
267+
}
268+
251269
const classMethods = [];
252270

253271
// Generate the __init__ method first.
@@ -295,7 +313,7 @@ export class ExtendedPythonGenerator extends PythonGenerator {
295313
this.opModeDetails = opModeDetails;
296314
}
297315

298-
getClassSpecificForInit(): string {
316+
getSuperInitParameters(): string {
299317
if (this.context?.getBaseClassName() == CLASS_NAME_OPMODE) {
300318
return 'robot'
301319
}

src/i18n/locales/en/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@
217217
"EVENT_NOT_IN_HOLDER": "This block can only go in the events section of the robot or mechanism.",
218218
"COMPONENT_NOT_IN_HOLDER": "This block can only go in the components section of the robot or mechanism.",
219219
"MECHANISM_NOT_IN_HOLDER": "This block can only go in the mechanisms section of the robot.",
220+
"OPMODE_STEPS_OR_PERIODIC_REQUIRED": "A PeriodicOpMode should have steps, the periodic method, or both.",
220221
"MECHANISM_NOT_FOUND": "This block refers to a mechanism that no longer exists."
221222
},
222223
"ERROR":{

0 commit comments

Comments
 (0)