Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/testing_before_pr_checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ For each PR, make sure each of these works
* [ ] Add an event to the Mechanism
* [ ] In Robot, make sure you can see the event handler in the toolbox Robot -> Mechanisms -> my_arm -> Events
* [ ] In Opmode, make sure you can see the event handler in the toolbox Robot -> Mechanisms -> my_arm -> Events
# Mechanims
# Mechanisms
* [ ] Add a public component to the mechanism
* [ ] Add a private component to the mechanism
* [ ] Make sure that in the Robot you can see the public component (and not the private one) in the toolbox
Expand Down
3 changes: 0 additions & 3 deletions server_python_scripts/blocks_base_classes/opmode.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ def __init__(self, robot: RobotBase):
def start(self) -> None:
self.robot.start()
def loop(self) -> None:
# Call steps method if it exists in the derived class
if hasattr(self, 'steps') and callable(self.steps):
self.steps()
self.robot.update()
def stop(self) -> None:
self.robot.stop()
Expand Down
4 changes: 2 additions & 2 deletions src/blocks/mrc_class_method_def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,8 @@ export const pythonFromBlock = function (
xfix2 = xfix1;
}
if (block.mrcPythonMethodName === '__init__') {
const classSpecific = generator.getClassSpecificForInit();
branch = generator.INDENT + 'super().__init__(' + classSpecific + ')\n' +
const superInitParameters = generator.getSuperInitParameters();
branch = generator.INDENT + 'super().__init__(' + superInitParameters + ')\n' +
generator.generateInitStatements() + branch;
}
else if (generator.inBaseClassMethod(blocklyName)) {
Expand Down
32 changes: 32 additions & 0 deletions src/blocks/mrc_opmode_details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,19 @@
*/
import * as Blockly from 'blockly';

import { PERIODIC_METHOD_NAME } from './utils/python';
import { Editor } from '../editor/editor';
import { ExtendedPythonGenerator, OpModeDetails } from '../editor/extended_python_generator';
import { createFieldDropdown } from '../fields/FieldDropdown';
import { MRC_STYLE_CLASS_BLOCKS } from '../themes/styles';

export const BLOCK_NAME = 'mrc_opmode_details';

const WARNING_ID_STEPS_OR_PERIODIC_REQUIRED = 'id_steps_or_periodic_required';

type OpmodeDetailsBlock = Blockly.Block & OpmodeDetailsMixin;
interface OpmodeDetailsMixin extends OpmodeDetailsMixinType {
mrcHasStepsOrPeriodicRequiredWarning: boolean,
}
type OpmodeDetailsMixinType = typeof OPMODE_DETAILS;

Expand All @@ -59,6 +64,7 @@ const OPMODE_DETAILS = {
* Block initialization.
*/
init: function (this: OpmodeDetailsBlock): void {
this.mrcHasStepsOrPeriodicRequiredWarning = false;
this.setStyle(MRC_STYLE_CLASS_BLOCKS);
this.appendDummyInput()
.appendField(Blockly.Msg.TYPE)
Expand All @@ -80,6 +86,24 @@ const OPMODE_DETAILS = {
this.getField('NAME')?.setTooltip(Blockly.Msg.OPMODE_NAME_TOOLTIP);
this.getField('GROUP')?.setTooltip(Blockly.Msg.OPMODE_GROUP_TOOLTIP);
},
checkOpMode(this: OpmodeDetailsBlock, editor: Editor): void {
if (editor.isStepsInWorkspace() ||
editor.getMethodNamesAlreadyOverriddenInWorkspace().includes(PERIODIC_METHOD_NAME)) {
// Remove the previous warning.
this.setWarningText(null, WARNING_ID_STEPS_OR_PERIODIC_REQUIRED);
this.mrcHasStepsOrPeriodicRequiredWarning = false;
} else {
// Otherwise, add a warning to the block.
if (!this.mrcHasStepsOrPeriodicRequiredWarning) {
this.setWarningText(Blockly.Msg.WARNING_OPMODE_STEPS_OR_PERIODIC_REQUIRED, WARNING_ID_STEPS_OR_PERIODIC_REQUIRED);
const icon = this.getIcon(Blockly.icons.IconType.WARNING);
if (icon) {
icon.setBubbleVisible(true);
}
this.mrcHasStepsOrPeriodicRequiredWarning = true;
}
}
}
}

export const setup = function () {
Expand All @@ -98,3 +122,11 @@ export const pythonFromBlock = function (
));
return '';
}

// Misc

export function checkOpMode(workspace: Blockly.Workspace, editor: Editor) {
workspace.getBlocksByType(BLOCK_NAME).forEach(block => {
(block as OpmodeDetailsBlock).checkOpMode(editor);
});
}
6 changes: 4 additions & 2 deletions src/blocks/mrc_steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import * as toolboxItems from '../toolbox/items';

export const BLOCK_NAME = 'mrc_steps';

export const STEPS_METHOD_NAME = '_steps';

const INPUT_CONDITION_PREFIX = 'CONDITION_';
const INPUT_STATEMENT_PREFIX = 'STATEMENT_';

Expand Down Expand Up @@ -219,7 +221,7 @@ export const pythonFromBlock = function (
block: StepsBlock,
generator: ExtendedPythonGenerator,
) {
let code = 'def steps(self):\n';
let code = 'def ' + STEPS_METHOD_NAME + '(self):\n';
code += generator.INDENT + 'if not hasattr(self, \'_initialized_steps\'):\n';
code += generator.INDENT.repeat(2) + 'self._current_step = \'' + block.mrcStepNames[0] + '\'\n';
code += generator.INDENT.repeat(2) + 'self._initialized_steps = True\n\n';
Expand All @@ -243,7 +245,7 @@ export const pythonFromBlock = function (
}
});

