Source code for embodichain.lab.sim.utility.cfg_utils

# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------

from embodichain.lab.sim.cfg import RobotCfg
from embodichain.lab.sim.solvers import SolverCfg
from embodichain.utils import logger


[docs] def merge_solver_cfg( default: dict[str, SolverCfg], provided: dict[str, any] ) -> dict[str, SolverCfg]: """Merge provided solver configuration into the default solver config. Rules: - For each arm key in provided, if the key exists in default, update fields provided. - If a provided value is a dict, update attributes on the SolverCfg-like object (or dict) by setting keys. - Primitive values or arrays/lists replace the target value. - Unknown keys in provided create new entries in the result. """ result = {} # copy defaults shallowly for k, v in default.items(): result[k] = v for k, v in provided.items(): if k in result: target = result[k] # if target has __dict__ or is a dataclass-like, set attrs if hasattr(target, "__dict__") or isinstance(target, dict): # if provided is a dict, set/override attributes if isinstance(v, dict): for sub_k, sub_v in v.items(): # try to set attribute if possible, otherwise assign into dict if hasattr(target, sub_k): try: setattr(target, sub_k, sub_v) except Exception: # fallback to dict assignment if object doesn't accept try: target[sub_k] = sub_v except Exception: pass else: try: target[sub_k] = sub_v except Exception: setattr(target, sub_k, sub_v) else: # non-dict provided value replaces the target entirely result[k] = v else: # target is a primitive, replace result[k] = v else: # new solver entry provided; include as-is result[k] = v return result
[docs] def merge_robot_cfg(base_cfg: RobotCfg, override_cfg_dict: dict[str, any]) -> RobotCfg: """Merge current robot configuration with overriding values from a dictionary. Args: base_cfg (RobotCfg): The base robot configuration. override_cfg_dict (dict[str, any]): Dictionary of overriding configuration values. Returns: RobotCfg: The merged robot configuration. """ robot_cfg = RobotCfg.from_dict(override_cfg_dict) for key, value in override_cfg_dict.items(): if key == "solver_cfg": # merge provided solver_cfg values into default solver config provided_solver_cfg = override_cfg_dict.get("solver_cfg") if provided_solver_cfg: for part, item in provided_solver_cfg.items(): if "class_type" in provided_solver_cfg[part]: base_cfg.solver_cfg[part] = robot_cfg.solver_cfg[part] else: try: merged = merge_solver_cfg( base_cfg.solver_cfg, provided_solver_cfg ) base_cfg.solver_cfg = merged except Exception: logger.log_error( f"Failed to merge solver_cfg, using provided config outright." ) elif key == "drive_pros": # merge joint drive properties user_drive_pros_dict = override_cfg_dict.get("drive_pros") if isinstance(user_drive_pros_dict, dict): for prop, val in user_drive_pros_dict.items(): # Get the current value in cfg (which has defaults) default_val = getattr(base_cfg.drive_pros, prop, None) if isinstance(val, dict) and isinstance(default_val, dict): # Merge dictionaries default_val.update(val) else: # Overwrite if not both dicts setattr(base_cfg.drive_pros, prop, val) else: logger.log_warning( "drive_pros should be a dictionary. Skipping drive_pros merge." ) elif key == "attrs": # merge physics attributes user_attrs_dict = override_cfg_dict.get("attrs") if isinstance(user_attrs_dict, dict): for attr_key, attr_val in user_attrs_dict.items(): setattr(base_cfg.attrs, attr_key, attr_val) else: logger.log_warning( "attrs should be a dictionary. Skipping attrs merge." ) elif key == "control_parts": # merge control parts user_control_parts_dict = override_cfg_dict.get("control_parts") if isinstance(user_control_parts_dict, dict): # Initialize control_parts if it is None to avoid TypeError on item assignment if base_cfg.control_parts is None: base_cfg.control_parts = {} for part_key, part_val in user_control_parts_dict.items(): base_cfg.control_parts[part_key] = part_val else: logger.log_warning( "control_parts should be a dictionary. Skipping control_parts merge." ) elif key == "urdf_cfg": if base_cfg.urdf_cfg is None: logger.log_warning( f"There is no defined urdf_cfg in base robot cfg. Skipping urdf_cfg merge." ) continue # merge urdf components user_urdf_cfg = override_cfg_dict.get("urdf_cfg") if isinstance(user_urdf_cfg, dict): for component in user_urdf_cfg.get("components", []): base_cfg.urdf_cfg.add_component( component_type=component.get("component_type"), urdf_path=component.get("urdf_path"), transform=component.get("transform"), ) else: logger.log_warning( "urdf_cfg should be a dictionary. Skipping urdf_cfg merge." ) else: setattr(base_cfg, key, getattr(robot_cfg, key)) return base_cfg