Source code for ogstools.materiallib.core.material
# Copyright (c) 2012-2025, OpenGeoSys Community (http://www.opengeosys.org)
#            Distributed under a Modified BSD License.
#            See accompanying file LICENSE.txt or
#            http://www.opengeosys.org/project/license
#
from __future__ import annotations
import logging
from typing import Any
from ogstools.materiallib.schema.required_properties import (
    required_property_names,
)
from .property import MaterialProperty
logger = logging.getLogger(__name__)
[docs]
class Material:
    """
    Represents a single material.
    - Can be constructed directly from YAML raw data.
    - Provides access to all properties.
    - Supports filtering by process schemas or property names.
    """
[docs]
    def __init__(self, name: str, raw_data: dict[str, Any]):
        self.name = name
        self.raw = raw_data  # full YAML (e.g. for debugging or export)
        self.properties: list[MaterialProperty] = []
        self._parse_properties() 
    def _parse_properties(self) -> None:
        block = self.raw.get("properties", {})
        if not block:
            logger.debug("Material %s has no properties", self.name)
        for prop_name, entries in block.items():
            for entry in entries if isinstance(entries, list) else [entries]:
                type_ = entry.get(
                    "type", "Constant"
                )  # Todo - Error if 'type' not found
                value = entry.get("value", None)
                extra = {
                    k: v for k, v in entry.items() if k not in ("type", "value")
                }
                prop = MaterialProperty(
                    name=prop_name, type_=type_, value=value, **extra
                )
                self.properties.append(prop)
    # -----------------------
    # Accessors
    # -----------------------
[docs]
    def property_names(self) -> list[str]:
        """
        Returns a list of all property names of this material.
        """
        return [p.name for p in self.properties] 
    # -----------------------
    # Filters (dummy for now)
    # -----------------------
[docs]
    def filter_process(self, process_schema: dict[str, Any]) -> Material:
        """
        Return a new Material containing only properties required by a given process schema.
        """
        allowed = required_property_names(process_schema)
        return self.filter_properties(allowed) 
[docs]
    def filter_properties(self, allowed: set[str]) -> Material:
        """
        Return a new Material containing only the properties in 'allowed',
        preserving all extra fields (e.g. scope, unit).
        """
        filtered_props = [p for p in self.properties if p.name in allowed]
        logger.debug(
            "Material %s: filtered %d/%d properties (%s)",
            self.name,
            len(filtered_props),
            len(self.properties),
            ", ".join(p.name for p in filtered_props),
        )
        # Build a raw_data dict with lists if multiple entries share the same name
        raw_block: dict[str, list[dict[str, Any]]] = {}
        for p in filtered_props:
            entry = {"type": p.type, "value": p.value, **p.extra}
            raw_block.setdefault(p.name, []).append(entry)
        filtered_raw = {"name": self.name, "properties": raw_block}
        # Create a new Material that parses only the filtered_raw
        return Material(name=self.name, raw_data=filtered_raw) 
    # -----------------------
    # Representation
    # -----------------------
    def __repr__(self) -> str:
        return (
            f"<Material '{self.name}' with {len(self.properties)} properties>"
        )