'', 'model_id' => 0, 'height' => 0, 'material' => '', 'fallback_url' => '', 'auto_rotate' => '', 'force_selector' => 'no', ], $attributes, 'ds82_3d_print_showcase'); $modelUrl = esc_url_raw((string) $attributes['model_url']); $modelId = absint($attributes['model_id']); $fallbackUrl = esc_url_raw((string) $attributes['fallback_url']); $attachmentPath = ''; if (!$modelUrl && $modelId) { $modelUrl = (string) wp_get_attachment_url($modelId); $attachmentPath = (string) get_attached_file($modelId); } $urlPath = (string) wp_parse_url($modelUrl, PHP_URL_PATH); $formatSource = $attachmentPath !== '' ? $attachmentPath : $urlPath; $modelFormat = $modelUrl !== '' ? strtolower((string) pathinfo($formatSource, PATHINFO_EXTENSION)) : 'default'; $supportedFormats = ['stl', '3mf', 'glb', 'gltf']; if ($modelUrl !== '' && !in_array($modelFormat, $supportedFormats, true)) { $message = sprintf( /* translators: 1: attachment ID, 2: detected file extension */ __('Attachment ID %1$d does not point to a supported 3D model. Detected format: %2$s. Supported formats are STL, 3MF, GLB and GLTF.', '3d-print-showcase'), $modelId, $modelFormat !== '' ? strtoupper($modelFormat) : __('unknown', '3d-print-showcase') ); return $this->renderFallbackOrError($fallbackUrl, $message, $attributes); } $settings = Settings::all(); $height = absint($attributes['height']) ?: (int) $settings['height']; // The material attribute acts as an optional filter. When omitted, all // published materials from the active provider are available. A single // identifier keeps backwards compatibility and becomes the default. $requestedMaterialIds = $this->parseMaterialIdentifiers((string) $attributes['material']); $availableMaterials = $this->resolveMaterials($requestedMaterialIds); $material = $availableMaterials ? reset($availableMaterials) : null; $colors = $material ? $material->colors() : [(string) $settings['default_model_color']]; $autoRotate = $attributes['auto_rotate'] === '' ? $settings['auto_rotate'] === 'yes' : in_array(strtolower((string) $attributes['auto_rotate']), ['1', 'yes', 'true'], true); $instanceId = 'ds82-3dps-shortcode-' . wp_rand(1000, 999999); $config = [ 'viewerId' => $instanceId, 'productId' => 0, 'modelUrl' => $modelUrl, 'modelFormat' => $modelFormat, 'height' => min(1200, max(240, $height)), 'backgroundColor' => $material?->backgroundColor ?: (string) $settings['background_color'], 'globalBackgroundColor' => (string) $settings['background_color'], 'defaultMaterial' => $material?->identifier() ?? '', 'materialEndpoint' => rest_url('ds82-3dps/v1/materials/'), 'materials' => array_map(static fn (Material $item): array => $item->jsonSerialize(), $availableMaterials), 'defaultModelColor' => $colors[0] ?? (string) $settings['default_model_color'], 'colors' => $colors, 'colorMode' => $material?->colorMode ?? 'single', 'colorModeType' => $material?->colorModeType ?? 'standard', 'appearanceType' => $material?->appearanceType ?? 'standard', 'roughness' => $material?->roughness ?? 0.55, 'metalness' => $material?->metalness ?? 0.0, 'opacity' => $material?->opacity ?? 1.0, 'textureUrl' => $material?->textureUrl ?? '', 'normalMapUrl' => $material?->normalMapUrl ?? '', 'fallbackImageUrl' => $fallbackUrl, 'autoRotate' => $autoRotate, 'fullscreen' => $settings['fullscreen'] === 'yes', 'lazyLoading' => $settings['lazy_loading'] === 'yes', 'maxPixelRatio' => (float) $settings['max_pixel_ratio'], 'fpsLimit' => (int) $settings['fps_limit'], 'strings' => [ 'loading' => __('Loading 3D model…', '3d-print-showcase'), 'materialLoading' => __('Loading selected color…', '3d-print-showcase'), 'error' => __('The 3D model could not be displayed.', '3d-print-showcase'), 'webglError' => __('WebGL is not supported by this browser.', '3d-print-showcase'), 'slicerProject3mf' => __('This 3MF file is a slicer project (Bambu Studio, OrcaSlicer, PrusaSlicer, or similar), not a standard 3D model. Export the model as a standard 3MF, GLB, or STL file.', '3d-print-showcase'), ], ]; $this->enqueue_assets(!empty($availableMaterials) && count($availableMaterials) > 1); $json = wp_json_encode($config, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES); $disclaimer = $settings['disclaimer_enabled'] === 'yes' ? (string) $settings['disclaimer_text'] : ''; ob_start(); ?>

