Formatted attributes array */ public static function getProductAttributes($modelData = []): array { if (!is_array($modelData)) { $modelData = []; } global $product; $product_id = ($product && method_exists($product, 'get_id')) ? $product->get_id() : 0; $meta = Utils::getPostMeta($product_id, '_bp3d_product_'); if (!$meta('all')) { return []; } $get_option = Utils::getSettings('_bp3d_settings_'); $model_src = self::effectiveModelUrl($modelData); $poster = $modelData['bp3d_poster_src'] ?? $modelData['bp3d_models']['0']['poster_src'] ?? ''; $model = [ 'modelUrl' => $model_src, 'poster' => $poster, ]; // AR lives on row 0 (Pro, Dokan vendors), so pair it only with row 0's own model; added only when on, so other products keep identical markup. $row0 = $modelData['bp3d_models'][0] ?? null; $ar_enabled = is_array($row0) && in_array($row0['enable_ar'] ?? '', ['1', 1, true, 'true', 'yes'], true) && trim($model_src) !== '' && trim($model_src) === trim(self::urlOf($row0['model_src'] ?? '')); if ($ar_enabled) { $model['arEnabled'] = true; $model['modelISOSrc'] = Utils::safeMediaUrl(self::urlOf($row0['model_iso_src'] ?? '')); $model['arPlacement'] = in_array($row0['ar_placement'] ?? '', ['floor', 'wall'], true) ? $row0['ar_placement'] : 'floor'; $model['arMode'] = in_array($row0['ar_mode'] ?? '', ['webxr', 'scene-viewer', 'quick-look'], true) ? $row0['ar_mode'] : 'webxr'; } $attributes = [ 'align' => 'center', 'uniqueId' => 'model' . ($product_id ?: get_the_ID()), 'O3DVSettings' => [ 'isFullscreen' => true, 'mouseControl' => true, 'zoom' => $meta('bp_3d_zooming', $get_option('bp_3d_zooming', '1'), true), ], 'model' => $model, 'zoom' => $meta('bp_3d_zooming', $get_option('bp_3d_zooming', '1'), true), 'lazyLoad' => $get_option('bp_3d_loading', 'lazy') === 'lazy', 'loading' => $get_option('bp_3d_loading', 'lazy') === 'lazy' ? 'lazy' : 'eager', 'preload' => 'auto', 'mouseControl' => $get_option('bp_camera_control', '1', true), 'fullscreen' => $get_option('bp_3d_fullscreen', '1', true), 'loadingPercentage' => false, 'progressBar' => $get_option('bp_3d_progressbar', '1', true), 'styles' => [ 'width' => '100%', 'height' => $meta('bp_3d_height', '350', false, 'height') . $meta('bp_3d_height', 'px', false, 'unit'), 'bgColor' => $meta('bp_model_bg', 'transparent'), ], 'woo' => true, 'placement' => 'shortcode', ]; if ($ar_enabled) { $attributes['arLink'] = get_permalink($product_id); } return $attributes; } /** * Model URL as the front end resolves it: flat key first, then row 0. * * @param array $modelData */ private static function effectiveModelUrl(array $modelData): string { return self::urlOf($modelData['bp3d_model_src'] ?? $modelData['bp3d_models']['0']['model_src'] ?? ''); } /** * @param mixed $value URL string or a media array with a 'url' key */ private static function urlOf($value): string { if (is_array($value)) { $value = $value['url'] ?? ''; } return is_string($value) ? $value : ''; } /** * Render the 3D model viewer HTML for a WooCommerce product. * * @param bool $return True to return HTML, false to echo * @param string $placement Placement context (shortcode, product-gallery, etc.) * @param bool $popup Whether this is a popup rendering * @return string Rendered HTML (when $return is true) */ public function get_3d_model_html($return = true, $placement = 'block'): string { global $product; if (!$product || !$product->get_id()) { return ''; } $meta = Utils::getPostMeta($product->get_id(), '_bp3d_product_'); $modelData = $meta('all'); // No model, no viewer: a product saved on a position without a model must not load an empty viewer and its scripts. if (!is_array($modelData) || trim(self::effectiveModelUrl($modelData)) === '') { return ''; } $finalData = self::getProductAttributes($modelData); $class = Utils::getThemeClass(); if ($placement === 'product-gallery-inline') { $class .= ' position_' . $meta('viewer_position'); } $finalData['position'] = $meta('viewer_position'); $finalData['is_not_compatible'] = !Utils::isCompatibleTheme(); $finalData['placement'] = $placement; $finalData['show_model_instead_thumbnail'] = $meta('show_model_instead_thumbnail', false, true); /** @var array $finalData */ $finalData = apply_filters('bp3d_woocommerce_model_attribute', $finalData); $active_class = $meta('show_model_instead_thumbnail', false, true) ? 'active' : ''; ob_start(); ?>