Skip to content

Commit 18a1d22

Browse files
committed
Merge branch 'mechanisms' of github.com:alan412/systemcore-blocks-interface into mechanisms
2 parents b63e3a0 + 596893f commit 18a1d22

12 files changed

+303
-75
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,13 @@ WARNING! This is not ready for use and is under heavy development of basic featu
1010
5. Once all dependencies have been installed, run ```npm run start```
1111
6. Navigate to localhost:3000 in the browser to open the frontend
1212
7. Make sure any python backends are running on your PC.
13+
14+
## Known Issues
15+
1. Mechanisms aren't limited to init
16+
2. Mechanisms aren't limited to only Robot or Mechanism class
17+
3. There is no default Robot class
18+
4. No way to specify whether an opmode is auto or teleop
19+
5. Workspace can have blocks
20+
6. No Robot workspace yet
21+
7. No Mechanism workspace yet
22+
8. Something weird is going on with currentModule getting unset

src/blocks/mrc_class_method_def.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ export type Parameter = {
4545
type ClassMethodDefBlock = Blockly.Block & ClassMethodDefMixin & Blockly.BlockSvg;
4646
interface ClassMethodDefMixin extends ClassMethodDefMixinType {
4747
mrcCanChangeSignature: boolean,
48-
mrcCanDelete: boolean,
4948
mrcReturnType: string,
5049
mrcParameters: Parameter[],
5150
mrcPythonMethodName: string,
@@ -58,10 +57,6 @@ type ClassMethodDefExtraState = {
5857
* Can change name and parameters and return type
5958
*/
6059
canChangeSignature: boolean,
61-
/**
62-
* Can delete from class
63-
*/
64-
canDelete: boolean,
6560
/**
6661
* The return type of the function.
6762
* Use 'None' for no return value.
@@ -100,7 +95,6 @@ const CLASS_METHOD_DEF = {
10095
this: ClassMethodDefBlock): ClassMethodDefExtraState {
10196
const extraState: ClassMethodDefExtraState = {
10297
canChangeSignature: this.mrcCanChangeSignature,
103-
canDelete: this.mrcCanDelete,
10498
returnType: this.mrcReturnType,
10599
params: [],
106100
};
@@ -124,7 +118,6 @@ const CLASS_METHOD_DEF = {
124118
extraState: ClassMethodDefExtraState
125119
): void {
126120
this.mrcCanChangeSignature = extraState.canChangeSignature;
127-
this.mrcCanDelete = extraState.canDelete;
128121
this.mrcPythonMethodName = extraState.pythonMethodName ? extraState.pythonMethodName : '';
129122
this.mrcReturnType = extraState.returnType;
130123
this.mrcParameters = [];
@@ -157,7 +150,6 @@ const CLASS_METHOD_DEF = {
157150
//Case because a current bug in blockly where it won't allow passing null to Blockly.Block.setMutator makes it necessary.
158151
(this as Blockly.BlockSvg).setMutator( null );
159152
}
160-
this.setDeletable(this.mrcCanDelete);
161153
this.mrcUpdateParams();
162154
},
163155
compose: function (this: ClassMethodDefBlock, containerBlock: any) {
@@ -347,6 +339,7 @@ export const setup = function() {
347339

348340
import { Order, PythonGenerator } from 'blockly/python';
349341

342+
350343
export const pythonFromBlock = function (
351344
block: ClassMethodDefBlock,
352345
generator: PythonGenerator,
@@ -404,6 +397,11 @@ export const pythonFromBlock = function (
404397
'(' +
405398
paramString +
406399
'):\n';
400+
401+
if(block.mrcPythonMethodName == '__init__'){
402+
code += generator.INDENT + "self.mechanisms = [];\n"
403+
404+
}
407405

408406
code +=
409407
xfix1 +

src/blocks/mrc_mechanism.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Porpoiseful LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* @fileoverview Create a mechanism with a name of a certain type
20+
* @author [email protected] (Alan Smith)
21+
*/
22+
import * as Blockly from 'blockly';
23+
import { Order, PythonGenerator } from 'blockly/python';
24+
25+
import { MRC_STYLE_FUNCTIONS } from '../themes/styles'
26+
import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
27+
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
28+
import { getAllowedTypesForSetCheck, getOutputCheck } from './utils/python';
29+
30+
export const BLOCK_NAME = 'mrc_mechanism';
31+
export const OUTPUT_NAME = 'mrc_mechansim';
32+
33+
export type ConstructorArg = {
34+
name: string,
35+
type: string,
36+
};
37+
38+
type MechanismExtraState = {
39+
importModule?: string,
40+
params?: ConstructorArg[],
41+
}
42+
43+
type MechanismBlock = Blockly.Block & MechanismMixin;
44+
interface MechanismMixin extends MechanismMixinType {
45+
mrcArgs: ConstructorArg[],
46+
mrcImportModule: string,
47+
48+
}
49+
type MechanismMixinType = typeof MECHANISM_FUNCTION;
50+
51+
const MECHANISM_FUNCTION = {
52+
/**
53+
* Block initialization.
54+
*/
55+
init: function (this: MechanismBlock): void {
56+
this.setStyle(MRC_STYLE_FUNCTIONS);
57+
this.appendDummyInput()
58+
.appendField(new Blockly.FieldTextInput('my_mech'), 'NAME')
59+
.appendField('of type')
60+
.appendField(createFieldNonEditableText(''), 'TYPE');
61+
this.setPreviousStatement(true);
62+
this.setNextStatement(true);
63+
},
64+
65+
/**
66+
* Returns the state of this block as a JSON serializable object.
67+
*/
68+
saveExtraState: function (this: MechanismBlock): MechanismExtraState {
69+
const extraState: MechanismExtraState = {
70+
};
71+
extraState.params = [];
72+
this.mrcArgs.forEach((arg) => {
73+
extraState.params!.push({
74+
'name': arg.name,
75+
'type': arg.type,
76+
});
77+
});
78+
if (this.mrcImportModule) {
79+
extraState.importModule = this.mrcImportModule;
80+
}
81+
return extraState;
82+
},
83+
/**
84+
* Applies the given state to this block.
85+
*/
86+
loadExtraState: function (this: MechanismBlock, extraState: MechanismExtraState): void {
87+
this.mrcImportModule = extraState.importModule ? extraState.importModule : '';
88+
this.mrcArgs = [];
89+
90+
if(extraState.params){
91+
extraState.params.forEach((arg) => {
92+
this.mrcArgs.push({
93+
'name': arg.name,
94+
'type': arg.type,
95+
});
96+
});
97+
}
98+
this.mrcArgs = extraState.params ? extraState.params : [];
99+
this.updateBlock_();
100+
},
101+
/**
102+
* Update the block to reflect the newly loaded extra state.
103+
*/
104+
updateBlock_: function(this: MechanismBlock): void {
105+
// Add input sockets for the arguments.
106+
for (let i = 0; i < this.mrcArgs.length; i++) {
107+
const input = this.appendValueInput('ARG' + i)
108+
.setAlign(Blockly.inputs.Align.RIGHT)
109+
.appendField(this.mrcArgs[i].name);
110+
if (this.mrcArgs[i].type) {
111+
input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type));
112+
}
113+
}
114+
}
115+
}
116+
117+
export const setup = function () {
118+
Blockly.Blocks[BLOCK_NAME] = MECHANISM_FUNCTION;
119+
}
120+
121+
//TODO: This needs to cause our own init to create the mechanisms line
122+
export const pythonFromBlock = function (
123+
mechanismBlock: MechanismBlock,
124+
generator: ExtendedPythonGenerator,
125+
) {
126+
if (mechanismBlock.mrcImportModule) {
127+
generator.addImport(mechanismBlock.mrcImportModule);
128+
}
129+
let code = 'self.mechanisms["' + mechanismBlock.getFieldValue('NAME') + '"] = '
130+
+ mechanismBlock.getFieldValue('TYPE') + '('
131+
132+
for (let i = 0; i < mechanismBlock.mrcArgs.length; i++) {
133+
const fieldName = 'ARG' + i;
134+
if(i != 0){
135+
code += ', '
136+
}
137+
code += mechanismBlock.mrcArgs[i].name + ' = ' + generator.valueToCode(mechanismBlock, fieldName, Order.NONE);
138+
}
139+
140+
code += ')' + "\n";
141+
142+
return code
143+
}

src/blocks/setup_custom_blocks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as MiscComment from './mrc_misc_comment';
99
import * as MiscEvaluateButIgnoreResult from './mrc_misc_evaluate_but_ignore_result';
1010
import * as SetPythonVariable from './mrc_set_python_variable';
1111
import * as ClassMethodDef from './mrc_class_method_def';
12+
import * as Mechanism from './mrc_mechanism';
1213

1314
const customBlocks = [
1415
CallPythonFunction,
@@ -20,6 +21,7 @@ const customBlocks = [
2021
MiscComment,
2122
MiscEvaluateButIgnoreResult,
2223
SetPythonVariable,
24+
Mechanism,
2325
];
2426

2527
export const setup = function(forBlock: any) {

src/editor/extended_python_generator.ts

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -159,30 +159,55 @@ export class ExtendedPythonGenerator extends PythonGenerator {
159159
this.definitions_['import_' + importModule] = 'import ' + importModule;
160160
}
161161

162-
workspaceToCode(workspace?: Blockly.Workspace): string {
163-
let code = super.workspaceToCode(workspace);
164-
if (!this.currentModule) {
165-
return code;
162+
classParentFromModuleType(moduleType : string) : string{
163+
if(moduleType == commonStorage.MODULE_TYPE_WORKSPACE){
164+
return "Robot";
166165
}
167-
168-
const className = this.currentModule.moduleName;
169-
const classType = this.currentModule.moduleType;
170-
171-
this.addImport(classType);
172-
173-
let prefix = "";
174-
for (let key in this.definitions_) {
175-
prefix += this.definitions_[key] + "\n";
166+
if(moduleType == commonStorage.MODULE_TYPE_OPMODE){
167+
return "OpMode";
176168
}
177-
if (prefix) {
178-
prefix += "\n";
169+
if(moduleType == commonStorage.MODULE_TYPE_MECHANISM){
170+
return "OpMode";
179171
}
172+
return "";
173+
}
180174

181-
let class_def = "class " + className + "(" + classType + "):\n";
175+
finish(code: string): string {
176+
if (!this.currentModule) {
177+
return super.finish(code);
178+
}
179+
let className = this.currentModule.moduleName;
180+
let classParent = this.classParentFromModuleType(this.currentModule.moduleType);
181+
this.addImport(classParent);
182+
183+
// Convert the definitions dictionary into a list.
184+
const imports = [];
185+
const definitions = [];
186+
for (let name in this.definitions_) {
187+
const def = this.definitions_[name];
188+
if (def.match(/^(from\s+\S+\s+)?import\s+\S+/)) {
189+
imports.push(def);
190+
} else {
191+
definitions.push(def);
192+
}
193+
}
194+
// Call Blockly.CodeGenerator's finish. This is required to be done this way
195+
// because we derive from PythonGenerator which dervies from CodeGenerator
196+
// This section except for the class_def part is all copied from Blockly's
197+
// PythonGenerator. It can't be derived because it needs the class insertion
198+
// in the middle.
199+
code = Blockly.CodeGenerator.prototype.finish(code);
200+
this.isInitialized = false;
201+
202+
let class_def = "class " + className + "(" + classParent + "):\n";
182203
if (!code) {
183204
code = "pass";
184205
}
185-
return prefix + class_def + this.prefixLines(code, this.INDENT);
206+
207+
this.nameDB_!.reset();
208+
const allDefs = imports.join('\n') + '\n\n' + definitions.join('\n\n');
209+
return allDefs.replace(/\n\n+/g, '\n\n').replace(/\n*$/, '\n\n\n') + class_def +
210+
this.prefixLines(code, this.INDENT);
186211
}
187212
}
188213

src/modules/mrc_module_opmode.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/modules/opmode_start.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"blocks": {
3+
"languageVersion": 0,
4+
"blocks": [
5+
{
6+
"type": "mrc_class_method_def",
7+
"id": "ElbONc{)s(,UprTW(|1C",
8+
"x": 10,
9+
"y": 10,
10+
"deletable": false,
11+
"extraState": {
12+
"canChangeSignature": false,
13+
"returnType": "None",
14+
"params": [],
15+
"pythonMethodName": "__init__"
16+
},
17+
"fields": {
18+
"NAME": "init"
19+
}
20+
},
21+
{
22+
"type": "mrc_class_method_def",
23+
"id": "wxFAh6eaR1|3fTuV:UAd",
24+
"x": 10,
25+
"y": 200,
26+
"deletable": false,
27+
"extraState": {
28+
"canChangeSignature": false,
29+
"returnType": "None",
30+
"params": []
31+
},
32+
"fields": {
33+
"NAME": "loop"
34+
}
35+
}
36+
]
37+
}
38+
}

src/storage/common_storage.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
import * as Blockly from 'blockly/core';
2323

2424
import {Block} from "../toolbox/items";
25-
import {create as createOpMode} from '../modules/mrc_module_opmode'
25+
import startingOpModeBlocks from '../modules/opmode_start.json';
26+
2627
import {extendedPythonGenerator} from '../editor/extended_python_generator';
2728

2829
// Types, constants, and functions related to modules, regardless of where the modules are stored.
@@ -204,8 +205,8 @@ export function newOpModeContent(workspaceName: string, opModeName: string): str
204205
// Create a headless blockly workspace.
205206
const headlessBlocklyWorkspace = new Blockly.Workspace();
206207
headlessBlocklyWorkspace.options.oneBasedIndex = false;
207-
createOpMode(headlessBlocklyWorkspace, false);
208-
208+
Blockly.serialization.workspaces.load(startingOpModeBlocks, headlessBlocklyWorkspace);
209+
209210
extendedPythonGenerator.setCurrentModule(module);
210211
const pythonCode = extendedPythonGenerator.workspaceToCode(headlessBlocklyWorkspace);
211212
const exportedBlocks = JSON.stringify(extendedPythonGenerator.getExportedBlocks(headlessBlocklyWorkspace));

0 commit comments

Comments
 (0)