generator.addClassMethodDefinition('steps', code);
generator.addClassMethodDefinition(STEPS_METHOD_NAME, code);

return ''
}
Expand Down
1 change: 1 addition & 0 deletions src/blocks/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export function customTokens(t: (key: string) => string): typeof Blockly.Msg {
WARNING_EVENT_NOT_IN_HOLDER: t('BLOCKLY.WARNING.EVENT_NOT_IN_HOLDER'),
WARNING_COMPONENT_NOT_IN_HOLDER: t('BLOCKLY.WARNING.COMPONENT_NOT_IN_HOLDER'),
WARNING_MECHANISM_NOT_IN_HOLDER: t('BLOCKLY.WARNING.MECHANISM_NOT_IN_HOLDER'),
WARNING_OPMODE_STEPS_OR_PERIODIC_REQUIRED: t('BLOCKLY.WARNING.OPMODE_STEPS_OR_PERIODIC_REQUIRED'),
MRC_CATEGORY_HARDWARE: t('BLOCKLY.CATEGORY.HARDWARE'),
MRC_CATEGORY_ROBOT: t('BLOCKLY.CATEGORY.ROBOT'),
MRC_CATEGORY_COMPONENTS: t('BLOCKLY.CATEGORY.COMPONENTS'),
Expand Down
5 changes: 4 additions & 1 deletion src/blocks/utils/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ import * as SetPythonVariable from "../mrc_set_python_variable";

export const MODULE_NAME_BLOCKS_BASE_CLASSES = 'blocks_base_classes';
export const CLASS_NAME_ROBOT_BASE = MODULE_NAME_BLOCKS_BASE_CLASSES + '.RobotBase';
export const CLASS_NAME_OPMODE = MODULE_NAME_BLOCKS_BASE_CLASSES + '.OpMode';
export const CLASS_NAME_MECHANISM = MODULE_NAME_BLOCKS_BASE_CLASSES + '.Mechanism';

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

export const robotPyData = generatedRobotPyData as PythonData;
const externalSamplesData = generatedExternalSamplesData as PythonData
Expand Down
25 changes: 19 additions & 6 deletions src/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import * as storageNames from '../storage/names';
import * as storageProject from '../storage/project';
import * as eventHandler from '../blocks/mrc_event_handler';
import * as classMethodDef from '../blocks/mrc_class_method_def';
import * as opmodeDetails from '../blocks/mrc_opmode_details';
import * as blockSteps from '../blocks/mrc_steps';
import * as mechanismComponentHolder from '../blocks/mrc_mechanism_component_holder';
import * as workspaces from '../blocks/utils/workspaces';
Expand Down Expand Up @@ -90,12 +91,7 @@ export class Editor {
return;
}
if (event.type === Blockly.Events.FINISHED_LOADING) {
// Remove the while-loading listener.
this.blocklyWorkspace.removeChangeListener(this.bindedOnChange);

// Add the after-loading listener.
this.bindedOnChange = this.onChangeAfterLoading.bind(this);
this.blocklyWorkspace.addChangeListener(this.bindedOnChange);
this.onFinishedLoading();
return;
}

Expand All @@ -116,6 +112,19 @@ export class Editor {
}
}

private onFinishedLoading(): void {
// Remove the while-loading listener.
this.blocklyWorkspace.removeChangeListener(this.bindedOnChange);

// Add the after-loading listener.
this.bindedOnChange = this.onChangeAfterLoading.bind(this);
this.blocklyWorkspace.addChangeListener(this.bindedOnChange);

if (this.module.moduleType === storageModule.ModuleType.OPMODE) {
opmodeDetails.checkOpMode(this.blocklyWorkspace, this);
}
}

