mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-25 17:57:02 +00:00
Refactor parse_distance_string into tool classmethod and add tests including e9eca5e behaviour
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
+239
-239
@@ -34,245 +34,6 @@ 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]:
|
|
||||||
"""
|
|
||||||
Parse a distance string with optional unit suffixes and convert to meters.
|
|
||||||
|
|
||||||
This function parses distance inputs with units (e.g., "5m", "10ft", "3.5cm")
|
|
||||||
and converts them to meters (SI units) for use in IFC models.
|
|
||||||
|
|
||||||
Supports:
|
|
||||||
- Metric units: mm, cm, dm, m
|
|
||||||
- Imperial units: ft/feet ('), in/inches (")
|
|
||||||
- Arithmetic expressions: +, -, *, /
|
|
||||||
- Fractions for imperial units (e.g., 1/2")
|
|
||||||
- Formula mode: values starting with "="
|
|
||||||
|
|
||||||
:param input_string: The string to parse (e.g., "5m", "10ft", "10'6\"", "3.5cm", "12in")
|
|
||||||
:param use_project_unit: If True, uses project unit scale; if False, uses Blender unit scale
|
|
||||||
:return: Tuple (is_valid, value_in_meters) where is_valid indicates successful parsing
|
|
||||||
and value_in_meters is the converted value in meters
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
>>> parse_distance_string("5m")
|
|
||||||
(True, 5.0)
|
|
||||||
>>> parse_distance_string("30cm")
|
|
||||||
(True, 0.3)
|
|
||||||
>>> parse_distance_string("10ft")
|
|
||||||
(True, 3.048)
|
|
||||||
>>> parse_distance_string("12in")
|
|
||||||
(True, 0.3048)
|
|
||||||
>>> parse_distance_string("5'6\"")
|
|
||||||
(True, 1.6764)
|
|
||||||
>>> parse_distance_string("invalid")
|
|
||||||
(False, 0.0)
|
|
||||||
"""
|
|
||||||
|
|
||||||
grammar_imperial = r"""
|
|
||||||
start: (FORMULA dim expr) | dim
|
|
||||||
dim: imperial
|
|
||||||
|
|
||||||
FORMULA: "="
|
|
||||||
|
|
||||||
imperial: feet_inches | feet_only | inches_only | plain_number
|
|
||||||
feet_only: NUMBER (FEET_SYM | FEET_TEXT)
|
|
||||||
inches_only: inch_value (INCH_SYM | INCH_TEXT)
|
|
||||||
feet_inches: NUMBER (FEET_SYM | FEET_TEXT) DASH? inch_value (INCH_SYM | INCH_TEXT)?
|
|
||||||
plain_number: NUMBER
|
|
||||||
|
|
||||||
inch_value: NUMBER fraction | fraction | NUMBER
|
|
||||||
|
|
||||||
fraction: NUMBER "/" NUMBER
|
|
||||||
|
|
||||||
expr: (ADD | SUB) dim | (MUL | DIV) NUMBER
|
|
||||||
|
|
||||||
NUMBER: /-?(?:\d+\.?\d*|\.\d+)/
|
|
||||||
FEET_SYM: "'"
|
|
||||||
FEET_TEXT: "ft"
|
|
||||||
INCH_SYM: "\""
|
|
||||||
INCH_TEXT: "in"
|
|
||||||
DASH: "-"
|
|
||||||
ADD: "+"
|
|
||||||
SUB: "-"
|
|
||||||
MUL: "*"
|
|
||||||
DIV: "/"
|
|
||||||
|
|
||||||
%ignore " "
|
|
||||||
"""
|
|
||||||
|
|
||||||
grammar_metric = r"""
|
|
||||||
start: FORMULA? dim expr?
|
|
||||||
dim: metric
|
|
||||||
|
|
||||||
FORMULA: "="
|
|
||||||
|
|
||||||
metric: NUMBER (MM | CM | DM | M | DEG)?
|
|
||||||
|
|
||||||
expr: (ADD | SUB | MUL | DIV) dim
|
|
||||||
|
|
||||||
NUMBER: /-?(?:\d+\.?\d*|\.\d+)/
|
|
||||||
MM: "mm"
|
|
||||||
CM: "cm"
|
|
||||||
DM: "dm"
|
|
||||||
M: "m"
|
|
||||||
DEG: "°"
|
|
||||||
ADD: "+"
|
|
||||||
SUB: "-"
|
|
||||||
MUL: "*"
|
|
||||||
DIV: "/"
|
|
||||||
|
|
||||||
%ignore " "
|
|
||||||
"""
|
|
||||||
|
|
||||||
class InputTransform(Transformer):
|
|
||||||
def NUMBER(self, n):
|
|
||||||
return float(n)
|
|
||||||
|
|
||||||
def fraction(self, numbers):
|
|
||||||
return numbers[0] / numbers[1]
|
|
||||||
|
|
||||||
def inch_value(self, args):
|
|
||||||
# Can be: NUMBER fraction, fraction, or NUMBER
|
|
||||||
if len(args) == 2:
|
|
||||||
# NUMBER fraction (e.g., "9 1/64")
|
|
||||||
return args[0] + args[1]
|
|
||||||
else:
|
|
||||||
# Just fraction or just NUMBER
|
|
||||||
return args[0]
|
|
||||||
|
|
||||||
def plain_number(self, args):
|
|
||||||
# A plain number in imperial context is assumed to be feet
|
|
||||||
feet = args[0]
|
|
||||||
# Convert feet to meters (1 foot = 0.3048 meters)
|
|
||||||
return feet * 0.3048
|
|
||||||
|
|
||||||
def feet_only(self, args):
|
|
||||||
# args[0] is the number of feet, args[1] is the unit token (we can ignore it)
|
|
||||||
feet = args[0]
|
|
||||||
# Convert feet to meters (1 foot = 0.3048 meters)
|
|
||||||
return feet * 0.3048
|
|
||||||
|
|
||||||
def inches_only(self, args):
|
|
||||||
# args[0] is the inch_value, args[1] is the unit token
|
|
||||||
inches = args[0]
|
|
||||||
# Convert inches to meters (1 inch = 0.0254 meters)
|
|
||||||
return inches * 0.0254
|
|
||||||
|
|
||||||
def feet_inches(self, args):
|
|
||||||
# Extract feet and inches values
|
|
||||||
feet = args[0]
|
|
||||||
# Find the inch_value (it's a number, not a token)
|
|
||||||
inches = None
|
|
||||||
for arg in args[1:]:
|
|
||||||
if isinstance(arg, (int, float)):
|
|
||||||
inches = arg
|
|
||||||
break
|
|
||||||
if inches is None:
|
|
||||||
inches = 0
|
|
||||||
|
|
||||||
# If feet is negative, inches should also be negative (subtractive)
|
|
||||||
if feet < 0:
|
|
||||||
inches = -inches
|
|
||||||
|
|
||||||
# Convert to meters
|
|
||||||
total_meters = (feet * 0.3048) + (inches * 0.0254)
|
|
||||||
return total_meters
|
|
||||||
|
|
||||||
def imperial(self, args):
|
|
||||||
# Just return the value from the sub-rule (feet_only, inches_only, or feet_inches)
|
|
||||||
return args[0]
|
|
||||||
|
|
||||||
def metric(self, args):
|
|
||||||
# args[0] is the NUMBER, args[1] if present is the unit
|
|
||||||
value = args[0]
|
|
||||||
if len(args) > 1:
|
|
||||||
unit = str(args[1])
|
|
||||||
# Convert to meters based on unit
|
|
||||||
if unit == "mm":
|
|
||||||
value = value / 1000.0
|
|
||||||
elif unit == "cm":
|
|
||||||
value = value / 100.0
|
|
||||||
elif unit == "dm":
|
|
||||||
value = value / 10.0
|
|
||||||
elif unit == "m":
|
|
||||||
value = value # already in meters
|
|
||||||
elif unit == "°":
|
|
||||||
value = value # degrees, pass through
|
|
||||||
# If no unit specified, assume it's already in the project's unit system
|
|
||||||
return value
|
|
||||||
|
|
||||||
def dim(self, args):
|
|
||||||
return args[0]
|
|
||||||
|
|
||||||
def expr(self, args):
|
|
||||||
op = args[0]
|
|
||||||
value = float(args[1])
|
|
||||||
if op == "+":
|
|
||||||
return lambda x: x + value
|
|
||||||
elif op == "-":
|
|
||||||
return lambda x: x - value
|
|
||||||
elif op == "*":
|
|
||||||
return lambda x: x * value
|
|
||||||
elif op == "/":
|
|
||||||
return lambda x: x / value
|
|
||||||
|
|
||||||
def FORMULA(self, args):
|
|
||||||
return args[0]
|
|
||||||
|
|
||||||
def start(self, args):
|
|
||||||
i = 0
|
|
||||||
if args[0] == "=":
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
if len(args) > 1:
|
|
||||||
raise ValueError("Invalid input.")
|
|
||||||
dimension = args[i]
|
|
||||||
if len(args) > i + 1:
|
|
||||||
expression = args[i + 1]
|
|
||||||
return expression(dimension)
|
|
||||||
else:
|
|
||||||
return dimension
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Determine unit scale
|
|
||||||
if use_project_unit and tool.Ifc.get():
|
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
|
||||||
else:
|
|
||||||
unit_scale = tool.Blender.get_unit_scale()
|
|
||||||
|
|
||||||
# Try to parse with the project's default grammar first
|
|
||||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
|
||||||
primary_parser = Lark(grammar_imperial)
|
|
||||||
fallback_parser = Lark(grammar_metric)
|
|
||||||
else:
|
|
||||||
primary_parser = Lark(grammar_metric)
|
|
||||||
fallback_parser = Lark(grammar_imperial)
|
|
||||||
|
|
||||||
# Try parsing with primary grammar
|
|
||||||
parse_tree = None
|
|
||||||
try:
|
|
||||||
parse_tree = primary_parser.parse(input_string)
|
|
||||||
except Exception as e:
|
|
||||||
# If primary fails, try fallback grammar (allows metric in imperial projects and vice versa)
|
|
||||||
try:
|
|
||||||
parse_tree = fallback_parser.parse(input_string)
|
|
||||||
except Exception as e2:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if parse_tree is None:
|
|
||||||
return False, 0.0
|
|
||||||
|
|
||||||
# Transform the parse tree to get the numeric result
|
|
||||||
transformer = InputTransform()
|
|
||||||
result = transformer.transform(parse_tree)
|
|
||||||
result = round(result, 6)
|
|
||||||
|
|
||||||
return True, result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return False, 0.0
|
|
||||||
|
|
||||||
|
|
||||||
class Unit(bonsai.core.tool.Unit):
|
class Unit(bonsai.core.tool.Unit):
|
||||||
UNIT_TYPE = Literal["LENGTHUNIT", "AREAUNIT", "VOLUMEUNIT", "MASSUNIT", "TIMEUNIT"]
|
UNIT_TYPE = Literal["LENGTHUNIT", "AREAUNIT", "VOLUMEUNIT", "MASSUNIT", "TIMEUNIT"]
|
||||||
|
|
||||||
@@ -300,6 +61,245 @@ class Unit(bonsai.core.tool.Unit):
|
|||||||
**kwargs,
|
**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.
|
||||||
|
|
||||||
|
This function parses distance inputs with units (e.g., "5m", "10ft", "3.5cm")
|
||||||
|
and converts them to meters (SI units) for use in IFC models.
|
||||||
|
|
||||||
|
Supports:
|
||||||
|
- Metric units: mm, cm, dm, m
|
||||||
|
- Imperial units: ft/feet ('), in/inches (")
|
||||||
|
- Arithmetic expressions: +, -, *, /
|
||||||
|
- Fractions for imperial units (e.g., 1/2")
|
||||||
|
- Formula mode: values starting with "="
|
||||||
|
|
||||||
|
:param input_string: The string to parse (e.g., "5m", "10ft", "10'6\"", "3.5cm", "12in")
|
||||||
|
:param use_project_unit: If True, uses project unit scale; if False, uses Blender unit scale
|
||||||
|
:return: Tuple (is_valid, value_in_meters) where is_valid indicates successful parsing
|
||||||
|
and value_in_meters is the converted value in meters
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> parse_distance_string("5m")
|
||||||
|
(True, 5.0)
|
||||||
|
>>> parse_distance_string("30cm")
|
||||||
|
(True, 0.3)
|
||||||
|
>>> parse_distance_string("10ft")
|
||||||
|
(True, 3.048)
|
||||||
|
>>> parse_distance_string("12in")
|
||||||
|
(True, 0.3048)
|
||||||
|
>>> parse_distance_string("5'6\"")
|
||||||
|
(True, 1.6764)
|
||||||
|
>>> parse_distance_string("invalid")
|
||||||
|
(False, 0.0)
|
||||||
|
"""
|
||||||
|
|
||||||
|
grammar_imperial = r"""
|
||||||
|
start: (FORMULA dim expr) | dim
|
||||||
|
dim: imperial
|
||||||
|
|
||||||
|
FORMULA: "="
|
||||||
|
|
||||||
|
imperial: feet_inches | feet_only | inches_only | plain_number
|
||||||
|
feet_only: NUMBER (FEET_SYM | FEET_TEXT)
|
||||||
|
inches_only: inch_value (INCH_SYM | INCH_TEXT)
|
||||||
|
feet_inches: NUMBER (FEET_SYM | FEET_TEXT) DASH? inch_value (INCH_SYM | INCH_TEXT)?
|
||||||
|
plain_number: NUMBER
|
||||||
|
|
||||||
|
inch_value: NUMBER fraction | fraction | NUMBER
|
||||||
|
|
||||||
|
fraction: NUMBER "/" NUMBER
|
||||||
|
|
||||||
|
expr: (ADD | SUB) dim | (MUL | DIV) NUMBER
|
||||||
|
|
||||||
|
NUMBER: /-?(?:\d+\.?\d*|\.\d+)/
|
||||||
|
FEET_SYM: "'"
|
||||||
|
FEET_TEXT: "ft"
|
||||||
|
INCH_SYM: "\""
|
||||||
|
INCH_TEXT: "in"
|
||||||
|
DASH: "-"
|
||||||
|
ADD: "+"
|
||||||
|
SUB: "-"
|
||||||
|
MUL: "*"
|
||||||
|
DIV: "/"
|
||||||
|
|
||||||
|
%ignore " "
|
||||||
|
"""
|
||||||
|
|
||||||
|
grammar_metric = r"""
|
||||||
|
start: FORMULA? dim expr?
|
||||||
|
dim: metric
|
||||||
|
|
||||||
|
FORMULA: "="
|
||||||
|
|
||||||
|
metric: NUMBER (MM | CM | DM | M | DEG)?
|
||||||
|
|
||||||
|
expr: (ADD | SUB | MUL | DIV) dim
|
||||||
|
|
||||||
|
NUMBER: /-?(?:\d+\.?\d*|\.\d+)/
|
||||||
|
MM: "mm"
|
||||||
|
CM: "cm"
|
||||||
|
DM: "dm"
|
||||||
|
M: "m"
|
||||||
|
DEG: "°"
|
||||||
|
ADD: "+"
|
||||||
|
SUB: "-"
|
||||||
|
MUL: "*"
|
||||||
|
DIV: "/"
|
||||||
|
|
||||||
|
%ignore " "
|
||||||
|
"""
|
||||||
|
|
||||||
|
class InputTransform(Transformer):
|
||||||
|
def NUMBER(self, n):
|
||||||
|
return float(n)
|
||||||
|
|
||||||
|
def fraction(self, numbers):
|
||||||
|
return numbers[0] / numbers[1]
|
||||||
|
|
||||||
|
def inch_value(self, args):
|
||||||
|
# Can be: NUMBER fraction, fraction, or NUMBER
|
||||||
|
if len(args) == 2:
|
||||||
|
# NUMBER fraction (e.g., "9 1/64")
|
||||||
|
return args[0] + args[1]
|
||||||
|
else:
|
||||||
|
# Just fraction or just NUMBER
|
||||||
|
return args[0]
|
||||||
|
|
||||||
|
def plain_number(self, args):
|
||||||
|
# A plain number in imperial context is assumed to be feet
|
||||||
|
feet = args[0]
|
||||||
|
# Convert feet to meters (1 foot = 0.3048 meters)
|
||||||
|
return feet * 0.3048
|
||||||
|
|
||||||
|
def feet_only(self, args):
|
||||||
|
# args[0] is the number of feet, args[1] is the unit token (we can ignore it)
|
||||||
|
feet = args[0]
|
||||||
|
# Convert feet to meters (1 foot = 0.3048 meters)
|
||||||
|
return feet * 0.3048
|
||||||
|
|
||||||
|
def inches_only(self, args):
|
||||||
|
# args[0] is the inch_value, args[1] is the unit token
|
||||||
|
inches = args[0]
|
||||||
|
# Convert inches to meters (1 inch = 0.0254 meters)
|
||||||
|
return inches * 0.0254
|
||||||
|
|
||||||
|
def feet_inches(self, args):
|
||||||
|
# Extract feet and inches values
|
||||||
|
feet = args[0]
|
||||||
|
# Find the inch_value (it's a number, not a token)
|
||||||
|
inches = None
|
||||||
|
for arg in args[1:]:
|
||||||
|
if isinstance(arg, (int, float)):
|
||||||
|
inches = arg
|
||||||
|
break
|
||||||
|
if inches is None:
|
||||||
|
inches = 0
|
||||||
|
|
||||||
|
# If feet is negative, inches should also be negative (subtractive)
|
||||||
|
if feet < 0:
|
||||||
|
inches = -inches
|
||||||
|
|
||||||
|
# Convert to meters
|
||||||
|
total_meters = (feet * 0.3048) + (inches * 0.0254)
|
||||||
|
return total_meters
|
||||||
|
|
||||||
|
def imperial(self, args):
|
||||||
|
# Just return the value from the sub-rule (feet_only, inches_only, or feet_inches)
|
||||||
|
return args[0]
|
||||||
|
|
||||||
|
def metric(self, args):
|
||||||
|
# args[0] is the NUMBER, args[1] if present is the unit
|
||||||
|
value = args[0]
|
||||||
|
if len(args) > 1:
|
||||||
|
unit = str(args[1])
|
||||||
|
# Convert to meters based on unit
|
||||||
|
if unit == "mm":
|
||||||
|
value = value / 1000.0
|
||||||
|
elif unit == "cm":
|
||||||
|
value = value / 100.0
|
||||||
|
elif unit == "dm":
|
||||||
|
value = value / 10.0
|
||||||
|
elif unit == "m":
|
||||||
|
value = value # already in meters
|
||||||
|
elif unit == "°":
|
||||||
|
value = value # degrees, pass through
|
||||||
|
# If no unit specified, assume it's already in the project's unit system
|
||||||
|
return value
|
||||||
|
|
||||||
|
def dim(self, args):
|
||||||
|
return args[0]
|
||||||
|
|
||||||
|
def expr(self, args):
|
||||||
|
op = args[0]
|
||||||
|
value = float(args[1])
|
||||||
|
if op == "+":
|
||||||
|
return lambda x: x + value
|
||||||
|
elif op == "-":
|
||||||
|
return lambda x: x - value
|
||||||
|
elif op == "*":
|
||||||
|
return lambda x: x * value
|
||||||
|
elif op == "/":
|
||||||
|
return lambda x: x / value
|
||||||
|
|
||||||
|
def FORMULA(self, args):
|
||||||
|
return args[0]
|
||||||
|
|
||||||
|
def start(self, args):
|
||||||
|
i = 0
|
||||||
|
if args[0] == "=":
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
if len(args) > 1:
|
||||||
|
raise ValueError("Invalid input.")
|
||||||
|
dimension = args[i]
|
||||||
|
if len(args) > i + 1:
|
||||||
|
expression = args[i + 1]
|
||||||
|
return expression(dimension)
|
||||||
|
else:
|
||||||
|
return dimension
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Determine unit scale
|
||||||
|
if use_project_unit and tool.Ifc.get():
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
else:
|
||||||
|
unit_scale = tool.Blender.get_unit_scale()
|
||||||
|
|
||||||
|
# Try to parse with the project's default grammar first
|
||||||
|
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||||
|
primary_parser = Lark(grammar_imperial)
|
||||||
|
fallback_parser = Lark(grammar_metric)
|
||||||
|
else:
|
||||||
|
primary_parser = Lark(grammar_metric)
|
||||||
|
fallback_parser = Lark(grammar_imperial)
|
||||||
|
|
||||||
|
# Try parsing with primary grammar
|
||||||
|
parse_tree = None
|
||||||
|
try:
|
||||||
|
parse_tree = primary_parser.parse(input_string)
|
||||||
|
except Exception as e:
|
||||||
|
# If primary fails, try fallback grammar (allows metric in imperial projects and vice versa)
|
||||||
|
try:
|
||||||
|
parse_tree = fallback_parser.parse(input_string)
|
||||||
|
except Exception as e2:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if parse_tree is None:
|
||||||
|
return False, 0.0
|
||||||
|
|
||||||
|
# Transform the parse tree to get the numeric result
|
||||||
|
transformer = InputTransform()
|
||||||
|
result = transformer.transform(parse_tree)
|
||||||
|
result = round(result, 6)
|
||||||
|
|
||||||
|
return True, result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return False, 0.0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_unit_props(cls) -> BIMUnitProperties:
|
def get_unit_props(cls) -> BIMUnitProperties:
|
||||||
return bpy.context.scene.BIMUnitProperties
|
return bpy.context.scene.BIMUnitProperties
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user