mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
384 lines
12 KiB
Python
Executable File
384 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) FIRST and other WPILib contributors.
|
|
# Open Source Software; you can modify and/or share it under the terms of
|
|
# the WPILib BSD license file in the root directory of this project.
|
|
|
|
# This script generates unit-specific interfaces and mutable and immutable implementations of
|
|
# those interfaces.
|
|
# Generated files will be located in wpiunits/src/generated/main/
|
|
|
|
import inspect
|
|
import os
|
|
import re
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
|
|
def output(outPath, outfn, contents):
|
|
if not os.path.exists(outPath):
|
|
os.makedirs(outPath)
|
|
|
|
outpathname = f"{outPath}/{outfn}"
|
|
|
|
if os.path.exists(outpathname):
|
|
with open(outpathname, "r") as f:
|
|
if f.read() == contents:
|
|
return
|
|
|
|
# File either doesn't exist or has different contents
|
|
with open(outpathname, "w", newline="\n") as f:
|
|
f.write(contents)
|
|
|
|
|
|
# The units for which multiply and divide mathematical operations are defined
|
|
MATH_OPERATION_UNITS = [
|
|
"Acceleration<?>",
|
|
"Angle",
|
|
"AngularAcceleration",
|
|
"AngularMomentum",
|
|
"AngularVelocity",
|
|
"Current",
|
|
"Dimensionless",
|
|
"Distance",
|
|
"Energy",
|
|
"Force",
|
|
"Frequency",
|
|
"LinearAcceleration",
|
|
"LinearMomentum",
|
|
"LinearVelocity",
|
|
"Mass",
|
|
"MomentOfInertia",
|
|
"Mult<?, ?>",
|
|
"Per<?, ?>",
|
|
"Power",
|
|
"Temperature",
|
|
"Time",
|
|
"Torque",
|
|
"Velocity<?>",
|
|
"Voltage",
|
|
]
|
|
|
|
# Configurations for all generated units
|
|
UNIT_CONFIGURATIONS = {
|
|
"Acceleration": {
|
|
"base_unit": "unit()",
|
|
"generics": {"D": {"extends": "Unit"}},
|
|
"multiply": {},
|
|
"divide": {},
|
|
},
|
|
"Angle": {
|
|
"base_unit": "Radians",
|
|
"multiply": {"Frequency": "AngularVelocity"},
|
|
"divide": {"Time": "AngularVelocity"},
|
|
},
|
|
"AngularAcceleration": {
|
|
"base_unit": "RadiansPerSecondPerSecond",
|
|
"multiply": {"Time": "AngularVelocity"},
|
|
"divide": {"Frequency": "AngularVelocity"},
|
|
},
|
|
"AngularMomentum": {
|
|
"base_unit": "KilogramMetersSquaredPerSecond",
|
|
"multiply": {},
|
|
"divide": {"AngularVelocity": "MomentOfInertia"},
|
|
},
|
|
"AngularVelocity": {
|
|
"base_unit": "RadiansPerSecond",
|
|
"multiply": {"Time": "Angle", "Frequency": "AngularAcceleration"},
|
|
"divide": {"Time": "AngularAcceleration"},
|
|
"extra": inspect.cleandoc(
|
|
"""
|
|
default Frequency asFrequency() { return Hertz.of(baseUnitMagnitude()); }
|
|
"""
|
|
),
|
|
},
|
|
"Current": {"base_unit": "Amps", "multiply": {"Voltage": "Power"}, "divide": {}},
|
|
"Dimensionless": {
|
|
"base_unit": "Value",
|
|
"multiply": {
|
|
"Angle": "Angle",
|
|
"AngularAcceleration": "AngularAcceleration",
|
|
"AngularMomentum": "AngularMomentum",
|
|
"AngularVelocity": "AngularVelocity",
|
|
"Current": "Current",
|
|
"Dimensionless": "Dimensionless",
|
|
"Distance": "Distance",
|
|
"Energy": "Energy",
|
|
"Force": "Force",
|
|
"Frequency": "Frequency",
|
|
"LinearAcceleration": "LinearAcceleration",
|
|
"LinearMomentum": "LinearMomentum",
|
|
"LinearVelocity": "LinearVelocity",
|
|
"Mass": "Mass",
|
|
"MomentOfInertia": "MomentOfInertia",
|
|
"Power": "Power",
|
|
"Temperature": "Temperature",
|
|
"Time": "Time",
|
|
"Torque": "Torque",
|
|
"Voltage": "Voltage",
|
|
},
|
|
"divide": {
|
|
"Time": "Frequency",
|
|
# TODO:
|
|
# "AngularVelocity": "Per<TimeUnit, AngleUnit>",
|
|
# "AngularAcceleration": "Per<TimeUnit, AngularVelocityUnit>",
|
|
# "LinearVelocity": "Per<TimeUnit, DistanceUnit>",
|
|
# "LinearAcceleration": "Per<TimeUnit, LinearVelocityUnit>",
|
|
# "Velocity<?>": "Per<TimeUnit, ?>",
|
|
# "Acceleration<?>": "Per<TimeUnit, VelocityUnit<?>>
|
|
# "Per<N, D>": "Per<D, N>"
|
|
},
|
|
},
|
|
"Distance": {
|
|
"base_unit": "Meters",
|
|
"multiply": {
|
|
"Frequency": "LinearVelocity",
|
|
# Distance x Force = Torque
|
|
# Force x Distance = Energy
|
|
"Force": "Torque",
|
|
},
|
|
"divide": {"Time": "LinearVelocity", "LinearVelocity": "Time"},
|
|
},
|
|
"Energy": {
|
|
"base_unit": "Joules",
|
|
"multiply": {"Frequency": "Power"},
|
|
"divide": {"Time": "Power"},
|
|
},
|
|
"Force": {
|
|
"base_unit": "Newtons",
|
|
"multiply": {
|
|
# Distance x Force = Torque
|
|
# Force x Distance = Energy
|
|
"Distance": "Energy"
|
|
},
|
|
"divide": {"Mass": "LinearAcceleration", "LinearAcceleration": "Mass"},
|
|
},
|
|
"Frequency": {
|
|
"base_unit": "Hertz",
|
|
"multiply": {
|
|
"Time": "Dimensionless",
|
|
"Distance": "LinearVelocity",
|
|
"LinearVelocity": "LinearAcceleration",
|
|
"Angle": "AngularVelocity",
|
|
"AngularVelocity": "AngularAcceleration",
|
|
},
|
|
"divide": {},
|
|
"extra": inspect.cleandoc(
|
|
"""
|
|
/** Converts this frequency to the time period between cycles. */
|
|
default Time asPeriod() { return Seconds.of(1 / baseUnitMagnitude()); }
|
|
"""
|
|
),
|
|
},
|
|
"LinearAcceleration": {
|
|
"base_unit": "MetersPerSecondPerSecond",
|
|
"multiply": {"Time": "LinearVelocity"},
|
|
"divide": {"Frequency": "LinearVelocity"},
|
|
},
|
|
"LinearMomentum": {
|
|
"base_unit": "KilogramMetersPerSecond",
|
|
"multiply": {"Frequency": "Force"},
|
|
"divide": {"Mass": "LinearVelocity", "LinearVelocity": "Mass", "Time": "Force"},
|
|
},
|
|
"LinearVelocity": {
|
|
"base_unit": "MetersPerSecond",
|
|
"multiply": {"Time": "Distance", "Frequency": "LinearAcceleration"},
|
|
"divide": {"Time": "LinearAcceleration"},
|
|
},
|
|
"Mass": {
|
|
"base_unit": "Kilograms",
|
|
"multiply": {"LinearAcceleration": "Force"},
|
|
"divide": {},
|
|
},
|
|
"MomentOfInertia": {
|
|
"base_unit": "KilogramSquareMeters",
|
|
"multiply": {"AngularVelocity": "AngularMomentum"},
|
|
"divide": {},
|
|
},
|
|
"Mult": {
|
|
"base_unit": "unit()",
|
|
"generics": {"A": {"extends": "Unit"}, "B": {"extends": "Unit"}},
|
|
"multiply": {},
|
|
"divide": {},
|
|
},
|
|
"Per": {
|
|
"base_unit": "unit()",
|
|
"generics": {"Dividend": {"extends": "Unit"}, "Divisor": {"extends": "Unit"}},
|
|
"multiply": {},
|
|
"divide": {},
|
|
"extra": inspect.cleandoc(
|
|
"""
|
|
default Measure<Dividend> timesDivisor(Measure<? extends Divisor> multiplier) {
|
|
return (Measure<Dividend>) baseUnit().numerator().ofBaseUnits(baseUnitMagnitude() * multiplier.baseUnitMagnitude());
|
|
}
|
|
|
|
default Measure<? extends PerUnit<Divisor, Dividend>> reciprocal() {
|
|
// May return a velocity if Divisor == TimeUnit, so we can't guarantee a "Per" instance
|
|
return baseUnit().reciprocal().ofBaseUnits(1 / baseUnitMagnitude());
|
|
}
|
|
"""
|
|
),
|
|
},
|
|
"Power": {
|
|
"base_unit": "Watts",
|
|
"multiply": {
|
|
"Time": "Energy",
|
|
},
|
|
"divide": {"Voltage": "Current", "Current": "Voltage", "Energy": "Frequency"},
|
|
},
|
|
"Temperature": {"base_unit": "Kelvin", "multiply": {}, "divide": {}},
|
|
"Time": {
|
|
"base_unit": "Seconds",
|
|
"multiply": {
|
|
"Frequency": "Dimensionless",
|
|
"AngularVelocity": "Angle",
|
|
"AngularAcceleration": "AngularVelocity",
|
|
"LinearVelocity": "Distance",
|
|
"LinearAcceleration": "LinearVelocity",
|
|
# TODO:
|
|
# "Acceleration<D>": "Velocity<D>"
|
|
# "Velocity<D>": "Measure<D>"
|
|
},
|
|
"divide": {
|
|
# Time specifically needs this to be called out so generated methods like
|
|
# `per(TimeUnit)` or `divide(Time)` will return dimensionless values instead of
|
|
# `Velocity<TimeUnit>` (i.e. a time per unit time ratio)
|
|
"Time": "Dimensionless"
|
|
},
|
|
"extra": inspect.cleandoc(
|
|
"""
|
|
default Frequency asFrequency() { return Hertz.of(1 / baseUnitMagnitude()); }
|
|
"""
|
|
),
|
|
},
|
|
"Torque": {
|
|
"base_unit": "NewtonMeters",
|
|
"multiply": {},
|
|
"divide": {"Distance": "Force", "Force": "Distance"},
|
|
},
|
|
"Velocity": {
|
|
"base_unit": "unit()",
|
|
"generics": {"D": {"extends": "Unit"}},
|
|
"multiply": {
|
|
"Time": {
|
|
"implementation": inspect.cleandoc(
|
|
"""
|
|
@Override
|
|
default Measure<D> times(Time multiplier) {
|
|
return (Measure<D>) unit().numerator().ofBaseUnits(baseUnitMagnitude() * multiplier.baseUnitMagnitude());
|
|
}
|
|
"""
|
|
)
|
|
}
|
|
},
|
|
"divide": {},
|
|
},
|
|
"Voltage": {"base_unit": "Volts", "multiply": {"Current": "Power"}, "divide": {}},
|
|
}
|
|
|
|
|
|
def generics_list(measure_name):
|
|
if "generics" in UNIT_CONFIGURATIONS[measure_name]:
|
|
args = []
|
|
for name, config in UNIT_CONFIGURATIONS[measure_name]["generics"].items():
|
|
if "extends" in config:
|
|
args.append("{} extends {}".format(name, config["extends"]))
|
|
elif "super" in config:
|
|
args.append("{} super {}".format(name, config["super"]))
|
|
else:
|
|
args.append(name)
|
|
|
|
return "<{}>".format(", ".join(args))
|
|
else:
|
|
return ""
|
|
|
|
|
|
def generics_usage(measure_name):
|
|
if "generics" in UNIT_CONFIGURATIONS[measure_name]:
|
|
args = UNIT_CONFIGURATIONS[measure_name]["generics"].keys()
|
|
|
|
return "<{}>".format(", ".join(args))
|
|
else:
|
|
return ""
|
|
|
|
|
|
def type_decl(measure_name):
|
|
return measure_name + generics_list(measure_name)
|
|
|
|
|
|
def type_usage(measure_name):
|
|
return measure_name + generics_usage(measure_name)
|
|
|
|
|
|
# measure-to-unit
|
|
def mtou(measure_name):
|
|
if (
|
|
measure_name in UNIT_CONFIGURATIONS
|
|
and "generics" in UNIT_CONFIGURATIONS[measure_name]
|
|
):
|
|
return "{}Unit{}".format(measure_name, generics_usage(measure_name))
|
|
else:
|
|
regex = re.compile(r"^(.*?)(<.*>)?$")
|
|
return re.sub(regex, "\\1Unit\\2", measure_name)
|
|
|
|
|
|
def indent(multiline_string, indentation):
|
|
"""
|
|
Indents a multiline string by `indentation` number of spaces
|
|
"""
|
|
return "\n".join(
|
|
list(map(lambda line: " " * indentation + line, multiline_string.split("\n")))
|
|
)
|
|
|
|
|
|
def main():
|
|
|
|
dirname, _ = os.path.split(os.path.abspath(__file__))
|
|
|
|
env = Environment(
|
|
loader=FileSystemLoader(f"{dirname}/src/generate/main/java"),
|
|
autoescape=False,
|
|
keep_trailing_newline=True,
|
|
)
|
|
|
|
interfaceTemplate = env.get_template("Measure-interface.java.jinja")
|
|
immutableTemplate = env.get_template("Measure-immutable.java.jinja")
|
|
mutableTemplate = env.get_template("Measure-mutable.java.jinja")
|
|
rootPath = f"{dirname}/src/generated/main/java/edu/wpi/first/units"
|
|
|
|
helpers = {
|
|
"type_decl": type_decl,
|
|
"type_usage": type_usage,
|
|
"generics_list": generics_list,
|
|
"generics_usage": generics_usage,
|
|
"mtou": mtou,
|
|
"indent": indent,
|
|
}
|
|
|
|
for unit_name in UNIT_CONFIGURATIONS:
|
|
interfaceContents = interfaceTemplate.render(
|
|
name=unit_name,
|
|
math_units=MATH_OPERATION_UNITS,
|
|
config=UNIT_CONFIGURATIONS,
|
|
helpers=helpers,
|
|
)
|
|
immutableContents = immutableTemplate.render(
|
|
name=unit_name,
|
|
units=MATH_OPERATION_UNITS,
|
|
config=UNIT_CONFIGURATIONS,
|
|
helpers=helpers,
|
|
)
|
|
mutableContents = mutableTemplate.render(
|
|
name=unit_name,
|
|
units=MATH_OPERATION_UNITS,
|
|
config=UNIT_CONFIGURATIONS,
|
|
helpers=helpers,
|
|
)
|
|
|
|
output(f"{rootPath}/measure", f"{unit_name}.java", interfaceContents)
|
|
output(f"{rootPath}/measure", f"Immutable{unit_name}.java", immutableContents)
|
|
output(f"{rootPath}/measure", f"Mut{unit_name}.java", mutableContents)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|