Source code for wolfhece.pyvertexvectors.polygon_pbr_material

from __future__ import annotations

import json
from dataclasses import dataclass, field
from pathlib import Path


[docs] def _float_pair(values, default: tuple[float, float]) -> tuple[float, float]: if not isinstance(values, (list, tuple)) or len(values) != 2: return default return float(values[0]), float(values[1])
[docs] def _float_vec3(values, default: tuple[float, float, float]) -> tuple[float, float, float]: if not isinstance(values, (list, tuple)) or len(values) != 3: return default return float(values[0]), float(values[1]), float(values[2])
[docs] def _float_vec4(values, default: tuple[float, float, float, float]) -> tuple[float, float, float, float]: if not isinstance(values, (list, tuple)) or len(values) != 4: return default return float(values[0]), float(values[1]), float(values[2]), float(values[3])
@dataclass
[docs] class PolygonPBRMaterial: """Optional PBR-like material for filled polygon rendering. The material blends texture details (albedo, normal, ORM, emissive) with global control factors (metallic_factor, roughness_factor, etc.) to achieve intuitive per-pixel variation and global override capability. Key shading formula: - metallic: blends ORM texture metallic channel with metallic_factor - factor=0 → fully controlled by texture - factor=1 → forces fully metallic everywhere - 0<factor<1 → smooth interpolation enables artistic control - roughness: scales ORM roughness channel by roughness_factor - normal: applies normalScale to detail intensity from normal texture """
[docs] enabled: bool = False
[docs] albedo_texture: str = ''
[docs] normal_texture: str = ''
[docs] orm_texture: str = ''
[docs] emissive_texture: str = ''
[docs] base_color_factor: tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0)
[docs] metallic_factor: float = 0.0
"""Metallic blending control [0..1]. 0 = texture determines metallic appearance. 1 = surface fully metallic. Use >0 to add metallic character to any surface."""
[docs] roughness_factor: float = 1.0
"""Surface roughness multiplier [0.04..1.0]. Scales the ORM texture roughness channel. Values <1 = smoother, >1 = rougher. Interacts with ORM texture; pure texture control requires factor=1."""
[docs] normal_scale: float = 1.0
"""Normal map intensity [0..∞]. Controls detail relief from normal texture. 0 = flat, 1 = full strength."""
[docs] occlusion_strength: float = 1.0
"""Ambient occlusion intensity [0..1]. Scales ORM texture AO channel into shadow; 0 = no shadows, 1 = full."""
[docs] emissive_factor: tuple[float, float, float] = (0.0, 0.0, 0.0)
"""Emissive RGB [0..∞]. Additive glow color. Blends with emissive texture if present."""
[docs] uv_scale: tuple[float, float] = (100.0, 100.0)
"""Texture tiling scale. Larger = more repetitions."""
[docs] uv_offset: tuple[float, float] = (0.0, 0.0)
"""Texture coordinate translation for animation or alignment."""
[docs] preset_name: str = ''
[docs] cushion_strength: float = 0.0
"""Cushion/pillow effect strength [0..2]. Tilts surface normals near polygon edges to simulate a raised border. 0 = flat, 1 = moderate pillow, 2 = strong 3D-like bevel."""
[docs] _version: int = field(default=2, init=False, repr=False)
@property
[docs] def has_any_texture(self) -> bool: return any((self.albedo_texture, self.normal_texture, self.orm_texture, self.emissive_texture))
[docs] def to_dict(self) -> dict: return { 'version': self._version, 'enabled': bool(self.enabled), 'albedo_texture': str(self.albedo_texture or ''), 'normal_texture': str(self.normal_texture or ''), 'orm_texture': str(self.orm_texture or ''), 'emissive_texture': str(self.emissive_texture or ''), 'base_color_factor': list(self.base_color_factor), 'metallic_factor': float(self.metallic_factor), 'roughness_factor': float(self.roughness_factor), 'normal_scale': float(self.normal_scale), 'occlusion_strength': float(self.occlusion_strength), 'emissive_factor': list(self.emissive_factor), 'uv_scale': list(self.uv_scale), 'uv_offset': list(self.uv_offset), 'preset_name': str(self.preset_name or ''), 'cushion_strength': float(self.cushion_strength), }
@classmethod
[docs] def from_dict(cls, data: dict | None) -> 'PolygonPBRMaterial': if not isinstance(data, dict): return cls() return cls( enabled=bool(data.get('enabled', False)), albedo_texture=str(data.get('albedo_texture', '') or ''), normal_texture=str(data.get('normal_texture', '') or ''), orm_texture=str(data.get('orm_texture', '') or ''), emissive_texture=str(data.get('emissive_texture', '') or ''), base_color_factor=_float_vec4(data.get('base_color_factor'), (1.0, 1.0, 1.0, 1.0)), metallic_factor=float(data.get('metallic_factor', 0.0)), roughness_factor=float(data.get('roughness_factor', 1.0)), normal_scale=float(data.get('normal_scale', 1.0)), occlusion_strength=float(data.get('occlusion_strength', 1.0)), emissive_factor=_float_vec3(data.get('emissive_factor'), (0.0, 0.0, 0.0)), uv_scale=_float_pair(data.get('uv_scale'), (100.0, 100.0)), uv_offset=_float_pair(data.get('uv_offset'), (0.0, 0.0)), preset_name=str(data.get('preset_name', '') or ''), cushion_strength=float(data.get('cushion_strength', 0.0)), )
[docs] def to_json(self) -> str: return json.dumps(self.to_dict(), separators=(',', ':'))
@classmethod
[docs] def from_json(cls, raw: str | None) -> 'PolygonPBRMaterial': if not raw: return cls() try: return cls.from_dict(json.loads(raw)) except Exception: return cls()
[docs] def get_pbr_presets_root(base_dir: Path | None = None) -> Path: """Return the expected root directory for built-in polygon PBR presets.""" if base_dir is None: # .../wolfhece/pyvertexvectors -> wolfhece package root base_dir = Path(__file__).resolve().parent.parent return base_dir / 'data' / 'pbr_presets'
[docs] def get_official_polygon_pbr_preset_names() -> list[str]: """Return the two official presets highlighted in the UI.""" return [ 'Official Pie - Glass Dashboard', 'Official Bar - Frosted Bars', ]
[docs] def _make_material( *, root: Path, preset_name: str, folder: str, metallic: float, roughness: float, normal_scale: float, occlusion_strength: float, emissive_factor: tuple[float, float, float], uv_scale: tuple[float, float], base_color_factor: tuple[float, float, float, float], with_emissive: bool = False, ) -> PolygonPBRMaterial: return PolygonPBRMaterial( enabled=True, albedo_texture=str((root / folder / 'albedo.png').resolve()), normal_texture=str((root / folder / 'normal.png').resolve()), orm_texture=str((root / folder / 'orm.png').resolve()), emissive_texture=str((root / folder / 'emissive.png').resolve()) if with_emissive else '', metallic_factor=metallic, roughness_factor=roughness, normal_scale=normal_scale, occlusion_strength=occlusion_strength, emissive_factor=emissive_factor, uv_scale=uv_scale, uv_offset=(0.0, 0.0), base_color_factor=base_color_factor, preset_name=preset_name, )
[docs] def get_builtin_polygon_pbr_presets(base_dir: Path | None = None) -> dict[str, PolygonPBRMaterial]: """Return the built-in polygon PBR preset catalogue.""" root = get_pbr_presets_root(base_dir) presets: dict[str, PolygonPBRMaterial] = {} # Official presets first. presets['Official Pie - Glass Dashboard'] = _make_material( root=root, preset_name='Official Pie - Glass Dashboard', folder='concrete', metallic=0.0, roughness=0.12, normal_scale=0.15, occlusion_strength=0.8, emissive_factor=(0.0, 0.0, 0.0), uv_scale=(18.0, 18.0), base_color_factor=(0.78, 0.9, 1.0, 1.0), ) presets['Official Bar - Frosted Bars'] = _make_material( root=root, preset_name='Official Bar - Frosted Bars', folder='concrete', metallic=0.0, roughness=0.18, normal_scale=0.12, occlusion_strength=0.85, emissive_factor=(0.0, 0.0, 0.0), uv_scale=(24.0, 24.0), base_color_factor=(0.7, 0.88, 1.0, 1.0), ) # Pie modern styles. presets['Pie - Neon Ring'] = _make_material( root=root, preset_name='Pie - Neon Ring', folder='asphalt', metallic=0.15, roughness=0.25, normal_scale=0.2, occlusion_strength=1.0, emissive_factor=(0.0, 0.85, 0.95), uv_scale=(14.0, 14.0), base_color_factor=(0.16, 0.2, 0.28, 1.0), with_emissive=True, ) presets['Pie - Soft Clay'] = _make_material( root=root, preset_name='Pie - Soft Clay', folder='concrete', metallic=0.0, roughness=0.85, normal_scale=0.05, occlusion_strength=0.9, emissive_factor=(0.0, 0.0, 0.0), uv_scale=(28.0, 28.0), base_color_factor=(0.95, 0.84, 0.78, 1.0), ) presets['Pie - Satin Metal'] = _make_material( root=root, preset_name='Pie - Satin Metal', folder='rusted_metal', metallic=0.55, roughness=0.35, normal_scale=0.2, occlusion_strength=0.95, emissive_factor=(0.0, 0.0, 0.0), uv_scale=(16.0, 16.0), base_color_factor=(0.63, 0.71, 0.79, 1.0), ) presets['Pie - Carbon UI'] = _make_material( root=root, preset_name='Pie - Carbon UI', folder='asphalt', metallic=0.2, roughness=0.6, normal_scale=0.35, occlusion_strength=1.0, emissive_factor=(0.0, 0.0, 0.0), uv_scale=(10.0, 10.0), base_color_factor=(0.18, 0.18, 0.2, 1.0), ) presets['Pie - Warm Gradient Pro'] = _make_material( root=root, preset_name='Pie - Warm Gradient Pro', folder='concrete', metallic=0.05, roughness=0.45, normal_scale=0.1, occlusion_strength=0.85, emissive_factor=(0.08, 0.03, 0.02), uv_scale=(22.0, 22.0), base_color_factor=(0.98, 0.62, 0.5, 1.0), ) # Bar modern styles. presets['Bar - Pulse Tech'] = _make_material( root=root, preset_name='Bar - Pulse Tech', folder='asphalt', metallic=0.25, roughness=0.3, normal_scale=0.2, occlusion_strength=1.0, emissive_factor=(0.55, 0.1, 0.8), uv_scale=(26.0, 26.0), base_color_factor=(0.2, 0.16, 0.34, 1.0), with_emissive=True, ) presets['Bar - Brushed Aluminum'] = _make_material( root=root, preset_name='Bar - Brushed Aluminum', folder='rusted_metal', metallic=0.8, roughness=0.42, normal_scale=0.25, occlusion_strength=0.9, emissive_factor=(0.0, 0.0, 0.0), uv_scale=(20.0, 20.0), base_color_factor=(0.74, 0.76, 0.8, 1.0), ) presets['Bar - Minimal Matte'] = _make_material( root=root, preset_name='Bar - Minimal Matte', folder='concrete', metallic=0.0, roughness=0.92, normal_scale=0.03, occlusion_strength=0.8, emissive_factor=(0.0, 0.0, 0.0), uv_scale=(35.0, 35.0), base_color_factor=(0.58, 0.58, 0.58, 1.0), ) presets['Bar - Night City'] = _make_material( root=root, preset_name='Bar - Night City', folder='asphalt', metallic=0.35, roughness=0.28, normal_scale=0.22, occlusion_strength=1.0, emissive_factor=(0.45, 0.1, 0.35), uv_scale=(24.0, 24.0), base_color_factor=(0.12, 0.18, 0.28, 1.0), with_emissive=True, ) presets['Bar - Earth Analytics'] = _make_material( root=root, preset_name='Bar - Earth Analytics', folder='concrete', metallic=0.05, roughness=0.72, normal_scale=0.12, occlusion_strength=1.0, emissive_factor=(0.0, 0.0, 0.0), uv_scale=(30.0, 30.0), base_color_factor=(0.54, 0.48, 0.36, 1.0), ) return presets