fix(oom): use mmap=True for checkpoint loading + malloc_trim + expandable_segments
Root cause: torch.load() reads 6.9GB .ckpt into Python heap + model params in CPU RAM = ~14GB peak, exceeding 16GB system RAM → OOM Killer. Fix 1 - mmap=True on all torch.load() calls (torch 2.7 supports this): With mmap, checkpoint storage is file-backed (not heap). Only the model parameters (also ~7GB) exist in physical RAM during loading. Peak RAM drops from ~14GB to ~7GB — within safe limits on 16GB machines. Files changed: pipelines.py, hunyuan3ddit.py, model.py (×2), flow_matching_sit.py Fix 2 - malloc_trim(0) after every gc.collect(): Forces glibc to return freed heap pages to OS immediately, so Python's memory pool doesn't hoard freed model memory before the next load. Fix 3 - PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True: Prevents CUDA allocator fragmentation between model switches. Fix 4 - Adaptive threshold recalculated: With mmap loading, loading a model requires ~7.5GB (model params) not 14GB. CPU offload threshold lowered from 16GB → 10.5GB, enabling fast path on machines with more headroom. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@@ -34,6 +34,8 @@ import random
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
|
||||
@@ -49,6 +51,18 @@ import numpy as np
|
||||
from hy3dshape.utils import logger
|
||||
from hy3dpaint.convert_utils import create_glb_with_pbr_materials
|
||||
|
||||
# Force OS to reclaim freed heap pages, reducing Python's RSS after model deletion.
|
||||
_libc = ctypes.CDLL(ctypes.util.find_library("c") or "libc.so.6", use_errno=True)
|
||||
|
||||
def _malloc_trim():
|
||||
try:
|
||||
_libc.malloc_trim(0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Allow CUDA allocator to use expandable segments, reducing fragmentation.
|
||||
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
||||
|
||||
# Globals for lazy load/unload
|
||||
i23d_worker = None
|
||||
tex_pipeline = None
|
||||
@@ -237,9 +251,10 @@ height="{height}" width="100%" frameborder="0"></iframe>'
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Approximate RAM required (GB) to hold one model in CPU while loading another.
|
||||
# Model weights: ~7GB each. Loading from disk stages ~7GB temporarily.
|
||||
# Total: 7 (existing in CPU) + 7 (loading new) + 2 (OS headroom) = 16GB.
|
||||
_RAM_THRESHOLD_GB = 16.0
|
||||
# With mmap=True loading, staging a model needs ~0 extra heap RAM.
|
||||
# So threshold = size of model in CPU RAM = ~7.5GB, plus 3GB headroom = 10.5GB.
|
||||
# With 16GB total, we need at least ~10.5GB free to safely offload i23d to CPU.
|
||||
_RAM_THRESHOLD_GB = 10.5
|
||||
|
||||
# Track whether i23d is offloaded to CPU RAM (vs deleted entirely).
|
||||
_i23d_on_cpu = False
|
||||
@@ -258,12 +273,12 @@ def _get_available_ram_gb():
|
||||
|
||||
|
||||
def _can_offload_to_cpu():
|
||||
"""Check if there's enough RAM to keep a model in CPU while loading another."""
|
||||
"""Check if there's enough RAM to keep i23d in CPU while loading tex."""
|
||||
available = _get_available_ram_gb()
|
||||
can = available >= _RAM_THRESHOLD_GB
|
||||
logger.info(
|
||||
f"RAM check: {available:.1f}GB available, "
|
||||
f"need {_RAM_THRESHOLD_GB:.0f}GB for CPU offload → "
|
||||
f"need {_RAM_THRESHOLD_GB:.1f}GB for CPU offload → "
|
||||
f"{'CPU offload (fast)' if can else 'full delete (safe)'}"
|
||||
)
|
||||
return can
|
||||
@@ -280,6 +295,8 @@ def _prepare_for_tex():
|
||||
logger.info("Offloading shape model to CPU RAM (fast path)...")
|
||||
i23d_worker.to('cpu')
|
||||
_i23d_on_cpu = True
|
||||
gc.collect()
|
||||
_malloc_trim()
|
||||
torch.cuda.empty_cache()
|
||||
else:
|
||||
logger.info("Deleting shape model entirely (safe path, limited RAM)...")
|
||||
@@ -288,6 +305,7 @@ def _prepare_for_tex():
|
||||
_i23d_on_cpu = False
|
||||
gc.collect()
|
||||
gc.collect()
|
||||
_malloc_trim()
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
_ensure_tex_pipeline()
|
||||
@@ -303,6 +321,7 @@ def _ensure_i23d_worker():
|
||||
elif i23d_worker is None:
|
||||
logger.info("Reloading shape model from disk to GPU (slow path)...")
|
||||
gc.collect()
|
||||
_malloc_trim()
|
||||
torch.cuda.empty_cache()
|
||||
from hy3dshape import Hunyuan3DDiTFlowMatchingPipeline
|
||||
i23d_worker = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(
|
||||
@@ -324,6 +343,7 @@ def _unload_tex_pipeline():
|
||||
tex_pipeline = None
|
||||
gc.collect()
|
||||
gc.collect()
|
||||
_malloc_trim()
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
|
||||
@@ -332,6 +352,7 @@ def _ensure_tex_pipeline():
|
||||
global tex_pipeline
|
||||
if tex_pipeline is None and tex_conf is not None:
|
||||
gc.collect()
|
||||
_malloc_trim()
|
||||
torch.cuda.empty_cache()
|
||||
from hy3dpaint.textureGenPipeline import Hunyuan3DPaintPipeline
|
||||
logger.info("Loading texture pipeline to GPU...")
|
||||
|
||||
@@ -143,7 +143,7 @@ class VectsetVAE(nn.Module):
|
||||
import safetensors.torch
|
||||
ckpt = safetensors.torch.load_file(ckpt_path, device='cpu')
|
||||
else:
|
||||
ckpt = torch.load(ckpt_path, map_location='cpu', weights_only=True)
|
||||
ckpt = torch.load(ckpt_path, map_location='cpu', weights_only=True, mmap=True)
|
||||
|
||||
model_kwargs = config['params']
|
||||
model_kwargs.update(kwargs)
|
||||
@@ -181,7 +181,7 @@ class VectsetVAE(nn.Module):
|
||||
)
|
||||
|
||||
def init_from_ckpt(self, path, ignore_keys=()):
|
||||
state_dict = torch.load(path, map_location="cpu")
|
||||
state_dict = torch.load(path, map_location="cpu", mmap=True)
|
||||
state_dict = state_dict.get("state_dict", state_dict)
|
||||
keys = list(state_dict.keys())
|
||||
for k in keys:
|
||||
|
||||
@@ -358,7 +358,7 @@ class Hunyuan3DDiT(nn.Module):
|
||||
if ckpt_path is not None:
|
||||
print('restored denoiser ckpt', ckpt_path)
|
||||
|
||||
ckpt = torch.load(ckpt_path, map_location="cpu")
|
||||
ckpt = torch.load(ckpt_path, map_location='cpu', mmap=True)
|
||||
if 'state_dict' not in ckpt:
|
||||
# deepspeed ckpt
|
||||
state_dict = {}
|
||||
|
||||
@@ -135,7 +135,7 @@ class Diffuser(pl.LightningModule):
|
||||
print(f"{context}: Restored training weights")
|
||||
|
||||
def init_from_ckpt(self, path, ignore_keys=()):
|
||||
ckpt = torch.load(path, map_location="cpu")
|
||||
ckpt = torch.load(path, map_location="cpu", mmap=True)
|
||||
if 'state_dict' not in ckpt:
|
||||
# deepspeed ckpt
|
||||
state_dict = {}
|
||||
|
||||
@@ -165,7 +165,7 @@ class Hunyuan3DDiTPipeline:
|
||||
ckpt[model_name] = {}
|
||||
ckpt[model_name][new_key] = value
|
||||
else:
|
||||
ckpt = torch.load(ckpt_path, map_location='cpu', weights_only=True)
|
||||
ckpt = torch.load(ckpt_path, map_location='cpu', weights_only=True, mmap=True)
|
||||
# load model
|
||||
model = instantiate_from_config(config['model'])
|
||||
model.load_state_dict(ckpt['model'])
|
||||
|
||||
BIN
mr_combined.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
save_dir/035d782f-2cb9-48d5-b886-2fcc2b560783/white_mesh.glb
Normal file
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- Import the component -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js" type="module"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const modelViewers = document.querySelectorAll('model-viewer');
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
modelViewers.forEach(modelViewer => {
|
||||
modelViewer.setAttribute(
|
||||
"environment-image",
|
||||
"/static/env_maps/gradient.jpg"
|
||||
);
|
||||
// if (!isSafari) {
|
||||
// modelViewer.setAttribute(
|
||||
// "environment-image",
|
||||
// "/static/env_maps/gradient.jpg"
|
||||
// );
|
||||
// } else {
|
||||
// modelViewer.addEventListener('load', (event) => {
|
||||
// const [material] = modelViewer.model.materials;
|
||||
// let color = [43, 44, 46, 255];
|
||||
// color = color.map(x => x / 255);
|
||||
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
|
||||
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
|
||||
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
|
||||
// });
|
||||
// }
|
||||
// modelViewer.addEventListener('load', (event) => {
|
||||
// const [material] = modelViewer.model.materials;
|
||||
// let color = [43, 44, 46, 255];
|
||||
// color = color.map(x => x / 255);
|
||||
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
|
||||
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
|
||||
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
|
||||
// });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.centered-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
border-color: #e5e7eb;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="centered-container">
|
||||
<div class="column is-mobile is-centered">
|
||||
<model-viewer id="modelviewer" style="height: 640px; width: 500px;"
|
||||
rotation-per-second="10deg"
|
||||
src="./white_mesh.glb/" disable-tap
|
||||
environment-image="neutral"
|
||||
camera-target="0m 0m 0m"
|
||||
camera-orbit="0deg 90deg 8m"
|
||||
orientation="0deg 0deg 0deg"
|
||||
shadow-intensity=".9"
|
||||
ar auto-rotate
|
||||
camera-controls>
|
||||
</model-viewer>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
save_dir/158c8b5c-889f-49bf-91c6-6a9a68e1bf7c/white_mesh.glb
Normal file
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- Import the component -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js" type="module"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const modelViewers = document.querySelectorAll('model-viewer');
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
modelViewers.forEach(modelViewer => {
|
||||
modelViewer.setAttribute(
|
||||
"environment-image",
|
||||
"/static/env_maps/gradient.jpg"
|
||||
);
|
||||
// if (!isSafari) {
|
||||
// modelViewer.setAttribute(
|
||||
// "environment-image",
|
||||
// "/static/env_maps/gradient.jpg"
|
||||
// );
|
||||
// } else {
|
||||
// modelViewer.addEventListener('load', (event) => {
|
||||
// const [material] = modelViewer.model.materials;
|
||||
// let color = [43, 44, 46, 255];
|
||||
// color = color.map(x => x / 255);
|
||||
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
|
||||
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
|
||||
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
|
||||
// });
|
||||
// }
|
||||
// modelViewer.addEventListener('load', (event) => {
|
||||
// const [material] = modelViewer.model.materials;
|
||||
// let color = [43, 44, 46, 255];
|
||||
// color = color.map(x => x / 255);
|
||||
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
|
||||
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
|
||||
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
|
||||
// });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.centered-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
border-color: #e5e7eb;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="centered-container">
|
||||
<div class="column is-mobile is-centered">
|
||||
<model-viewer id="modelviewer" style="height: 640px; width: 500px;"
|
||||
rotation-per-second="10deg"
|
||||
src="./white_mesh.glb/" disable-tap
|
||||
environment-image="neutral"
|
||||
camera-target="0m 0m 0m"
|
||||
camera-orbit="0deg 90deg 8m"
|
||||
orientation="0deg 0deg 0deg"
|
||||
shadow-intensity=".9"
|
||||
ar auto-rotate
|
||||
camera-controls>
|
||||
</model-viewer>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
save_dir/2060c53c-2a02-4d64-b8cc-29aa26430980/textured_mesh.glb
Normal file
BIN
save_dir/523cfdb8-0d48-4440-aa0f-344e9ba1deee/white_mesh.glb
Normal file
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- Import the component -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js" type="module"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const modelViewers = document.querySelectorAll('model-viewer');
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
modelViewers.forEach(modelViewer => {
|
||||
modelViewer.setAttribute(
|
||||
"environment-image",
|
||||
"/static/env_maps/gradient.jpg"
|
||||
);
|
||||
// if (!isSafari) {
|
||||
// modelViewer.setAttribute(
|
||||
// "environment-image",
|
||||
// "/static/env_maps/gradient.jpg"
|
||||
// );
|
||||
// } else {
|
||||
// modelViewer.addEventListener('load', (event) => {
|
||||
// const [material] = modelViewer.model.materials;
|
||||
// let color = [43, 44, 46, 255];
|
||||
// color = color.map(x => x / 255);
|
||||
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
|
||||
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
|
||||
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
|
||||
// });
|
||||
// }
|
||||
// modelViewer.addEventListener('load', (event) => {
|
||||
// const [material] = modelViewer.model.materials;
|
||||
// let color = [43, 44, 46, 255];
|
||||
// color = color.map(x => x / 255);
|
||||
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
|
||||
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
|
||||
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
|
||||
// });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.centered-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
border-color: #e5e7eb;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="centered-container">
|
||||
<div class="column is-mobile is-centered">
|
||||
<model-viewer id="modelviewer" style="height: 640px; width: 500px;"
|
||||
rotation-per-second="10deg"
|
||||
src="./white_mesh.glb/" disable-tap
|
||||
environment-image="neutral"
|
||||
camera-target="0m 0m 0m"
|
||||
camera-orbit="0deg 90deg 8m"
|
||||
orientation="0deg 0deg 0deg"
|
||||
shadow-intensity=".9"
|
||||
ar auto-rotate
|
||||
camera-controls>
|
||||
</model-viewer>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
save_dir/59ef39b3-1ba4-4a6c-8bf1-fd7bdbd64fe4/white_mesh.glb
Normal file
59994
save_dir/59ef39b3-1ba4-4a6c-8bf1-fd7bdbd64fe4/white_mesh.obj
Normal file
BIN
save_dir/6a57a16c-f38b-4439-9bb2-bd87b78b8bf0/white_mesh.glb
Normal file
60002
save_dir/6a57a16c-f38b-4439-9bb2-bd87b78b8bf0/white_mesh.obj
Normal file
BIN
save_dir/7d1297b9-e1e7-409c-9693-748dca1b73df/white_mesh.glb
Normal file
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- Import the component -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js" type="module"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const modelViewers = document.querySelectorAll('model-viewer');
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
modelViewers.forEach(modelViewer => {
|
||||
modelViewer.setAttribute(
|
||||
"environment-image",
|
||||
"/static/env_maps/gradient.jpg"
|
||||
);
|
||||
// if (!isSafari) {
|
||||
// modelViewer.setAttribute(
|
||||
// "environment-image",
|
||||
// "/static/env_maps/gradient.jpg"
|
||||
// );
|
||||
// } else {
|
||||
// modelViewer.addEventListener('load', (event) => {
|
||||
// const [material] = modelViewer.model.materials;
|
||||
// let color = [43, 44, 46, 255];
|
||||
// color = color.map(x => x / 255);
|
||||
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
|
||||
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
|
||||
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
|
||||
// });
|
||||
// }
|
||||
// modelViewer.addEventListener('load', (event) => {
|
||||
// const [material] = modelViewer.model.materials;
|
||||
// let color = [43, 44, 46, 255];
|
||||
// color = color.map(x => x / 255);
|
||||
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
|
||||
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
|
||||
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
|
||||
// });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.centered-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
border-color: #e5e7eb;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="centered-container">
|
||||
<div class="column is-mobile is-centered">
|
||||
<model-viewer id="modelviewer" style="height: 640px; width: 500px;"
|
||||
rotation-per-second="10deg"
|
||||
src="./white_mesh.glb/" disable-tap
|
||||
environment-image="neutral"
|
||||
camera-target="0m 0m 0m"
|
||||
camera-orbit="0deg 90deg 8m"
|
||||
orientation="0deg 0deg 0deg"
|
||||
shadow-intensity=".9"
|
||||
ar auto-rotate
|
||||
camera-controls>
|
||||
</model-viewer>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
save_dir/86ad7de9-b196-4e02-9a5e-34b82cbd0280/textured_mesh.glb
Normal file
321
save_dir/86ad7de9-b196-4e02-9a5e-34b82cbd0280/textured_mesh.html
Normal file
@@ -0,0 +1,321 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- Import the component -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js"
|
||||
type="module"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.centered-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modelviewer-panel-button {
|
||||
height: 30px;
|
||||
margin: 4px 4px;
|
||||
padding: 0px 14px;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.modelviewer-panel-button.checked {
|
||||
background: #6567C9;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modelviewer-panel-button:hover {
|
||||
background-color: #e2e6ea;
|
||||
}
|
||||
|
||||
.modelviewer-panel-button-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.centered-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="centered-container">
|
||||
<div class="centered-container">
|
||||
<div class="column is-mobile is-centered">
|
||||
<model-viewer id="modelviewer" style="height: 600px; width: 500px;"
|
||||
rotation-per-second="10deg"
|
||||
src="./textured_mesh.glb/" disable-tap
|
||||
environment-image="neutral"
|
||||
camera-target="0m 0m 0m"
|
||||
camera-orbit="0deg 90deg 12m"
|
||||
orientation="0deg 0deg 0deg"
|
||||
shadow-intensity=".9"
|
||||
ar auto-rotate
|
||||
camera-controls>
|
||||
</model-viewer>
|
||||
</div>
|
||||
|
||||
<div class="modelviewer-panel-button-container">
|
||||
<div id="appearance-button" class="modelviewer-panel-button small checked" onclick="showTexture()">
|
||||
Appearance
|
||||
</div>
|
||||
<div id="geometry-button" class="modelviewer-panel-button small" onclick="hideTexture()">Geometry</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let modelViewer;
|
||||
let window_state = {
|
||||
isModelLoaded: false,
|
||||
isHidden: false,
|
||||
savedMaterials: null,
|
||||
savedExposure: null
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
modelViewer = document.getElementById('modelviewer');
|
||||
|
||||
// 等待模型加载完成
|
||||
modelViewer.addEventListener('load', () => {
|
||||
console.log('Model loaded, materials available:', modelViewer.model.materials.length);
|
||||
window_state.isModelLoaded = true;
|
||||
|
||||
// 调试:打印材质信息
|
||||
modelViewer.model.materials.forEach((mat, idx) => {
|
||||
console.log(`Material ${idx}:`, {
|
||||
name: mat.name,
|
||||
hasBaseColor: !!mat.pbrMetallicRoughness.baseColorTexture,
|
||||
hasMetallicRoughness: !!mat.pbrMetallicRoughness.metallicRoughnessTexture,
|
||||
hasNormal: !!mat.normalTexture
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
modelViewer.addEventListener('error', (event) => {
|
||||
console.error('Model loading error:', event);
|
||||
});
|
||||
});
|
||||
|
||||
function ensureModelLoaded() {
|
||||
if (!window_state.isModelLoaded || !modelViewer.model || !modelViewer.model.materials) {
|
||||
console.warn('Model not loaded yet');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function saveMaterialsState() {
|
||||
if (!ensureModelLoaded()) return false;
|
||||
|
||||
console.log('Saving materials state...');
|
||||
window_state.savedMaterials = [];
|
||||
window_state.savedExposure = modelViewer.exposure;
|
||||
|
||||
for (let i = 0; i < modelViewer.model.materials.length; i++) {
|
||||
const material = modelViewer.model.materials[i];
|
||||
const pbr = material.pbrMetallicRoughness;
|
||||
|
||||
const materialState = {
|
||||
baseColorTexture: null,
|
||||
metallicRoughnessTexture: null,
|
||||
normalTexture: null,
|
||||
baseColorFactor: null,
|
||||
metallicFactor: null,
|
||||
roughnessFactor: null
|
||||
};
|
||||
|
||||
// 保存纹理引用
|
||||
try {
|
||||
if (pbr.baseColorTexture && pbr.baseColorTexture.texture) {
|
||||
materialState.baseColorTexture = pbr.baseColorTexture.texture;
|
||||
console.log(`Saved baseColorTexture for material ${i}`);
|
||||
}
|
||||
if (pbr.metallicRoughnessTexture && pbr.metallicRoughnessTexture.texture) {
|
||||
materialState.metallicRoughnessTexture = pbr.metallicRoughnessTexture.texture;
|
||||
console.log(`Saved metallicRoughnessTexture for material ${i}`);
|
||||
}
|
||||
if (material.normalTexture && material.normalTexture.texture) {
|
||||
materialState.normalTexture = material.normalTexture.texture;
|
||||
console.log(`Saved normalTexture for material ${i}`);
|
||||
}
|
||||
|
||||
// 保存材质参数
|
||||
materialState.baseColorFactor = [...pbr.baseColorFactor];
|
||||
materialState.metallicFactor = pbr.metallicFactor;
|
||||
materialState.roughnessFactor = pbr.roughnessFactor;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error saving material ${i}:`, error);
|
||||
}
|
||||
|
||||
window_state.savedMaterials.push(materialState);
|
||||
}
|
||||
|
||||
console.log('Materials state saved:', window_state.savedMaterials);
|
||||
return true;
|
||||
}
|
||||
|
||||
function hideTexture() {
|
||||
console.log('hideTexture called');
|
||||
|
||||
if (!ensureModelLoaded()) {
|
||||
console.error('Cannot hide texture: model not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
let appearanceButton = document.getElementById('appearance-button');
|
||||
let geometryButton = document.getElementById('geometry-button');
|
||||
appearanceButton.classList.remove('checked');
|
||||
geometryButton.classList.add('checked');
|
||||
|
||||
// 如果已经隐藏,直接返回
|
||||
if (window_state.isHidden) return;
|
||||
|
||||
// 第一次隐藏时保存状态
|
||||
if (!window_state.savedMaterials) {
|
||||
if (!saveMaterialsState()) return;
|
||||
}
|
||||
|
||||
// 隐藏所有纹理
|
||||
try {
|
||||
for (let i = 0; i < modelViewer.model.materials.length; i++) {
|
||||
const material = modelViewer.model.materials[i];
|
||||
const pbr = material.pbrMetallicRoughness;
|
||||
|
||||
if (pbr.baseColorTexture) {
|
||||
pbr.baseColorTexture.setTexture(null);
|
||||
}
|
||||
if (pbr.metallicRoughnessTexture) {
|
||||
pbr.metallicRoughnessTexture.setTexture(null);
|
||||
}
|
||||
if (material.normalTexture) {
|
||||
material.normalTexture.setTexture(null);
|
||||
}
|
||||
}
|
||||
|
||||
window_state.isHidden = true;
|
||||
modelViewer.environmentImage = '/static/env_maps/gradient.jpg';
|
||||
modelViewer.exposure = 4;
|
||||
|
||||
console.log('Textures hidden successfully');
|
||||
} catch (error) {
|
||||
console.error('Error hiding textures:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function showTexture() {
|
||||
console.log('showTexture called');
|
||||
|
||||
if (!ensureModelLoaded()) {
|
||||
console.error('Cannot show texture: model not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
let appearanceButton = document.getElementById('appearance-button');
|
||||
let geometryButton = document.getElementById('geometry-button');
|
||||
appearanceButton.classList.add('checked');
|
||||
geometryButton.classList.remove('checked');
|
||||
|
||||
// 如果不在隐藏状态,直接返回
|
||||
if (!window_state.isHidden) return;
|
||||
|
||||
// 如果没有保存的材质状态,无法恢复
|
||||
if (!window_state.savedMaterials) {
|
||||
console.warn('No saved materials to restore');
|
||||
return;
|
||||
}
|
||||
|
||||
// 恢复纹理
|
||||
try {
|
||||
for (let i = 0; i < modelViewer.model.materials.length && i < window_state.savedMaterials.length; i++) {
|
||||
const material = modelViewer.model.materials[i];
|
||||
const pbr = material.pbrMetallicRoughness;
|
||||
const savedMaterial = window_state.savedMaterials[i];
|
||||
|
||||
// 恢复纹理
|
||||
if (savedMaterial.baseColorTexture && pbr.baseColorTexture) {
|
||||
pbr.baseColorTexture.setTexture(savedMaterial.baseColorTexture);
|
||||
console.log(`Restored baseColorTexture for material ${i}`);
|
||||
}
|
||||
if (savedMaterial.metallicRoughnessTexture && pbr.metallicRoughnessTexture) {
|
||||
pbr.metallicRoughnessTexture.setTexture(savedMaterial.metallicRoughnessTexture);
|
||||
console.log(`Restored metallicRoughnessTexture for material ${i}`);
|
||||
}
|
||||
if (savedMaterial.normalTexture && material.normalTexture) {
|
||||
material.normalTexture.setTexture(savedMaterial.normalTexture);
|
||||
console.log(`Restored normalTexture for material ${i}`);
|
||||
}
|
||||
|
||||
// 恢复材质参数
|
||||
if (savedMaterial.baseColorFactor) {
|
||||
pbr.setBaseColorFactor(savedMaterial.baseColorFactor);
|
||||
}
|
||||
if (typeof savedMaterial.metallicFactor === 'number') {
|
||||
pbr.setMetallicFactor(savedMaterial.metallicFactor);
|
||||
}
|
||||
if (typeof savedMaterial.roughnessFactor === 'number') {
|
||||
pbr.setRoughnessFactor(savedMaterial.roughnessFactor);
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复环境设置
|
||||
modelViewer.environmentImage = '/static/env_maps/white.jpg';
|
||||
if (window_state.savedExposure !== undefined) {
|
||||
modelViewer.exposure = window_state.savedExposure;
|
||||
}
|
||||
|
||||
window_state.isHidden = false;
|
||||
console.log('Textures restored successfully');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error restoring textures:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加调试函数
|
||||
function debugMaterials() {
|
||||
if (!ensureModelLoaded()) return;
|
||||
|
||||
console.log('=== Current Materials Debug ===');
|
||||
modelViewer.model.materials.forEach((mat, idx) => {
|
||||
const pbr = mat.pbrMetallicRoughness;
|
||||
console.log(`Material ${idx}:`, {
|
||||
name: mat.name,
|
||||
baseColorTexture: pbr.baseColorTexture?.texture || null,
|
||||
metallicRoughnessTexture: pbr.metallicRoughnessTexture?.texture || null,
|
||||
normalTexture: mat.normalTexture?.texture || null,
|
||||
baseColorFactor: pbr.baseColorFactor,
|
||||
metallicFactor: pbr.metallicFactor,
|
||||
roughnessFactor: pbr.roughnessFactor
|
||||
});
|
||||
});
|
||||
console.log('Window state:', window_state);
|
||||
console.log('===============================');
|
||||
}
|
||||
|
||||
// 暴露调试函数到全局
|
||||
window.debugMaterials = debugMaterials;
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
save_dir/86ad7de9-b196-4e02-9a5e-34b82cbd0280/textured_mesh.jpg
Normal file
|
After Width: | Height: | Size: 301 KiB |
@@ -0,0 +1,9 @@
|
||||
newmtl Material
|
||||
Kd 0.8 0.8 0.8
|
||||
Ke 0.0 0.0 0.0
|
||||
Ni 1.5
|
||||
d 1.0
|
||||
illum 2
|
||||
map_Kd textured_mesh.jpg
|
||||
map_Pm textured_mesh_metallic.jpg
|
||||
map_Pr textured_mesh_roughness.jpg
|
||||
93012
save_dir/86ad7de9-b196-4e02-9a5e-34b82cbd0280/textured_mesh.obj
Normal file
|
After Width: | Height: | Size: 394 KiB |
|
After Width: | Height: | Size: 526 KiB |
BIN
save_dir/86ad7de9-b196-4e02-9a5e-34b82cbd0280/white_mesh.glb
Normal file
59984
save_dir/86ad7de9-b196-4e02-9a5e-34b82cbd0280/white_mesh.obj
Normal file
BIN
save_dir/9071a24f-1463-4db6-8ca5-2f7114f1b1b7/white_mesh.glb
Normal file
60000
save_dir/9071a24f-1463-4db6-8ca5-2f7114f1b1b7/white_mesh.obj
Normal file
BIN
save_dir/912df087-ecd5-4c64-9f74-593680977fe6/textured_mesh.glb
Normal file
321
save_dir/912df087-ecd5-4c64-9f74-593680977fe6/textured_mesh.html
Normal file
@@ -0,0 +1,321 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- Import the component -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js"
|
||||
type="module"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.centered-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modelviewer-panel-button {
|
||||
height: 30px;
|
||||
margin: 4px 4px;
|
||||
padding: 0px 14px;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.modelviewer-panel-button.checked {
|
||||
background: #6567C9;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modelviewer-panel-button:hover {
|
||||
background-color: #e2e6ea;
|
||||
}
|
||||
|
||||
.modelviewer-panel-button-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.centered-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="centered-container">
|
||||
<div class="centered-container">
|
||||
<div class="column is-mobile is-centered">
|
||||
<model-viewer id="modelviewer" style="height: 600px; width: 500px;"
|
||||
rotation-per-second="10deg"
|
||||
src="./textured_mesh.glb/" disable-tap
|
||||
environment-image="neutral"
|
||||
camera-target="0m 0m 0m"
|
||||
camera-orbit="0deg 90deg 12m"
|
||||
orientation="0deg 0deg 0deg"
|
||||
shadow-intensity=".9"
|
||||
ar auto-rotate
|
||||
camera-controls>
|
||||
</model-viewer>
|
||||
</div>
|
||||
|
||||
<div class="modelviewer-panel-button-container">
|
||||
<div id="appearance-button" class="modelviewer-panel-button small checked" onclick="showTexture()">
|
||||
Appearance
|
||||
</div>
|
||||
<div id="geometry-button" class="modelviewer-panel-button small" onclick="hideTexture()">Geometry</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let modelViewer;
|
||||
let window_state = {
|
||||
isModelLoaded: false,
|
||||
isHidden: false,
|
||||
savedMaterials: null,
|
||||
savedExposure: null
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
modelViewer = document.getElementById('modelviewer');
|
||||
|
||||
// 等待模型加载完成
|
||||
modelViewer.addEventListener('load', () => {
|
||||
console.log('Model loaded, materials available:', modelViewer.model.materials.length);
|
||||
window_state.isModelLoaded = true;
|
||||
|
||||
// 调试:打印材质信息
|
||||
modelViewer.model.materials.forEach((mat, idx) => {
|
||||
console.log(`Material ${idx}:`, {
|
||||
name: mat.name,
|
||||
hasBaseColor: !!mat.pbrMetallicRoughness.baseColorTexture,
|
||||
hasMetallicRoughness: !!mat.pbrMetallicRoughness.metallicRoughnessTexture,
|
||||
hasNormal: !!mat.normalTexture
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
modelViewer.addEventListener('error', (event) => {
|
||||
console.error('Model loading error:', event);
|
||||
});
|
||||
});
|
||||
|
||||
function ensureModelLoaded() {
|
||||
if (!window_state.isModelLoaded || !modelViewer.model || !modelViewer.model.materials) {
|
||||
console.warn('Model not loaded yet');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function saveMaterialsState() {
|
||||
if (!ensureModelLoaded()) return false;
|
||||
|
||||
console.log('Saving materials state...');
|
||||
window_state.savedMaterials = [];
|
||||
window_state.savedExposure = modelViewer.exposure;
|
||||
|
||||
for (let i = 0; i < modelViewer.model.materials.length; i++) {
|
||||
const material = modelViewer.model.materials[i];
|
||||
const pbr = material.pbrMetallicRoughness;
|
||||
|
||||
const materialState = {
|
||||
baseColorTexture: null,
|
||||
metallicRoughnessTexture: null,
|
||||
normalTexture: null,
|
||||
baseColorFactor: null,
|
||||
metallicFactor: null,
|
||||
roughnessFactor: null
|
||||
};
|
||||
|
||||
// 保存纹理引用
|
||||
try {
|
||||
if (pbr.baseColorTexture && pbr.baseColorTexture.texture) {
|
||||
materialState.baseColorTexture = pbr.baseColorTexture.texture;
|
||||
console.log(`Saved baseColorTexture for material ${i}`);
|
||||
}
|
||||
if (pbr.metallicRoughnessTexture && pbr.metallicRoughnessTexture.texture) {
|
||||
materialState.metallicRoughnessTexture = pbr.metallicRoughnessTexture.texture;
|
||||
console.log(`Saved metallicRoughnessTexture for material ${i}`);
|
||||
}
|
||||
if (material.normalTexture && material.normalTexture.texture) {
|
||||
materialState.normalTexture = material.normalTexture.texture;
|
||||
console.log(`Saved normalTexture for material ${i}`);
|
||||
}
|
||||
|
||||
// 保存材质参数
|
||||
materialState.baseColorFactor = [...pbr.baseColorFactor];
|
||||
materialState.metallicFactor = pbr.metallicFactor;
|
||||
materialState.roughnessFactor = pbr.roughnessFactor;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error saving material ${i}:`, error);
|
||||
}
|
||||
|
||||
window_state.savedMaterials.push(materialState);
|
||||
}
|
||||
|
||||
console.log('Materials state saved:', window_state.savedMaterials);
|
||||
return true;
|
||||
}
|
||||
|
||||
function hideTexture() {
|
||||
console.log('hideTexture called');
|
||||
|
||||
if (!ensureModelLoaded()) {
|
||||
console.error('Cannot hide texture: model not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
let appearanceButton = document.getElementById('appearance-button');
|
||||
let geometryButton = document.getElementById('geometry-button');
|
||||
appearanceButton.classList.remove('checked');
|
||||
geometryButton.classList.add('checked');
|
||||
|
||||
// 如果已经隐藏,直接返回
|
||||
if (window_state.isHidden) return;
|
||||
|
||||
// 第一次隐藏时保存状态
|
||||
if (!window_state.savedMaterials) {
|
||||
if (!saveMaterialsState()) return;
|
||||
}
|
||||
|
||||
// 隐藏所有纹理
|
||||
try {
|
||||
for (let i = 0; i < modelViewer.model.materials.length; i++) {
|
||||
const material = modelViewer.model.materials[i];
|
||||
const pbr = material.pbrMetallicRoughness;
|
||||
|
||||
if (pbr.baseColorTexture) {
|
||||
pbr.baseColorTexture.setTexture(null);
|
||||
}
|
||||
if (pbr.metallicRoughnessTexture) {
|
||||
pbr.metallicRoughnessTexture.setTexture(null);
|
||||
}
|
||||
if (material.normalTexture) {
|
||||
material.normalTexture.setTexture(null);
|
||||
}
|
||||
}
|
||||
|
||||
window_state.isHidden = true;
|
||||
modelViewer.environmentImage = '/static/env_maps/gradient.jpg';
|
||||
modelViewer.exposure = 4;
|
||||
|
||||
console.log('Textures hidden successfully');
|
||||
} catch (error) {
|
||||
console.error('Error hiding textures:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function showTexture() {
|
||||
console.log('showTexture called');
|
||||
|
||||
if (!ensureModelLoaded()) {
|
||||
console.error('Cannot show texture: model not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
let appearanceButton = document.getElementById('appearance-button');
|
||||
let geometryButton = document.getElementById('geometry-button');
|
||||
appearanceButton.classList.add('checked');
|
||||
geometryButton.classList.remove('checked');
|
||||
|
||||
// 如果不在隐藏状态,直接返回
|
||||
if (!window_state.isHidden) return;
|
||||
|
||||
// 如果没有保存的材质状态,无法恢复
|
||||
if (!window_state.savedMaterials) {
|
||||
console.warn('No saved materials to restore');
|
||||
return;
|
||||
}
|
||||
|
||||
// 恢复纹理
|
||||
try {
|
||||
for (let i = 0; i < modelViewer.model.materials.length && i < window_state.savedMaterials.length; i++) {
|
||||
const material = modelViewer.model.materials[i];
|
||||
const pbr = material.pbrMetallicRoughness;
|
||||
const savedMaterial = window_state.savedMaterials[i];
|
||||
|
||||
// 恢复纹理
|
||||
if (savedMaterial.baseColorTexture && pbr.baseColorTexture) {
|
||||
pbr.baseColorTexture.setTexture(savedMaterial.baseColorTexture);
|
||||
console.log(`Restored baseColorTexture for material ${i}`);
|
||||
}
|
||||
if (savedMaterial.metallicRoughnessTexture && pbr.metallicRoughnessTexture) {
|
||||
pbr.metallicRoughnessTexture.setTexture(savedMaterial.metallicRoughnessTexture);
|
||||
console.log(`Restored metallicRoughnessTexture for material ${i}`);
|
||||
}
|
||||
if (savedMaterial.normalTexture && material.normalTexture) {
|
||||
material.normalTexture.setTexture(savedMaterial.normalTexture);
|
||||
console.log(`Restored normalTexture for material ${i}`);
|
||||
}
|
||||
|
||||
// 恢复材质参数
|
||||
if (savedMaterial.baseColorFactor) {
|
||||
pbr.setBaseColorFactor(savedMaterial.baseColorFactor);
|
||||
}
|
||||
if (typeof savedMaterial.metallicFactor === 'number') {
|
||||
pbr.setMetallicFactor(savedMaterial.metallicFactor);
|
||||
}
|
||||
if (typeof savedMaterial.roughnessFactor === 'number') {
|
||||
pbr.setRoughnessFactor(savedMaterial.roughnessFactor);
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复环境设置
|
||||
modelViewer.environmentImage = '/static/env_maps/white.jpg';
|
||||
if (window_state.savedExposure !== undefined) {
|
||||
modelViewer.exposure = window_state.savedExposure;
|
||||
}
|
||||
|
||||
window_state.isHidden = false;
|
||||
console.log('Textures restored successfully');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error restoring textures:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加调试函数
|
||||
function debugMaterials() {
|
||||
if (!ensureModelLoaded()) return;
|
||||
|
||||
console.log('=== Current Materials Debug ===');
|
||||
modelViewer.model.materials.forEach((mat, idx) => {
|
||||
const pbr = mat.pbrMetallicRoughness;
|
||||
console.log(`Material ${idx}:`, {
|
||||
name: mat.name,
|
||||
baseColorTexture: pbr.baseColorTexture?.texture || null,
|
||||
metallicRoughnessTexture: pbr.metallicRoughnessTexture?.texture || null,
|
||||
normalTexture: mat.normalTexture?.texture || null,
|
||||
baseColorFactor: pbr.baseColorFactor,
|
||||
metallicFactor: pbr.metallicFactor,
|
||||
roughnessFactor: pbr.roughnessFactor
|
||||
});
|
||||
});
|
||||
console.log('Window state:', window_state);
|
||||
console.log('===============================');
|
||||
}
|
||||
|
||||
// 暴露调试函数到全局
|
||||
window.debugMaterials = debugMaterials;
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
save_dir/c2c522df-a42a-4907-b31a-1a753d505608/white_mesh.glb
Normal file
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- Import the component -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js" type="module"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const modelViewers = document.querySelectorAll('model-viewer');
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
modelViewers.forEach(modelViewer => {
|
||||
modelViewer.setAttribute(
|
||||
"environment-image",
|
||||
"/static/env_maps/gradient.jpg"
|
||||
);
|
||||
// if (!isSafari) {
|
||||
// modelViewer.setAttribute(
|
||||
// "environment-image",
|
||||
// "/static/env_maps/gradient.jpg"
|
||||
// );
|
||||
// } else {
|
||||
// modelViewer.addEventListener('load', (event) => {
|
||||
// const [material] = modelViewer.model.materials;
|
||||
// let color = [43, 44, 46, 255];
|
||||
// color = color.map(x => x / 255);
|
||||
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
|
||||
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
|
||||
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
|
||||
// });
|
||||
// }
|
||||
// modelViewer.addEventListener('load', (event) => {
|
||||
// const [material] = modelViewer.model.materials;
|
||||
// let color = [43, 44, 46, 255];
|
||||
// color = color.map(x => x / 255);
|
||||
// material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属
|
||||
// material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度
|
||||
// material.pbrMetallicRoughness.setBaseColorFactor(color); // CornflowerBlue in RGB
|
||||
// });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.centered-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
border-color: #e5e7eb;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="centered-container">
|
||||
<div class="column is-mobile is-centered">
|
||||
<model-viewer id="modelviewer" style="height: 640px; width: 500px;"
|
||||
rotation-per-second="10deg"
|
||||
src="./white_mesh.glb/" disable-tap
|
||||
environment-image="neutral"
|
||||
camera-target="0m 0m 0m"
|
||||
camera-orbit="0deg 90deg 8m"
|
||||
orientation="0deg 0deg 0deg"
|
||||
shadow-intensity=".9"
|
||||
ar auto-rotate
|
||||
camera-controls>
|
||||
</model-viewer>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
save_dir/env_maps/gradient.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
save_dir/env_maps/white.jpg
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
test/images/114.jpg
Normal file
|
After Width: | Height: | Size: 490 KiB |
BIN
test/images/20260312-162312.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
test/images/20260312-172851.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
test/images/20260312-172855.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
test/images/20260312-172900.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
test/images/20260312-172904.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
test/images/chair.jpg
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
test/images/desk.jpg
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
test/images/desk1.jpg
Normal file
|
After Width: | Height: | Size: 184 KiB |