Skip to content
Open
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
8 changes: 4 additions & 4 deletions ardupilot_methodic_configurator/data_model_par_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,19 +261,19 @@ def _format_params(self, file_format: str = "missionplanner") -> list[str]:
sorted_dict = ParDict(sorted_items)
formatted_params = [
(
f"{key},{format(parameter.value, '.6f').rstrip('0').rstrip('.')} # {parameter.comment}"
f"{key},{format(parameter.value, '.10f').rstrip('0').rstrip('.')} # {parameter.comment}"
if isinstance(parameter, Par) and parameter.comment
else f"{key},{format(parameter.value if isinstance(parameter, Par) else parameter, '.6f').rstrip('0').rstrip('.')}" # noqa: E501 # pylint: disable=line-too-long
else f"{key},{format(parameter.value if isinstance(parameter, Par) else parameter, '.10f').rstrip('0').rstrip('.')}" # noqa: E501 # pylint: disable=line-too-long
)
for key, parameter in sorted_dict.items()
]
elif file_format == "mavproxy":
sorted_dict = ParDict(dict(sorted(self.items())))
formatted_params = [
(
f"{key:<16} {parameter.value:<8.6f} # {parameter.comment}"
f"{key:<16} {parameter.value:<14.10f} # {parameter.comment}"
if isinstance(parameter, Par) and parameter.comment
else f"{key:<16} {parameter.value if isinstance(parameter, Par) else parameter:<8.6f}"
else f"{key:<16} {parameter.value if isinstance(parameter, Par) else parameter:<14.10f}"
)
for key, parameter in sorted_dict.items()
]
Expand Down
78 changes: 75 additions & 3 deletions tests/test_data_model_par_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,8 @@ def test_user_can_export_parameters_to_mavproxy_format(self, parameter_dict) ->
content = f.read()

# MAVProxy format uses fixed-width columns
assert "ACRO_YAW_P 4.500000 # Yaw P gain" in content
assert "GPS_TYPE 1.000000" in content
assert "ACRO_YAW_P 4.5000000000 # Yaw P gain" in content
assert "GPS_TYPE 1.0000000000 " in content
finally:
os.unlink(output_file)

Expand Down Expand Up @@ -949,7 +949,7 @@ def test_user_can_format_parameters_for_mavproxy(self, parameter_dict) -> None:

# Assert: Correct MAVProxy format (space-separated, fixed width)
yaw_line = next((line for line in formatted if "ACRO_YAW_P" in line), "")
assert yaw_line == "ACRO_YAW_P 4.500000 # Yaw P gain"
assert yaw_line == "ACRO_YAW_P 4.5000000000 # Yaw P gain"

def test_user_receives_error_for_unsupported_format(self, parameter_dict) -> None:
"""
Expand All @@ -965,6 +965,78 @@ def test_user_receives_error_for_unsupported_format(self, parameter_dict) -> Non
with pytest.raises(ParamFileError, match="Unsupported file format"):
parameter_dict._format_params("invalid_format") # pylint: disable=protected-access

def test_user_preserves_small_parameter_values_on_export(self) -> None:
"""
User preserves parameter values smaller than 1e-6 during export.

GIVEN: A user has parameters with very small but nonzero values
WHEN: They format using MissionPlanner or MAVProxy format
THEN: The formatted strings should retain the small values
"""
# Arrange: Parameters with values that would be truncated by .6f formatting
small_value_params = ParDict(
{
"TINY_VAL": Par(0.0000001, "Very small value"),
"MICRO_VAL": Par(0.0000005, "Another small value"),
"BOUNDARY_VAL": Par(0.000001, "Boundary value"),
"NORMAL_VAL": Par(1.5, "Normal value"),
}
)

for file_format in ("missionplanner", "mavproxy"):
formatted = small_value_params._format_params(file_format) # pylint: disable=protected-access

tiny_found = False
micro_found = False
boundary_found = False
for line in formatted:
if "TINY_VAL" in line:
assert "0.0000001" in line, f"Value 1e-7 lost in {file_format} format: {line}"
tiny_found = True
if "MICRO_VAL" in line:
assert "0.0000005" in line, f"Value 5e-7 lost in {file_format} format: {line}"
micro_found = True
if "BOUNDARY_VAL" in line:
assert "0.000001" in line, f"Value 1e-6 lost in {file_format} format: {line}"
boundary_found = True

assert tiny_found, f"TINY_VAL not found in {file_format} formatted output"
assert micro_found, f"MICRO_VAL not found in {file_format} formatted output"
assert boundary_found, f"BOUNDARY_VAL not found in {file_format} formatted output"

def test_user_preserves_small_values_on_export_reimport_round_trip(self) -> None:
"""
User preserves small parameter values through a full export→reimport round-trip.

GIVEN: A user has parameters with very small but nonzero values
WHEN: They export to a file and reimport it
THEN: The numeric values should match within float32 tolerance
"""
small_value_params = ParDict(
{
"TINY_VAL": Par(0.0000001, "Very small value"),
"BOUNDARY_VAL": Par(0.000001, "Boundary value"),
"NORMAL_VAL": Par(1.5, "Normal value"),
}
)

for file_format in ("missionplanner", "mavproxy"):
with tempfile.NamedTemporaryFile(mode="w", suffix=".param", delete=False) as f:
output_file = f.name

try:
small_value_params.export_to_param(output_file, file_format)
reloaded = ParDict.load_param_file_into_dict(output_file)

for key, original in small_value_params.items():
original_val = original.value if isinstance(original, Par) else original
reloaded_val = reloaded[key].value if isinstance(reloaded[key], Par) else reloaded[key]
assert abs(reloaded_val - original_val) < 1e-10, (
f"{key}: value {original_val} became {reloaded_val} after {file_format} round-trip"
)
finally:
os.unlink(output_file)

def test_user_can_annotate_parameters_with_comments(self, parameter_dict) -> None:
"""
User can add comments to parameters using a lookup table.
Expand Down
Loading