source) return $material; $override = $this->get($material->identifier()); if (!$override) return $material; $sourceData = array_merge($material->sourceData, $override); $useSourceAlpha = $material->source === 'acf_local' && isset($material->sourceData['acf_color_alpha']) && (float) $material->sourceData['acf_color_alpha'] < 1.0 && empty($override['opacity_overridden']); $opacity = $useSourceAlpha ? $material->opacity : (float)($override['opacity'] ?? $material->opacity); $transparent = $useSourceAlpha ? $material->transparent : (array_key_exists('transparent',$override) ? !empty($override['transparent']) : $material->transparent); return new Material( source: $material->source, sourceId: $material->sourceId, slug: $material->slug, name: $material->name, materialType: $material->materialType, appearanceType: (string)($override['appearance_type'] ?? $material->appearanceType ?? 'standard'), colorMode: $material->colorMode, colorModeType: (string)($override['color_mode_type'] ?? $material->colorModeType ?? 'standard'), manufacturer: $material->manufacturer, baseColor: $material->baseColor, secondaryColors: $material->secondaryColors, backgroundColor: $this->nullable($override['background_color'] ?? $material->backgroundColor), textureUrl: $this->nullable($override['texture_url'] ?? $material->textureUrl), normalMapUrl: $this->nullable($override['normal_map_url'] ?? $material->normalMapUrl), roughness: (float)($override['roughness'] ?? $material->roughness), metalness: (float)($override['metalness'] ?? $material->metalness), opacity: $opacity, transparent: $transparent, previewImageUrl: $material->previewImageUrl, previewVideoUrl: $material->previewVideoUrl, sourceData: $sourceData ); } public function get(string $identifier): array { $items = get_option(self::OPTION, []); return is_array($items) && is_array($items[$identifier] ?? null) ? $items[$identifier] : []; } public function editing_values(Material $material): array { $data = $material->jsonSerialize(); return array_merge([ 'appearance_type' => (string)($data['appearance_type'] ?? 'standard'), 'color_mode_type' => (string)($data['color_mode_type'] ?? 'standard'), 'color_direction' => (string)($data['color_direction'] ?? 'left_right'), 'color_blend_percent' => (float)($data['color_blend_percent'] ?? 45), 'color_shares' => (array)($data['color_shares'] ?? [25,25,25,25]), 'layer_band_percent' => (float)($data['layer_band_percent'] ?? 1), 'layer_variation_percent' => (float)($data['layer_variation_percent'] ?? 10), 'layer_blend_percent' => (float)($data['layer_blend_percent'] ?? 6), 'background_color' => (string)($data['background_color'] ?? ''), 'texture_url' => (string)($data['texture_url'] ?? ''), 'normal_map_url' => (string)($data['normal_map_url'] ?? ''), 'roughness' => (float)($data['roughness'] ?? .55), 'metalness' => (float)($data['metalness'] ?? .05), 'opacity' => (float)($data['opacity'] ?? 1), 'transparent' => !empty($data['transparent']), 'advanced_enabled' => !empty($data['advanced_enabled']), 'clearcoat' => (float)($data['clearcoat'] ?? 0), 'sheen' => (float)($data['sheen'] ?? 0), 'iridescence' => (float)($data['iridescence'] ?? 0), 'transmission' => (float)($data['transmission'] ?? 0), 'ior' => (float)($data['ior'] ?? 1.5), 'light_key' => (float)($data['light_key'] ?? 1.55), 'light_fill' => (float)($data['light_fill'] ?? .55), 'light_ambient' => (float)($data['light_ambient'] ?? .85), 'exposure' => (float)($data['exposure'] ?? 1), ], $this->get($material->identifier())); } public function save(Material $material, array $input): void { if ('internal' === $material->source) return; $items = get_option(self::OPTION, []); $items = is_array($items) ? $items : []; $layout = sanitize_key((string)($input['color_mode_type'] ?? 'standard')); if (!in_array($layout,['standard','gradient','rainbow','layered'],true)) $layout='standard'; $direction = sanitize_key((string)($input['color_direction'] ?? ($layout==='rainbow'?'bottom_top':'left_right'))); if (!in_array($direction,['left_right','right_left','front_back','back_front','bottom_top','top_bottom'],true)) $direction='left_right'; $appearance = sanitize_key((string)($input['appearance_type'] ?? 'standard')); if (!in_array($appearance,['standard','matte','glossy','silk','satin','metallic','pearl','transparent'],true)) $appearance='standard'; $shares=[]; for($i=1;$i<=4;$i++) $shares[]=min(100,max(1,(float)($input['color_share_'.$i] ?? 25))); $items[$material->identifier()] = [ 'appearance_type'=>$appearance, 'color_mode_type'=>$layout, 'color_direction'=>$direction, 'color_blend_percent'=>min(100,max(0,(float)($input['color_blend_percent'] ?? 45))), 'color_shares'=>$shares, 'layer_band_percent'=>min(50,max(.25,(float)($input['layer_band_percent'] ?? 1))), 'layer_variation_percent'=>min(25,max(0,(float)($input['layer_variation_percent'] ?? 10))), 'layer_blend_percent'=>min(20,max(0,(float)($input['layer_blend_percent'] ?? 6))), 'background_color'=>sanitize_hex_color((string)($input['background_color'] ?? '')) ?: '', 'texture_url'=>esc_url_raw((string)($input['texture_url'] ?? '')), 'normal_map_url'=>esc_url_raw((string)($input['normal_map_url'] ?? '')), 'roughness'=>min(1,max(0,(float)($input['roughness'] ?? .55))), 'metalness'=>min(1,max(0,(float)($input['metalness'] ?? .05))), 'opacity'=>min(1,max(0,(float)($input['opacity'] ?? 1))), 'transparent'=>isset($input['transparent']) && 'yes'===(string)$input['transparent'], 'opacity_overridden'=>true, 'advanced_enabled'=>isset($input['advanced_enabled']) && 'yes'===(string)$input['advanced_enabled'], 'clearcoat'=>min(1,max(0,(float)($input['clearcoat'] ?? 0))), 'sheen'=>min(1,max(0,(float)($input['sheen'] ?? 0))), 'iridescence'=>min(1,max(0,(float)($input['iridescence'] ?? 0))), 'transmission'=>min(1,max(0,(float)($input['transmission'] ?? 0))), 'ior'=>min(2.5,max(1,(float)($input['ior'] ?? 1.5))), 'light_key'=>min(3,max(0,(float)($input['light_key'] ?? 1.55))), 'light_fill'=>min(2,max(0,(float)($input['light_fill'] ?? .55))), 'light_ambient'=>min(2,max(0,(float)($input['light_ambient'] ?? .85))), 'exposure'=>min(2,max(.25,(float)($input['exposure'] ?? 1))), ]; update_option(self::OPTION,$items,false); } public function delete(string $identifier): void { $items=get_option(self::OPTION,[]); if(!is_array($items)) return; unset($items[$identifier]); update_option(self::OPTION,$items,false); } private function nullable(mixed $value): ?string { $value=trim((string)$value); return ''===$value?null:$value; } }