Органайзер для кошачьего корма: 3D-модель, STL для печати, просмотрщик

This commit is contained in:
apuc committed 2026-09-29 12:43:43 +03:00
commit bd44338cbd
19 files changed
+61107

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
.opencode/
__pycache__/
*.pyc
.venv/
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+333
View File
@@ -0,0 +1,333 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Параметрический генератор органайзера для кошачьего корма (вариант 2).
Дизайн по мотивам cat_organaizer: высокая коробка + крышка из 2 частей
(верх с кошачьими ушками + низ с защёлками). БЕЗ боковой конструкции.
Коробка делится на сегменты для печати на любом принтере.
Экспорт: box_seg_*.stl, lid_top.stl, lid_bottom.stl, box_one_piece.stl,
box.obj, box.glb, preview.png, preview.html.
"""
import base64
import numpy as np
import trimesh
from trimesh.creation import extrude_polygon, cone, box
from shapely.geometry import Polygon
# ============================ ПАРАМЕТРЫ ============================
# Размеры ОДНОГО пакета (пауча) влажного корма (Д × Ш × В), мм
PACKET = (135.0, 90.0, 18.0) # Whiskas 85 г (оценка — ИЗМЕРЬ СВОЙ ПАКЕТ!)
PACKETS = 20 # сколько пакетов должно помещаться
H_CLEAR = 5.0 # зазор по высоте стопки, мм
# Габариты коробки (как в оригинальном дизайне cat_organaizer)
BOX_L = 131.5 # длина коробки (X), мм
BOX_W = 156.2 # ширина коробки (Y), мм
WALL = 3.0 # толщина стенок, мм
BASE = 15.0 # толщина сплошного дна, мм
CORNER_R = 10.0 # радиус скругления углов, мм
# Соединение сегментов
RIM_OVERHANG = 3.0 # выступ бортика, мм
RIM_HEIGHT = 8.0 # высота бортика, мм
LID_CLEARANCE = 0.4 # зазор посадки, мм
SEG_OVERLAP = 12.0 # высота юбки-соединения, мм
SEGMENTS = 2 # на сколько частей резать коробку
# Крышка
LID_THICK = 24.0 # толщина крышки-верха, мм
LID_BOTTOM_WALL = 6.0 # толщина дна крышки-верха (под защёлки), мм
LID_BOTTOM_THICK = 9.0 # толщина крышки-низа, мм
CLIP = 8.0 # размер защёлки, мм
CLIP_H = 8.0 # высота защёлки, мм
CLIP_INSET = 10.0 # отступ защёлок от краёв, мм
HOLE_OVER = 0.5 # запас отверстия под защёлку, мм
HOLE_DEPTH = 12.0 # глубина отверстий, мм
# Кошачьи ушки
EAR_R = 15.0 # радиус основания ушка, мм
EAR_H = 24.0 # высота ушка, мм
EAR_W_SCALE = 0.65 # сжатие ушка по ширине (эллипс)
EAR_TILT = 18.0 # наклон ушек наружу, градусы
EAR_FWD = 8.0 # наклон ушек вперёд, градусы
EAR_SIDE = 22.0 # отступ ушек от боковых кромок, мм
EAR_BACK = 12.0 # отступ ушек от задней кромки, мм
SEG = 64 # сегменты окружностей
# ===================================================================
def rounded_rect(w, h, r, seg=SEG):
"""Скруглённый прямоугольник (shapely Polygon), центр в (0,0)."""
rect = Polygon([(-w / 2, -h / 2), (w / 2, -h / 2), (w / 2, h / 2), (-w / 2, h / 2)])
return rect.buffer(-r, resolution=seg).buffer(r, resolution=seg)
def make_box_segments():
"""Коробка из SEGMENTS сегментов. Возвращает (список мешей, габариты)."""
H_int = PACKETS * PACKET[2] + H_CLEAR
L_ext, W_ext = BOX_L, BOX_W
L_int, W_int = L_ext - 2 * WALL, W_ext - 2 * WALL
H_ext = H_int + BASE
seg_h = H_ext / SEGMENTS
inner_r = max(CORNER_R - WALL, 0.5)
outer_prof = rounded_rect(L_ext, W_ext, CORNER_R)
inner_prof = rounded_rect(L_int, W_int, inner_r)
segments = []
for i in range(SEGMENTS):
is_top = (i == SEGMENTS - 1)
if i == 0:
# нижний сегмент: с дном
seg = extrude_polygon(outer_prof, seg_h).difference(
extrude_polygon(inner_prof, seg_h - BASE).apply_translation([0, 0, BASE]))
else:
# верхние сегменты: кольцо без дна
seg = extrude_polygon(outer_prof, seg_h).difference(extrude_polygon(inner_prof, seg_h))
# юбка-соединение (низ): свисает вниз и надевается на бортик нижнего сегмента
sk_out = rounded_rect(L_ext + 2 * (RIM_OVERHANG + WALL + LID_CLEARANCE),
W_ext + 2 * (RIM_OVERHANG + WALL + LID_CLEARANCE),
CORNER_R + RIM_OVERHANG + WALL + LID_CLEARANCE)
sk_in = rounded_rect(L_ext + 2 * (RIM_OVERHANG + LID_CLEARANCE),
W_ext + 2 * (RIM_OVERHANG + LID_CLEARANCE),
CORNER_R + RIM_OVERHANG + LID_CLEARANCE)
skirt = extrude_polygon(sk_out, SEG_OVERLAP).difference(extrude_polygon(sk_in, SEG_OVERLAP))
skirt.apply_translation([0, 0, -SEG_OVERLAP])
step = extrude_polygon(sk_in, WALL).difference(extrude_polygon(outer_prof, WALL))
seg = seg.union(skirt).union(step)
# верхний бортик (рим) — на всех сегментах, кроме самого верхнего
if not is_top:
rim_outer = rounded_rect(L_ext + 2 * RIM_OVERHANG, W_ext + 2 * RIM_OVERHANG,
CORNER_R + RIM_OVERHANG)
rim_inner = rounded_rect(L_ext + 0.1, W_ext + 0.1, CORNER_R + 0.05)
rim = extrude_polygon(rim_outer, RIM_HEIGHT).difference(
extrude_polygon(rim_inner, RIM_HEIGHT))
rim.apply_translation([0, 0, seg_h - RIM_HEIGHT])
seg = seg.union(rim)
seg.apply_translation([0, 0, i * seg_h])
segments.append(seg)
return segments, (L_ext, W_ext, H_ext)
def make_lid_top(L_ext, W_ext):
"""Крышка-верх: полая пластина с ушками и 4 отверстиями под защёлки."""
# внешняя пластина
outer = extrude_polygon(rounded_rect(L_ext, W_ext, CORNER_R), LID_THICK)
# полость (оболочка: верх WALL, дно LID_BOTTOM_WALL, стенки WALL)
cavity = extrude_polygon(rounded_rect(L_ext - 2 * WALL, W_ext - 2 * WALL,
CORNER_R - WALL),
LID_THICK - WALL - LID_BOTTOM_WALL)
cavity.apply_translation([0, 0, LID_BOTTOM_WALL])
lid = outer.difference(cavity)
# кошачьи ушки (задняя кромка, -Y)
for side in (-1, 1):
ear = cone(radius=EAR_R, height=EAR_H, sections=SEG)
ear.apply_scale([1.0, EAR_W_SCALE, 1.0])
R = trimesh.transformations.rotation_matrix(np.radians(-EAR_FWD), [1, 0, 0], point=[0, 0, 0])
R = trimesh.transformations.rotation_matrix(np.radians(side * EAR_TILT), [0, 1, 0],
point=[0, 0, 0]) @ R
ear.apply_transform(R)
ear.apply_translation([side * (L_ext / 2 - EAR_SIDE),
-(W_ext / 2 - EAR_BACK), LID_THICK])
lid = lid.union(ear)
# 4 отверстия под защёлки (снизу, по углам) — позиции совпадают с защёлками
cx = L_ext / 2 - CLIP_INSET - CLIP / 2
cy = W_ext / 2 - CLIP_INSET - CLIP / 2
for sx in (-1, 1):
for sy in (-1, 1):
hole = box(extents=[CLIP + HOLE_OVER, CLIP + HOLE_OVER, HOLE_DEPTH])
hole.apply_translation([sx * cx, sy * cy, HOLE_DEPTH / 2])
lid = lid.difference(hole)
return lid
def make_lid_bottom(L_ext, W_ext):
"""Крышка-низ: пластина-заглушка с 4 защёлками по углам."""
L_bot = L_ext - 2 * WALL - LID_CLEARANCE
W_bot = W_ext - 2 * WALL - LID_CLEARANCE
plate = extrude_polygon(rounded_rect(L_bot, W_bot, CORNER_R - WALL), LID_BOTTOM_THICK)
lid = plate
# защёлки — на тех же позициях, что и отверстия в крышке-верх
cx = L_ext / 2 - CLIP_INSET - CLIP / 2
cy = W_ext / 2 - CLIP_INSET - CLIP / 2
for sx in (-1, 1):
for sy in (-1, 1):
clip = box(extents=[CLIP, CLIP, CLIP_H])
clip.apply_translation([sx * cx, sy * cy, LID_BOTTOM_THICK + CLIP_H / 2])
lid = lid.union(clip)
return lid
def write_preview_html(glb_path, out_path, info_lines):
glb = open(glb_path, 'rb').read()
b64 = base64.b64encode(glb).decode()
info_html = "<br>".join(info_lines)
html = """<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Органайзер для кошачьего корма — 3D просмотр</title>
<style>
body { margin:0; overflow:hidden; font-family:'Segoe UI',Arial,sans-serif; background:#1a1d24; color:#e8e8e8; }
#info { position:absolute; top:12px; left:12px; z-index:10; background:rgba(20,24,32,.85);
padding:10px 14px; border-radius:8px; font-size:13px; line-height:1.5; max-width:320px;
border:1px solid rgba(255,255,255,.12); backdrop-filter:blur(4px); }
#info b { color:#ffb703; }
#hint { position:absolute; bottom:12px; left:50%; transform:translateX(-50%); z-index:10;
font-size:12px; color:#9aa3b2; background:rgba(20,24,32,.7); padding:6px 12px; border-radius:20px; }
canvas { display:block; }
</style>
</head>
<body>
<div id="info"><b>Органайзер для кошачьего корма</b><br>__INFO__</div>
<div id="hint">ЛКМ — вращение · колесо — масштаб · ПКМ — сдвиг</div>
<script type="importmap">
{ "imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1d24);
const camera = new THREE.PerspectiveCamera(45, innerWidth/innerHeight, 1, 3000);
camera.position.set(420, 340, 500);
const renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 180, 0);
controls.enableDamping = true;
scene.add(new THREE.HemisphereLight(0xffffff, 0x444455, 0.9));
const sun = new THREE.DirectionalLight(0xffffff, 1.6);
sun.position.set(200, 500, 150); sun.castShadow = true; scene.add(sun);
const fill = new THREE.DirectionalLight(0x88aaff, 0.5);
fill.position.set(-300, 200, -250); scene.add(fill);
const grid = new THREE.GridHelper(500, 25, 0x3a4150, 0x2a2f3a);
grid.position.y = -1; scene.add(grid);
const b64 = "__GLB_B64__";
const bin = atob(b64);
const arr = new Uint8Array(bin.length);
for (let i=0;i<bin.length;i++) arr[i] = bin.charCodeAt(i);
const loader = new GLTFLoader();
loader.parse(arr.buffer, '', (gltf) => {
const model = gltf.scene;
model.traverse(o => { if (o.isMesh) { o.castShadow = true; o.receiveShadow = true; } });
scene.add(model);
const box = new THREE.Box3().setFromObject(model);
const c = box.getCenter(new THREE.Vector3());
controls.target.copy(c);
camera.position.copy(c).add(new THREE.Vector3(420, 340, 500));
}, (e) => console.error(e));
window.addEventListener('resize', () => {
camera.aspect = innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
function animate(){ requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); }
animate();
</script>
</body>
</html>"""
html = html.replace("__GLB_B64__", b64).replace("__INFO__", info_html)
open(out_path, 'w', encoding='utf-8').write(html)
def main():
segments, (L_ext, W_ext, H_ext) = make_box_segments()
lid_top = make_lid_top(L_ext, W_ext)
lid_bottom = make_lid_bottom(L_ext, W_ext)
H_int = PACKETS * PACKET[2] + H_CLEAR
print("=== РАЗМЕРЫ (мм) ===")
print(f"Пакет (оценка): {PACKET[0]} x {PACKET[1]} x {PACKET[2]} | пакетов: {PACKETS}")
print(f"Внутренний размер коробки: {L_ext-2*WALL:.1f} x {W_ext-2*WALL:.1f} x {H_int:.1f}")
print(f"Наружный размер коробки: {L_ext:.1f} x {W_ext:.1f} x {H_ext:.1f}")
print(f"Сегментов: {SEGMENTS} (по {H_ext/SEGMENTS:.1f} мм)")
print(f"Крышка-верх: {lid_top.bounds[1][0]-lid_top.bounds[0][0]:.1f} x "
f"{lid_top.bounds[1][1]-lid_top.bounds[0][1]:.1f} x "
f"{lid_top.bounds[1][2]-lid_top.bounds[0][2]:.1f} мм")
print(f"Крышка-низ: {lid_bottom.bounds[1][0]-lid_bottom.bounds[0][0]:.1f} x "
f"{lid_bottom.bounds[1][1]-lid_bottom.bounds[0][1]:.1f} x "
f"{lid_bottom.bounds[1][2]-lid_bottom.bounds[0][2]:.1f} мм")
for i, m in enumerate(segments):
print(f"сегмент {i+1}: водонепроницаем={m.is_watertight}, объём={m.volume/1000:.0f} см3")
for name, m in (("крышка-верх", lid_top), ("крышка-низ", lid_bottom)):
print(f"{name}: водонепроницаема={m.is_watertight}, объём={m.volume/1000:.0f} см3")
# экспорт
for i, m in enumerate(segments):
m.export(f"box_seg_{i+1}.stl")
lid_top.export("lid_top.stl")
lid_bottom.export("lid_bottom.stl")
one = segments[0]
for m in segments[1:]:
one = one.union(m)
one.export("box_one_piece.stl")
# сборка для просмотра: коробка + крышка-низ (в отверстии) + крышка-верх
lid_bot_on = lid_bottom.copy().apply_translation([0, 0, H_ext])
lid_top_on = lid_top.copy().apply_translation([0, 0, H_ext + LID_BOTTOM_THICK])
scene = trimesh.Scene(segments + [lid_bot_on, lid_top_on])
scene.export("box.glb")
combined = one.union(lid_bot_on).union(lid_top_on)
combined.export("box.obj")
print("\nЭкспортировано: box_seg_1.stl..box_seg_N.stl, lid_top.stl, lid_bottom.stl, box_one_piece.stl, box.obj, box.glb")
# превью PNG
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure(figsize=(9, 11))
ax = fig.add_subplot(111, projection="3d")
colors = ["#8ecae6", "#a8dadc", "#bde0fe", "#cdb4db"]
for i, m in enumerate(segments):
pc = Poly3DCollection(m.triangles, alpha=0.85, facecolor=colors[i % len(colors)],
edgecolor="k", linewidth=0.1)
ax.add_collection3d(pc)
pc = Poly3DCollection(lid_bot_on.triangles, alpha=0.9, facecolor="#90e0ef",
edgecolor="k", linewidth=0.1)
ax.add_collection3d(pc)
pc = Poly3DCollection(lid_top_on.triangles, alpha=0.9, facecolor="#ffb703",
edgecolor="k", linewidth=0.1)
ax.add_collection3d(pc)
b = combined.bounds
ax.set_xlim(b[0][0], b[1][0]); ax.set_ylim(b[0][1], b[1][1]); ax.set_zlim(b[0][2], b[1][2])
ax.set_box_aspect((b[1][0]-b[0][0], b[1][1]-b[0][1], b[1][2]-b[0][2]))
ax.set_xlabel("X, мм"); ax.set_ylabel("Y, мм"); ax.set_zlabel("Z, мм")
ax.view_init(elev=20, azim=-60)
plt.tight_layout()
plt.savefig("preview.png", dpi=100)
print("Превью: preview.png")
except Exception as e:
print("Превью PNG не создано:", e)
# интерактивный просмотрщик
info = [
f"Вмещает {PACKETS} пакетов ({PACKET[0]:.0f}×{PACKET[1]:.0f}×{PACKET[2]:.0f} мм)",
f"Внутр.: {L_ext-2*WALL:.0f} × {W_ext-2*WALL:.0f} × {H_int:.0f} мм",
f"Наруж.: {L_ext:.0f} × {W_ext:.0f} × {H_ext:.0f} мм",
f"Сегментов: {SEGMENTS} · Без боковой конструкции",
f"Крышка: верх с ушками + низ с защёлками",
]
write_preview_html("box.glb", "preview.html", info)
print("Просмотрщик: preview.html")
if __name__ == "__main__":
main()
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+111
View File
@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Органайзер для кошачьего корма — сравнение</title>
<style>
body { margin:0; overflow:hidden; font-family:'Segoe UI',Arial,sans-serif; background:#1a1d24; color:#e8e8e8; }
#info { position:absolute; top:12px; left:12px; z-index:10; background:rgba(20,24,32,.85);
padding:10px 14px; border-radius:8px; font-size:13px; line-height:1.5; max-width:300px;
border:1px solid rgba(255,255,255,.12); backdrop-filter:blur(4px); }
#info b { color:#ffb703; }
#toggle { position:absolute; top:12px; left:50%; transform:translateX(-50%); z-index:10;
display:flex; background:rgba(20,24,32,.85); border:1px solid rgba(255,255,255,.15);
border-radius:10px; padding:4px; backdrop-filter:blur(4px); }
#toggle button { background:transparent; color:#9aa3b2; border:none; padding:8px 18px;
border-radius:7px; font-size:13px; cursor:pointer; transition:all .15s; }
#toggle button.active { background:#ffb703; color:#1a1d24; font-weight:600; }
#hint { position:absolute; bottom:12px; left:50%; transform:translateX(-50%); z-index:10;
font-size:12px; color:#9aa3b2; background:rgba(20,24,32,.7); padding:6px 12px; border-radius:20px; }
canvas { display:block; }
</style>
</head>
<body>
<div id="info"><b>Органайзер для кошачьего корма</b><br>
Коробка: 131.5 × 156.2 × 200 мм<br>
Крышка: верх с ушками + низ с защёлками<br>
<span id="modeLabel" style="color:#ffb703"></span></div>
<div id="toggle">
<button id="btnOrig" class="active">Оригинал</button>
<button id="btnEdit">Без боковой конструкции</button>
</div>
<div id="hint">ЛКМ — вращение · колесо — масштаб · ПКМ — сдвиг</div>
<script type="importmap">
{ "imports": {
"three": "./vendor/three.module.js",
"three/addons/": "./vendor/examples/jsm/"
}}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1d24);
const camera = new THREE.PerspectiveCamera(45, innerWidth/innerHeight, 1, 3000);
camera.position.set(320, 260, 380);
const renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 110, 0);
controls.enableDamping = true;
scene.add(new THREE.HemisphereLight(0xffffff, 0x444455, 0.9));
const sun = new THREE.DirectionalLight(0xffffff, 1.6);
sun.position.set(200, 400, 150); sun.castShadow = true; scene.add(sun);
const fill = new THREE.DirectionalLight(0x88aaff, 0.5);
fill.position.set(-250, 150, -200); scene.add(fill);
const grid = new THREE.GridHelper(400, 20, 0x3a4150, 0x2a2f3a);
grid.position.y = -1; scene.add(grid);
function loadGLB(b64str) {
return new Promise((resolve, reject) => {
const bin = atob(b64str);
const arr = new Uint8Array(bin.length);
for (let i=0;i<bin.length;i++) arr[i] = bin.charCodeAt(i);
new GLTFLoader().parse(arr.buffer, '', (gltf) => {
const model = gltf.scene;
model.traverse(o => { if (o.isMesh) { o.castShadow = true; o.receiveShadow = true; } });
scene.add(model);
resolve(model);
}, reject);
});
}
const models = {};
Promise.all([
loadGLB("Z2xURgIAAACw+QMAXAkAAEpTT057InNjZW5lIjowLCJzY2VuZXMiOlt7Im5vZGVzIjpbMCwxLDJdfV0sImFzc2V0Ijp7InZlcnNpb24iOiIyLjAiLCJnZW5lcmF0b3IiOiJodHRwczovL2dpdGh1Yi5jb20vbWlrZWRoL3RyaW1lc2gifSwiYWNjZXNzb3JzIjpbeyJjb21wb25lbnRUeXBlIjo1MTI1LCJ0eXBlIjoiU0NBTEFSIiwiYnVmZmVyVmlldyI6MCwiY291bnQiOjI1NTY2LCJtYXgiOls0MjYwXSwibWluIjpbMF19LHsiY29tcG9uZW50VHlwZSI6NTEyNiwidHlwZSI6IlZFQzMiLCJieXRlT2Zmc2V0IjowLCJidWZmZXJWaWV3IjoxLCJjb3VudCI6NDI2MSwibWF4IjpbNjUuNzY2MDA2NDY5NzI2NTYsNzguMDgwNTUxMTQ3NDYwOTQsMjAwLjBdLCJtaW4iOlstNjUuNzY2MDA2NDY5NzI2NTYsLTc4LjA4MDU1MTE0NzQ2MDk0LDAuMF19LHsiY29tcG9uZW50VHlwZSI6NTEyMSwibm9ybWFsaXplZCI6dHJ1ZSwidHlwZSI6IlZFQzQiLCJieXRlT2Zmc2V0IjowLCJidWZmZXJWaWV3IjoyLCJjb3VudCI6NDI2MSwibWF4IjpbMTQyLDIwMiwyMzAsMjU1XSwibWluIjpbMTQyLDIwMiwyMzAsMjU1XX0seyJjb21wb25lbnRUeXBlIjo1MTI1LCJ0eXBlIjoiU0NBTEFSIiwiYnVmZmVyVmlldyI6MywiY291bnQiOjE3MTYsIm1heCI6WzI4N10sIm1pbiI6WzBdfSx7ImNvbXBvbmVudFR5cGUiOjUxMjYsInR5cGUiOiJWRUMzIiwiYnl0ZU9mZnNldCI6MCwiYnVmZmVyVmlldyI6NCwiY291bnQiOjI4OCwibWF4IjpbNTAuMTAwMDA2MTAzNTE1NjI1LDc1LjA5OTk5ODQ3NDEyMTEsMjA5LjBdLCJtaW4iOlstNTAuMTAwMDA2MTAzNTE1NjI1LC03NS4wOTk5OTg0NzQxMjExLDIwMC4wXX0seyJjb21wb25lbnRUeXBlIjo1MTIxLCJub3JtYWxpemVkIjp0cnVlLCJ0eXBlIjoiVkVDNCIsImJ5dGVPZmZzZXQiOjAsImJ1ZmZlclZpZXciOjUsImNvdW50IjoyODgsIm1heCI6WzE2OCwyMTgsMjIwLDI1NV0sIm1pbiI6WzE2OCwyMTgsMjIwLDI1NV19LHsiY29tcG9uZW50VHlwZSI6NTEyNSwidHlwZSI6IlNDQUxBUiIsImJ1ZmZlclZpZXciOjYsImNvdW50IjoxMTQyNCwibWF4IjpbMTkwNV0sIm1pbiI6WzBdfSx7ImNvbXBvbmVudFR5cGUiOjUxMjYsInR5cGUiOiJWRUMzIiwiYnl0ZU9mZnNldCI6MCwiYnVmZmVyVmlldyI6NywiY291bnQiOjE5MDYsIm1heCI6WzUyLjUsNzcuNSwyMzMuMF0sIm1pbiI6Wy01Mi41LC03Ny41LDIwOS4wXX0seyJjb21wb25lbnRUeXBlIjo1MTIxLCJub3JtYWxpemVkIjp0cnVlLCJ0eXBlIjoiVkVDNCIsImJ5dGVPZmZzZXQiOjAsImJ1ZmZlclZpZXciOjgsImNvdW50IjoxOTA2LCJtYXgiOlsyNTUsMTgzLDMsMjU1XSwibWluIjpbMjU1LDE4MywzLDI1NV19XSwibWVzaGVzIjpbeyJuYW1lIjoiZ2VvbWV0cnlfMCIsImV4dHJhcyI6eyJ1bml0cyI6Im1pbGxpbWV0ZXIiLCJwcm9jZXNzZWQiOnRydWV9LCJwcmltaXRpdmVzIjpbeyJhdHRyaWJ1dGVzIjp7IlBPU0lUSU9OIjoxLCJDT0xPUl8wIjoyfSwiaW5kaWNlcyI6MCwibW9kZSI6NH1dfSx7Im5hbWUiOiJnZW9tZXRyeV8xIiwiZXh0cmFzIjp7InVuaXRzIjoibWlsbGltZXRlciIsInByb2Nlc3NlZCI6dHJ1ZX0sInByaW1pdGl2ZXMiOlt7ImF0dHJpYnV0ZXMiOnsiUE9TSVRJT04iOjQsIkNPTE9SXzAiOjV9LCJpbmRpY2VzIjozLCJtb2RlIjo0fV19LHsibmFtZSI6Imdlb21ldHJ5XzIiLCJleHRyYXMiOnsidW5pdHMiOiJtaWxsaW1ldGVyIiwicHJvY2Vzc2VkIjp0cnVlfSwicHJpbWl0aXZlcyI6W3siYXR0cmlidXRlcyI6eyJQT1NJVElPTiI6NywiQ09MT1JfMCI6OH0sImluZGljZXMiOjYsIm1vZGUiOjR9XX1d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 truncated
loadGLB("Z2xURgIAAABM9QMAOAkAAEpTT057InNjZW5lIjowLCJzY2VuZXMiOlt7Im5vZGVzIjpbMCwxLDJdfV0sImFzc2V0Ijp7InZlcnNpb24iOiIyLjAiLCJnZW5lcmF0b3IiOiJodHRwczovL2dpdGh1Yi5jb20vbWlrZWRoL3RyaW1lc2gifSwiYWNjZXNzb3JzIjpbeyJjb21wb25lbnRUeXBlIjo1MTI1LCJ0eXBlIjoiU0NBTEFSIiwiYnVmZmVyVmlldyI6MCwiY291bnQiOjI1Mzk4LCJtYXgiOls0MjM0XSwibWluIjpbMF19LHsiY29tcG9uZW50VHlwZSI6NTEyNiwidHlwZSI6IlZFQzMiLCJieXRlT2Zmc2V0IjowLCJidWZmZXJWaWV3IjoxLCJjb3VudCI6NDIzNSwibWF4IjpbNjUuNzY2MDA2NDY5NzI2NTYsNzguMDgwNTUxMTQ3NDYwOTQsMjAwLjBdLCJtaW4iOlstNjUuNzY2MDA2NDY5NzI2NTYsLTc4LjA4MDU1MTE0NzQ2MDk0LDAuMF19LHsiY29tcG9uZW50VHlwZSI6NTEyMSwibm9ybWFsaXplZCI6dHJ1ZSwidHlwZSI6IlZFQzQiLCJieXRlT2Zmc2V0IjowLCJidWZmZXJWaWV3IjoyLCJjb3VudCI6NDIzNSwibWF4IjpbMTQyLDIwMiwyMzAsMjU1XSwibWluIjpbMTQyLDIwMiwyMzAsMjU1XX0seyJjb21wb25lbnRUeXBlIjo1MTI1LCJ0eXBlIjoiU0NBTEFSIiwiYnVmZmVyVmlldyI6MywiY291bnQiOjE3MTYsIm1heCI6WzI4N10sIm1pbiI6WzBdfSx7ImNvbXBvbmVudFR5cGUiOjUxMjYsInR5cGUiOiJWRUMzIiwiYnl0ZU9mZnNldCI6MCwiYnVmZmVyVmlldyI6NCwiY291bnQiOjI4OCwibWF4IjpbNTAuMTAwMDA2MTAzNTE1NjI1LDc1LjA5OTk5ODQ3NDEyMTEsMjA5LjBdLCJtaW4iOlstNTAuMTAwMDA2MTAzNTE1NjI1LC03NS4wOTk5OTg0NzQxMjExLDIwMC4wXX0seyJjb21wb25lbnRUeXBlIjo1MTIxLCJub3JtYWxpemVkIjp0cnVlLCJ0eXBlIjoiVkVDNCIsImJ5dGVPZmZzZXQiOjAsImJ1ZmZlclZpZXciOjUsImNvdW50IjoyODgsIm1heCI6WzE2OCwyMTgsMjIwLDI1NV0sIm1pbiI6WzE2OCwyMTgsMjIwLDI1NV19LHsiY29tcG9uZW50VHlwZSI6NTEyNSwidHlwZSI6IlNDQUxBUiIsImJ1ZmZlclZpZXciOjYsImNvdW50IjoxMTQyNCwibWF4IjpbMTkwNV0sIm1pbiI6WzBdfSx7ImNvbXBvbmVudFR5cGUiOjUxMjYsInR5cGUiOiJWRUMzIiwiYnl0ZU9mZnNldCI6MCwiYnVmZmVyVmlldyI6NywiY291bnQiOjE5MDYsIm1heCI6WzUyLjUsNzcuNSwyMzMuMF0sIm1pbiI6Wy01Mi41LC03Ny41LDIwOS4wXX0seyJjb21wb25lbnRUeXBlIjo1MTIxLCJub3JtYWxpemVkIjp0cnVlLCJ0eXBlIjoiVkVDNCIsImJ5dGVPZmZzZXQiOjAsImJ1ZmZlclZpZXciOjgsImNvdW50IjoxOTA2LCJtYXgiOlsyNTUsMTgzLDMsMjU1XSwibWluIjpbMjU1LDE4MywzLDI1NV19XSwibWVzaGVzIjpbeyJuYW1lIjoiZ2VvbWV0cnlfMCIsImV4dHJhcyI6e30sInByaW1pdGl2ZXMiOlt7ImF0dHJpYnV0ZXMiOnsiUE9TSVRJT04iOjEsIkNPTE9SXzAiOjJ9LCJpbmRpY2VzIjowLCJtb2RlIjo0fV19LHsibmFtZSI6Imdlb21ldHJ5XzEiLCJleHRyYXMiOnsidW5pdHMiOiJtaWxsaW1ldGVyIiwicHJvY2Vzc2VkIjp0cnVlfSwicHJpbWl0aXZlcyI6W3siYXR0cmlidXRlcyI6eyJQT1NJVElPTiI6NCwiQ09MT1JfMCI6NX0sImluZGljZXMiOjMsIm1vZGUiOjR9XX0seyJuYW1lIjoiZ2VvbWV0cnlfMiIsImV4dHJhcyI6eyJ1bml0cyI6Im1pbGxpbWV0ZXIiLCJwcm9jZXNzZWQiOnRydWV9LCJwcmltaXRpdmVzIjpbeyJhdHRyaWJ1dGVzIjp7IlBPU0lUSU9OIjo3LCJDT0xPUl8wIjo4fSwiaW5kaWNlcyI6NiwibW9kZSI6NH1dfV0sIm5vZGVzIjpbeyJuYW1lIjoiZ2VvbWV0cnlfMCIsIm1lc2gi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 truncated
]).then(() => {
const box = new THREE.Box3().setFromObject(models.orig);
const c = box.getCenter(new THREE.Vector3());
controls.target.copy(c);
camera.position.copy(c).add(new THREE.Vector3(320, 260, 380));
});
function setMode(mode) {
models.orig.visible = (mode === 'orig');
models.edit.visible = (mode === 'edit');
document.getElementById('btnOrig').classList.toggle('active', mode === 'orig');
document.getElementById('btnEdit').classList.toggle('active', mode === 'edit');
document.getElementById('modeLabel').textContent =
mode === 'orig' ? 'Показан: оригинал' : 'Показан: без боковой конструкции';
}
document.getElementById('btnOrig').onclick = () => setMode('orig');
document.getElementById('btnEdit').onclick = () => setMode('edit');
setMode('orig');
window.addEventListener('resize', () => {
camera.aspect = innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
function animate(){ requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); }
animate();
</script>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

+80
View File
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Органайзер — без боковой конструкции</title>
<style>
body { margin:0; overflow:hidden; font-family:'Segoe UI',Arial,sans-serif; background:#1a1d24; color:#e8e8e8; }
#info { position:absolute; top:12px; left:12px; z-index:10; background:rgba(20,24,32,.85);
padding:10px 14px; border-radius:8px; font-size:13px; line-height:1.5; max-width:300px;
border:1px solid rgba(255,255,255,.12); backdrop-filter:blur(4px); }
#info b { color:#ffb703; }
#hint { position:absolute; bottom:12px; left:50%; transform:translateX(-50%); z-index:10;
font-size:12px; color:#9aa3b2; background:rgba(20,24,32,.7); padding:6px 12px; border-radius:20px; }
canvas { display:block; }
</style>
</head>
<body>
<div id="info"><b>Органайзер — без боковой конструкции</b><br>Коробка: 131.5 × 156.2 × 200 мм<br>Крышка: верх с ушками + низ с защёлками<br>Боковая конструкция удалена</div>
<div id="hint">ЛКМ — вращение · колесо — масштаб · ПКМ — сдвиг</div>
<script type="importmap">
{ "imports": {
"three": "./vendor/three.module.js",
"three/addons/": "./vendor/examples/jsm/"
}}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1d24);
const camera = new THREE.PerspectiveCamera(45, innerWidth/innerHeight, 1, 3000);
camera.position.set(320, 260, 380);
const renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 110, 0);
controls.enableDamping = true;
scene.add(new THREE.HemisphereLight(0xffffff, 0x444455, 0.9));
const sun = new THREE.DirectionalLight(0xffffff, 1.8);
sun.castShadow = true;
scene.add(sun);
scene.add(sun.target);
const fill = new THREE.DirectionalLight(0x88aaff, 0.35);
fill.position.set(-250, 150, -200); scene.add(fill);
const grid = new THREE.GridHelper(400, 20, 0x3a4150, 0x2a2f3a);
grid.position.y = -1; scene.add(grid);
const b64 = "Z2xURgIAAABM9QMAOAkAAEpTT057InNjZW5lIjowLCJzY2VuZXMiOlt7Im5vZGVzIjpbMCwxLDJdfV0sImFzc2V0Ijp7InZlcnNpb24iOiIyLjAiLCJnZW5lcmF0b3IiOiJodHRwczovL2dpdGh1Yi5jb20vbWlrZWRoL3RyaW1lc2gifSwiYWNjZXNzb3JzIjpbeyJjb21wb25lbnRUeXBlIjo1MTI1LCJ0eXBlIjoiU0NBTEFSIiwiYnVmZmVyVmlldyI6MCwiY291bnQiOjI1Mzk4LCJtYXgiOls0MjM0XSwibWluIjpbMF19LHsiY29tcG9uZW50VHlwZSI6NTEyNiwidHlwZSI6IlZFQzMiLCJieXRlT2Zmc2V0IjowLCJidWZmZXJWaWV3IjoxLCJjb3VudCI6NDIzNSwibWF4IjpbNjUuNzY2MDA2NDY5NzI2NTYsNzguMDgwNTUxMTQ3NDYwOTQsMjAwLjBdLCJtaW4iOlstNjUuNzY2MDA2NDY5NzI2NTYsLTc4LjA4MDU1MTE0NzQ2MDk0LDAuMF19LHsiY29tcG9uZW50VHlwZSI6NTEyMSwibm9ybWFsaXplZCI6dHJ1ZSwidHlwZSI6IlZFQzQiLCJieXRlT2Zmc2V0IjowLCJidWZmZXJWaWV3IjoyLCJjb3VudCI6NDIzNSwibWF4IjpbMTQyLDIwMiwyMzAsMjU1XSwibWluIjpbMTQyLDIwMiwyMzAsMjU1XX0seyJjb21wb25lbnRUeXBlIjo1MTI1LCJ0eXBlIjoiU0NBTEFSIiwiYnVmZmVyVmlldyI6MywiY291bnQiOjE3MTYsIm1heCI6WzI4N10sIm1pbiI6WzBdfSx7ImNvbXBvbmVudFR5cGUiOjUxMjYsInR5cGUiOiJWRUMzIiwiYnl0ZU9mZnNldCI6MCwiYnVmZmVyVmlldyI6NCwiY291bnQiOjI4OCwibWF4IjpbNTAuMTAwMDA2MTAzNTE1NjI1LDc1LjA5OTk5ODQ3NDEyMTEsMjA5LjBdLCJtaW4iOlstNTAuMTAwMDA2MTAzNTE1NjI1LC03NS4wOTk5OTg0NzQxMjExLDIwMC4wXX0seyJjb21wb25lbnRUeXBlIjo1MTIxLCJub3JtYWxpemVkIjp0cnVlLCJ0eXBlIjoiVkVDNCIsImJ5dGVPZmZzZXQiOjAsImJ1ZmZlclZpZXciOjUsImNvdW50IjoyODgsIm1heCI6WzE2OCwyMTgsMjIwLDI1NV0sIm1pbiI6WzE2OCwyMTgsMjIwLDI1NV19LHsiY29tcG9uZW50VHlwZSI6NTEyNSwidHlwZSI6IlNDQUxBUiIsImJ1ZmZlclZpZXciOjYsImNvdW50IjoxMTQyNCwibWF4IjpbMTkwNV0sIm1pbiI6WzBdfSx7ImNvbXBvbmVudFR5cGUiOjUxMjYsInR5cGUiOiJWRUMzIiwiYnl0ZU9mZnNldCI6MCwiYnVmZmVyVmlldyI6NywiY291bnQiOjE5MDYsIm1heCI6WzUyLjUsNzcuNSwyMzMuMF0sIm1pbiI6Wy01Mi41LC03Ny41LDIwOS4wXX0seyJjb21wb25lbnRUeXBlIjo1MTIxLCJub3JtYWxpemVkIjp0cnVlLCJ0eXBlIjoiVkVDNCIsImJ5dGVPZmZzZXQiOjAsImJ1ZmZlclZpZXciOjgsImNvdW50IjoxOTA2LCJtYXgiOlsyNTUsMTgzLDMsMjU1XSwibWluIjpbMjU1LDE4MywzLDI1NV19XSwibWVzaGVzIjpbeyJuYW1lIjoiZ2VvbWV0cnlfMCIsImV4dHJhcyI6e30sInByaW1pdGl2ZXMiOlt7ImF0dHJpYnV0ZXMiOnsiUE9TSVRJT04iOjEsIkNPTE9SXzAiOjJ9LCJpbmRpY2VzIjowLCJtb2RlIjo0fV19LHsibmFtZSI6Imdlb21ldHJ5XzEiLCJleHRyYXMiOnsidW5pdHMiOiJtaWxsaW1ldGVyIiwicHJvY2Vzc2VkIjp0cnVlfSwicHJpbWl0aXZlcyI6W3siYXR0cmlidXRlcyI6eyJQT1NJVElPTiI6NCwiQ09MT1JfMCI6NX0sImluZGljZXMiOjMsIm1vZGUiOjR9XX0seyJuYW1lIjoiZ2VvbWV0cnlfMiIsImV4dHJhcyI6eyJ1bml0cyI6Im1pbGxpbWV0ZXIiLCJwcm9jZXNzZWQiOnRydWV9LCJwcmltaXRpdmVzIjpbeyJhdHRyaWJ1dGVzIjp7IlBPU0lUSU9OIjo3LCJDT0xPUl8wIjo4fSwiaW5kaWNlcyI6NiwibW9kZSI6NH1dfV0sIm5vZGVzIjpbeyJuYW1lIjoiZ2VvbWV0cnlfMCIsIm1lc2gi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 truncated
const bin = atob(b64);
const arr = new Uint8Array(bin.length);
for (let i=0;i<bin.length;i++) arr[i] = bin.charCodeAt(i);
new GLTFLoader().parse(arr.buffer, '', (gltf) => {
const model = gltf.scene;
model.traverse(o => { if (o.isMesh) { o.castShadow = true; o.receiveShadow = true; } });
scene.add(model);
const box = new THREE.Box3().setFromObject(model);
const c = box.getCenter(new THREE.Vector3());
controls.target.copy(c);
camera.position.copy(c).add(new THREE.Vector3(320, 260, 380));
}, (e) => console.error(e));
window.addEventListener('resize', () => {
camera.aspect = innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
function animate(){ requestAnimationFrame(animate); controls.update();
sun.position.copy(camera.position).add(new THREE.Vector3(60, 80, 40));
sun.target.position.copy(controls.target);
renderer.render(scene, camera); }
animate();
</script>
</body>
</html>
+80
View File
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Органайзер — оригинал</title>
<style>
body { margin:0; overflow:hidden; font-family:'Segoe UI',Arial,sans-serif; background:#1a1d24; color:#e8e8e8; }
#info { position:absolute; top:12px; left:12px; z-index:10; background:rgba(20,24,32,.85);
padding:10px 14px; border-radius:8px; font-size:13px; line-height:1.5; max-width:300px;
border:1px solid rgba(255,255,255,.12); backdrop-filter:blur(4px); }
#info b { color:#8ecae6; }
#hint { position:absolute; bottom:12px; left:50%; transform:translateX(-50%); z-index:10;
font-size:12px; color:#9aa3b2; background:rgba(20,24,32,.7); padding:6px 12px; border-radius:20px; }
canvas { display:block; }
</style>
</head>
<body>
<div id="info"><b>Органайзер — оригинал</b><br>Коробка: 131.5 × 156.2 × 200 мм<br>Крышка: верх с ушками + низ с защёлками<br>С боковой конструкцией (как в файле)</div>
<div id="hint">ЛКМ — вращение · колесо — масштаб · ПКМ — сдвиг</div>
<script type="importmap">
{ "imports": {
"three": "./vendor/three.module.js",
"three/addons/": "./vendor/examples/jsm/"
}}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1d24);
const camera = new THREE.PerspectiveCamera(45, innerWidth/innerHeight, 1, 3000);
camera.position.set(320, 260, 380);
const renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 110, 0);
controls.enableDamping = true;
scene.add(new THREE.HemisphereLight(0xffffff, 0x444455, 0.9));
const sun = new THREE.DirectionalLight(0xffffff, 1.8);
sun.castShadow = true;
scene.add(sun);
scene.add(sun.target);
const fill = new THREE.DirectionalLight(0x88aaff, 0.35);
fill.position.set(-250, 150, -200); scene.add(fill);
const grid = new THREE.GridHelper(400, 20, 0x3a4150, 0x2a2f3a);
grid.position.y = -1; scene.add(grid);
const b64 = "Z2xURgIAAACw+QMAXAkAAEpTT057InNjZW5lIjowLCJzY2VuZXMiOlt7Im5vZGVzIjpbMCwxLDJdfV0sImFzc2V0Ijp7InZlcnNpb24iOiIyLjAiLCJnZW5lcmF0b3IiOiJodHRwczovL2dpdGh1Yi5jb20vbWlrZWRoL3RyaW1lc2gifSwiYWNjZXNzb3JzIjpbeyJjb21wb25lbnRUeXBlIjo1MTI1LCJ0eXBlIjoiU0NBTEFSIiwiYnVmZmVyVmlldyI6MCwiY291bnQiOjI1NTY2LCJtYXgiOls0MjYwXSwibWluIjpbMF19LHsiY29tcG9uZW50VHlwZSI6NTEyNiwidHlwZSI6IlZFQzMiLCJieXRlT2Zmc2V0IjowLCJidWZmZXJWaWV3IjoxLCJjb3VudCI6NDI2MSwibWF4IjpbNjUuNzY2MDA2NDY5NzI2NTYsNzguMDgwNTUxMTQ3NDYwOTQsMjAwLjBdLCJtaW4iOlstNjUuNzY2MDA2NDY5NzI2NTYsLTc4LjA4MDU1MTE0NzQ2MDk0LDAuMF19LHsiY29tcG9uZW50VHlwZSI6NTEyMSwibm9ybWFsaXplZCI6dHJ1ZSwidHlwZSI6IlZFQzQiLCJieXRlT2Zmc2V0IjowLCJidWZmZXJWaWV3IjoyLCJjb3VudCI6NDI2MSwibWF4IjpbMTQyLDIwMiwyMzAsMjU1XSwibWluIjpbMTQyLDIwMiwyMzAsMjU1XX0seyJjb21wb25lbnRUeXBlIjo1MTI1LCJ0eXBlIjoiU0NBTEFSIiwiYnVmZmVyVmlldyI6MywiY291bnQiOjE3MTYsIm1heCI6WzI4N10sIm1pbiI6WzBdfSx7ImNvbXBvbmVudFR5cGUiOjUxMjYsInR5cGUiOiJWRUMzIiwiYnl0ZU9mZnNldCI6MCwiYnVmZmVyVmlldyI6NCwiY291bnQiOjI4OCwibWF4IjpbNTAuMTAwMDA2MTAzNTE1NjI1LDc1LjA5OTk5ODQ3NDEyMTEsMjA5LjBdLCJtaW4iOlstNTAuMTAwMDA2MTAzNTE1NjI1LC03NS4wOTk5OTg0NzQxMjExLDIwMC4wXX0seyJjb21wb25lbnRUeXBlIjo1MTIxLCJub3JtYWxpemVkIjp0cnVlLCJ0eXBlIjoiVkVDNCIsImJ5dGVPZmZzZXQiOjAsImJ1ZmZlclZpZXciOjUsImNvdW50IjoyODgsIm1heCI6WzE2OCwyMTgsMjIwLDI1NV0sIm1pbiI6WzE2OCwyMTgsMjIwLDI1NV19LHsiY29tcG9uZW50VHlwZSI6NTEyNSwidHlwZSI6IlNDQUxBUiIsImJ1ZmZlclZpZXciOjYsImNvdW50IjoxMTQyNCwibWF4IjpbMTkwNV0sIm1pbiI6WzBdfSx7ImNvbXBvbmVudFR5cGUiOjUxMjYsInR5cGUiOiJWRUMzIiwiYnl0ZU9mZnNldCI6MCwiYnVmZmVyVmlldyI6NywiY291bnQiOjE5MDYsIm1heCI6WzUyLjUsNzcuNSwyMzMuMF0sIm1pbiI6Wy01Mi41LC03Ny41LDIwOS4wXX0seyJjb21wb25lbnRUeXBlIjo1MTIxLCJub3JtYWxpemVkIjp0cnVlLCJ0eXBlIjoiVkVDNCIsImJ5dGVPZmZzZXQiOjAsImJ1ZmZlclZpZXciOjgsImNvdW50IjoxOTA2LCJtYXgiOlsyNTUsMTgzLDMsMjU1XSwibWluIjpbMjU1LDE4MywzLDI1NV19XSwibWVzaGVzIjpbeyJuYW1lIjoiZ2VvbWV0cnlfMCIsImV4dHJhcyI6eyJ1bml0cyI6Im1pbGxpbWV0ZXIiLCJwcm9jZXNzZWQiOnRydWV9LCJwcmltaXRpdmVzIjpbeyJhdHRyaWJ1dGVzIjp7IlBPU0lUSU9OIjoxLCJDT0xPUl8wIjoyfSwiaW5kaWNlcyI6MCwibW9kZSI6NH1dfSx7Im5hbWUiOiJnZW9tZXRyeV8xIiwiZXh0cmFzIjp7InVuaXRzIjoibWlsbGltZXRlciIsInByb2Nlc3NlZCI6dHJ1ZX0sInByaW1pdGl2ZXMiOlt7ImF0dHJpYnV0ZXMiOnsiUE9TSVRJT04iOjQsIkNPTE9SXzAiOjV9LCJpbmRpY2VzIjozLCJtb2RlIjo0fV19LHsibmFtZSI6Imdlb21ldHJ5XzIiLCJleHRyYXMiOnsidW5pdHMiOiJtaWxsaW1ldGVyIiwicHJvY2Vzc2VkIjp0cnVlfSwicHJpbWl0aXZlcyI6W3siYXR0cmlidXRlcyI6eyJQT1NJVElPTiI6NywiQ09MT1JfMCI6OH0sImluZGljZXMiOjYsIm1vZGUiOjR9XX1d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 truncated
const bin = atob(b64);
const arr = new Uint8Array(bin.length);
for (let i=0;i<bin.length;i++) arr[i] = bin.charCodeAt(i);
new GLTFLoader().parse(arr.buffer, '', (gltf) => {
const model = gltf.scene;
model.traverse(o => { if (o.isMesh) { o.castShadow = true; o.receiveShadow = true; } });
scene.add(model);
const box = new THREE.Box3().setFromObject(model);
const c = box.getCenter(new THREE.Vector3());
controls.target.copy(c);
camera.position.copy(c).add(new THREE.Vector3(320, 260, 380));
}, (e) => console.error(e));
window.addEventListener('resize', () => {
camera.aspect = innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
function animate(){ requestAnimationFrame(animate); controls.update();
sun.position.copy(camera.position).add(new THREE.Vector3(60, 80, 40));
sun.target.position.copy(controls.target);
renderer.render(scene, camera); }
animate();
</script>
</body>
</html>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+53044
View File
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.