feat: 完善 SCDM-first 参数化编辑交付版
This commit is contained in:
@@ -10,6 +10,18 @@ from typing import Callable, Iterable
|
||||
RELATION_REF_PATTERN = re.compile(
|
||||
r"\b(?P<kind>Face|Edge)(?P<object_id>\d+)\.(?P<parameter>[A-Za-z0-9_\u4e00-\u9fff]+)\b"
|
||||
)
|
||||
RELATION_UNIT_LITERAL_PATTERN = re.compile(
|
||||
r"(?<![A-Za-z0-9_.])(?P<number>(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)\s*(?P<unit>mm|毫米|cm|厘米|m|米)\b",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
RELATION_UNIT_MULTIPLIERS = {
|
||||
"mm": 1.0,
|
||||
"毫米": 1.0,
|
||||
"cm": 10.0,
|
||||
"厘米": 10.0,
|
||||
"m": 1000.0,
|
||||
"米": 1000.0,
|
||||
}
|
||||
|
||||
|
||||
class RelationFormulaError(ValueError):
|
||||
@@ -112,7 +124,10 @@ def parse_relation_formula(text: str) -> RelationFormula:
|
||||
references.append(_ref_from_match(match))
|
||||
return f"__ref{len(references) - 1}"
|
||||
|
||||
# 用户输入的 Face85.直径 不能直接丢给 eval;先替换成内部占位符,
|
||||
# 后面只允许这些占位符和白名单 AST 节点参与计算。
|
||||
safe_expression = RELATION_REF_PATTERN.sub(replace_ref, expression)
|
||||
safe_expression = _replace_unit_literals(safe_expression)
|
||||
try:
|
||||
tree = ast.parse(safe_expression, mode="eval")
|
||||
except SyntaxError as exc:
|
||||
@@ -136,6 +151,8 @@ def evaluate_relation_formula(
|
||||
namespace[f"__ref{index}"] = _coerce_formula_value(value_resolver(ref))
|
||||
code = compile(formula.safe_expression, "<relation-formula>", "eval")
|
||||
try:
|
||||
# 这里仍然使用 Python 表达式能力,但 builtins 为空,AST 也已校验过。
|
||||
# 关系式只承担参数求值,不允许调用函数、访问属性或执行任意代码。
|
||||
value = eval(code, {"__builtins__": {}}, namespace)
|
||||
except ZeroDivisionError as exc:
|
||||
raise RelationFormulaError("关系式中出现除以 0。") from exc
|
||||
@@ -158,6 +175,8 @@ def validate_relation_formula_graph(formulas: Iterable[RelationFormula]) -> None
|
||||
reference_tokens = [ref.token for ref in formula.references]
|
||||
if target_token in reference_tokens:
|
||||
raise RelationFormulaError(f"关系式不能引用自身:{target_token}。")
|
||||
# 只把“由其它公式控制的参数”纳入依赖图;普通测量值由模型/cache 提供,
|
||||
# 不参与循环依赖判断。
|
||||
graph[target_token] = [ref_token for ref_token in reference_tokens if ref_token in target_tokens]
|
||||
|
||||
visit_state: dict[str, str] = {}
|
||||
@@ -213,6 +232,18 @@ def _ref_from_match(match: re.Match[str]) -> ObjectParameterRef:
|
||||
)
|
||||
|
||||
|
||||
def _replace_unit_literals(expression: str) -> str:
|
||||
def replace(match: re.Match[str]) -> str:
|
||||
number_text = str(match.group("number"))
|
||||
unit = str(match.group("unit"))
|
||||
multiplier = RELATION_UNIT_MULTIPLIERS.get(unit) or RELATION_UNIT_MULTIPLIERS.get(unit.lower())
|
||||
if multiplier is None:
|
||||
raise RelationFormulaError(f"不支持的单位:{unit}")
|
||||
return f"({number_text}*{multiplier:.12g})"
|
||||
|
||||
return RELATION_UNIT_LITERAL_PATTERN.sub(replace, expression)
|
||||
|
||||
|
||||
def _validate_expression_tree(tree: ast.AST, ref_count: int) -> None:
|
||||
allowed = (
|
||||
ast.Expression,
|
||||
|
||||
Reference in New Issue
Block a user