import configparser import json import copy import math class ConfParse: def __init__(self, config_def_json_files_paths): self.config_def_json_files_paths = config_def_json_files_paths self.configs = {} for file in self.config_def_json_files_paths: with open(file, 'r') as f: jf = json.load(f) if "settings" in jf: setting_json = jf["settings"] for key, value in setting_json.items(): # print(key, value) all_items = self._expend_children(value["children"]) for item_key, item_value in all_items.items(): self.configs[item_key] = item_value elif "overrides" in jf: override_json = jf["overrides"] for key, value in override_json.items(): if key in self.configs: for item_key, item_value in value.items(): self.configs[key][item_key] = item_value else: self.configs[key] = value def _expend_children(self, config): tmp_c = {} for key,val in config.items(): if "children" in val: tmp_c.update(self._expend_children(val["children"])) val.pop("children", None) tmp_c[key] = val return tmp_c def add_inst_cfg(self, inst_cfg_files_paths): copy_settings = copy.deepcopy(self.configs) config = configparser.ConfigParser() for file in inst_cfg_files_paths: config.read(file) if config.has_section('values'): for key, val in config.items('values'): v = str(val) if key not in copy_settings: copy_settings[key] = {} if v.startswith("="): copy_settings[key]["value"] = v[1:] else: copy_settings[key]["value"] = v return copy_settings def parse_configs(self, settings): class ConfigStr(str): def __mul__(self, other): raise TypeError() def __rmul__(self, other): raise TypeError() def __add__(self, other): raise TypeError() def __radd__(self, other): raise TypeError() def __sub__(self, other): raise TypeError() def __rsub__(self, other): raise TypeError() def __truediv__(self, other): raise TypeError() def __rtruediv__(self, other): raise TypeError() def __pow__(self, other): raise TypeError() def __rpow__(self, other): raise TypeError() parsed_settings = copy.deepcopy(settings) last_unparsed = -1 while True: unparsed = 0 # 构建上下文环境变量上下文,用作eval的变量替换 context = {} for k, v in parsed_settings.items(): # 上下文里的变量取值:优先使用 value(如果有),否则使用 default_value if "value" in v: val = v["value"] elif "default_value" in v: val = v["default_value"] else: val = None if isinstance(val, str): if val.lower() == "true": val = True elif val.lower() == "false": val = False else: try: val = int(val) except ValueError: try: val = float(val) except ValueError: val = ConfigStr(val) context[k] = val # 自定义函数实现 def resolveOrValue(key): return context.get(key) def extruderValues(key): # 兼容简易的多挤出机查询逻辑,目前单挤出机环境下返回一个单元素列表 return [context.get(key)] def extruderValue(extruder_position, key): # 对于单挤出机环境或者全局配置,直接忽略 extruder_position,返回指定的 key 对应的值 return context.get(key) def defaultExtruderPosition(): return 0 # 提供基础的运算函数支持 builtin_funcs = { "max": max, "min": min, "abs": abs, "round": round, "int": int, "float": float, "bool": bool, "math": math, "resolveOrValue": resolveOrValue, "extruderValues": extruderValues, "extruderValue": extruderValue, "defaultExtruderPosition": defaultExtruderPosition } for key, val_dict in parsed_settings.items(): for field, field_val in val_dict.items(): # 仅对字符串进行尝试计算 if "type" == field: continue if isinstance(field_val, str): try: # 如果是一个普通的纯字符串,比如分类名(如"Machine Type" ),eval可能会抛出SyntaxError或NameError # 如果它是一个python表达式,则会被顺利计算出结果 evaluated = eval(field_val, {"__builtins__": builtin_funcs}, context) # 避免将原本只是用来作类型声明的 "int"/"float" 纯字符串,被错误求值为内置 Python type class 打断 JSON 序列化 if evaluated != field_val and not isinstance(evaluated, type): if val_dict.get("type") == "str" and not isinstance(evaluated, str): if isinstance(evaluated, (list, dict)): import json val_dict[field] = json.dumps(evaluated).replace(" ", "") else: val_dict[field] = str(evaluated) else: val_dict[field] = evaluated except Exception: # 解析失败(例如是个普通英文字符串或者依赖还没被解开)则暂不做处理 unparsed += 1 # 如果在这一轮中未解析出的表达式数量不再减少,说明已经到达极限,跳出循环 if unparsed == last_unparsed: break last_unparsed = unparsed return parsed_settings