private onChangeAfterLoading(event: Blockly.Events.Abstract) {
if (!this.blocklyWorkspace.rendered) {
// This editor has been abandoned.
Expand Down Expand Up @@ -169,6 +178,10 @@ export class Editor {
}
}
}

if (this.module.moduleType === storageModule.ModuleType.OPMODE) {
opmodeDetails.checkOpMode(this.blocklyWorkspace, this);
}
}

public makeCurrent(
Expand Down
20 changes: 19 additions & 1 deletion src/editor/extended_python_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ import { PythonGenerator } from 'blockly/python';
import { createGeneratorContext, GeneratorContext } from './generator_context';
import * as mechanismContainerHolder from '../blocks/mrc_mechanism_component_holder';
import * as eventHandler from '../blocks/mrc_event_handler';
import { STEPS_METHOD_NAME } from '../blocks/mrc_steps';

import {
MODULE_NAME_BLOCKS_BASE_CLASSES,
CLASS_NAME_OPMODE,
PERIODIC_METHOD_NAME,
getClassData,
} from '../blocks/utils/python';
import * as storageModule from '../storage/module';
Expand Down Expand Up @@ -248,6 +251,21 @@ export class ExtendedPythonGenerator extends PythonGenerator {

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

if (this.getModuleType() === storageModule.ModuleType.OPMODE) {
// If the user has a steps method, we need to generate code to call it from the periodic method.
if (STEPS_METHOD_NAME in this.classMethods) {
let periodicCode: string;
if (PERIODIC_METHOD_NAME in this.classMethods) {
periodicCode = this.classMethods[PERIODIC_METHOD_NAME];
} else {
periodicCode = `def ${PERIODIC_METHOD_NAME}(self):\n`;
periodicCode += this.INDENT + `super().${PERIODIC_METHOD_NAME}()\n`;
}
periodicCode += this.INDENT + `self.${STEPS_METHOD_NAME}()\n`;
this.classMethods[PERIODIC_METHOD_NAME] = periodicCode;
}
}

const classMethods = [];

// Generate the __init__ method first.
Expand Down Expand Up @@ -295,7 +313,7 @@ export class ExtendedPythonGenerator extends PythonGenerator {
this.opModeDetails = opModeDetails;
}

getClassSpecificForInit(): string {
getSuperInitParameters(): string {
if (this.context?.getBaseClassName() == CLASS_NAME_OPMODE) {
return 'robot'
}
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@
"EVENT_NOT_IN_HOLDER": "This block can only go in the events section of the robot or mechanism.",
"COMPONENT_NOT_IN_HOLDER": "This block can only go in the components section of the robot or mechanism.",
"MECHANISM_NOT_IN_HOLDER": "This block can only go in the mechanisms section of the robot.",
"OPMODE_STEPS_OR_PERIODIC_REQUIRED": "A PeriodicOpMode should have steps, the periodic method, or both.",
"MECHANISM_NOT_FOUND": "This block refers to a mechanism that no longer exists."
},
"ERROR":{
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@
"EVENT_NOT_IN_HOLDER": "Este bloque solo puede ir en la sección de eventos del robot o mecanismo.",
"COMPONENT_NOT_IN_HOLDER": "Este bloque solo puede ir en la sección de componentes del robot o mecanismo.",
"MECHANISM_NOT_IN_HOLDER": "Este bloque solo puede ir en la sección de mecanismos del robot.",
"OPMODE_STEPS_OR_PERIODIC_REQUIRED": "Un PeriodicOpMode debe tener pasos, el método periódico o ambos.",
"MECHANISM_NOT_FOUND": "Este bloque se refiere a un mecanismo que ya no existe."
},
"ERROR":{
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/he/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@
"EVENT_NOT_IN_HOLDER": "בלוק זה יכול להיכנס רק לאזור האירועים של הרובוט או המנגנון.",
"COMPONENT_NOT_IN_HOLDER": "בלוק זה יכול להיכנס רק לאזור הרכיבים של הרובוט או המנגנון.",
"MECHANISM_NOT_IN_HOLDER": "בלוק זה יכול להיכנס רק לחלק המנגנונים של הרובוט.",
"OPMODE_STEPS_OR_PERIODIC_REQUIRED": "ל-PeriodicOpMode צריך להיות שלבים, את השיטה המחזורית, או את שניהם.",
"MECHANISM_NOT_FOUND": "בלוק זה מתייחס למנגנון שכבר לא קיים."
},
"ERROR": {
Expand Down
1 change: 0 additions & 1 deletion src/modules/opmode_start.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"type": "mrc_class_method_def",
"x": 10,
"y": 190,
"deletable": false,
"editable": false,
"extraState": {
"canChangeSignature": false,
Expand Down