diff --git a/src/blocks/mrc_class_method_def.ts b/src/blocks/mrc_class_method_def.ts index 9b081f96..b4696dfc 100644 --- a/src/blocks/mrc_class_method_def.ts +++ b/src/blocks/mrc_class_method_def.ts @@ -458,7 +458,8 @@ export const pythonFromBlock = function ( xfix2 = xfix1; } if(block.mrcPythonMethodName == '__init__'){ - branch = generator.INDENT + 'super().__init__(robot)\n' + + let class_specific = generator.getClassSpecificForInit(); + branch = generator.INDENT + 'super().__init__(' + class_specific + ')\n' + generator.defineClassVariables() + branch; } if (returnValue) { @@ -469,6 +470,10 @@ export const pythonFromBlock = function ( let params = block.mrcParameters; let paramString = "self"; + if (block.mrcPythonMethodName == '__init__') { + paramString += generator.getListOfPorts(false); + } + if (params.length != 0) { block.mrcParameters.forEach((param) => { paramString += ', ' + param.name; diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts new file mode 100644 index 00000000..374b411a --- /dev/null +++ b/src/blocks/mrc_component.ts @@ -0,0 +1,156 @@ +/** + * @license + * Copyright 2025 Porpoiseful LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Create a component with a name of a certain type + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly'; +import { Order } from 'blockly/python'; + +import { MRC_STYLE_COMPONENTS } from '../themes/styles' +import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; +import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; +import { getAllowedTypesForSetCheck } from './utils/python'; + +export const BLOCK_NAME = 'mrc_component'; +export const OUTPUT_NAME = 'mrc_component'; + +export type ConstructorArg = { + name: string, + type: string, +}; + +type ComponentExtraState = { + importModule?: string, + hideParams?: boolean, + params?: ConstructorArg[], +} + +type ComponentBlock = Blockly.Block & ComponentMixin; +interface ComponentMixin extends ComponentMixinType { + mrcArgs: ConstructorArg[], + hideParams: boolean, + mrcImportModule: string, +} +type ComponentMixinType = typeof COMPONENT; + +const COMPONENT = { + /** + * Block initialization. + */ + init: function (this: ComponentBlock): void { + this.setStyle(MRC_STYLE_COMPONENTS); + this.appendDummyInput() + .appendField(new Blockly.FieldTextInput('my_mech'), 'NAME') + .appendField('of type') + .appendField(createFieldNonEditableText(''), 'TYPE'); + this.setPreviousStatement(true, OUTPUT_NAME); + this.setNextStatement(true, OUTPUT_NAME); + }, + + /** + * Returns the state of this block as a JSON serializable object. + */ + saveExtraState: function (this: ComponentBlock): ComponentExtraState { + const extraState: ComponentExtraState = { + }; + extraState.params = []; + this.mrcArgs.forEach((arg) => { + extraState.params!.push({ + 'name': arg.name, + 'type': arg.type, + }); + }); + if (this.mrcImportModule) { + extraState.importModule = this.mrcImportModule; + } + if (this.hideParams) { + extraState.hideParams = this.hideParams; + } + return extraState; + }, + /** + * Applies the given state to this block. + */ + loadExtraState: function (this: ComponentBlock, extraState: ComponentExtraState): void { + this.mrcImportModule = extraState.importModule ? extraState.importModule : ''; + this.hideParams = extraState.hideParams ? extraState.hideParams : false; + this.mrcArgs = []; + + if (extraState.params) { + extraState.params.forEach((arg) => { + this.mrcArgs.push({ + 'name': arg.name, + 'type': arg.type, + }); + }); + } + this.mrcArgs = extraState.params ? extraState.params : []; + this.updateBlock_(); + }, + /** + * Update the block to reflect the newly loaded extra state. + */ + updateBlock_: function (this: ComponentBlock): void { + if (this.hideParams == false) { + // Add input sockets for the arguments. + for (let i = 0; i < this.mrcArgs.length; i++) { + const input = this.appendValueInput('ARG' + i) + .setAlign(Blockly.inputs.Align.RIGHT) + .appendField(this.mrcArgs[i].name); + if (this.mrcArgs[i].type) { + input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type)); + } + } + } + } +} + +export const setup = function () { + Blockly.Blocks[BLOCK_NAME] = COMPONENT; +} + +export const pythonFromBlock = function ( + block: ComponentBlock, + generator: ExtendedPythonGenerator, +) { + if (block.mrcImportModule) { + generator.addImport(block.mrcImportModule); + } + let code = 'self.' + block.getFieldValue('NAME') + ' = ' + block.getFieldValue('TYPE') + '('; + + for (let i = 0; i < block.mrcArgs.length; i++) { + const fieldName = 'ARG' + i; + if (i != 0) { + code += ', ' + } + if(block.hideParams){ + let extension = ''; + if(i != 0){ + extension = '_' + (i + 1).toString(); + } + const newPort = block.getFieldValue('NAME') + extension + '_port'; + generator.addHardwarePort(newPort, block.mrcArgs[i].type); + code += block.mrcArgs[i].name + " = " + newPort; + }else{ + code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); + } + } + code += ')\n' + "self.hardware.append(self." + block.getFieldValue('NAME') + ")\n"; + return code; +} diff --git a/src/blocks/mrc_mechanism.ts b/src/blocks/mrc_mechanism.ts index 56d8c6ad..5d0c4387 100644 --- a/src/blocks/mrc_mechanism.ts +++ b/src/blocks/mrc_mechanism.ts @@ -22,7 +22,7 @@ import * as Blockly from 'blockly'; import { Order } from 'blockly/python'; -import { MRC_STYLE_FUNCTIONS } from '../themes/styles' +import { MRC_STYLE_MECHANISMS } from '../themes/styles' import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import { getAllowedTypesForSetCheck } from './utils/python'; @@ -44,22 +44,22 @@ type MechanismBlock = Blockly.Block & MechanismMixin; interface MechanismMixin extends MechanismMixinType { mrcArgs: ConstructorArg[], mrcImportModule: string, - } -type MechanismMixinType = typeof MECHANISM_FUNCTION; +type MechanismMixinType = typeof MECHANISM; -const MECHANISM_FUNCTION = { +const MECHANISM = { /** * Block initialization. */ init: function (this: MechanismBlock): void { - this.setStyle(MRC_STYLE_FUNCTIONS); + this.setStyle(MRC_STYLE_MECHANISMS); this.appendDummyInput() .appendField(new Blockly.FieldTextInput('my_mech'), 'NAME') .appendField('of type') .appendField(createFieldNonEditableText(''), 'TYPE'); - this.setPreviousStatement(true); - this.setNextStatement(true); + this.setPreviousStatement(true, OUTPUT_NAME); + this.setNextStatement(true, OUTPUT_NAME); + //this.setOutput(true, OUTPUT_NAME); }, /** @@ -74,7 +74,7 @@ const MECHANISM_FUNCTION = { 'name': arg.name, 'type': arg.type, }); - }); + }); if (this.mrcImportModule) { extraState.importModule = this.mrcImportModule; } @@ -87,7 +87,7 @@ const MECHANISM_FUNCTION = { this.mrcImportModule = extraState.importModule ? extraState.importModule : ''; this.mrcArgs = []; - if(extraState.params){ + if (extraState.params) { extraState.params.forEach((arg) => { this.mrcArgs.push({ 'name': arg.name, @@ -101,42 +101,40 @@ const MECHANISM_FUNCTION = { /** * Update the block to reflect the newly loaded extra state. */ - updateBlock_: function(this: MechanismBlock): void { - // Add input sockets for the arguments. - for (let i = 0; i < this.mrcArgs.length; i++) { - const input = this.appendValueInput('ARG' + i) - .setAlign(Blockly.inputs.Align.RIGHT) - .appendField(this.mrcArgs[i].name); - if (this.mrcArgs[i].type) { - input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type)); - } + updateBlock_: function (this: MechanismBlock): void { + // Add input sockets for the arguments. + for (let i = 0; i < this.mrcArgs.length; i++) { + const input = this.appendValueInput('ARG' + i) + .setAlign(Blockly.inputs.Align.RIGHT) + .appendField(this.mrcArgs[i].name); + if (this.mrcArgs[i].type) { + input.setCheck(getAllowedTypesForSetCheck(this.mrcArgs[i].type)); } } + } } export const setup = function () { - Blockly.Blocks[BLOCK_NAME] = MECHANISM_FUNCTION; + Blockly.Blocks[BLOCK_NAME] = MECHANISM; } export const pythonFromBlock = function ( - mechanismBlock: MechanismBlock, + block: MechanismBlock, generator: ExtendedPythonGenerator, ) { - if (mechanismBlock.mrcImportModule) { - generator.addImport(mechanismBlock.mrcImportModule); + if (block.mrcImportModule) { + generator.addImport(block.mrcImportModule); } - generator.setHasMechanism(); - let code = 'self.mechanisms["' + mechanismBlock.getFieldValue('NAME') + '"] = ' - + mechanismBlock.getFieldValue('TYPE') + '(' - for (let i = 0; i < mechanismBlock.mrcArgs.length; i++) { - const fieldName = 'ARG' + i; - if(i != 0){ - code += ', ' - } - code += mechanismBlock.mrcArgs[i].name + ' = ' + generator.valueToCode(mechanismBlock, fieldName, Order.NONE); + let code = 'self.' + block.getFieldValue('NAME') + ' = ' + block.getFieldValue('TYPE') + '('; + + for (let i = 0; i < block.mrcArgs.length; i++) { + const fieldName = 'ARG' + i; + if (i != 0) { + code += ', ' } - - code += ')' + "\n"; + code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE); + } + code += ')\n' + "self.hardware.append(self." + block.getFieldValue('NAME') + ")\n"; - return code + return code; } diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts new file mode 100644 index 00000000..b72b1810 --- /dev/null +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -0,0 +1,173 @@ +/** + * @license + * Copyright 2025 Porpoiseful LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks to hold mechanisms and containers + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly'; + +import { MRC_STYLE_MECHANISMS } from '../themes/styles'; +import * as ChangeFramework from './utils/change_framework'; +import { getLegalName } from './utils/python'; +import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; +import { OUTPUT_NAME as MECHANISM_OUTPUT } from './mrc_mechanism'; +import { BLOCK_NAME as MRC_MECHANISM_NAME } from './mrc_mechanism'; +import { BLOCK_NAME as MRC_COMPONENT_NAME } from './mrc_component'; + +import { OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component'; + +export const BLOCK_NAME = 'mrc_mechanism_component_holder'; + +export const MECHANISM = 'mechanism'; +export const COMPONENT = 'component'; + +type MechanismComponentHolderExtraState = { + hideMechanisms?: boolean; +} + +type MechanismComponentHolderBlock = Blockly.Block & MechanismComponentHolderMixin; +interface MechanismComponentHolderMixin extends MechanismComponentHolderMixinType { + mrcHideMechanisms: boolean; +} +type MechanismComponentHolderMixinType = typeof MECHANISM_COMPONENT_HOLDER; + +function setName(block: Blockly.BlockSvg){ + const parentBlock = ChangeFramework.getParentOfType(block, BLOCK_NAME); + if (parentBlock) { + const variableBlocks = parentBlock!.getDescendants(true) + const otherNames: string[] = [] + variableBlocks?.forEach(function (variableBlock) { + if (variableBlock != block) { + otherNames.push(variableBlock.getFieldValue('NAME')); + } + }); + const currentName = block.getFieldValue('NAME'); + block.setFieldValue(getLegalName(currentName, otherNames), 'NAME'); + } +} + +const MECHANISM_COMPONENT_HOLDER = { + /** + * Block initialization. + */ + init: function (this: MechanismComponentHolderBlock): void { + this.setInputsInline(false); + this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms'); + this.appendStatementInput('COMPONENTS').setCheck(COMPONENT_OUTPUT).appendField('Components'); + + this.setOutput(false); + this.setStyle(MRC_STYLE_MECHANISMS); + ChangeFramework.registerCallback(MRC_COMPONENT_NAME, [Blockly.Events.BLOCK_MOVE, Blockly.Events.BLOCK_CHANGE], this.onBlockChanged); + ChangeFramework.registerCallback(MRC_MECHANISM_NAME, [Blockly.Events.BLOCK_MOVE, Blockly.Events.BLOCK_CHANGE], this.onBlockChanged); + + }, + saveExtraState: function (this: MechanismComponentHolderBlock): MechanismComponentHolderExtraState { + const extraState: MechanismComponentHolderExtraState = { + }; + if (this.mrcHideMechanisms == true) { + extraState.hideMechanisms = this.mrcHideMechanisms; + } + return extraState; + }, + /** + * Applies the given state to this block. + */ + loadExtraState: function (this: MechanismComponentHolderBlock, extraState: MechanismComponentHolderExtraState): void { + this.mrcHideMechanisms = (extraState.hideMechanisms == undefined) ? false : extraState.hideMechanisms; + this.updateBlock_(); + }, + /** + * Update the block to reflect the newly loaded extra state. + */ + updateBlock_: function (this: MechanismComponentHolderBlock): void { + if (this.mrcHideMechanisms) { + if (this.getInput('MECHANISMS')) { + this.removeInput('MECHANISMS') + } + } + else { + if (this.getInput('MECHANISMS') == null) { + this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms'); + this.moveInputBefore('MECHANISMS', 'COMPONENTS') + } + } + }, + onBlockChanged: function (block: Blockly.BlockSvg, blockEvent: Blockly.Events.BlockBase) { + if (blockEvent.type == Blockly.Events.BLOCK_MOVE) { + let blockMoveEvent = blockEvent as Blockly.Events.BlockMove; + if (blockMoveEvent.reason?.includes('connect')) { + setName(block); + } + } + else { + if (blockEvent.type == Blockly.Events.BLOCK_CHANGE) { + setName(block); + } + } + }, + +} + +export const setup = function () { + Blockly.Blocks[BLOCK_NAME] = MECHANISM_COMPONENT_HOLDER; +} + +function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator) { + let code = 'def define_hardware(self):\n' + generator.INDENT + 'self.hardware = []\n'; + + let mechanisms = ''; + let components = ''; + + mechanisms = generator.statementToCode(block, 'MECHANISMS'); + components = generator.statementToCode(block, 'COMPONENTS'); + + const body = mechanisms + components; + if (body != '') { + code += body; + } + + generator.addClassMethodDefinition('define_hardware', code); +} + +function pythonFromBlockInMechanism(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator) { + let components = ''; + + components = generator.statementToCode(block, 'COMPONENTS'); + + let code = 'def define_hardware(self' + generator.getListOfPorts(false) + '):\n' + + generator.INDENT + 'self.hardware = []\n'; + + if (components != '') { + code += components; + } + + generator.addClassMethodDefinition('define_hardware', code); +} + +export const pythonFromBlock = function ( + block: MechanismComponentHolderBlock, + generator: ExtendedPythonGenerator, +) { + if (block.getInput('MECHANISMS')) { + pythonFromBlockInRobot(block, generator); + } + else { + pythonFromBlockInMechanism(block, generator); + } + return '' +} \ No newline at end of file diff --git a/src/blocks/mrc_opmode_details.ts b/src/blocks/mrc_opmode_details.ts new file mode 100644 index 00000000..d1e7ba85 --- /dev/null +++ b/src/blocks/mrc_opmode_details.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2025 Porpoiseful LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Block for Opmode details + * @author alan@porpoiseful.com (Alan Smith) + */ + +/** + * @license + * Copyright 2025 Porpoiseful LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Create a component with a name of a certain type + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly'; + +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'; + +type OpmodeDetailsBlock = Blockly.Block & OpmodeDetailsMixin; +interface OpmodeDetailsMixin extends OpmodeDetailsMixinType { +} +type OpmodeDetailsMixinType = typeof OPMODE_DETAILS; + +const OPMODE_DETAILS = { + /** + * Block initialization. + */ + init: function (this: OpmodeDetailsBlock): void { + this.setStyle(MRC_STYLE_CLASS_BLOCKS); + this.appendDummyInput() + .appendField('Type') + .appendField(createFieldDropdown(['Auto', 'Teleop', 'Test']), 'TYPE') + .appendField(' ') + .appendField('Enabled') + .appendField(new Blockly.FieldCheckbox(true), 'ENABLED'); + + this.appendDummyInput() + .appendField('Display Name') + .appendField(new Blockly.FieldTextInput(''), 'NAME') + this.appendDummyInput() + .appendField('Display Group') + .appendField(new Blockly.FieldTextInput(''), 'GROUP'); + + this.getField('TYPE')?.setTooltip('What sort of OpMode this is'); + this.getField('ENABLED')?.setTooltip('Whether the OpMode is shown on Driver Station'); + this.getField('NAME')?.setTooltip('The name shown on the Driver Station. If blank will use the class name.'); + this.getField('GROUP')?.setTooltip('An optional group to group OpModes on Driver Station'); + }, +} + +export const setup = function () { + Blockly.Blocks[BLOCK_NAME] = OPMODE_DETAILS; +} + +export const pythonFromBlock = function ( + block: OpmodeDetailsBlock, + generator: ExtendedPythonGenerator, +) { + generator.setOpModeDetails(new OpModeDetails( + block.getFieldValue('NAME'), + block.getFieldValue('GROUP'), + block.getFieldValue('ENABLED') == 'TRUE', + block.getFieldValue('TYPE') + )); + return ''; +} diff --git a/src/blocks/mrc_port.ts b/src/blocks/mrc_port.ts new file mode 100644 index 00000000..6be68cf7 --- /dev/null +++ b/src/blocks/mrc_port.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2025 Porpoiseful LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Create a component with a name of a certain type + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly'; +import { Order } from 'blockly/python'; + +import { MRC_STYLE_PORTS } from '../themes/styles' +import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; +import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; +import { createFieldDropdown } from '../fields/FieldDropdown'; + +export const BLOCK_NAME = 'mrc_port'; +export const OUTPUT_NAME = 'mrc_port'; + + +type PortBlock = Blockly.Block & PortMixin; +interface PortMixin extends PortMixinType { +} +type PortMixinType = typeof PORT; + +const PORT = { + /** + * Block initialization. + */ + init: function (this: PortBlock): void { + this.setStyle(MRC_STYLE_PORTS); + this.appendDummyInput() + .appendField(createFieldNonEditableText(''), 'TYPE') + .appendField(new Blockly.FieldTextInput(''), 'PORT_NUM'); + + //this.setOutput(true, OUTPUT_NAME); + this.setOutput(true); + }, +} + +export const setup = function () { + Blockly.Blocks[BLOCK_NAME] = PORT; +} + +export const pythonFromBlock = function ( + block: PortBlock, + generator: ExtendedPythonGenerator, +) { + //TODO (Alan) : Specify the type here as well + let code = block.getFieldValue('PORT_NUM'); + + return [code, Order.ATOMIC]; +} diff --git a/src/blocks/setup_custom_blocks.ts b/src/blocks/setup_custom_blocks.ts index 0f40f6f5..645e888c 100644 --- a/src/blocks/setup_custom_blocks.ts +++ b/src/blocks/setup_custom_blocks.ts @@ -9,6 +9,10 @@ import * as MiscEvaluateButIgnoreResult from './mrc_misc_evaluate_but_ignore_res import * as SetPythonVariable from './mrc_set_python_variable'; import * as ClassMethodDef from './mrc_class_method_def'; import * as Mechanism from './mrc_mechanism'; +import * as Component from './mrc_component'; +import * as MechanismContainerHolder from './mrc_mechanism_component_holder'; +import * as Port from './mrc_port'; +import * as OpModeDetails from './mrc_opmode_details'; const customBlocks = [ CallPythonFunction, @@ -21,6 +25,10 @@ const customBlocks = [ MiscEvaluateButIgnoreResult, SetPythonVariable, Mechanism, + Component, + MechanismContainerHolder, + Port, + OpModeDetails ]; export const setup = function(forBlock: any) { diff --git a/src/blocks/utils/connection_checker.ts b/src/blocks/utils/connection_checker.ts new file mode 100644 index 00000000..cda8a768 --- /dev/null +++ b/src/blocks/utils/connection_checker.ts @@ -0,0 +1,80 @@ +/** + * @license + * Copyright 2025 Porpoiseful LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview A Blockly plugin that makes connection type checks include methods + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly/core'; +import { OUTPUT_NAME as MECHANISM_OUTPUT } from '../mrc_mechanism'; +import { OUTPUT_NAME as COMPONENT_OUTPUT } from '../mrc_component'; + +export class MethodConnectionChecker extends Blockly.ConnectionChecker { + /** + * Constructor for the connection checker. + */ + constructor() { + super(); + } + + /** + * Type check arrays must either intersect or both be null. + * @override + */ + doTypeChecks(a : Blockly.Connection, b: Blockly.Connection) { + const checkArrayOne = a.getCheck(); + const checkArrayTwo = b.getCheck(); + + if (!checkArrayOne || !checkArrayTwo) { + // if either one has mrc_component, they must match + if((checkArrayOne && (checkArrayOne.indexOf(COMPONENT_OUTPUT) != -1)) || + (checkArrayTwo && (checkArrayTwo.indexOf(COMPONENT_OUTPUT) != -1))){ + return false; + } + // if either one has mrc_mechanism, they must match + if((checkArrayOne && (checkArrayOne.indexOf(MECHANISM_OUTPUT) != -1)) || + (checkArrayTwo && (checkArrayTwo.indexOf(MECHANISM_OUTPUT) != -1))){ + return false; + } + + return true; + } + + // Find any intersection in the check lists. + for (let i = 0; i < checkArrayOne.length; i++) { + if (checkArrayTwo.indexOf(checkArrayOne[i]) != -1) { + return true; + } + } + // No intersection. + + return false; + } +} + +export const registrationType = Blockly.registry.Type.CONNECTION_CHECKER; +export const registrationName = 'HardwareConnectionChecker'; + +Blockly.registry.register( + registrationType, + registrationName, + MethodConnectionChecker, +); + +export const pluginInfo = { + [registrationType as any]: registrationName, +}; \ No newline at end of file diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index fe60c24a..887aaf9e 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -24,8 +24,27 @@ import { PythonGenerator } from 'blockly/python'; import { GeneratorContext } from './generator_context'; import { Block } from '../toolbox/items'; import { FunctionArg } from '../blocks/mrc_call_python_function'; +import * as MechanismContainerHolder from '../blocks/mrc_mechanism_component_holder'; import * as commonStorage from '../storage/common_storage'; +export class OpModeDetails { + constructor(private name: string, private group : string, private enabled : boolean, private type : string) {} + annotations() : string{ + let code = ''; + + if(this.enabled){ + code += '@' + this.type + "\n"; + if(this.name){ + code += '@name("' + this.name + '")\n'; + } + if(this.group){ + code += '@group("' + this.group + '")\n'; + } + } + return code; + } +} + // Extends the python generator to collect some information about functions and // variables that have been defined so they can be used in other modules. @@ -34,7 +53,10 @@ export class ExtendedPythonGenerator extends PythonGenerator { private context: GeneratorContext | null = null; private classMethods: {[key: string]: string} = Object.create(null); - + private ports: {[key: string]: string} = Object.create(null); + // Opmode details + private details : OpModeDetails | null = null; + constructor() { super('Python'); } @@ -63,8 +85,10 @@ export class ExtendedPythonGenerator extends PythonGenerator { defineClassVariables() : string { let variableDefinitions = ''; - if (this.context?.getHasMechanisms()) { - variableDefinitions += this.INDENT + "self.mechanisms = []\n"; + if (this.context?.getHasHardware()) { + variableDefinitions += this.INDENT + "self.define_hardware("; + variableDefinitions += this.getListOfPorts(true); + variableDefinitions += ')'; } return variableDefinitions; @@ -73,8 +97,8 @@ export class ExtendedPythonGenerator extends PythonGenerator { const varName = super.getVariableName(nameOrId); return "self." + varName; } - setHasMechanism() : void{ - this.context?.setHasMechanism(); + setHasHardware() : void{ + this.context?.setHasHardware(); } mrcWorkspaceToCode(workspace: Blockly.Workspace, context: GeneratorContext): string { @@ -82,6 +106,10 @@ export class ExtendedPythonGenerator extends PythonGenerator { this.context = context; this.context.clear(); + if (this.workspace.getBlocksByType(MechanismContainerHolder.BLOCK_NAME).length > 0){ + this.setHasHardware(); + } + const code = super.workspaceToCode(workspace); this.workspace = workspace; @@ -103,18 +131,47 @@ export class ExtendedPythonGenerator extends PythonGenerator { this.classMethods[methodName] = code; } + /** + * Add a Hardware Port + */ + addHardwarePort(portName: string, type: string): void{ + this.ports[portName] = type; + } + + getListOfPorts(startWithFirst: boolean): string{ + let returnString = '' + let firstPort = startWithFirst; + for (const port in this.ports) { + if(!firstPort){ + returnString += ', '; + } + else{ + firstPort = false; + } + returnString += port; + } + return returnString; + } + finish(code: string): string { if (this.context && this.workspace) { const className = this.context.getClassName(); const classParent = this.context.getClassParent(); + const annotations = this.details?.annotations(); this.addImport(classParent); + const classDef = 'class ' + className + '(' + classParent + '):\n'; const classMethods = []; for (const name in this.classMethods) { classMethods.push(this.classMethods[name]) } this.classMethods = Object.create(null); + this.ports = Object.create(null); code = classDef + this.prefixLines(classMethods.join('\n\n'), this.INDENT); + if(annotations){ + code = annotations + code; + } + this.details = null; this.context.setExportedBlocks(this.produceExportedBlocks(this.workspace)); } @@ -231,6 +288,16 @@ export class ExtendedPythonGenerator extends PythonGenerator { } return exportedBlocks; } + setOpModeDetails(details : OpModeDetails) { + this.details = details; + } + getClassSpecificForInit() : string{ + let classParent = this.context?.getClassParent(); + if (classParent == 'OpMode'){ + return 'robot' + } + return '' + } } export const extendedPythonGenerator = new ExtendedPythonGenerator(); diff --git a/src/editor/generator_context.ts b/src/editor/generator_context.ts index 4532e2cb..e6007ade 100644 --- a/src/editor/generator_context.ts +++ b/src/editor/generator_context.ts @@ -34,7 +34,7 @@ export class GeneratorContext { private exportedBlocks: Block[] = []; // Has mechanisms (ie, needs in init) - private hasMechanisms = false; + private hasHardware = false; setModule(module: commonStorage.Module | null) { this.module = module; @@ -43,15 +43,15 @@ export class GeneratorContext { clear(): void { this.clearExportedBlocks(); - this.hasMechanisms = false; + this.hasHardware= false; } - setHasMechanism():void{ - this.hasMechanisms = true; + setHasHardware():void{ + this.hasHardware = true; } - getHasMechanisms():boolean{ - return this.hasMechanisms; + getHasHardware():boolean{ + return this.hasHardware; } getClassName(): string { diff --git a/src/modules/mechanism_start.json b/src/modules/mechanism_start.json index fa26e3bb..d3f6a04b 100644 --- a/src/modules/mechanism_start.json +++ b/src/modules/mechanism_start.json @@ -6,7 +6,7 @@ "type": "mrc_class_method_def", "id": "ElbONc{)s(,UprTW(|1C", "x": 10, - "y": 10, + "y": 210, "deletable": false, "extraState": { "canChangeSignature": false, @@ -24,7 +24,7 @@ "type": "mrc_class_method_def", "id": "wxFAh6eaR1|3fTuV:UAd", "x": 10, - "y": 200, + "y": 410, "deletable": false, "extraState": { "canChangeSignature": false, @@ -36,6 +36,16 @@ "fields": { "NAME": "update" } + }, + { + "type": "mrc_mechanism_component_holder", + "id": "ElbOAb{)s(,UprTW(|1C", + "x": 10, + "y": 10, + "deletable": false, + "extraState":{ + "hideMechanisms" : true + } } ] } diff --git a/src/modules/opmode_start.json b/src/modules/opmode_start.json index 55b5f11b..66e40497 100644 --- a/src/modules/opmode_start.json +++ b/src/modules/opmode_start.json @@ -2,11 +2,24 @@ "blocks": { "languageVersion": 0, "blocks": [ + { + "type": "mrc_opmode_details", + "id": "ElbONc{)gh,UprTW(|1C", + "x": 10, + "y": 10, + "deletable": false, + "fields": { + "NAME": "", + "GROUP": "", + "TYPE": "Auto", + "ENABLED": true + } + }, { "type": "mrc_class_method_def", "id": "ElbONc{)s(,UprTW(|1C", "x": 10, - "y": 10, + "y": 120, "deletable": false, "extraState": { "canChangeSignature": false, @@ -29,7 +42,7 @@ "type": "mrc_class_method_def", "id": "wxFAh6eaR1|3fTuV:UAd", "x": 10, - "y": 200, + "y": 300, "deletable": false, "extraState": { "canChangeSignature": false, diff --git a/src/modules/robot_start.json b/src/modules/robot_start.json index 6452b44e..cb06cbf8 100644 --- a/src/modules/robot_start.json +++ b/src/modules/robot_start.json @@ -6,7 +6,7 @@ "type": "mrc_class_method_def", "id": "ElbONc{)s(,UprTW(|1C", "x": 10, - "y": 10, + "y": 210, "deletable": false, "extraState": { "canChangeSignature": false, @@ -19,6 +19,30 @@ "fields": { "NAME": "init" } + }, + { + "type": "mrc_class_method_def", + "id": "ElbONc{46f,UprTW(|1C", + "x": 210, + "y": 210, + "deletable": false, + "extraState": { + "canChangeSignature": false, + "canBeCalledWithinClass": false, + "canBeCalledOutsideClass": false, + "returnType": "None", + "params": [] + }, + "fields": { + "NAME": "update" + } + }, + { + "type": "mrc_mechanism_component_holder", + "id": "ElbOAb{)s(,UprTW(|1C", + "x": 10, + "y": 10, + "deletable": false } ] } diff --git a/src/reactComponents/BlocklyComponent.tsx b/src/reactComponents/BlocklyComponent.tsx index cff5c310..f56e4ebe 100644 --- a/src/reactComponents/BlocklyComponent.tsx +++ b/src/reactComponents/BlocklyComponent.tsx @@ -3,6 +3,8 @@ import React from 'react'; import * as Blockly from 'blockly/core'; import * as locale from 'blockly/msg/en'; import * as MrcTheme from '../themes/mrc_theme_dark' +import {pluginInfo as HardwareConnectionsPluginInfo} from '../blocks/utils/connection_checker'; + import 'blockly/blocks'; // Includes standard blocks like controls_if, logic_compare, etc. @@ -48,6 +50,9 @@ const BlocklyComponent = React.forwardRef((_, ref) wheel: true }, oneBasedIndex: false, + plugins: { + ...HardwareConnectionsPluginInfo, + } }; // Set Blockly locale diff --git a/src/themes/styles.ts b/src/themes/styles.ts index cf750cbc..ef86d0cc 100644 --- a/src/themes/styles.ts +++ b/src/themes/styles.ts @@ -28,15 +28,18 @@ export const MRC_STYLE_COMMENTS = 'mrc_style_comments'; export const MRC_STYLE_MISC = 'mrc_style_misc'; export const MRC_STYLE_CLASS_BLOCKS = 'mrc_style_class_blocks'; export const MRC_CATEGORY_STYLE_METHODS = 'mrc_category_style_methods'; +export const MRC_STYLE_MECHANISMS = 'mrc_style_mechanisms'; +export const MRC_STYLE_COMPONENTS = 'mrc_style_components'; +export const MRC_STYLE_PORTS = 'mrc_style_ports'; -export const add_mrc_styles = function(theme : Blockly.Theme) : Blockly.Theme { - theme.setBlockStyle(MRC_STYLE_FUNCTIONS,{ +export const add_mrc_styles = function (theme: Blockly.Theme): Blockly.Theme { + theme.setBlockStyle(MRC_STYLE_FUNCTIONS, { colourPrimary: "#805ba5", colourSecondary: "#e6deed", colourTertiary: "#664984", hat: "" }); - theme.setBlockStyle(MRC_STYLE_ENUM,{ + theme.setBlockStyle(MRC_STYLE_ENUM, { colourPrimary: "#5ba5a5", colourSecondary: "#deeded", colourTertiary: "#498484", @@ -46,13 +49,13 @@ export const add_mrc_styles = function(theme : Blockly.Theme) : Blockly.Theme { colourPrimary: "#5ba55b", colourSecondary: "#deedde", colourTertiary: "#498449", - hat:"" + hat: "" }); theme.setBlockStyle(MRC_STYLE_CLASS_BLOCKS, { colourPrimary: "#4a148c", - colourSecondary:"#AD7BE9", - colourTertiary:"#CDB6E9", - hat:"" + colourSecondary: "#AD7BE9", + colourTertiary: "#CDB6E9", + hat: "" }); theme.setCategoryStyle(MRC_CATEGORY_STYLE_METHODS, { colour: '#4A148C', @@ -62,13 +65,33 @@ export const add_mrc_styles = function(theme : Blockly.Theme) : Blockly.Theme { colourPrimary: "#5b5ba5", colourSecondary: "#dedeed", colourTertiary: "#494984", - hat:"" + hat: "" }); theme.setBlockStyle(MRC_STYLE_MISC, { colourPrimary: "#5b5ba5", colourSecondary: "#dedeed", colourTertiary: "#494984", - hat:"" + hat: "" }); + theme.setBlockStyle(MRC_STYLE_MECHANISMS, { + "colourPrimary": "#5b61a5", + "colourSecondary": "#dedfed", + "colourTertiary": "#494e84", + hat: "" + + }); + theme.setBlockStyle(MRC_STYLE_COMPONENTS, { + "colourPrimary": "#5b80a5", + "colourSecondary": "#dee6ed", + "colourTertiary": "#496684", + hat: "" + }); + theme.setBlockStyle(MRC_STYLE_PORTS, { + colourPrimary: "#5ba55b", + colourSecondary: "#deedde", + colourTertiary: "#498449", + hat: "" + }); + return theme; } \ No newline at end of file diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts new file mode 100644 index 00000000..5895810c --- /dev/null +++ b/src/toolbox/hardware_category.ts @@ -0,0 +1,224 @@ +const category_robot = +{ + kind: 'category', + name: 'Hardware', + contents: [ + { + kind: 'label', + text: 'Mechanisms', + }, + { + kind: 'block', + type: 'mrc_mechanism', + fields: { + NAME: 'claw', + TYPE: 'Claw' + }, + extraState: { + importModule: 'claw', + params: [{ name: 'gripper_port', type: 'int' }, + { name: 'piece_sensor_port', type: 'int' }, + ] + }, + inputs: { + ARG0: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 1 + }, + }, + }, + ARG1: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartIO', + PORT_NUM: 1 + }, + }, + }, + } + }, + { + kind: 'block', + type: 'mrc_mechanism', + fields: { + NAME: 'drive', + TYPE: 'DriveMecanum' + }, + extraState: { + importModule: 'DriveMecanum', + params: [{ name: 'front_left_drive_port', type: 'int' }, + { name: 'front_right_drive_port', type: 'int' }, + { name: 'back_left_drive_port', type: 'int' }, + { name: 'back_right_drive_port', type: 'int' }, + ] + }, + inputs: { + ARG0: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 1 + }, + }, + }, + ARG1: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 2 + }, + }, + }, + ARG2: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 3 + }, + }, + }, + ARG3: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 4 + }, + }, + }, + } + }, + { + kind: 'label', + text: 'Components', + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_motor', + TYPE: 'SmartMotor' + }, + extraState: { + importModule: 'smart_motor', + params: [{ name: 'motor_port', type: 'int' }] + }, + inputs: { + ARG0: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartMotor', + PORT_NUM: 1 + }, + }, + }, + } + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_color_range_sensor', + TYPE: 'ColorRangeSensor' + }, + extraState: { + importModule: 'color_range_sensor', + params: [{ name: 'i2c_port', type: 'int' }] + }, + inputs: { + ARG0: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'I2C', + PORT_NUM: 1 + }, + }, + }, + } + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_touch_sensor', + TYPE: 'RevTouchSensor' + }, + extraState: { + importModule: 'rev_touch_sensor', + params: [{ name: 'smartIO_port', type: 'int' }] + }, + inputs: { + ARG0: { + shadow: { + type: 'mrc_port', + fields: { + TYPE: 'SmartIO', + PORT_NUM: 1 + }, + }, + }, + } + }, + ], +} + +const category_mechanism = +{ + kind: 'category', + name: 'Hardware', + contents: [ + { + kind: 'label', + text: 'Components', + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_motor', + TYPE: 'SmartMotor' + }, + extraState: { + importModule: 'smart_motor', + params: [{ name: 'motor_port', type: 'int' }], + hideParams: true + }, + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_color_range_sensor', + TYPE: 'ColorRangeSensor' + }, + extraState: { + importModule: 'color_range_sensor', + params: [{ name: 'i2c_port', type: 'int' }], + hideParams: true + }, + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_touch_sensor', + TYPE: 'RevTouchSensor' + }, + extraState: { + importModule: 'rev_touch_sensor', + params: [{ name: 'smartIO_port', type: 'int' }], + hideParams: true + }, + }, + ], +} +export const category = category_mechanism; \ No newline at end of file diff --git a/src/toolbox/mechanism_category.ts b/src/toolbox/mechanism_category.ts deleted file mode 100644 index c87d184b..00000000 --- a/src/toolbox/mechanism_category.ts +++ /dev/null @@ -1,51 +0,0 @@ -export const category = -{ - kind: 'category', - name: 'Mechanisms', - contents: [ - { - kind: 'block', - type: 'mrc_mechanism', - fields: { - NAME: 'my_servo', - TYPE: 'Servo' - }, - extraState: { - importModule : 'Servo', - params: [{name:'port',type:'int'}] - }, - inputs: { - ARG0: { - shadow: { - type: 'math_number', - fields: { - NUM: 1, - }, - }, - }, - } - }, - { - kind: 'block', - type: 'mrc_mechanism', - fields: { - NAME: 'my_motor', - TYPE: 'DcMotor' - }, - extraState: { - importModule : 'DcMotor', - params: [{name:'port',type:'int'}] - }, - inputs: { - ARG0: { - shadow: { - type: 'math_number', - fields: { - NUM: 1, - }, - }, - }, - } - }, - ], -} \ No newline at end of file diff --git a/src/toolbox/methods_category.ts b/src/toolbox/methods_category.ts index d41ced35..30097f3a 100644 --- a/src/toolbox/methods_category.ts +++ b/src/toolbox/methods_category.ts @@ -87,6 +87,10 @@ export class MethodsCategory { // Add a block that lets the user define a new method. contents.push( + { + kind: 'block', + type: 'mrc_mechanism_container_holder', + }, { kind: 'label', text: 'Custom Methods', diff --git a/src/toolbox/robot_category.ts b/src/toolbox/robot_category.ts new file mode 100644 index 00000000..e7bd2768 --- /dev/null +++ b/src/toolbox/robot_category.ts @@ -0,0 +1,134 @@ +export const category = +{ + kind: 'category', + name: 'Robot', + contents: [ + { + kind: 'category', + name: 'drive', + contents: [ + { + kind: 'block', + type: 'mrc_call_python_function', + extraState: { + functionKind: 'instance_component', + returnType: '', + args: [ + { + name: 'forward_speed', + type: 'float', + }, + { + name: 'strafe_right_speed', + type: 'float', + }, + { + name: 'rotate_cw_speed', + type: 'float', + }, + ], + tooltip: 'Drive (robot relative)', + importModule: '', + componentClassName: 'rev.ColorRangeSensor', + componentName: 'robot.drive', + }, + fields: { + COMPONENT_NAME: 'robot.drive', + FUNC: 'drive_field_relative', + }, + inputs: {}, + }, + ] + }, + { + kind: 'category', + name: 'claw', + contents: [ + { + kind: 'category', + name: 'gripper', + contents: [ + + ], + }, + { + kind: 'category', + name: 'piece_sensor', + contents: [ + // def get_color_rgb(self) -> tuple[int, int, int]: + { + kind: 'block', + type: 'mrc_call_python_function', + extraState: { + functionKind: 'instance_component', + returnType: 'tuple[int, int, int]', + args: [], + tooltip: 'Get the color in rgb (red, green, blue).', + importModule: '', + componentClassName: 'rev.ColorRangeSensor', + componentName: 'colorSensor', + }, + fields: { + COMPONENT_NAME: 'robot.claw.piece_sensor', + FUNC: 'get_color_rgb', + }, + inputs: {}, + }, + // def get_color_hsv(self) -> tuple[int, int, int]: + { + kind: 'block', + type: 'mrc_call_python_function', + extraState: { + functionKind: 'instance_component', + returnType: 'tuple[int, int, int]', + args: [], + tooltip: 'Get the color in hsv (hue, saturation, value).', + importModule: '', + componentClassName: 'rev.ColorRangeSensor', + componentName: 'colorSensor', + }, + fields: { + COMPONENT_NAME: 'robot.claw.piece_sensor', + FUNC: 'get_color_hsv', + }, + inputs: {}, + }, + // def get_distance_mm(self) -> float: + { + kind: 'block', + type: 'mrc_call_python_function', + extraState: { + functionKind: 'instance_component', + returnType: 'float', + args: [], + tooltip: 'Get the distance of the object seen.', + importModule: '', + componentClassName: 'rev.ColorRangeSensor', + componentName: 'colorSensor', + }, + fields: { + COMPONENT_NAME: 'robot.claw.piece_sensor', + FUNC: 'get_distance_mm', + }, + inputs: {}, + }, + ], + }, + ] + }, + { + kind: 'category', + name: 'flywheel', + contents: [ + + ], + }, + { + kind: 'category', + name: 'shooter', + contents: [ + + ], + }, + ], +} \ No newline at end of file diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index a456ed45..023c6a34 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -29,6 +29,8 @@ import {category as listsCategory} from './lists_category'; import {category as miscCategory} from './misc_category'; import {category as methodsCategory} from './methods_category'; import {category as componentSampleCategory} from './component_samples_category'; +import {category as hardwareCategory} from './hardware_category'; +import {category as robotCategory} from './robot_category'; export function getToolboxJSON( opt_includeExportedBlocksFromProject: toolboxItems.ContentsType[], @@ -60,12 +62,16 @@ export function getToolboxJSON( { kind: 'sep', }, - miscCategory, + robotCategory, + { + kind: 'sep', + }, logicCategory, loopCategory, mathCategory, textCategory, listsCategory, + miscCategory, { kind: 'sep', }, @@ -76,7 +82,8 @@ export function getToolboxJSON( custom: 'VARIABLE', }, methodsCategory, - componentSampleCategory, + hardwareCategory, + //componentSampleCategory, ]); return {