1) : ?> renderMaterialSelector($availableMaterials, (string) $material?->identifier(), $instanceId, $settings); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> ' . esc_html__('The User 3D Viewer is disabled in plugin settings.', '3d-print-showcase') . '

' : ''; } $all = $this->resolveMaterials([]); $selected = array_map('strval', (array) ($settings['user_viewer_materials'] ?? [])); $mode = (string) ($settings['user_viewer_material_mode'] ?? 'all'); if ('include' === $mode) { $allowed = array_intersect_key($all, array_flip($selected)); } elseif ('exclude' === $mode) { $allowed = array_diff_key($all, array_flip($selected)); } else { $allowed = $all; } $materialIds = implode(',', array_keys($allowed)); $viewer = $this->shortcode([ 'height' => absint($attributes['height'] ?? 0), 'material' => $materialIds, 'auto_rotate' => (string) ($attributes['auto_rotate'] ?? ''), 'force_selector' => 'yes', ]); $formats = array_values(array_intersect(['stl', 'glb', '3mf'], (array) ($settings['user_viewer_formats'] ?? ['stl', 'glb']))); $accept = implode(',', array_map(static fn(string $format): string => '.' . $format, $formats)); $maxSize = min(200, max(1, absint($settings['user_viewer_max_size'] ?? 25))); wp_enqueue_script('ds82-3dps-user-viewer', DS82_3DPS_URL . 'assets/js/frontend/user-viewer.js', [], DS82_3DPS_VERSION, true); ob_start(); ?>

sanitize_text_field(trim($identifier)), preg_split('/[,|]+/', $raw) ?: [] )))); } /** @param string[] $requested @return array */ private function resolveMaterials(array $requested): array { $items = []; if (!$requested) { foreach ($this->materials->all() as $identifier => $material) { if ($material instanceof Material) { $items[(string) $identifier] = $material; } } return $items; } foreach ($requested as $identifier) { $material = $this->materials->find($identifier); if ($material) { $items[$material->identifier()] = $material; } } return $items; } private function swatchStyle(array $colors, string $type): string { $colors = array_values(array_filter(array_map('sanitize_hex_color', $colors))) ?: ['#b7b7b7']; if (1 === count($colors)) { return 'background:' . $colors[0] . ';'; } $type = strtolower($type); if (str_contains($type, 'layered') || str_contains($type, 'vrstven')) { $stops = []; $bandCount = 12; foreach (range(0, $bandCount - 1) as $band) { $color = $colors[$band % count($colors)]; $start = round(($band / $bandCount) * 100, 2); $end = round((($band + 1) / $bandCount) * 100, 2); $stops[] = $color . ' ' . $start . '%'; $stops[] = $color . ' ' . $end . '%'; } return 'background:linear-gradient(to top,' . implode(',', $stops) . ');'; } if (str_contains($type, 'duhov') || str_contains($type, 'rainbow')) { return 'background:linear-gradient(to top,' . implode(',', $colors) . ');'; } if (str_contains($type, 'přech') || str_contains($type, 'gradient')) { return 'background:linear-gradient(to right,' . implode(',', $colors) . ');'; } return 'background:conic-gradient(' . implode(',', $colors) . ');'; } /** @param array $materials */ private function renderMaterialSelector(array $materials, string $default, string $viewerId, array $settings): string { $displayMode = (string) ($settings['appearance_display'] ?? 'labels'); $tooltips = 'yes' === (string) ($settings['appearance_tooltips'] ?? 'yes'); ob_start(); ?> colors()))) ?: ['#b7b7b7']; $defaultStyle = $this->swatchStyle($defaultColors, (string) $defaultMaterial->colorModeType); ?>
', $height, esc_attr($background), esc_url($fallbackUrl) ); } return current_user_can('manage_options') ? '

' . esc_html($message) . '

' : ''; } public function module_script_tag(string $tag, string $handle, string $src): string { return $handle === 'ds82-3dps-viewer' && !str_contains($tag, 'type=') ? str_replace('