Skip to content

Commit ef11e08

Browse files
authored
Added a few more components, including some that have non-port arguments (#368)
* Changes to python_tools/python_util.py: Fixed isEnum. Removed getComponentPortNameAndType. Changes to python_tools/json_util.py: Generate JSON componentArgs for component constructors, instead of componentPortName and componentPortType. Added _createArgData. Added _processComponent. * Moved PortType and related functions from module_content.ts to python_json_types.ts Added preupgrade_009_to_0010 to change Component.ports to Component.args. * Added valueForComponentArgInput to value.ts. It creates blocks to plug into component input sockets. * Changed blocks and the python generator where it used to assume that all components have a port argument and no other arguments. Fixed the code that generates the robot when it creates a mechanism, passing one tuple of args for each component in the mechanism. Fixed the code that generates a mechanism's __init__ and define_hardware methods. The __init__ and define_hardware methods take one parameter (which is is a tuple) for each component in the mechanism. The define_hardware method uses the * operator to unpack the elements of the tuple and pass each element as a separate positional argument to the component constructor. * Increment the current version to 0.0.10. Upgrade old projects so that Components have args instead of ports.
1 parent b3ecd98 commit ef11e08

16 files changed

+2374
-3506
lines changed

python_tools/json_util.py

Lines changed: 113 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@
6060
_KEY_ALIASES = 'aliases'
6161
_KEY_SUBCLASSES = 'subclasses'
6262
_KEY_IS_COMPONENT = 'isComponent'
63-
_KEY_COMPONENT_PORT_NAME = 'componentPortName'
64-
_KEY_COMPONENT_PORT_TYPE = 'componentPortType'
63+
_KEY_COMPONENT_ARGS = 'componentArgs'
6564

6665

6766
def ignoreModule(module_name: str) -> bool:
@@ -120,6 +119,8 @@ def _getClassName(self, o, containing_class_name: str = None) -> str:
120119
o = o.replace(exported_full_class_name + ']', exported_class_name + ']')
121120
if o.find(exported_full_class_name + ',') != -1:
122121
o = o.replace(exported_full_class_name + ',', exported_class_name + ',')
122+
o = o.replace('typing.SupportsInt', 'int')
123+
o = o.replace('typing.SupportsFloat', 'float')
123124
return o
124125
raise Exception(f'Invalid argument {o}')
125126

@@ -136,6 +137,13 @@ def _createFunctionIsEnumValue(
136137
self, enum_cls: type) -> typing.Callable[[object], bool]:
137138
return lambda value: type(value) == enum_cls
138139

140+
def _createArgData(self, arg_name: str, arg_type: str, default_value: str = ''):
141+
arg_data = {}
142+
arg_data[_KEY_ARGUMENT_NAME] = arg_name
143+
arg_data[_KEY_ARGUMENT_TYPE] = arg_type
144+
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = default_value if default_value else ''
145+
return arg_data
146+
139147
def _processModule(self, module) -> dict:
140148
module_data = {}
141149
module_name = self._getModuleName(module)
@@ -194,14 +202,10 @@ def _processModule(self, module) -> dict:
194202
continue
195203
args = []
196204
for i in range(len(arg_names)):
197-
arg_data = {}
198-
arg_data[_KEY_ARGUMENT_NAME] = arg_names[i]
199-
arg_data[_KEY_ARGUMENT_TYPE] = self._getClassName(arg_types[i])
200-
if arg_default_values[i] is not None:
201-
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = arg_default_values[i]
202-
else:
203-
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = ''
204-
args.append(arg_data)
205+
args.append(self._createArgData(
206+
arg_names[i],
207+
self._getClassName(arg_types[i]),
208+
arg_default_values[i] if arg_default_values[i] is not None else ''))
205209
function_data = {}
206210
function_data[_KEY_FUNCTION_NAME] = function_name
207211
function_data[_KEY_FUNCTION_RETURN_TYPE] = self._getClassName(return_type)
@@ -352,22 +356,14 @@ def _processClass(self, cls):
352356
declaring_class_name = self._getClassName(arg_type, class_name)
353357
# Don't append the self argument to the args array.
354358
continue
355-
arg_data = {}
356-
arg_data[_KEY_ARGUMENT_NAME] = arg_name
357-
arg_data[_KEY_ARGUMENT_TYPE] = self._getClassName(arg_type, class_name)
358-
if arg_default_values[i] is not None:
359-
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = arg_default_values[i]
360-
else:
361-
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = ''
362-
args.append(arg_data)
359+
args.append(self._createArgData(
360+
arg_name,
361+
self._getClassName(arg_type, class_name),
362+
arg_default_values[i] if arg_default_values[i] is not None else ''))
363363
constructor_data[_KEY_FUNCTION_ARGS] = args
364364
constructor_data[_KEY_FUNCTION_DECLARING_CLASS_NAME] = declaring_class_name
365365
constructor_data[_KEY_FUNCTION_RETURN_TYPE] = declaring_class_name
366-
componentPortTuple = python_util.getComponentPortNameAndType(declaring_class_name, value)
367-
if componentPortTuple is not None:
368-
constructor_data[_KEY_COMPONENT_PORT_NAME] = componentPortTuple[0]
369-
constructor_data[_KEY_COMPONENT_PORT_TYPE] = componentPortTuple[1]
370-
class_data[_KEY_IS_COMPONENT] = True
366+
self._processComponent(class_data, constructor_data, declaring_class_name, arg_names, arg_types, arg_default_values)
371367
constructors.append(constructor_data)
372368
class_data[_KEY_CONSTRUCTORS] = constructors
373369

@@ -406,14 +402,10 @@ def _processClass(self, cls):
406402
found_self_arg = True
407403
if arg_type != full_class_name:
408404
declaring_class_name = self._getClassName(arg_type, class_name)
409-
arg_data = {}
410-
arg_data[_KEY_ARGUMENT_NAME] = arg_name
411-
arg_data[_KEY_ARGUMENT_TYPE] = self._getClassName(arg_type, class_name)
412-
if arg_default_values[i] is not None:
413-
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = arg_default_values[i]
414-
else:
415-
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = ''
416-
args.append(arg_data)
405+
args.append(self._createArgData(
406+
arg_name,
407+
self._getClassName(arg_type, class_name),
408+
arg_default_values[i] if arg_default_values[i] is not None else ''))
417409
function_data = {}
418410
function_data[_KEY_FUNCTION_NAME] = function_name
419411
function_data[_KEY_FUNCTION_RETURN_TYPE] = self._getClassName(return_type, class_name)
@@ -456,6 +448,96 @@ def _processClass(self, cls):
456448
class_data[_KEY_ENUMS] = sorted(enums, key=lambda enum_data: enum_data[_KEY_ENUM_CLASS_NAME])
457449
return class_data
458450

451+
452+
def _processComponent(self, class_data, constructor_data, declaring_class_name, arg_names, arg_types, arg_default_values):
453+
"""Determine whether this is a component and, if so, update the class_data and
454+
constructor_data."""
455+
456+
# TODO(lizlooney): Replace the following temporary fake code with code that
457+
# looks at doc string and/or parameter type aliases to tell whether this is
458+
# a component and what the args are.
459+
460+
if declaring_class_name == 'wpilib_placeholders.ExpansionHubMotor':
461+
args = []
462+
args.append(self._createArgData('expansion_hub_motor', 'SYSTEMCORE_USB_PORT__EXPANSION_HUB_MOTOR_PORT'))
463+
constructor_data[_KEY_COMPONENT_ARGS] = args
464+
constructor_data[_KEY_IS_COMPONENT] = True
465+
class_data[_KEY_IS_COMPONENT] = True
466+
return
467+
468+
if declaring_class_name == 'wpilib_placeholders.ExpansionHubServo':
469+
args = []
470+
args.append(self._createArgData('expansion_hub_servo', 'SYSTEMCORE_USB_PORT__EXPANSION_HUB_SERVO_PORT'))
471+
constructor_data[_KEY_COMPONENT_ARGS] = args
472+
constructor_data[_KEY_IS_COMPONENT] = True
473+
class_data[_KEY_IS_COMPONENT] = True
474+
return
475+
476+
if declaring_class_name == 'wpilib.AddressableLED':
477+
args = []
478+
args.append(self._createArgData('smart_io_port', 'SYSTEMCORE_SMART_IO_PORT'))
479+
constructor_data[_KEY_COMPONENT_ARGS] = args
480+
constructor_data[_KEY_IS_COMPONENT] = True
481+
class_data[_KEY_IS_COMPONENT] = True
482+
return
483+
484+
if declaring_class_name == 'wpilib.AnalogEncoder':
485+
if (len(arg_names) == 4 and
486+
arg_names[0] == 'self' and
487+
arg_names[1] == 'channel' and
488+
arg_names[2] == 'fullRange' and
489+
arg_names[3] == 'expectedZero'):
490+
args = []
491+
args.append(self._createArgData('smart_io_port', 'SYSTEMCORE_SMART_IO_PORT'))
492+
args.append(self._createArgData('full_range', self._getClassName(arg_types[2]), '1.0'))
493+
args.append(self._createArgData('expected_zero', self._getClassName(arg_types[3]), '0.0'))
494+
constructor_data[_KEY_COMPONENT_ARGS] = args
495+
constructor_data[_KEY_COMPONENT_ARGS]
496+
constructor_data[_KEY_IS_COMPONENT] = True
497+
class_data[_KEY_IS_COMPONENT] = True
498+
return
499+
500+
if declaring_class_name == 'wpilib.AnalogPotentiometer':
501+
if (len(arg_names) == 4 and
502+
arg_names[0] == 'self' and
503+
arg_names[1] == 'channel' and
504+
arg_names[2] == 'fullRange' and
505+
arg_names[3] == 'offset'):
506+
args = []
507+
args.append(self._createArgData('smart_io_port', 'SYSTEMCORE_SMART_IO_PORT'))
508+
args.append(self._createArgData('full_range', self._getClassName(arg_types[2]), arg_default_values[2]))
509+
args.append(self._createArgData('offset', self._getClassName(arg_types[3]), arg_default_values[3]))
510+
constructor_data[_KEY_COMPONENT_ARGS] = args
511+
constructor_data[_KEY_IS_COMPONENT] = True
512+
class_data[_KEY_IS_COMPONENT] = True
513+
return
514+
515+
if declaring_class_name == 'wpilib.DigitalInput':
516+
args = []
517+
args.append(self._createArgData('smart_io_port', 'SYSTEMCORE_SMART_IO_PORT'))
518+
constructor_data[_KEY_COMPONENT_ARGS] = args
519+
constructor_data[_KEY_IS_COMPONENT] = True
520+
class_data[_KEY_IS_COMPONENT] = True
521+
return
522+
523+
if declaring_class_name == 'wpilib.OnboardIMU':
524+
if (len(arg_names) == 2 and
525+
arg_names[0] == 'self' and
526+
arg_names[1] == 'mountOrientation'):
527+
args = []
528+
args.append(self._createArgData('mount_orientation', self._getClassName(arg_types[1]), arg_default_values[1]))
529+
constructor_data[_KEY_COMPONENT_ARGS] = args
530+
constructor_data[_KEY_IS_COMPONENT] = True
531+
class_data[_KEY_IS_COMPONENT] = True
532+
return
533+
534+
if declaring_class_name == 'wpilib.PWMSparkMax':
535+
args = []
536+
args.append(self._createArgData('smart_io_port', 'SYSTEMCORE_SMART_IO_PORT'))
537+
constructor_data[_KEY_COMPONENT_ARGS] = args
538+
constructor_data[_KEY_IS_COMPONENT] = True
539+
class_data[_KEY_IS_COMPONENT] = True
540+
459541
def _processClasses(self):
460542
class_data_list = []
461543
for cls in self._getPublicClasses():

python_tools/python_util.py

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,16 @@ def isEnum(object):
183183
inspect.isroutine(object.__init__) and
184184
inspect.ismethoddescriptor(object.__init__) and
185185
hasattr(object.__init__, "__doc__") and
186-
object.__init__.__doc__ == f"__init__(self: {getFullClassName(object)}, value: int) -> None\n" and
186+
(
187+
object.__init__.__doc__ == f"__init__(self: {getFullClassName(object)}, value: int) -> None\n" or
188+
object.__init__.__doc__ == f"__init__(self: {getFullClassName(object)}, value: typing.SupportsInt) -> None\n"
189+
) and
187190
hasattr(object, "name") and
188191
inspect.isdatadescriptor(object.name) and
189-
object.name.__doc__ == 'name(self: object) -> str\n' and
192+
(
193+
object.name.__doc__ == 'name(self: object) -> str\n' or
194+
object.name.__doc__ == 'name(self: object, /) -> str\n'
195+
) and
190196
hasattr(object, "value") and
191197
inspect.isdatadescriptor(object.value))
192198

