Refactor parse_distance_string into tool classmethod and add tests including e9eca5e behaviour

This commit is contained in:
Dion Moult
2026-01-27 19:13:43 +11:00
parent 6ad9949571
commit 2a60f46516
5 changed files with 252 additions and 244 deletions
@@ -157,7 +157,6 @@ from mathutils.kdtree import KDTree
import bonsai.tool as tool import bonsai.tool as tool
from bonsai.bim.module.drawing.shaders import ExtrusionGuidesShader from bonsai.bim.module.drawing.shaders import ExtrusionGuidesShader
from bonsai.tool.unit import parse_distance_string
SNAP_POINT_SIZE = 10.0 SNAP_POINT_SIZE = 10.0
SNAP_POINT_COLOR = (1.0, 0.5, 0.0, 1.0) SNAP_POINT_COLOR = (1.0, 0.5, 0.0, 1.0)
@@ -1028,7 +1027,7 @@ class NumericInputState:
return return
input_str = self.get_input_string() input_str = self.get_input_string()
is_valid, value = parse_distance_string(input_str) is_valid, value = tool.Unit.parse_distance_string(input_str)
if is_valid: if is_valid:
self.parsed_value = value self.parsed_value = value
+1 -2
View File
@@ -39,7 +39,6 @@ import bonsai.core.geometry
import bonsai.tool as tool import bonsai.tool as tool
from bonsai.bim.module.spatial.data import SpatialDecompositionData from bonsai.bim.module.spatial.data import SpatialDecompositionData
from bonsai.bim.prop import ObjProperty from bonsai.bim.prop import ObjProperty
from bonsai.tool.unit import parse_distance_string
def get_subelement_class( def get_subelement_class(
@@ -52,7 +51,7 @@ def get_subelement_class(
def update_elevation(self: "BIMContainer", context: bpy.types.Context) -> None: def update_elevation(self: "BIMContainer", context: bpy.types.Context) -> None:
# Try to parse the input string with unit support first # Try to parse the input string with unit support first
is_valid, parsed_elevation = parse_distance_string(self.elevation, use_project_unit=True) is_valid, parsed_elevation = tool.Unit.parse_distance_string(self.elevation, use_project_unit=True)
if is_valid: if is_valid:
elevation = parsed_elevation elevation = parsed_elevation
-1
View File
@@ -1132,7 +1132,6 @@ class Unit:
def set_active_unit(cls, unit): pass def set_active_unit(cls, unit): pass
def get_project_currency_unit(cls): pass def get_project_currency_unit(cls): pass
def get_currency_name(cls): pass def get_currency_name(cls): pass
def add_mass_and_time_units(cls): pass
@interface @interface
class Voider: class Voider:
+29 -29
View File
@@ -34,7 +34,35 @@ if TYPE_CHECKING:
from bonsai.bim.module.unit.prop import BIMUnitProperties from bonsai.bim.module.unit.prop import BIMUnitProperties
def parse_distance_string(input_string: str, use_project_unit: bool = True) -> tuple[bool, float]: class Unit(bonsai.core.tool.Unit):
UNIT_TYPE = Literal["LENGTHUNIT", "AREAUNIT", "VOLUMEUNIT", "MASSUNIT", "TIMEUNIT"]
@staticmethod
def format_distance(meters: float, use_imperial: bool = None, **kwargs) -> str:
"""
Format a distance value in meters to a string in the project's unit system.
:param meters: The distance value in meters
:param use_imperial: If True, format as imperial; if False, format as metric; if None, auto-detect from scene
:param kwargs: Additional arguments to pass to the underlying format_distance function
(hide_units, precision, decimal_places, etc.)
:return: Formatted string with units
"""
# Import the comprehensive format_distance from the helper module
from bonsai.bim.module.drawing import helper
# The comprehensive function expects value in scene units, not meters
# So we pass meters directly since it handles unit conversion internally
return helper.format_distance(
meters,
hide_units=kwargs.get("hide_units", False),
precision=kwargs.get("precision"),
decimal_places=kwargs.get("decimal_places"),
**kwargs,
)
@classmethod
def parse_distance_string(cls, input_string: str, use_project_unit: bool = True) -> tuple[bool, float]:
""" """
Parse a distance string with optional unit suffixes and convert to meters. Parse a distance string with optional unit suffixes and convert to meters.
@@ -272,34 +300,6 @@ def parse_distance_string(input_string: str, use_project_unit: bool = True) -> t
except Exception as e: except Exception as e:
return False, 0.0 return False, 0.0
class Unit(bonsai.core.tool.Unit):
UNIT_TYPE = Literal["LENGTHUNIT", "AREAUNIT", "VOLUMEUNIT", "MASSUNIT", "TIMEUNIT"]
@staticmethod
def format_distance(meters: float, use_imperial: bool = None, **kwargs) -> str:
"""
Format a distance value in meters to a string in the project's unit system.
:param meters: The distance value in meters
:param use_imperial: If True, format as imperial; if False, format as metric; if None, auto-detect from scene
:param kwargs: Additional arguments to pass to the underlying format_distance function
(hide_units, precision, decimal_places, etc.)
:return: Formatted string with units
"""
# Import the comprehensive format_distance from the helper module
from bonsai.bim.module.drawing import helper
# The comprehensive function expects value in scene units, not meters
# So we pass meters directly since it handles unit conversion internally
return helper.format_distance(
meters,
hide_units=kwargs.get("hide_units", False),
precision=kwargs.get("precision"),
decimal_places=kwargs.get("decimal_places"),
**kwargs,
)
@classmethod @classmethod
def get_unit_props(cls) -> BIMUnitProperties: def get_unit_props(cls) -> BIMUnitProperties:
return bpy.context.scene.BIMUnitProperties return bpy.context.scene.BIMUnitProperties
+11
View File
@@ -33,6 +33,17 @@ class TestImplementsTool(NewFile):
assert isinstance(subject(), bonsai.core.tool.Unit) assert isinstance(subject(), bonsai.core.tool.Unit)
class TestParseDistanceString(NewFile):
def test_run(self):
assert subject.parse_distance_string("5m") == (True, 5.0)
assert subject.parse_distance_string("30cm") == (True, 0.3)
assert subject.parse_distance_string("10ft") == (True, 3.048)
assert subject.parse_distance_string("12in") == (True, 0.3048)
assert subject.parse_distance_string("5'6\"") == (True, 1.6764)
assert subject.parse_distance_string("-5'6\"") == (True, -1.6764)
assert subject.parse_distance_string("invalid") == (False, 0.0)
class TestClearActiveUnit(NewFile): class TestClearActiveUnit(NewFile):
def test_run(self): def test_run(self):
props = tool.Unit.get_unit_props() props = tool.Unit.get_unit_props()