@@ -579,25 +585,3 @@ def collectSubclasses(classes: list[type]) -> dict[str, list[str]]:
579585
if subclass_name not in subclass_names:
580586
subclass_names.append(subclass_name)
581587
return dict_class_name_to_subclass_names
582-
583-
584-
def getComponentPortNameAndType(declaring_class_name, constructor) -> (str, str):
585-
"""Determine whether this is a component and, if so, get the port types that
586-
correspond to the constructor parameters. The returned value is a single
587-
string consisting of one or more port types. Multiple port types are
588-
separated by __ (two underscores). Each port type matches one of the PortType
589-
enum values in src/storage/module_content.ts."""
590-
591-
# TODO(lizlooney): Replace the following temporary fake code with code that
592-
# looks at doc string and/or parameter type aliases to tell whether this is
593-
# a component and what the port types are.
594-
if declaring_class_name == "wpilib_placeholders.ExpansionHubMotor":
595-
return ("expansion_hub_motor", "SYSTEMCORE_USB_PORT__EXPANSION_HUB_MOTOR_PORT")
596-
if declaring_class_name == "wpilib_placeholders.ExpansionHubServo":
597-
return ("expansion_hub_servo", "SYSTEMCORE_USB_PORT__EXPANSION_HUB_SERVO_PORT")
598-
if declaring_class_name == "wpilib.PWMSparkMax":
599-
return ("smart_io_port", "SYSTEMCORE_SMART_IO_PORT")
600-
if declaring_class_name == "wpilib.AddressableLED":
601-
return ("smart_io_port", "SYSTEMCORE_SMART_IO_PORT")
602-
603-
return None

src/blocks/mrc_class_method_def.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -471,20 +471,20 @@ export const pythonFromBlock = function (
471471
branch = generator.PASS;
472472
}
473473

474-
const params = block.mrcParameters;
475474
let paramString = 'self';
475+
476476
if (generator.getModuleType() === storageModule.ModuleType.MECHANISM && block.mrcPythonMethodName === '__init__') {
477-
const ports: string[] = generator.getComponentPortParameters();
478-
if (ports.length) {
479-
paramString += ', ' + ports.join(', ');
477+
const mechanismInitArgNames: string[] = generator.getMechanismInitArgNames();
478+
if (mechanismInitArgNames.length) {
479+
paramString += ', ' + mechanismInitArgNames.join(', ');
480480
}
481481
}
482482

483483
if (generator.getModuleType() === storageModule.ModuleType.OPMODE && block.mrcPythonMethodName === '__init__') {
484484
paramString += ', robot';
485485
}
486486

487-
if (params.length != 0) {
487+
if (block.mrcParameters.length != 0) {
488488
block.mrcParameters.forEach(param => {
489489
paramString += ', ' + param.name;
490490
});

0 commit comments

Comments
 (0)