1
This commit is contained in:
@ -1,160 +1,233 @@
|
|||||||
# SDK 调用示例
|
# SDK 调用示例
|
||||||
|
|
||||||
本目录包含两种 SDK 调用方式的完整示例。
|
本目录包含完整的 SDK 调用示例,展示了两种不同的集成方式。
|
||||||
|
|
||||||
## 📁 文件说明
|
## 📁 文件说明
|
||||||
|
|
||||||
- `example-module.html` - ES Module 方式调用示例
|
### 完整 Demo(推荐)
|
||||||
- `example-global.html` - 全局脚本方式调用示例
|
- `demo-module.html` - **ES Module 方式完整示例**(推荐)
|
||||||
|
- `demo-global.html` - **全局脚本方式完整示例**
|
||||||
|
- `app-global.js` - 全局脚本方式的业务逻辑文件
|
||||||
|
|
||||||
## 🚀 两种调用方式对比
|
### 简单示例
|
||||||
|
- `example-module.html` - ES Module 方式简单示例
|
||||||
|
- `example-global.html` - 全局脚本方式简单示例
|
||||||
|
|
||||||
### 1. ES Module 方式 (推荐)
|
## 🎯 核心设计理念
|
||||||
|
|
||||||
**文件:** `example-module.html`
|
### 业务逻辑与 SDK 解耦
|
||||||
|
|
||||||
**特点:**
|
根目录的 `index.js` 是一个**通用的业务逻辑层**,通过 `initApp(kernel)` 接收外部传入的 kernel 实例,实现了:
|
||||||
- ✅ 现代化的模块化开发方式
|
|
||||||
- ✅ 支持按需导入,代码更清晰
|
✅ **SDK 调用方式无关** - 同一套业务逻辑,适配两种 SDK 引入方式
|
||||||
- ✅ 更好的 IDE 智能提示
|
✅ **代码复用** - 避免重复编写业务逻辑
|
||||||
- ✅ 适合现代前端框架(Vue、React、Angular)
|
✅ **易于维护** - 业务逻辑集中管理,修改一处即可
|
||||||
|
|
||||||
|
**架构图:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 应用层 (HTML) │
|
||||||
|
│ ┌─────────────┐ ┌─────────────┐ │
|
||||||
|
│ │ ES Module │ │ 全局脚本 │ │
|
||||||
|
│ │ 方式引入 │ │ 方式引入 │ │
|
||||||
|
│ └──────┬──────┘ └──────┬──────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ ▼ ▼ │
|
||||||
|
│ ┌─────────────────────────────────┐ │
|
||||||
|
│ │ 通用业务逻辑 (index.js) │ │
|
||||||
|
│ │ - initApp(kernel) │ │
|
||||||
|
│ │ - init() │ │
|
||||||
|
│ │ - getAutoLoadModelList() │ │
|
||||||
|
│ │ - getHotspot() │ │
|
||||||
|
│ │ - getProductConfig() │ │
|
||||||
|
│ └──────────────┬──────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────────────────┐ │
|
||||||
|
│ │ SDK Kernel │ │
|
||||||
|
│ │ (从外部注入) │ │
|
||||||
|
│ └─────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 方式 1: ES Module(推荐)
|
||||||
|
|
||||||
|
**文件:** `demo-module.html`
|
||||||
|
|
||||||
**引入方式:**
|
|
||||||
```html
|
```html
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
// 1. 从 CDN 导入 SDK
|
||||||
import { kernel } from 'https://sdk.zguiy.com/zt/assets/index.js';
|
import { kernel } from 'https://sdk.zguiy.com/zt/assets/index.js';
|
||||||
|
|
||||||
kernel.init(config);
|
// 2. 导入业务逻辑
|
||||||
</script>
|
import { initApp, init, getAutoLoadModelList } from '../index.js';
|
||||||
```
|
|
||||||
|
|
||||||
**适用场景:**
|
|
||||||
- 现代浏览器环境(Chrome 61+, Firefox 60+, Safari 11+, Edge 16+)
|
|
||||||
- 使用构建工具的项目(Vite、Webpack、Rollup)
|
|
||||||
- Vue/React/Angular 等框架项目
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 全局脚本方式 (兼容性好)
|
|
||||||
|
|
||||||
**文件:** `example-global.html`
|
|
||||||
|
|
||||||
**特点:**
|
|
||||||
- ✅ 兼容性最好,支持所有浏览器
|
|
||||||
- ✅ 无需构建工具,直接引入即可使用
|
|
||||||
- ✅ 适合传统 HTML 页面
|
|
||||||
- ⚠️ 会污染全局命名空间
|
|
||||||
|
|
||||||
**引入方式:**
|
|
||||||
```html
|
|
||||||
<script src="https://sdk.zguiy.com/zt/assets/index.global.js"></script>
|
|
||||||
<script>
|
|
||||||
const sdkKernel = window.faceSDK.kernel;
|
|
||||||
|
|
||||||
sdkKernel.init(config);
|
// 3. 注入 kernel 实例
|
||||||
|
initApp(kernel);
|
||||||
|
|
||||||
|
// 4. 初始化应用
|
||||||
|
await init();
|
||||||
|
await getAutoLoadModelList();
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
**适用场景:**
|
**特点:**
|
||||||
- 需要兼容旧版浏览器
|
- ✅ 现代化模块化开发
|
||||||
- 传统 HTML 页面(无构建工具)
|
- ✅ 直接引用根目录的 `index.js`
|
||||||
- 快速原型开发
|
- ✅ 支持 async/await
|
||||||
- 第三方页面集成
|
- ✅ 更好的 IDE 智能提示
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📖 使用方法
|
### 方式 2: 全局脚本
|
||||||
|
|
||||||
### 基础配置
|
**文件:** `demo-global.html` + `app-global.js`
|
||||||
|
|
||||||
两种方式的配置参数完全相同:
|
```html
|
||||||
|
<!-- 1. 引入 SDK -->
|
||||||
|
<script src="https://sdk.zguiy.com/zt/assets/index.global.js"></script>
|
||||||
|
|
||||||
|
<!-- 2. 引入业务逻辑 -->
|
||||||
|
<script src="./app-global.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 3. 获取 SDK kernel
|
||||||
|
var kernel = window.faceSDK.kernel;
|
||||||
|
|
||||||
|
// 4. 注入 kernel 实例
|
||||||
|
window.AppLogic.initApp(kernel);
|
||||||
|
|
||||||
|
// 5. 初始化应用
|
||||||
|
(async function() {
|
||||||
|
await window.AppLogic.init();
|
||||||
|
await window.AppLogic.getAutoLoadModelList();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**特点:**
|
||||||
|
- ✅ 兼容所有浏览器
|
||||||
|
- ✅ 无需构建工具
|
||||||
|
- ✅ 使用 `app-global.js`(根目录 `index.js` 的全局脚本版本)
|
||||||
|
- ⚠️ 需要维护两份业务逻辑文件
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 业务逻辑 API
|
||||||
|
|
||||||
|
### 初始化
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const config = {
|
// 注入 kernel 实例(必须首先调用)
|
||||||
// 必填:渲染容器 ID
|
initApp(kernel)
|
||||||
container: 'renderDom',
|
|
||||||
|
// 初始化 SDK 配置
|
||||||
// 方式1:自动加载模型(从后端 API 获取)
|
await init(customConfig?)
|
||||||
autoLoadModels: true,
|
|
||||||
autoLoadModelsUrl: 'https://ztserver.zguiy.com/api/models/auto-load/list',
|
// 加载模型列表
|
||||||
|
await getAutoLoadModelList()
|
||||||
// 方式2:手动指定模型列表
|
|
||||||
// modelUrlList: [
|
|
||||||
// 'https://sdk.zguiy.com/resurces/model/model1.glb',
|
|
||||||
// 'https://sdk.zguiy.com/resurces/model/model2.glb'
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// 环境配置
|
|
||||||
env: {
|
|
||||||
envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env',
|
|
||||||
intensity: 1.2, // 环境光强度
|
|
||||||
rotationY: 0.3 // 环境贴图旋转角度
|
|
||||||
},
|
|
||||||
|
|
||||||
// 相机配置
|
|
||||||
camera: {
|
|
||||||
alpha: Math.PI / 2, // 水平角度
|
|
||||||
beta: Math.PI / 3, // 垂直角度
|
|
||||||
radius: 50 // 距离
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 产品配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 获取产品配置并应用
|
||||||
|
await getProductConfig(sku)
|
||||||
|
|
||||||
|
// 获取放置区域
|
||||||
|
await getPlacementZone(sku)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 热点管理
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 加载并渲染热点
|
||||||
|
await getHotspot()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 事件处理
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 执行放置区域事件
|
||||||
|
await getEvent(dropzone_data, sku)
|
||||||
|
|
||||||
|
// 执行产品切换事件
|
||||||
|
await executeEvent2(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 完整 Demo 功能
|
||||||
|
|
||||||
|
两个完整 demo(`demo-module.html` 和 `demo-global.html`)都包含:
|
||||||
|
|
||||||
|
### UI 功能
|
||||||
|
- ✅ 3D 模型渲染画布
|
||||||
|
- ✅ 配置面板(产品选择、热点控制)
|
||||||
|
- ✅ 加载进度显示
|
||||||
|
- ✅ 点击信息提示
|
||||||
|
|
||||||
|
### 交互功能
|
||||||
|
- ✅ 产品切换
|
||||||
|
- ✅ 热点加载
|
||||||
|
- ✅ 模型点击事件
|
||||||
|
- ✅ 热点点击事件
|
||||||
|
|
||||||
### 事件监听
|
### 事件监听
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 模型加载进度
|
// 加载进度
|
||||||
kernel.on('model:load:progress', (data) => {
|
kernel.on('model:load:progress', (data) => {
|
||||||
console.log('加载进度:', data.progress); // 0-1
|
console.log('进度:', data.progress);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 模型加载完成
|
// 加载完成
|
||||||
kernel.on('model:loaded', (data) => {
|
kernel.on('model:loaded', (data) => {
|
||||||
console.log('加载完成:', data.models);
|
console.log('模型列表:', data.models);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 模型点击事件
|
// 模型点击
|
||||||
kernel.on('model:click', (data) => {
|
kernel.on('model:click', (data) => {
|
||||||
console.log('点击:', data.meshName, data.position);
|
console.log('点击:', data.meshName);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 热点点击事件
|
// 热点点击
|
||||||
kernel.on('hotspot:click', (event) => {
|
kernel.on('hotspot:click', (event) => {
|
||||||
console.log('热点:', event.name, event.payload);
|
console.log('热点:', event.name);
|
||||||
});
|
|
||||||
|
|
||||||
// 错误事件
|
|
||||||
kernel.on('error', (error) => {
|
|
||||||
console.error('错误:', error.message);
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌐 在线演示
|
## 🔧 本地运行
|
||||||
|
|
||||||
### 本地运行
|
### ES Module 方式
|
||||||
|
|
||||||
1. **ES Module 方式:**
|
```bash
|
||||||
```bash
|
# 需要本地服务器(ES Module 不支持 file:// 协议)
|
||||||
# 需要本地服务器(因为 ES Module 不支持 file:// 协议)
|
cd examples
|
||||||
npx serve .
|
npx serve .
|
||||||
# 访问 http://localhost:3000/example-module.html
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **全局脚本方式:**
|
# 访问 http://localhost:3000/demo-module.html
|
||||||
```bash
|
```
|
||||||
# 可以直接双击打开,或使用本地服务器
|
|
||||||
# 双击 example-global.html 即可
|
|
||||||
```
|
|
||||||
|
|
||||||
### 部署到服务器
|
### 全局脚本方式
|
||||||
|
|
||||||
将示例文件上传到任意 Web 服务器即可访问。
|
```bash
|
||||||
|
# 可以直接双击打开
|
||||||
|
# 或使用本地服务器
|
||||||
|
cd examples
|
||||||
|
npx serve .
|
||||||
|
|
||||||
|
# 访问 http://localhost:3000/demo-global.html
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔧 集成到项目
|
## 📦 集成到项目
|
||||||
|
|
||||||
### Vue 3 项目
|
### Vue 3 项目
|
||||||
|
|
||||||
@ -166,15 +239,17 @@ kernel.on('error', (error) => {
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { kernel } from 'https://sdk.zguiy.com/zt/assets/index.js';
|
import { kernel } from 'https://sdk.zguiy.com/zt/assets/index.js';
|
||||||
|
import { initApp, init, getAutoLoadModelList } from '@/utils/app-logic.js';
|
||||||
|
|
||||||
const canvasRef = ref(null);
|
const canvasRef = ref(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
kernel.init({
|
// 注入 kernel
|
||||||
container: canvasRef.value,
|
initApp(kernel);
|
||||||
autoLoadModels: true,
|
|
||||||
autoLoadModelsUrl: 'https://ztserver.zguiy.com/api/models/auto-load/list'
|
// 初始化
|
||||||
});
|
await init({ container: canvasRef.value });
|
||||||
|
await getAutoLoadModelList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
@ -188,13 +263,18 @@ function ModelViewer() {
|
|||||||
const canvasRef = useRef(null);
|
const canvasRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
import('https://sdk.zguiy.com/zt/assets/index.js').then(({ kernel }) => {
|
(async () => {
|
||||||
kernel.init({
|
// 动态导入 SDK
|
||||||
container: canvasRef.current,
|
const { kernel } = await import('https://sdk.zguiy.com/zt/assets/index.js');
|
||||||
autoLoadModels: true,
|
const { initApp, init, getAutoLoadModelList } = await import('./app-logic.js');
|
||||||
autoLoadModelsUrl: 'https://ztserver.zguiy.com/api/models/auto-load/list'
|
|
||||||
});
|
// 注入 kernel
|
||||||
});
|
initApp(kernel);
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
await init({ container: canvasRef.current });
|
||||||
|
await getAutoLoadModelList();
|
||||||
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <canvas ref={canvasRef} />;
|
return <canvas ref={canvasRef} />;
|
||||||
@ -213,12 +293,15 @@ function ModelViewer() {
|
|||||||
<canvas id="renderDom"></canvas>
|
<canvas id="renderDom"></canvas>
|
||||||
|
|
||||||
<script src="https://sdk.zguiy.com/zt/assets/index.global.js"></script>
|
<script src="https://sdk.zguiy.com/zt/assets/index.global.js"></script>
|
||||||
|
<script src="./app-global.js"></script>
|
||||||
<script>
|
<script>
|
||||||
window.faceSDK.kernel.init({
|
var kernel = window.faceSDK.kernel;
|
||||||
container: 'renderDom',
|
window.AppLogic.initApp(kernel);
|
||||||
autoLoadModels: true,
|
|
||||||
autoLoadModelsUrl: 'https://ztserver.zguiy.com/api/models/auto-load/list'
|
(async function() {
|
||||||
});
|
await window.AppLogic.init();
|
||||||
|
await window.AppLogic.getAutoLoadModelList();
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -226,58 +309,68 @@ function ModelViewer() {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📚 API 文档
|
## 🔄 业务逻辑文件说明
|
||||||
|
|
||||||
### 初始化
|
### 根目录 `index.js` (ES Module 版本)
|
||||||
|
|
||||||
```javascript
|
- 使用 ES6+ 语法
|
||||||
kernel.init(config)
|
- 支持 `import/export`
|
||||||
```
|
- 适用于现代构建工具
|
||||||
|
- **推荐用于新项目**
|
||||||
|
|
||||||
### 事件系统
|
### `app-global.js` (全局脚本版本)
|
||||||
|
|
||||||
```javascript
|
- 使用 ES5 语法
|
||||||
kernel.on(eventName, callback) // 监听事件
|
- 通过 IIFE 包装
|
||||||
kernel.off(eventName, callback) // 取消监听
|
- 暴露到 `window.AppLogic`
|
||||||
kernel.emit(eventName, data) // 触发事件
|
- 适用于传统 HTML 页面
|
||||||
```
|
|
||||||
|
|
||||||
### 可用事件
|
**两个文件功能完全相同,只是语法不同。**
|
||||||
|
|
||||||
| 事件名 | 说明 | 回调参数 |
|
|
||||||
|--------|------|----------|
|
|
||||||
| `model:load:progress` | 模型加载进度 | `{ progress: number }` |
|
|
||||||
| `model:loaded` | 模型加载完成 | `{ models: Array }` |
|
|
||||||
| `model:click` | 模型点击 | `{ meshName, position, materialName }` |
|
|
||||||
| `hotspot:click` | 热点点击 | `{ id, name, payload }` |
|
|
||||||
| `error` | 错误 | `{ message, code }` |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ❓ 常见问题
|
## ❓ 常见问题
|
||||||
|
|
||||||
### Q: ES Module 方式报错 "CORS policy"?
|
### Q: 为什么需要两个业务逻辑文件?
|
||||||
**A:** ES Module 必须通过 HTTP(S) 协议访问,不能使用 `file://` 协议。请使用本地服务器(如 `npx serve`)。
|
|
||||||
|
|
||||||
### Q: 全局脚本方式找不到 `window.faceSDK`?
|
**A:** 因为 ES Module 和全局脚本的语法不兼容:
|
||||||
**A:** 确保 `index.global.js` 已完全加载。可以在 `<script>` 标签中添加 `onload` 事件:
|
- ES Module 使用 `import/export`,不能在全局脚本中使用
|
||||||
```html
|
- 全局脚本需要通过 `window` 对象暴露 API
|
||||||
<script src="https://sdk.zguiy.com/zt/assets/index.global.js" onload="initSDK()"></script>
|
|
||||||
```
|
如果你的项目只使用一种方式,只需要维护对应的文件即可。
|
||||||
|
|
||||||
### Q: 如何选择使用哪种方式?
|
### Q: 如何选择使用哪种方式?
|
||||||
|
|
||||||
**A:**
|
**A:**
|
||||||
- 现代项目 + 构建工具 → 使用 **ES Module**
|
- **现代项目 + 构建工具** → 使用 ES Module (`demo-module.html`)
|
||||||
- 传统 HTML 页面 + 无构建工具 → 使用 **全局脚本**
|
- **传统 HTML + 无构建工具** → 使用全局脚本 (`demo-global.html`)
|
||||||
- 需要兼容旧浏览器 → 使用 **全局脚本**
|
- **需要兼容旧浏览器** → 使用全局脚本
|
||||||
|
|
||||||
|
### Q: 可以只维护一份业务逻辑吗?
|
||||||
|
|
||||||
|
**A:** 可以!有两种方案:
|
||||||
|
1. **只用 ES Module**:使用构建工具(如 Vite)将 ES Module 转换为全局脚本
|
||||||
|
2. **只用全局脚本**:所有项目都使用 `app-global.js`
|
||||||
|
|
||||||
|
### Q: `initApp(kernel)` 必须调用吗?
|
||||||
|
|
||||||
|
**A:** 是的!这是核心设计:
|
||||||
|
```javascript
|
||||||
|
// ❌ 错误 - 直接调用会报错
|
||||||
|
await init(); // Error: 请先调用 initApp(kernel)
|
||||||
|
|
||||||
|
// ✅ 正确 - 先注入 kernel
|
||||||
|
initApp(kernel);
|
||||||
|
await init();
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📞 技术支持
|
## 📚 相关资源
|
||||||
|
|
||||||
- SDK 文档: https://sdk.zguiy.com/docs
|
- SDK 文档: https://sdk.zguiy.com/docs
|
||||||
- 后端 API: https://ztserver.zguiy.com
|
- 后端 API: https://ztserver.zguiy.com
|
||||||
- 问题反馈: [GitHub Issues](https://github.com/your-repo/issues)
|
- GitHub: [项目地址]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
369
examples/app-global copy.js
Normal file
369
examples/app-global copy.js
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
/**
|
||||||
|
* 业务逻辑 - 全局脚本版本
|
||||||
|
* 适配全局脚本方式调用
|
||||||
|
*/
|
||||||
|
(function (window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// API 配置
|
||||||
|
var apiConfig = {
|
||||||
|
baseUrl: 'https://ztserver.zguiy.com',
|
||||||
|
getApiUrl: function (path) {
|
||||||
|
return this.baseUrl + path;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 存储 kernel 实例
|
||||||
|
var kernelInstance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化应用逻辑 - 注入 kernel 实例
|
||||||
|
*/
|
||||||
|
function initApp(kernel) {
|
||||||
|
if (!kernel) {
|
||||||
|
throw new Error('kernel 实例是必需的');
|
||||||
|
}
|
||||||
|
kernelInstance = kernel;
|
||||||
|
console.log('应用逻辑已初始化,kernel 实例已注入');
|
||||||
|
return kernelInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前 kernel 实例
|
||||||
|
*/
|
||||||
|
function getKernel() {
|
||||||
|
if (!kernelInstance) {
|
||||||
|
throw new Error('请先调用 initApp(kernel) 初始化 kernel 实例');
|
||||||
|
}
|
||||||
|
return kernelInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
async function init(customConfig) {
|
||||||
|
var kernel = getKernel();
|
||||||
|
customConfig = customConfig || {};
|
||||||
|
|
||||||
|
var defaultConfig = {
|
||||||
|
container: document.querySelector('#renderDom'),
|
||||||
|
modelUrlList: [],
|
||||||
|
env: {
|
||||||
|
envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env',
|
||||||
|
intensity: 1.2,
|
||||||
|
rotationY: 0.3,
|
||||||
|
background: true
|
||||||
|
},
|
||||||
|
gizmo: {
|
||||||
|
position: true,
|
||||||
|
rotation: true,
|
||||||
|
scale: true
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
enable: true,
|
||||||
|
color: "#2196F3",
|
||||||
|
thickness: 1,
|
||||||
|
occlusionStrength: 0.1,
|
||||||
|
occlusionThreshold: 0.0002
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 合并配置
|
||||||
|
var config = Object.assign({}, defaultConfig, customConfig);
|
||||||
|
kernel.init(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化加载模型
|
||||||
|
*/
|
||||||
|
async function getAutoLoadModelList() {
|
||||||
|
var kernel = getKernel();
|
||||||
|
|
||||||
|
var url = apiConfig.getApiUrl('/api/models/auto-load/list');
|
||||||
|
console.log('API URL:', url);
|
||||||
|
|
||||||
|
var response = await fetch(url);
|
||||||
|
var data = await response.json();
|
||||||
|
var models = data.data;
|
||||||
|
|
||||||
|
models.forEach(function (model) {
|
||||||
|
console.log(model.placement_zone);
|
||||||
|
if (model.placement_zone) {
|
||||||
|
var placementZone = model.placement_zone;
|
||||||
|
kernel.dropZone.setData({
|
||||||
|
color: placementZone.color,
|
||||||
|
alpha: +placementZone.alpha,
|
||||||
|
thickness: placementZone.thickness,
|
||||||
|
showBorder: !placementZone.show_border,
|
||||||
|
borderColor: placementZone.border_color,
|
||||||
|
walls: placementZone.walls
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel.model.add({
|
||||||
|
modelId: model.category,
|
||||||
|
modelUrl: model.file_url,
|
||||||
|
modelControlType: model.model_control_type
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取放置区域
|
||||||
|
*/
|
||||||
|
async function getPlacementZone(sku) {
|
||||||
|
var kernel = getKernel();
|
||||||
|
|
||||||
|
var response = await fetch(apiConfig.getApiUrl('/api/product-configs/by-sku/' + sku));
|
||||||
|
var result = await response.json();
|
||||||
|
|
||||||
|
if (result.code === 200) {
|
||||||
|
var data = result.data;
|
||||||
|
var enable_placement_zone = data.enable_placement_zone;
|
||||||
|
var wall_divisions = data.wall_divisions;
|
||||||
|
|
||||||
|
if (enable_placement_zone && wall_divisions !== undefined) {
|
||||||
|
kernel.dropZone.clearZones();
|
||||||
|
|
||||||
|
var divisions = wall_divisions.map(function (wall) {
|
||||||
|
return {
|
||||||
|
name: wall.name,
|
||||||
|
divisions: wall.divisions
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
kernel.dropZone.updateDivisions(divisions);
|
||||||
|
kernel.dropZone.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行事件
|
||||||
|
*/
|
||||||
|
async function getEvent(dropzone_data, sku) {
|
||||||
|
console.log(sku);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var response = await fetch(apiConfig.getApiUrl('/api/product-configs/by-sku/' + sku));
|
||||||
|
var result = await response.json();
|
||||||
|
|
||||||
|
if (result.code === 200 && result.data) {
|
||||||
|
console.log('SKU配置数据:', result.data);
|
||||||
|
console.log('关联事件:', result.data.events);
|
||||||
|
|
||||||
|
await executeEvent(dropzone_data, result);
|
||||||
|
} else {
|
||||||
|
console.log('未查询到数据');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('查询SKU配置或替换模型失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击放置区域执行事件
|
||||||
|
*/
|
||||||
|
async function executeEvent(dropzone_data, result) {
|
||||||
|
var kernel = getKernel();
|
||||||
|
|
||||||
|
var wallName = dropzone_data.wallName;
|
||||||
|
var index = dropzone_data.index;
|
||||||
|
var transform = dropzone_data.transform;
|
||||||
|
var position = transform.position;
|
||||||
|
var rotation = transform.rotation;
|
||||||
|
|
||||||
|
var events = result.data.events;
|
||||||
|
for (var i = 0; i < events.length; i++) {
|
||||||
|
var event = events[i];
|
||||||
|
|
||||||
|
if (event.event_type === 'change_model') {
|
||||||
|
console.log(event);
|
||||||
|
|
||||||
|
var targetData = event.target_data;
|
||||||
|
var id = targetData.id;
|
||||||
|
var name = targetData.name;
|
||||||
|
var file_url = targetData.file_url;
|
||||||
|
var model_control_type = targetData.model_control_type;
|
||||||
|
var category = targetData.category;
|
||||||
|
|
||||||
|
console.log('替换百叶模型:', event);
|
||||||
|
console.log('替换百叶模型类型:', category);
|
||||||
|
|
||||||
|
var modelId = id + '_' + Date.now();
|
||||||
|
|
||||||
|
kernel.dropZone.recordModelPlacement(wallName, index, modelId);
|
||||||
|
|
||||||
|
await kernel.model.add({
|
||||||
|
modelId: modelId,
|
||||||
|
modelUrl: file_url,
|
||||||
|
modelControlType: model_control_type,
|
||||||
|
drag: {
|
||||||
|
enable: true,
|
||||||
|
axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z',
|
||||||
|
step: 0.1
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
position: position,
|
||||||
|
rotation: rotation
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('百叶模型已放置为 ' + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.event_type === 'change_color') {
|
||||||
|
var materialName = event.material_name;
|
||||||
|
var colorData = event.target_data;
|
||||||
|
|
||||||
|
console.log('替换百叶模型颜色:', colorData);
|
||||||
|
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: colorData.color,
|
||||||
|
albedoTexture: colorData.color_map_url,
|
||||||
|
normalMap: colorData.normal_map_url,
|
||||||
|
metallic: colorData.metallic,
|
||||||
|
roughness: colorData.roughness
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('百叶模型颜色已替换为 ' + colorData.color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 换棚子
|
||||||
|
*/
|
||||||
|
const executeEvent2 = async (result) => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
|
const { wallName, index, transform } = dropzone_data;
|
||||||
|
const { position, rotation } = transform;
|
||||||
|
|
||||||
|
for (const event of result.data.events) {
|
||||||
|
if (event.event_type === 'change_model') {
|
||||||
|
console.log(event.target_data);
|
||||||
|
|
||||||
|
const { id, name, file_url, model_control_type, category } = event.target_data;
|
||||||
|
console.log('替换百叶模型:', event);
|
||||||
|
console.log('替换百叶模型类型:', category);
|
||||||
|
|
||||||
|
// 生成唯一的模型ID
|
||||||
|
const modelId = id + '_' + Date.now();
|
||||||
|
|
||||||
|
// 先记录模型放置(会自动处理替换逻辑)
|
||||||
|
kernel.dropZone.recordModelPlacement(wallName, index, modelId);
|
||||||
|
console.log(Math.abs(rotation.y - 90), Math.abs(rotation.y - 90) > 5 ? 'x' : 'z');
|
||||||
|
// 加载并放置模型
|
||||||
|
await kernel.model.add({
|
||||||
|
modelId: modelId,
|
||||||
|
modelUrl: file_url,
|
||||||
|
modelControlType: model_control_type,
|
||||||
|
drag: {
|
||||||
|
enable: true,
|
||||||
|
axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z',
|
||||||
|
step: 0.1,
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
position: position,
|
||||||
|
rotation: rotation,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`百叶模型已放置为 ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.event_type === 'change_color') {
|
||||||
|
const materialName = event.material_name;
|
||||||
|
const { color, color_map_url, normal_map_url, metallic, roughness } = event.target_data;
|
||||||
|
console.log('替换百叶模型颜色:', event.target_data);
|
||||||
|
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: color,
|
||||||
|
albedoTexture: color_map_url,
|
||||||
|
normalMap: normal_map_url,
|
||||||
|
metallic: metallic,
|
||||||
|
roughness: roughness
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`百叶模型颜色已替换为 ${color}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载热点
|
||||||
|
*/
|
||||||
|
async function getHotspot() {
|
||||||
|
var kernel = getKernel();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var response = await fetch(apiConfig.getApiUrl('/api/hotspots?status=active&page=1&pageSize=100'));
|
||||||
|
var result = await response.json();
|
||||||
|
|
||||||
|
if (result.code === 200 && result.data.list.length > 0) {
|
||||||
|
var hotspots = result.data.list.map(function (item) {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
type: 'hotspot',
|
||||||
|
name: item.name,
|
||||||
|
meshName: item.name,
|
||||||
|
icon: item.image_url,
|
||||||
|
position: [item.position_x, item.position_y, item.position_z],
|
||||||
|
radius: item.radius,
|
||||||
|
color: "#000000",
|
||||||
|
payload: {
|
||||||
|
skus: item.skus || []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
kernel.hotspot.render(hotspots);
|
||||||
|
console.log('热点渲染成功:', hotspots);
|
||||||
|
} else {
|
||||||
|
console.log('没有可用的热点数据');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取热点数据失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取产品配置
|
||||||
|
*/
|
||||||
|
async function getProductConfig(sku) {
|
||||||
|
try {
|
||||||
|
var response = await fetch(apiConfig.getApiUrl('/api/product-configs/by-sku/' + sku));
|
||||||
|
var result = await response.json();
|
||||||
|
|
||||||
|
if (result.code === 200) {
|
||||||
|
console.log(result.data);
|
||||||
|
var enable_placement_zone = result.data.enable_placement_zone;
|
||||||
|
|
||||||
|
if (enable_placement_zone) {
|
||||||
|
getPlacementZone(sku);
|
||||||
|
} else {
|
||||||
|
executeEvent2(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取产品配置失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露到全局
|
||||||
|
window.AppLogic = {
|
||||||
|
initApp: initApp,
|
||||||
|
init: init,
|
||||||
|
getAutoLoadModelList: getAutoLoadModelList,
|
||||||
|
getPlacementZone: getPlacementZone,
|
||||||
|
getEvent: getEvent,
|
||||||
|
executeEvent: executeEvent,
|
||||||
|
executeEvent2: executeEvent2,
|
||||||
|
getHotspot: getHotspot,
|
||||||
|
getProductConfig: getProductConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
})(window);
|
||||||
342
examples/app-global.js
Normal file
342
examples/app-global.js
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
// 存储 kernel 实例
|
||||||
|
let kernelInstance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化应用逻辑 - 注入 kernel 实例
|
||||||
|
* @param {Object} kernel - SDK kernel 实例
|
||||||
|
* @returns {Object} kernel 实例
|
||||||
|
*/
|
||||||
|
const initApp = (kernel) => {
|
||||||
|
if (!kernel) {
|
||||||
|
throw new Error('kernel 实例是必需的');
|
||||||
|
}
|
||||||
|
kernelInstance = kernel;
|
||||||
|
console.log('应用逻辑已初始化,kernel 实例已注入');
|
||||||
|
return kernelInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前 kernel 实例
|
||||||
|
*/
|
||||||
|
const getKernel = () => {
|
||||||
|
if (!kernelInstance) {
|
||||||
|
throw new Error('请先调用 initApp(kernel) 初始化 kernel 实例');
|
||||||
|
}
|
||||||
|
return kernelInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
//初始化
|
||||||
|
const init = async (customConfig = {}) => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
container: document.querySelector('#renderDom'),
|
||||||
|
modelUrlList: [],
|
||||||
|
env: { envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env', intensity: 1.2, rotationY: 0.3, background: true },
|
||||||
|
gizmo: {
|
||||||
|
position: false,
|
||||||
|
rotation: false,
|
||||||
|
scale: false
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
enable: true,
|
||||||
|
color: "#2196F3",
|
||||||
|
thickness: 1,
|
||||||
|
occlusionStrength: 0.1,
|
||||||
|
occlusionThreshold: 0.0002
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 合并用户自定义配置
|
||||||
|
const config = { ...defaultConfig, ...customConfig };
|
||||||
|
kernel.init(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
//初始化加载模型
|
||||||
|
const getAutoLoadModelList = async () => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
|
const url = getApiUrl('/api/models/auto-load/list')
|
||||||
|
console.log('API URL:', url)
|
||||||
|
const response = await fetch(url)
|
||||||
|
const data = await response.json()
|
||||||
|
const models = data.data // 这就是模型列表
|
||||||
|
|
||||||
|
models.forEach(model => {
|
||||||
|
console.log(model.placement_zone);
|
||||||
|
if (model.placement_zone) {
|
||||||
|
const { alpha, border_color, color, show_border, thickness, walls } = model.placement_zone
|
||||||
|
kernel.dropZone.setData({
|
||||||
|
|
||||||
|
color: color,
|
||||||
|
alpha: +alpha,
|
||||||
|
thickness: thickness,
|
||||||
|
showBorder: !show_border,
|
||||||
|
borderColor: border_color,
|
||||||
|
walls: walls
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel.model.add({
|
||||||
|
modelId: model.category,
|
||||||
|
modelUrl: model.file_url,
|
||||||
|
modelControlType: model.model_control_type,
|
||||||
|
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取放置区域
|
||||||
|
const getPlacementZone = async (sku) => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
|
const response = await fetch(getApiUrl(`/api/product-configs/by-sku/${sku}`));
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.code === 200) {
|
||||||
|
// await initPlacementZoneConfig();
|
||||||
|
const { enable_placement_zone, wall_divisions } = result.data;
|
||||||
|
// const {position_x, position_y, position_z} = data;
|
||||||
|
if (enable_placement_zone && wall_divisions != undefined) {
|
||||||
|
|
||||||
|
// 只清除旧的放置区域网格,不清除模型
|
||||||
|
kernel.dropZone.clearZones();
|
||||||
|
const divisions = wall_divisions.map(wall => ({
|
||||||
|
name: wall.name, // 获取最后一个下划线后的部分
|
||||||
|
divisions: wall.divisions
|
||||||
|
}))
|
||||||
|
|
||||||
|
kernel.dropZone.updateDivisions(divisions);
|
||||||
|
// 显示放置区域
|
||||||
|
kernel.dropZone.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//执行事件
|
||||||
|
const getEvent = async (dropzone_data, sku) => {
|
||||||
|
// 将模型放置到该区域
|
||||||
|
try {
|
||||||
|
const response = await fetch(getApiUrl(`/api/product-configs/by-sku/${sku}`));
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.code === 200 && result.data) {
|
||||||
|
console.log('SKU配置数据:', result.data);
|
||||||
|
console.log('关联事件:', result.data.events);
|
||||||
|
|
||||||
|
// 使用 for...of 循环以支持 await
|
||||||
|
await executeEvent(dropzone_data, result)
|
||||||
|
} else {
|
||||||
|
console.log(`未查询到数据`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`查询SKU配置或替换模型失败:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//点击放置区域执行事件
|
||||||
|
const executeEvent = async (dropzone_data, result) => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
|
const { wallName, index, transform } = dropzone_data;
|
||||||
|
const { position, rotation } = transform;
|
||||||
|
|
||||||
|
for (const event of result.data.events) {
|
||||||
|
if (event.event_type === 'change_model') {
|
||||||
|
console.log(event.target_data);
|
||||||
|
|
||||||
|
const { id, name, file_url, model_control_type, category } = event.target_data;
|
||||||
|
console.log('替换百叶模型:', event);
|
||||||
|
console.log('替换百叶模型类型:', category);
|
||||||
|
|
||||||
|
// 生成唯一的模型ID
|
||||||
|
const modelId = id + '_' + Date.now();
|
||||||
|
|
||||||
|
// 先记录模型放置(会自动处理替换逻辑)
|
||||||
|
kernel.dropZone.recordModelPlacement(wallName, index, modelId);
|
||||||
|
console.log(Math.abs(rotation.y - 90), Math.abs(rotation.y - 90) > 5 ? 'x' : 'z');
|
||||||
|
// 加载并放置模型
|
||||||
|
await kernel.model.add({
|
||||||
|
modelId: modelId,
|
||||||
|
modelUrl: file_url,
|
||||||
|
modelControlType: model_control_type,
|
||||||
|
drag: {
|
||||||
|
enable: true,
|
||||||
|
axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z',
|
||||||
|
step: 0.1,
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
position: position,
|
||||||
|
rotation: rotation,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`百叶模型已放置为 ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.event_type === 'change_color') {
|
||||||
|
const materialName = event.material_name;
|
||||||
|
const { color, color_map_url, normal_map_url, metallic, roughness } = event.target_data;
|
||||||
|
console.log('替换百叶模型颜色:', event.target_data);
|
||||||
|
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: color,
|
||||||
|
albedoTexture: color_map_url,
|
||||||
|
normalMap: normal_map_url,
|
||||||
|
metallic: metallic,
|
||||||
|
roughness: roughness
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`百叶模型颜色已替换为 ${color}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//换棚子
|
||||||
|
const executeEvent2 = async (result) => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
|
// 检查是否有模型更换事件
|
||||||
|
const hasModelChange = result.data.events.some(e => e.event_type === 'change_model');
|
||||||
|
|
||||||
|
// 只有在需要更换模型时才清除
|
||||||
|
if (hasModelChange) {
|
||||||
|
kernel.dropZone.clearZones();
|
||||||
|
kernel.model.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先处理所有 change_model 事件
|
||||||
|
for (const event of result.data.events) {
|
||||||
|
if (event.event_type === 'change_model') {
|
||||||
|
const { target_data } = event;
|
||||||
|
console.log(event.target_data);
|
||||||
|
if (!target_data) {
|
||||||
|
console.error('change_model事件缺少target_data')
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { id, name, file_url, model_control_type, category, placement_zone } = target_data;
|
||||||
|
console.log('替换百叶模型:', event);
|
||||||
|
console.log('替换百叶模型类型:', category);
|
||||||
|
|
||||||
|
if (placement_zone) {
|
||||||
|
const { alpha, border_color, color, show_border, thickness, walls } = placement_zone
|
||||||
|
kernel.dropZone.setData({
|
||||||
|
color: color,
|
||||||
|
alpha: +alpha,
|
||||||
|
thickness: thickness,
|
||||||
|
showBorder: !show_border,
|
||||||
|
borderColor: border_color,
|
||||||
|
walls: walls
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载并放置模型(使用 category 作为 modelId)
|
||||||
|
await kernel.model.add({
|
||||||
|
modelId: category,
|
||||||
|
modelUrl: file_url,
|
||||||
|
modelControlType: model_control_type,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`百叶模型已放置为 ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待模型加载完成后,再处理 change_color 事件
|
||||||
|
for (const event of result.data.events) {
|
||||||
|
if (event.event_type === 'change_color') {
|
||||||
|
const materialName = event.material_name;
|
||||||
|
const { color, color_map_url, normal_map_url, metallic, roughness } = event.target_data;
|
||||||
|
console.log('替换百叶模型颜色:', event.target_data);
|
||||||
|
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: color,
|
||||||
|
albedoTexture: color_map_url,
|
||||||
|
normalMap: normal_map_url,
|
||||||
|
metallic: metallic,
|
||||||
|
roughness: roughness
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`百叶模型颜色已替换为 ${color}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//加载热点
|
||||||
|
const getHotspot = async () => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从后端获取激活状态的热点列表
|
||||||
|
const response = await fetch(getApiUrl('/api/hotspots?status=active&page=1&pageSize=100'));
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.code === 200 && result.data.list.length > 0) {
|
||||||
|
// 将后端数据转换为 SDK 需要的格式
|
||||||
|
const hotspots = result.data.list.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
type: 'hotspot',
|
||||||
|
name: item.name,
|
||||||
|
meshName: item.name, // 可以根据实际情况调整
|
||||||
|
icon: item.image_url,
|
||||||
|
position: [item.position_x, item.position_y, item.position_z],
|
||||||
|
radius: item.radius,
|
||||||
|
color: "#000000",
|
||||||
|
payload: {
|
||||||
|
skus: item.skus || [],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 渲染热点
|
||||||
|
kernel.hotspot.render(hotspots);
|
||||||
|
console.log('热点渲染成功:', hotspots);
|
||||||
|
} else {
|
||||||
|
console.log('没有可用的热点数据');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取热点数据失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//点击右侧按钮自动判断
|
||||||
|
const getProductConfig = async (sku) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${getApiUrl(`/api/product-configs/by-sku/${sku}`)}`);
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.code === 200) {
|
||||||
|
console.log(result.data);
|
||||||
|
const { enable_placement_zone } = result.data;
|
||||||
|
// await initPlacementZoneConfig();
|
||||||
|
if (enable_placement_zone) {
|
||||||
|
getPlacementZone(sku)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
executeEvent2(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取产品配置失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API 配置
|
||||||
|
const API_BASE_URL = 'https://ztserver.zguiy.com';
|
||||||
|
|
||||||
|
const getApiUrl = (path) => {
|
||||||
|
return `${API_BASE_URL}${path}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将所有函数挂载到 window.AppLogic
|
||||||
|
window.AppLogic = {
|
||||||
|
initApp,
|
||||||
|
init,
|
||||||
|
getAutoLoadModelList,
|
||||||
|
getPlacementZone,
|
||||||
|
getEvent,
|
||||||
|
executeEvent,
|
||||||
|
executeEvent2,
|
||||||
|
getHotspot,
|
||||||
|
getProductConfig
|
||||||
|
};
|
||||||
904
examples/demo-global.html
Normal file
904
examples/demo-global.html
Normal file
@ -0,0 +1,904 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>3D Model Showcase SDK - TS</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#renderDom {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-panel {
|
||||||
|
width: 320px;
|
||||||
|
background: rgba(30, 30, 45, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-panel::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-panel::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-panel::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-title {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-info {
|
||||||
|
background: rgba(76, 175, 80, 0.2);
|
||||||
|
border: 1px solid rgba(76, 175, 80, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-info-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4caf50;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-info-item {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-info-label {
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
min-width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-info-value {
|
||||||
|
color: #fff;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-category {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header {
|
||||||
|
padding: 12px 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: background 0.3s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header.active {
|
||||||
|
background: rgba(76, 175, 80, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-arrow {
|
||||||
|
transition: transform 0.3s;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-arrow.expanded {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: grid-template-rows 0.3s ease, padding 0.3s ease;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-content.expanded {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-content>* {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-label {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-btn.selected {
|
||||||
|
background: rgba(76, 175, 80, 0.6);
|
||||||
|
border-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-checkbox:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-checkbox input[type="checkbox"] {
|
||||||
|
margin-right: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-checkbox label {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进度条样式 */
|
||||||
|
#progress-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 80%;
|
||||||
|
max-width: 500px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-bar {
|
||||||
|
width: 0%;
|
||||||
|
height: 10px;
|
||||||
|
background: linear-gradient(90deg, #4CAF50, #45a049);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-text {
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<!-- 画布区域 -->
|
||||||
|
<div id="canvas-container">
|
||||||
|
<canvas id="renderDom"></canvas>
|
||||||
|
<div id="progress-container" style="display: none;">
|
||||||
|
<div id="progress-bar"></div>
|
||||||
|
<div id="progress-text">0%</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 生成放置区域按钮 -->
|
||||||
|
<button id="dropzone-btn" style="
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #21c7ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||||
|
z-index: 100;
|
||||||
|
">生成放置区域</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 配置面板 -->
|
||||||
|
<div id="config-panel">
|
||||||
|
<div class="config-title">选装选配</div>
|
||||||
|
|
||||||
|
<!-- 点击信息显示区域 -->
|
||||||
|
<div id="click-info" class="click-info" style="display: none;">
|
||||||
|
<div class="click-info-title">点击信息</div>
|
||||||
|
<div id="click-info-content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 棚子尺寸 -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header" data-category="size">
|
||||||
|
<span class="category-title">棚子尺寸</span>
|
||||||
|
<span class="category-arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="size-1">3*3</button>
|
||||||
|
<button class="option-btn" data-option="size-2">3x6</button>
|
||||||
|
<button class="option-btn" data-option="size-3">10x13EM星空篷</button>
|
||||||
|
<button class="option-btn" data-option="size-4">全铁3x6</button>
|
||||||
|
<button class="option-btn" data-option="size-1">10x12</button>
|
||||||
|
<button class="option-btn" data-option="size-2">10x10星空篷</button>
|
||||||
|
<button class="option-btn" data-option="size-3">10x13星空篷</button>
|
||||||
|
<button class="option-btn" data-option="size-4">10x20星空篷</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 棚子类型 -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header" data-category="type">
|
||||||
|
<span class="category-title">棚子类型</span>
|
||||||
|
<span class="category-arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="type-1">平顶</button>
|
||||||
|
<button class="option-btn" data-option="type-2">尖顶</button>
|
||||||
|
<button class="option-btn" data-option="type-3">弧形</button>
|
||||||
|
<button class="option-btn" data-option="type-4">异形</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 百叶 (单选) -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header" data-category="louver">
|
||||||
|
<span class="category-title">百叶</span>
|
||||||
|
<span class="category-arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="louver-1">整体</button>
|
||||||
|
<button class="option-btn" data-option="louver-2">3m百叶</button>
|
||||||
|
<button class="option-btn" data-option="louver-3">3m下拉帘</button>
|
||||||
|
<button class="option-btn" data-option="louver-4">百叶4</button>
|
||||||
|
<button class="option-btn" data-option="louver-4">卷帘小</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 配色 -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header" data-category="color">
|
||||||
|
<span class="category-title">配色</span>
|
||||||
|
<span class="category-arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="color-1">SPF111S1010C</button>
|
||||||
|
<button class="option-btn" data-option="color-2">SPF111S1010W</button>
|
||||||
|
<button class="option-btn" data-option="color-3">SPF111S1010TA</button>
|
||||||
|
<button class="option-btn" data-option="color-4">木色</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="hotspot-btn">生成热点</button>
|
||||||
|
<button id="prevent-btn">生成防止区域</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 模型信息框(用于2D转3D显示) -->
|
||||||
|
<div id="model-info-box" style="display: none;">
|
||||||
|
<div style="
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
min-width: 200px;
|
||||||
|
">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px; color: #4CAF50;">模型信息</div>
|
||||||
|
<div id="info-name" style="margin-bottom: 5px;">名称: -</div>
|
||||||
|
<div id="info-position" style="margin-bottom: 10px; font-size: 12px; color: #666;">坐标: -</div>
|
||||||
|
|
||||||
|
<!-- 颜色按钮 -->
|
||||||
|
<div id="color-buttons" style="display: none; gap: 8px; margin-bottom: 8px;">
|
||||||
|
<button id="color-btn-1" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #FFFFFF;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">白色</button>
|
||||||
|
<button id="color-btn-2" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #000000;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">黑色</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 旋转按钮 -->
|
||||||
|
<div id="rotation-buttons" style="display: none; gap: 8px; margin-bottom: 8px;">
|
||||||
|
<button id="rotation-btn-90" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #2196F3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">旋转90°</button>
|
||||||
|
<button id="rotation-btn-180" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #2196F3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">旋转180°</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 8px;">
|
||||||
|
<button id="remove-model-btn" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #f44336;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">移除</button>
|
||||||
|
<button id="close-info-btn" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">关闭</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 全局脚本方式引入 SDK -->
|
||||||
|
<script src="https://sdk.zguiy.com/zt/assets/index.global.js"></script>
|
||||||
|
|
||||||
|
<!-- 业务逻辑脚本 -->
|
||||||
|
<script src="./app-global.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 从全局对象获取 SDK kernel
|
||||||
|
var kernel = window.faceSDK && window.faceSDK.kernel;
|
||||||
|
|
||||||
|
if (!kernel) {
|
||||||
|
console.error('faceSDK kernel 不可用,请确认 index.global.js 已正确加载');
|
||||||
|
alert('SDK 加载失败,请检查网络连接');
|
||||||
|
} else {
|
||||||
|
// 注入 kernel 实例到业务逻辑
|
||||||
|
window.AppLogic.initApp(kernel);
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
await window.AppLogic.init();
|
||||||
|
await window.AppLogic.getAutoLoadModelList();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
kernel.on('model:load:progress', (data) => {
|
||||||
|
console.log('模型加载事件', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
kernel.on('model:loaded', (data) => {
|
||||||
|
console.log('模型加载完成', data);
|
||||||
|
// 隐藏进度条
|
||||||
|
const progressContainer = document.getElementById('progress-container');
|
||||||
|
if (progressContainer) {
|
||||||
|
progressContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
kernel.on('all:ready', (data) => {
|
||||||
|
console.log('所有模块加载完,', data);
|
||||||
|
kernel.material.apply({
|
||||||
|
target: 'Material__2',
|
||||||
|
attribute: 'alpha',
|
||||||
|
value: 0.5,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== UI 交互逻辑 ==========
|
||||||
|
|
||||||
|
// 折叠面板切换
|
||||||
|
document.querySelectorAll('.category-header').forEach(function (header) {
|
||||||
|
header.addEventListener('click', function () {
|
||||||
|
var content = this.nextElementSibling;
|
||||||
|
var arrow = this.querySelector('.category-arrow');
|
||||||
|
|
||||||
|
// 切换展开/收起状态
|
||||||
|
content.classList.toggle('expanded');
|
||||||
|
arrow.classList.toggle('expanded');
|
||||||
|
this.classList.toggle('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var sku = ""
|
||||||
|
// 单选按钮逻辑
|
||||||
|
document.querySelectorAll('.option-btn').forEach(function (btn) {
|
||||||
|
btn.addEventListener('click', async function () {
|
||||||
|
var optionGroup = this.parentElement;
|
||||||
|
var category = this.closest('.config-category');
|
||||||
|
var categoryName = category.querySelector('.category-header').dataset.category;
|
||||||
|
|
||||||
|
// 同一组内取消其他选中状态
|
||||||
|
optionGroup.querySelectorAll('.option-btn').forEach(function (b) {
|
||||||
|
b.classList.remove('selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 选中当前按钮
|
||||||
|
this.classList.add('selected');
|
||||||
|
|
||||||
|
// 触发自定义事件
|
||||||
|
var event = new CustomEvent('config:change', {
|
||||||
|
detail: {
|
||||||
|
category: categoryName,
|
||||||
|
value: this.dataset.option,
|
||||||
|
text: this.textContent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
console.log('配置变更:', {
|
||||||
|
category: categoryName,
|
||||||
|
value: this.dataset.option,
|
||||||
|
text: this.textContent
|
||||||
|
});
|
||||||
|
|
||||||
|
var currentText = this.textContent;
|
||||||
|
|
||||||
|
sku = currentText;
|
||||||
|
await window.AppLogic.getProductConfig(currentText)
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
document.querySelector('#hotspot-btn').addEventListener('click', async function () {
|
||||||
|
|
||||||
|
await window.AppLogic.getHotspot();
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 监听热点点击事件
|
||||||
|
window.addEventListener('hotspot:click', function (event) {
|
||||||
|
console.log('热点被点击:', event.detail);
|
||||||
|
var id = event.detail.id;
|
||||||
|
var name = event.detail.name;
|
||||||
|
var payload = event.detail.payload;
|
||||||
|
|
||||||
|
var clickInfoDiv = document.getElementById('click-info');
|
||||||
|
var clickInfoContent = document.getElementById('click-info-content');
|
||||||
|
|
||||||
|
var html = '<div class="click-info-item">' +
|
||||||
|
'<span class="click-info-label">类型:</span>' +
|
||||||
|
'<span class="click-info-value">热点</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="click-info-item">' +
|
||||||
|
'<span class="click-info-label">名称:</span>' +
|
||||||
|
'<span class="click-info-value">' + name + '</span>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
if (payload && payload.skus && payload.skus.length > 0) {
|
||||||
|
html += '<div class="click-info-item">' +
|
||||||
|
'<span class="click-info-label">关联SKU:</span>' +
|
||||||
|
'<span class="click-info-value">' + payload.skus.join(', ') + '</span>' +
|
||||||
|
'</div>';
|
||||||
|
} else {
|
||||||
|
html += '<div class="click-info-item">' +
|
||||||
|
'<span class="click-info-label">关联SKU:</span>' +
|
||||||
|
'<span class="click-info-value">无</span>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
clickInfoContent.innerHTML = html;
|
||||||
|
clickInfoDiv.style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听模型点击事件
|
||||||
|
window.addEventListener('model:click', function (event) {
|
||||||
|
console.log('模型被点击:', event.detail);
|
||||||
|
var meshName = event.detail.meshName;
|
||||||
|
var materialName = event.detail.materialName;
|
||||||
|
var modelControlType = event.detail.modelControlType;
|
||||||
|
|
||||||
|
var clickInfoDiv = document.getElementById('click-info');
|
||||||
|
var clickInfoContent = document.getElementById('click-info-content');
|
||||||
|
|
||||||
|
var html = '<div class="click-info-item">' +
|
||||||
|
'<span class="click-info-label">类型:</span>' +
|
||||||
|
'<span class="click-info-value">模型</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="click-info-item">' +
|
||||||
|
'<span class="click-info-label">网格名称:</span>' +
|
||||||
|
'<span class="click-info-value">' + meshName + '</span>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
if (materialName) {
|
||||||
|
html += '<div class="click-info-item">' +
|
||||||
|
'<span class="click-info-label">材质名称:</span>' +
|
||||||
|
'<span class="click-info-value">' + materialName + '</span>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelControlType) {
|
||||||
|
html += '<div class="click-info-item">' +
|
||||||
|
'<span class="click-info-label">控制类型:</span>' +
|
||||||
|
'<span class="click-info-value">' + modelControlType + '</span>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
clickInfoContent.innerHTML = html;
|
||||||
|
clickInfoDiv.style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// 多选复选框逻辑
|
||||||
|
document.querySelectorAll('.option-checkbox input[type="checkbox"]').forEach(function (checkbox) {
|
||||||
|
checkbox.addEventListener('change', async function () {
|
||||||
|
var category = this.closest('.config-category');
|
||||||
|
var categoryName = category.querySelector('.category-header').dataset.category;
|
||||||
|
var optionGroup = this.closest('.option-group');
|
||||||
|
var checked = this.checked;
|
||||||
|
|
||||||
|
// 获取当前组所有选中的值
|
||||||
|
var selectedValues = Array.from(
|
||||||
|
optionGroup.querySelectorAll('input[type="checkbox"]:checked')
|
||||||
|
).map(function (cb) {
|
||||||
|
return {
|
||||||
|
value: cb.dataset.option,
|
||||||
|
text: cb.nextElementSibling.textContent
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 触发自定义事件
|
||||||
|
var event = new CustomEvent('config:change', {
|
||||||
|
detail: {
|
||||||
|
category: categoryName,
|
||||||
|
values: selectedValues,
|
||||||
|
checked: this.checked,
|
||||||
|
currentValue: this.dataset.option
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
console.log('配置变更(多选):', {
|
||||||
|
category: categoryName,
|
||||||
|
selectedValues: selectedValues,
|
||||||
|
checked: this.checked,
|
||||||
|
currentValue: this.dataset.option
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听配置变更事件(供外部使用)
|
||||||
|
document.addEventListener('config:change', function (e) {
|
||||||
|
// 这里可以根据配置变更来操作 3D 模型
|
||||||
|
// 例如:
|
||||||
|
// if (e.detail.category === 'size') {
|
||||||
|
// kernel.model.replace({ modelId: 'shed', modelUrl: `/models/shed-${e.detail.value}.glb`, modelControlType: 'rotation' });
|
||||||
|
// }
|
||||||
|
// if (e.detail.category === 'color') {
|
||||||
|
// kernel.material.apply({
|
||||||
|
// target: 'ShedMaterial',
|
||||||
|
// attribute: 'baseColor',
|
||||||
|
// value: getColorValue(e.detail.value)
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 模型信息框按钮事件 ==========
|
||||||
|
|
||||||
|
// 关闭按钮事件
|
||||||
|
document.getElementById('close-info-btn').addEventListener('click', function () {
|
||||||
|
kernel.domTo3D.detach('model-info');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 白色按钮事件
|
||||||
|
document.getElementById('color-btn-1').addEventListener('click', function () {
|
||||||
|
var materialName = window.getCurrentMaterialName();
|
||||||
|
if (materialName) {
|
||||||
|
console.log('切换为白色,材质名:', materialName);
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: '#FFFFFF',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('没有选中材质');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 黑色按钮事件
|
||||||
|
document.getElementById('color-btn-2').addEventListener('click', function () {
|
||||||
|
var materialName = window.getCurrentMaterialName();
|
||||||
|
if (materialName) {
|
||||||
|
console.log('切换为黑色,材质名:', materialName);
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: '#000000',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('没有选中材质');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 旋转90度按钮事件
|
||||||
|
document.getElementById('rotation-btn-90').addEventListener('click', function () {
|
||||||
|
var pickedMesh = window.getCurrentPickedMesh();
|
||||||
|
if (pickedMesh) {
|
||||||
|
var modelName = kernel.model.findModelNameByMesh?.(pickedMesh);
|
||||||
|
if (modelName) {
|
||||||
|
console.log('旋转90度,模型名:', modelName);
|
||||||
|
kernel.transform.rotation({
|
||||||
|
modelId: modelName,
|
||||||
|
vector3: { x: 0, y: 90, z: 0 }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('未找到模型名称');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('没有选中的网格');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 旋转180度按钮事件
|
||||||
|
document.getElementById('rotation-btn-180').addEventListener('click', function () {
|
||||||
|
var pickedMesh = window.getCurrentPickedMesh();
|
||||||
|
if (pickedMesh) {
|
||||||
|
var modelName = kernel.model.findModelNameByMesh?.(pickedMesh);
|
||||||
|
if (modelName) {
|
||||||
|
console.log('旋转180度,模型名:', modelName);
|
||||||
|
kernel.transform.rotation({
|
||||||
|
modelId: modelName,
|
||||||
|
vector3: { x: 0, y: 30, z: 0 }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('未找到模型名称');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('没有选中的网格');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 移除按钮事件
|
||||||
|
document.getElementById('remove-model-btn').addEventListener('click', function () {
|
||||||
|
var pickedMesh = window.getCurrentPickedMesh();
|
||||||
|
if (pickedMesh) {
|
||||||
|
var meshName = pickedMesh.name;
|
||||||
|
var success = kernel.model.remove(meshName);
|
||||||
|
if (success) {
|
||||||
|
console.log('模型已移除');
|
||||||
|
// 关闭信息框
|
||||||
|
kernel.domTo3D.detach('model-info');
|
||||||
|
} else {
|
||||||
|
console.log('移除失败:未找到该网格所属的模型');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('没有选中的网格');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成放置区域按钮事件
|
||||||
|
var dropZoneVisible = false;
|
||||||
|
document.getElementById('dropzone-btn').addEventListener('click', function () {
|
||||||
|
if (!dropZoneVisible) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 更新按钮文字
|
||||||
|
document.getElementById('dropzone-btn').textContent = '隐藏放置区域';
|
||||||
|
console.log('已生成并显示放置区域');
|
||||||
|
} else {
|
||||||
|
// 隐藏放置区域
|
||||||
|
kernel.dropZone.hideAll();
|
||||||
|
dropZoneVisible = false;
|
||||||
|
|
||||||
|
// 更新按钮文字
|
||||||
|
document.getElementById('dropzone-btn').textContent = '生成放置区域';
|
||||||
|
console.log('已隐藏放置区域');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// 监听放置区域点击事件
|
||||||
|
kernel.on('dropzone:click', async function (dropzone_data) {
|
||||||
|
window.AppLogic.getEvent(dropzone_data, sku)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 存储当前选中的材质名和网格
|
||||||
|
var currentMaterialName = '';
|
||||||
|
var currentPickedMesh = null;
|
||||||
|
|
||||||
|
kernel.on('model:click', function (data) {
|
||||||
|
console.log('模型点击事件', data);
|
||||||
|
console.log('模型控制类型:', data.modelControlType);
|
||||||
|
switch (data.modelControlType) {
|
||||||
|
case "color":
|
||||||
|
// DOM 2D转3D 示例:点击模型时显示信息框
|
||||||
|
if (data.pickedMesh && data.pickedPoint) {
|
||||||
|
var meshName = data.pickedMesh.name;
|
||||||
|
var position = data.pickedPoint; // 使用点击位置的坐标
|
||||||
|
currentMaterialName = data.materialName || ''; // 保存材质名
|
||||||
|
currentPickedMesh = data.pickedMesh; // 保存网格对象
|
||||||
|
|
||||||
|
// 获取已创建的DOM元素
|
||||||
|
var infoDiv = document.getElementById('model-info-box');
|
||||||
|
// 更新信息内容
|
||||||
|
document.getElementById('info-name').textContent = '名称: ' + meshName;
|
||||||
|
document.getElementById('info-position').textContent = '坐标: [' + position.x.toFixed(2) + ', ' + position.y.toFixed(2) + ', ' + position.z.toFixed(2) + ']';
|
||||||
|
|
||||||
|
// 显示颜色按钮,隐藏旋转按钮
|
||||||
|
document.getElementById('color-buttons').style.display = 'flex';
|
||||||
|
document.getElementById('rotation-buttons').style.display = 'none';
|
||||||
|
|
||||||
|
// 将DOM附加到点击的3D坐标(会自动显示)
|
||||||
|
kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "rotation":
|
||||||
|
// 显示旋转控制UI
|
||||||
|
if (data.pickedMesh && data.pickedPoint) {
|
||||||
|
var meshName = data.pickedMesh.name;
|
||||||
|
var position = data.pickedPoint;
|
||||||
|
currentPickedMesh = data.pickedMesh; // 保存网格对象
|
||||||
|
|
||||||
|
// 获取已创建的DOM元素
|
||||||
|
var infoDiv = document.getElementById('model-info-box');
|
||||||
|
// 更新信息内容
|
||||||
|
document.getElementById('info-name').textContent = '名称: ' + meshName;
|
||||||
|
document.getElementById('info-position').textContent = '坐标: [' + position.x.toFixed(2) + ', ' + position.y.toFixed(2) + ', ' + position.z.toFixed(2) + ']';
|
||||||
|
|
||||||
|
// 显示旋转按钮,隐藏颜色按钮
|
||||||
|
document.getElementById('rotation-buttons').style.display = 'flex';
|
||||||
|
document.getElementById('color-buttons').style.display = 'none';
|
||||||
|
|
||||||
|
// 将DOM附加到点击的3D坐标(会自动显示)
|
||||||
|
kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// 暴露到全局,供 index.html 使用
|
||||||
|
window.getCurrentMaterialName = function () { return currentMaterialName; };
|
||||||
|
window.getCurrentPickedMesh = function () { return currentPickedMesh; };
|
||||||
|
|
||||||
|
// 暴露 kernel 到全局,方便调试
|
||||||
|
|
||||||
|
|
||||||
|
kernel.on('hotspot:click', function (event) {
|
||||||
|
console.log('热点被点击:', event);
|
||||||
|
|
||||||
|
var id = event.id;
|
||||||
|
var name = event.name;
|
||||||
|
var payload = event.payload;
|
||||||
|
|
||||||
|
if (payload && payload.skus && payload.skus.length > 0) {
|
||||||
|
console.log('热点关联的SKU列表:', payload.skus);
|
||||||
|
// 这里可以根据 SKU 列表做进一步处理,比如显示产品信息
|
||||||
|
} else {
|
||||||
|
console.log('该热点没有关联SKU');
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (name === "卷帘门") {
|
||||||
|
// kernel.door.toggle({ upY: 28, downY: 0, speed: 12 });
|
||||||
|
|
||||||
|
// // Y轴剖切,只作用于卷帘门网格,保留下方,剖掉上方
|
||||||
|
// var clipHeight = 28; // 调整这个值找到合适的剖切高度
|
||||||
|
// console.log('设置剖切:', clipHeight);
|
||||||
|
// kernel.clipping.setY(clipHeight, true, ['Box005.001', 'Box006.001']);
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
944
examples/demo-module.html
Normal file
944
examples/demo-module.html
Normal file
@ -0,0 +1,944 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>3D Model Showcase SDK - TS</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#renderDom {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-panel {
|
||||||
|
width: 320px;
|
||||||
|
background: rgba(30, 30, 45, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-panel::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-panel::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-panel::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-title {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-info {
|
||||||
|
background: rgba(76, 175, 80, 0.2);
|
||||||
|
border: 1px solid rgba(76, 175, 80, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-info-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4caf50;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-info-item {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-info-label {
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
min-width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-info-value {
|
||||||
|
color: #fff;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-category {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header {
|
||||||
|
padding: 12px 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: background 0.3s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header.active {
|
||||||
|
background: rgba(76, 175, 80, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-arrow {
|
||||||
|
transition: transform 0.3s;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-arrow.expanded {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: grid-template-rows 0.3s ease, padding 0.3s ease;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-content.expanded {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-content>* {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-label {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-btn.selected {
|
||||||
|
background: rgba(76, 175, 80, 0.6);
|
||||||
|
border-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-checkbox:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-checkbox input[type="checkbox"] {
|
||||||
|
margin-right: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-checkbox label {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进度条样式 */
|
||||||
|
#progress-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 80%;
|
||||||
|
max-width: 500px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-bar {
|
||||||
|
width: 0%;
|
||||||
|
height: 10px;
|
||||||
|
background: linear-gradient(90deg, #4CAF50, #45a049);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: width 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-text {
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<!-- 画布区域 -->
|
||||||
|
<div id="canvas-container">
|
||||||
|
<canvas id="renderDom"></canvas>
|
||||||
|
<div id="progress-container" style="display: none;">
|
||||||
|
<div id="progress-bar"></div>
|
||||||
|
<div id="progress-text">0%</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 生成放置区域按钮 -->
|
||||||
|
<button id="dropzone-btn" style="
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #21c7ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||||
|
z-index: 100;
|
||||||
|
">生成放置区域</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 配置面板 -->
|
||||||
|
<div id="config-panel">
|
||||||
|
<div class="config-title">选装选配</div>
|
||||||
|
|
||||||
|
<!-- 点击信息显示区域 -->
|
||||||
|
<div id="click-info" class="click-info" style="display: none;">
|
||||||
|
<div class="click-info-title">点击信息</div>
|
||||||
|
<div id="click-info-content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 棚子尺寸 -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header" data-category="size">
|
||||||
|
<span class="category-title">棚子尺寸</span>
|
||||||
|
<span class="category-arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="size-1">3*3</button>
|
||||||
|
<button class="option-btn" data-option="size-2">3x6</button>
|
||||||
|
<button class="option-btn" data-option="size-3">10x13EM星空篷</button>
|
||||||
|
<button class="option-btn" data-option="size-4">全铁3x6</button>
|
||||||
|
<button class="option-btn" data-option="size-1">10x12</button>
|
||||||
|
<button class="option-btn" data-option="size-2">10x10星空篷</button>
|
||||||
|
<button class="option-btn" data-option="size-3">10x13星空篷</button>
|
||||||
|
<button class="option-btn" data-option="size-4">10x20星空篷</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 棚子类型 -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header" data-category="type">
|
||||||
|
<span class="category-title">棚子类型</span>
|
||||||
|
<span class="category-arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="type-1">平顶</button>
|
||||||
|
<button class="option-btn" data-option="type-2">尖顶</button>
|
||||||
|
<button class="option-btn" data-option="type-3">弧形</button>
|
||||||
|
<button class="option-btn" data-option="type-4">异形</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 百叶 (单选) -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header" data-category="louver">
|
||||||
|
<span class="category-title">百叶</span>
|
||||||
|
<span class="category-arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="louver-1">整体</button>
|
||||||
|
<button class="option-btn" data-option="louver-2">3m百叶</button>
|
||||||
|
<button class="option-btn" data-option="louver-3">3m下拉帘</button>
|
||||||
|
<button class="option-btn" data-option="louver-4">百叶4</button>
|
||||||
|
<button class="option-btn" data-option="louver-4">卷帘小</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 配色 -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header" data-category="color">
|
||||||
|
<span class="category-title">配色</span>
|
||||||
|
<span class="category-arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="color-1">Charcoal</button>
|
||||||
|
<button class="option-btn" data-option="color-2">Cherry</button>
|
||||||
|
<button class="option-btn" data-option="color-3">黑色</button>
|
||||||
|
<button class="option-btn" data-option="color-4">木色</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="hotspot-btn">生成热点</button>
|
||||||
|
<button id="prevent-btn">生成防止区域</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 模型信息框(用于2D转3D显示) -->
|
||||||
|
<div id="model-info-box" style="display: none;">
|
||||||
|
<div style="
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
min-width: 200px;
|
||||||
|
">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px; color: #4CAF50;">模型信息</div>
|
||||||
|
<div id="info-name" style="margin-bottom: 5px;">名称: -</div>
|
||||||
|
<div id="info-position" style="margin-bottom: 10px; font-size: 12px; color: #666;">坐标: -</div>
|
||||||
|
|
||||||
|
<!-- 颜色按钮 -->
|
||||||
|
<div id="color-buttons" style="display: none; gap: 8px; margin-bottom: 8px;">
|
||||||
|
<button id="color-btn-1" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #FFFFFF;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">白色</button>
|
||||||
|
<button id="color-btn-2" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #000000;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">黑色</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 旋转按钮 -->
|
||||||
|
<div id="rotation-buttons" style="display: none; gap: 8px; margin-bottom: 8px;">
|
||||||
|
<button id="rotation-btn-90" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #2196F3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">旋转90°</button>
|
||||||
|
<button id="rotation-btn-180" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #2196F3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">旋转180°</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 8px;">
|
||||||
|
<button id="remove-model-btn" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #f44336;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">移除</button>
|
||||||
|
<button id="close-info-btn" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
">关闭</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module" src="../index.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
import { kernel } from 'https://sdk.zguiy.com/zt/assets/index.js';
|
||||||
|
import { initApp, init, getAutoLoadModelList, getPlacementZone, getEvent, getHotspot, executeEvent2, getProductConfig } from './index.js';
|
||||||
|
|
||||||
|
// 注入 kernel 实例到业务逻辑
|
||||||
|
initApp(kernel);
|
||||||
|
|
||||||
|
await init()
|
||||||
|
await getAutoLoadModelList()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
kernel.on('model:load:progress', (data) => {
|
||||||
|
console.log('模型加载事件', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
kernel.on('model:loaded', (data) => {
|
||||||
|
console.log('模型加载完成', data);
|
||||||
|
// 隐藏进度条
|
||||||
|
const progressContainer = document.getElementById('progress-container');
|
||||||
|
if (progressContainer) {
|
||||||
|
progressContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
kernel.on('all:ready', (data) => {
|
||||||
|
console.log('所有模块加载完,', data);
|
||||||
|
kernel.material.apply({
|
||||||
|
target: 'Material__2',
|
||||||
|
attribute: 'alpha',
|
||||||
|
value: 0.5,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== UI 交互逻辑 ==========
|
||||||
|
|
||||||
|
// 折叠面板切换
|
||||||
|
document.querySelectorAll('.category-header').forEach(header => {
|
||||||
|
header.addEventListener('click', function () {
|
||||||
|
const content = this.nextElementSibling;
|
||||||
|
const arrow = this.querySelector('.category-arrow');
|
||||||
|
|
||||||
|
// 切换展开/收起状态
|
||||||
|
content.classList.toggle('expanded');
|
||||||
|
arrow.classList.toggle('expanded');
|
||||||
|
this.classList.toggle('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let sku = ""
|
||||||
|
// 单选按钮逻辑
|
||||||
|
document.querySelectorAll('.option-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async function () {
|
||||||
|
const optionGroup = this.parentElement;
|
||||||
|
const category = this.closest('.config-category');
|
||||||
|
const categoryName = category.querySelector('.category-header').dataset.category;
|
||||||
|
|
||||||
|
// 同一组内取消其他选中状态
|
||||||
|
optionGroup.querySelectorAll('.option-btn').forEach(b => {
|
||||||
|
b.classList.remove('selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 选中当前按钮
|
||||||
|
this.classList.add('selected');
|
||||||
|
|
||||||
|
// 触发自定义事件
|
||||||
|
const event = new CustomEvent('config:change', {
|
||||||
|
detail: {
|
||||||
|
category: categoryName,
|
||||||
|
value: this.dataset.option,
|
||||||
|
text: this.textContent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
console.log('配置变更:', {
|
||||||
|
category: categoryName,
|
||||||
|
value: this.dataset.option,
|
||||||
|
text: this.textContent
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentText = this.textContent;
|
||||||
|
await getProductConfig(currentText)
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
document.querySelector('#hotspot-btn').addEventListener('click', async function () {
|
||||||
|
|
||||||
|
await getHotspot();
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 监听热点点击事件
|
||||||
|
window.addEventListener('hotspot:click', (event) => {
|
||||||
|
console.log('热点被点击:', event.detail);
|
||||||
|
const { id, name, payload } = event.detail;
|
||||||
|
|
||||||
|
const clickInfoDiv = document.getElementById('click-info');
|
||||||
|
const clickInfoContent = document.getElementById('click-info-content');
|
||||||
|
|
||||||
|
let html = `<div class="click-info-item">
|
||||||
|
<span class="click-info-label">类型:</span>
|
||||||
|
<span class="click-info-value">热点</span>
|
||||||
|
</div>
|
||||||
|
<div class="click-info-item">
|
||||||
|
<span class="click-info-label">名称:</span>
|
||||||
|
<span class="click-info-value">${name}</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
if (payload && payload.skus && payload.skus.length > 0) {
|
||||||
|
html += `<div class="click-info-item">
|
||||||
|
<span class="click-info-label">关联SKU:</span>
|
||||||
|
<span class="click-info-value">${payload.skus.join(', ')}</span>
|
||||||
|
</div>`;
|
||||||
|
} else {
|
||||||
|
html += `<div class="click-info-item">
|
||||||
|
<span class="click-info-label">关联SKU:</span>
|
||||||
|
<span class="click-info-value">无</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
clickInfoContent.innerHTML = html;
|
||||||
|
clickInfoDiv.style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听模型点击事件
|
||||||
|
window.addEventListener('model:click', (event) => {
|
||||||
|
console.log('模型被点击:', event.detail);
|
||||||
|
const { meshName, materialName, modelControlType } = event.detail;
|
||||||
|
|
||||||
|
const clickInfoDiv = document.getElementById('click-info');
|
||||||
|
const clickInfoContent = document.getElementById('click-info-content');
|
||||||
|
|
||||||
|
let html = `<div class="click-info-item">
|
||||||
|
<span class="click-info-label">类型:</span>
|
||||||
|
<span class="click-info-value">模型</span>
|
||||||
|
</div>
|
||||||
|
<div class="click-info-item">
|
||||||
|
<span class="click-info-label">网格名称:</span>
|
||||||
|
<span class="click-info-value">${meshName}</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
if (materialName) {
|
||||||
|
html += `<div class="click-info-item">
|
||||||
|
<span class="click-info-label">材质名称:</span>
|
||||||
|
<span class="click-info-value">${materialName}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelControlType) {
|
||||||
|
html += `<div class="click-info-item">
|
||||||
|
<span class="click-info-label">控制类型:</span>
|
||||||
|
<span class="click-info-value">${modelControlType}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
clickInfoContent.innerHTML = html;
|
||||||
|
clickInfoDiv.style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// 多选复选框逻辑
|
||||||
|
document.querySelectorAll('.option-checkbox input[type="checkbox"]').forEach(checkbox => {
|
||||||
|
checkbox.addEventListener('change', async function () {
|
||||||
|
const category = this.closest('.config-category');
|
||||||
|
const categoryName = category.querySelector('.category-header').dataset.category;
|
||||||
|
const optionGroup = this.closest('.option-group');
|
||||||
|
const checked = this.checked;
|
||||||
|
|
||||||
|
// 获取当前组所有选中的值
|
||||||
|
const selectedValues = Array.from(
|
||||||
|
optionGroup.querySelectorAll('input[type="checkbox"]:checked')
|
||||||
|
).map(cb => ({
|
||||||
|
value: cb.dataset.option,
|
||||||
|
text: cb.nextElementSibling.textContent
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 触发自定义事件
|
||||||
|
const event = new CustomEvent('config:change', {
|
||||||
|
detail: {
|
||||||
|
category: categoryName,
|
||||||
|
values: selectedValues,
|
||||||
|
checked: this.checked,
|
||||||
|
currentValue: this.dataset.option
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
console.log('配置变更(多选):', {
|
||||||
|
category: categoryName,
|
||||||
|
selectedValues: selectedValues,
|
||||||
|
checked: this.checked,
|
||||||
|
currentValue: this.dataset.option
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听配置变更事件(供外部使用)
|
||||||
|
document.addEventListener('config:change', function (e) {
|
||||||
|
// 这里可以根据配置变更来操作 3D 模型
|
||||||
|
// 例如:
|
||||||
|
// if (e.detail.category === 'size') {
|
||||||
|
// kernel.model.replace({ modelId: 'shed', modelUrl: `/models/shed-${e.detail.value}.glb`, modelControlType: 'rotation' });
|
||||||
|
// }
|
||||||
|
// if (e.detail.category === 'color') {
|
||||||
|
// kernel.material.apply({
|
||||||
|
// target: 'ShedMaterial',
|
||||||
|
// attribute: 'baseColor',
|
||||||
|
// value: getColorValue(e.detail.value)
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========== 模型信息框按钮事件 ==========
|
||||||
|
|
||||||
|
// 关闭按钮事件
|
||||||
|
document.getElementById('close-info-btn').addEventListener('click', () => {
|
||||||
|
kernel.domTo3D.detach('model-info');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 白色按钮事件
|
||||||
|
document.getElementById('color-btn-1').addEventListener('click', () => {
|
||||||
|
const materialName = window.getCurrentMaterialName();
|
||||||
|
if (materialName) {
|
||||||
|
console.log('切换为白色,材质名:', materialName);
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: '#FFFFFF',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('没有选中材质');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 黑色按钮事件
|
||||||
|
document.getElementById('color-btn-2').addEventListener('click', () => {
|
||||||
|
const materialName = window.getCurrentMaterialName();
|
||||||
|
if (materialName) {
|
||||||
|
console.log('切换为黑色,材质名:', materialName);
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: '#000000',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('没有选中材质');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 旋转90度按钮事件
|
||||||
|
document.getElementById('rotation-btn-90').addEventListener('click', () => {
|
||||||
|
const pickedMesh = window.getCurrentPickedMesh();
|
||||||
|
if (pickedMesh) {
|
||||||
|
const modelName = kernel.model.findModelNameByMesh?.(pickedMesh);
|
||||||
|
if (modelName) {
|
||||||
|
console.log('旋转90度,模型名:', modelName);
|
||||||
|
kernel.transform.rotation({
|
||||||
|
modelId: modelName,
|
||||||
|
vector3: { x: 0, y: 90, z: 0 }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('未找到模型名称');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('没有选中的网格');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 旋转180度按钮事件
|
||||||
|
document.getElementById('rotation-btn-180').addEventListener('click', () => {
|
||||||
|
const pickedMesh = window.getCurrentPickedMesh();
|
||||||
|
if (pickedMesh) {
|
||||||
|
const modelName = kernel.model.findModelNameByMesh?.(pickedMesh);
|
||||||
|
if (modelName) {
|
||||||
|
console.log('旋转180度,模型名:', modelName);
|
||||||
|
kernel.transform.rotation({
|
||||||
|
modelId: modelName,
|
||||||
|
vector3: { x: 0, y: 30, z: 0 }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('未找到模型名称');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('没有选中的网格');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 移除按钮事件
|
||||||
|
document.getElementById('remove-model-btn').addEventListener('click', () => {
|
||||||
|
const pickedMesh = window.getCurrentPickedMesh();
|
||||||
|
if (pickedMesh) {
|
||||||
|
const meshName = pickedMesh.name;
|
||||||
|
const success = kernel.model.remove(meshName);
|
||||||
|
if (success) {
|
||||||
|
console.log('模型已移除');
|
||||||
|
// 关闭信息框
|
||||||
|
kernel.domTo3D.detach('model-info');
|
||||||
|
} else {
|
||||||
|
console.log('移除失败:未找到该网格所属的模型');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('没有选中的网格');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成放置区域按钮事件
|
||||||
|
let dropZoneVisible = false;
|
||||||
|
document.getElementById('dropzone-btn').addEventListener('click', () => {
|
||||||
|
if (!dropZoneVisible) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 更新按钮文字
|
||||||
|
document.getElementById('dropzone-btn').textContent = '隐藏放置区域';
|
||||||
|
console.log('已生成并显示放置区域');
|
||||||
|
} else {
|
||||||
|
// 隐藏放置区域
|
||||||
|
kernel.dropZone.hideAll();
|
||||||
|
dropZoneVisible = false;
|
||||||
|
|
||||||
|
// 更新按钮文字
|
||||||
|
document.getElementById('dropzone-btn').textContent = '生成放置区域';
|
||||||
|
console.log('已隐藏放置区域');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化放置区域配置数据(只需设置一次)
|
||||||
|
const initPlacementZoneConfig = (divisions = 3) => {
|
||||||
|
|
||||||
|
// 只清除旧的放置区域网格,不清除模型
|
||||||
|
kernel.dropZone.clearZones();
|
||||||
|
// 调整 baseY 来控制整体高度(正数向上,负数向下)
|
||||||
|
const baseY = 0.09; // 修改这个值来调整整体高度
|
||||||
|
const height = 2.27;
|
||||||
|
// 调整 offset 来控制每个面向外或向内的偏移
|
||||||
|
// 正数 = 向外移动,负数 = 向内移动
|
||||||
|
const wallOffset = 0; // 修改这个值来调整墙面偏移
|
||||||
|
|
||||||
|
kernel.dropZone.setData({
|
||||||
|
|
||||||
|
color: "#21c7ff",
|
||||||
|
alpha: 0.3,
|
||||||
|
thickness: 2,
|
||||||
|
showBorder: true,
|
||||||
|
borderColor: "#ffffff",
|
||||||
|
walls: [
|
||||||
|
{
|
||||||
|
name: 'front',
|
||||||
|
startPoint: [-1.45, baseY, -1.45],
|
||||||
|
endPoint: [1.45, baseY, -1.45],
|
||||||
|
height: height,
|
||||||
|
divisions: divisions,
|
||||||
|
offset: wallOffset // 向外或向内偏移
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'back',
|
||||||
|
startPoint: [1.45, baseY, 1.45],
|
||||||
|
endPoint: [-1.45, baseY, 1.45],
|
||||||
|
height: height,
|
||||||
|
divisions: divisions,
|
||||||
|
offset: wallOffset
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'left',
|
||||||
|
startPoint: [-1.45, baseY, 1.45],
|
||||||
|
endPoint: [-1.45, baseY, -1.45],
|
||||||
|
height: height,
|
||||||
|
divisions: divisions,
|
||||||
|
offset: wallOffset
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'right',
|
||||||
|
startPoint: [1.45, baseY, -1.45],
|
||||||
|
endPoint: [1.45, baseY, 1.45],
|
||||||
|
height: height,
|
||||||
|
divisions: divisions,
|
||||||
|
offset: wallOffset
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
kernel.dropZone.generateDropZones(divisions);
|
||||||
|
// 显示放置区域
|
||||||
|
kernel.dropZone.show();
|
||||||
|
dropZoneVisible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 监听放置区域点击事件
|
||||||
|
kernel.on('dropzone:click', async (dropzone_data) => {
|
||||||
|
getEvent(dropzone_data, sku)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 存储当前选中的材质名和网格
|
||||||
|
let currentMaterialName = '';
|
||||||
|
let currentPickedMesh = null;
|
||||||
|
|
||||||
|
kernel.on('model:click', (data) => {
|
||||||
|
console.log('模型点击事件', data);
|
||||||
|
console.log('模型控制类型:', data.modelControlType);
|
||||||
|
switch (data.modelControlType) {
|
||||||
|
case "color":
|
||||||
|
// DOM 2D转3D 示例:点击模型时显示信息框
|
||||||
|
if (data.pickedMesh && data.pickedPoint) {
|
||||||
|
const meshName = data.pickedMesh.name;
|
||||||
|
const position = data.pickedPoint; // 使用点击位置的坐标
|
||||||
|
currentMaterialName = data.materialName || ''; // 保存材质名
|
||||||
|
currentPickedMesh = data.pickedMesh; // 保存网格对象
|
||||||
|
|
||||||
|
// 获取已创建的DOM元素
|
||||||
|
const infoDiv = document.getElementById('model-info-box');
|
||||||
|
// 更新信息内容
|
||||||
|
document.getElementById('info-name').textContent = `名称: ${meshName}`;
|
||||||
|
document.getElementById('info-position').textContent = `坐标: [${position.x.toFixed(2)}, ${position.y.toFixed(2)}, ${position.z.toFixed(2)}]`;
|
||||||
|
|
||||||
|
// 显示颜色按钮,隐藏旋转按钮
|
||||||
|
document.getElementById('color-buttons').style.display = 'flex';
|
||||||
|
document.getElementById('rotation-buttons').style.display = 'none';
|
||||||
|
|
||||||
|
// 将DOM附加到点击的3D坐标(会自动显示)
|
||||||
|
kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "rotation":
|
||||||
|
// 显示旋转控制UI
|
||||||
|
if (data.pickedMesh && data.pickedPoint) {
|
||||||
|
const meshName = data.pickedMesh.name;
|
||||||
|
const position = data.pickedPoint;
|
||||||
|
currentPickedMesh = data.pickedMesh; // 保存网格对象
|
||||||
|
|
||||||
|
// 获取已创建的DOM元素
|
||||||
|
const infoDiv = document.getElementById('model-info-box');
|
||||||
|
// 更新信息内容
|
||||||
|
document.getElementById('info-name').textContent = `名称: ${meshName}`;
|
||||||
|
document.getElementById('info-position').textContent = `坐标: [${position.x.toFixed(2)}, ${position.y.toFixed(2)}, ${position.z.toFixed(2)}]`;
|
||||||
|
|
||||||
|
// 显示旋转按钮,隐藏颜色按钮
|
||||||
|
document.getElementById('rotation-buttons').style.display = 'flex';
|
||||||
|
document.getElementById('color-buttons').style.display = 'none';
|
||||||
|
|
||||||
|
// 将DOM附加到点击的3D坐标(会自动显示)
|
||||||
|
kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// 暴露到全局,供 index.html 使用
|
||||||
|
window.getCurrentMaterialName = () => currentMaterialName;
|
||||||
|
window.getCurrentPickedMesh = () => currentPickedMesh;
|
||||||
|
|
||||||
|
// 暴露 kernel 到全局,方便调试
|
||||||
|
|
||||||
|
|
||||||
|
kernel.on('hotspot:click', (event) => {
|
||||||
|
console.log('热点被点击:', event);
|
||||||
|
|
||||||
|
const { id, name, payload } = event;
|
||||||
|
|
||||||
|
if (payload && payload.skus && payload.skus.length > 0) {
|
||||||
|
console.log('热点关联的SKU列表:', payload.skus);
|
||||||
|
// 这里可以根据 SKU 列表做进一步处理,比如显示产品信息
|
||||||
|
} else {
|
||||||
|
console.log('该热点没有关联SKU');
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (name === "卷帘门") {
|
||||||
|
// kernel.door.toggle({ upY: 28, downY: 0, speed: 12 });
|
||||||
|
|
||||||
|
// // Y轴剖切,只作用于卷帘门网格,保留下方,剖掉上方
|
||||||
|
// const clipHeight = 28; // 调整这个值找到合适的剖切高度
|
||||||
|
// console.log('设置剖切:', clipHeight);
|
||||||
|
// kernel.clipping.setY(clipHeight, true, ['Box005.001', 'Box006.001']);
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
358521
examples/index.global.js
Normal file
358521
examples/index.global.js
Normal file
File diff suppressed because one or more lines are too long
298005
examples/index.js
298005
examples/index.js
File diff suppressed because one or more lines are too long
16
index.html
16
index.html
@ -301,7 +301,7 @@
|
|||||||
<button class="option-btn" data-option="size-2">3x6</button>
|
<button class="option-btn" data-option="size-2">3x6</button>
|
||||||
<button class="option-btn" data-option="size-3">10x13EM星空篷</button>
|
<button class="option-btn" data-option="size-3">10x13EM星空篷</button>
|
||||||
<button class="option-btn" data-option="size-4">全铁3x6</button>
|
<button class="option-btn" data-option="size-4">全铁3x6</button>
|
||||||
<button class="option-btn" data-option="size-1">10x12</button>
|
<button class="option-btn" data-option="size-1">10x12</button>
|
||||||
<button class="option-btn" data-option="size-2">10x10星空篷</button>
|
<button class="option-btn" data-option="size-2">10x10星空篷</button>
|
||||||
<button class="option-btn" data-option="size-3">10x13星空篷</button>
|
<button class="option-btn" data-option="size-3">10x13星空篷</button>
|
||||||
<button class="option-btn" data-option="size-4">10x20星空篷</button>
|
<button class="option-btn" data-option="size-4">10x20星空篷</button>
|
||||||
@ -350,9 +350,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="category-content">
|
<div class="category-content">
|
||||||
<div class="option-group">
|
<div class="option-group">
|
||||||
<button class="option-btn" data-option="color-1">Charcoal</button>
|
<button class="option-btn" data-option="color-1">SPF111S1010C</button>
|
||||||
<button class="option-btn" data-option="color-2">Cherry</button>
|
<button class="option-btn" data-option="color-2">SPF111S1010W</button>
|
||||||
<button class="option-btn" data-option="color-3">黑色</button>
|
<button class="option-btn" data-option="color-3">SPF111S1010TA</button>
|
||||||
<button class="option-btn" data-option="color-4">木色</button>
|
<button class="option-btn" data-option="color-4">木色</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -454,7 +454,10 @@
|
|||||||
<script type="module" src="./index.js"></script>
|
<script type="module" src="./index.js"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { kernel } from './src/main.ts';
|
import { kernel } from './src/main.ts';
|
||||||
import { init, getAutoLoadModelList, getPlacementZone, getEvent, getHotspot,executeEvent2 ,getProductConfig} from './index.js';
|
import { initApp, init, getAutoLoadModelList, getPlacementZone, getEvent, getHotspot, executeEvent2, getProductConfig } from './index.js';
|
||||||
|
|
||||||
|
// 注入 kernel 实例到业务逻辑
|
||||||
|
initApp(kernel);
|
||||||
|
|
||||||
await init()
|
await init()
|
||||||
await getAutoLoadModelList()
|
await getAutoLoadModelList()
|
||||||
@ -531,7 +534,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const currentText = this.textContent;
|
const currentText = this.textContent;
|
||||||
await getProductConfig(currentText)
|
sku = currentText;
|
||||||
|
await getProductConfig(currentText)
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
59
index.js
59
index.js
@ -1,10 +1,38 @@
|
|||||||
import { EXRCubeTexture } from '@babylonjs/core';
|
import { EXRCubeTexture } from '@babylonjs/core';
|
||||||
import { kernel } from './src/main.ts';
|
|
||||||
import apiConfig from './src/config.js';
|
import apiConfig from './src/config.js';
|
||||||
|
|
||||||
|
// 存储 kernel 实例
|
||||||
|
let kernelInstance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化应用逻辑 - 注入 kernel 实例
|
||||||
|
* @param {Object} kernel - SDK kernel 实例
|
||||||
|
* @returns {Object} kernel 实例
|
||||||
|
*/
|
||||||
|
export const initApp = (kernel) => {
|
||||||
|
if (!kernel) {
|
||||||
|
throw new Error('kernel 实例是必需的');
|
||||||
|
}
|
||||||
|
kernelInstance = kernel;
|
||||||
|
console.log('应用逻辑已初始化,kernel 实例已注入');
|
||||||
|
return kernelInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前 kernel 实例
|
||||||
|
*/
|
||||||
|
const getKernel = () => {
|
||||||
|
if (!kernelInstance) {
|
||||||
|
throw new Error('请先调用 initApp(kernel) 初始化 kernel 实例');
|
||||||
|
}
|
||||||
|
return kernelInstance;
|
||||||
|
};
|
||||||
|
|
||||||
//初始化
|
//初始化
|
||||||
export const init = async () => {
|
export const init = async (customConfig = {}) => {
|
||||||
const config = {
|
const kernel = getKernel();
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
container: document.querySelector('#renderDom'),
|
container: document.querySelector('#renderDom'),
|
||||||
modelUrlList: [],
|
modelUrlList: [],
|
||||||
env: { envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env', intensity: 1.2, rotationY: 0.3, background: true },
|
env: { envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env', intensity: 1.2, rotationY: 0.3, background: true },
|
||||||
@ -21,14 +49,16 @@ export const init = async () => {
|
|||||||
occlusionThreshold: 0.0002
|
occlusionThreshold: 0.0002
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 合并用户自定义配置
|
||||||
|
const config = { ...defaultConfig, ...customConfig };
|
||||||
kernel.init(config);
|
kernel.init(config);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//初始化加载模型
|
//初始化加载模型
|
||||||
export const getAutoLoadModelList = async () => {
|
export const getAutoLoadModelList = async () => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
const url = apiConfig.getApiUrl('/api/models/auto-load/list')
|
const url = apiConfig.getApiUrl('/api/models/auto-load/list')
|
||||||
console.log('API URL:', url)
|
console.log('API URL:', url)
|
||||||
console.log('apiConfig:', apiConfig)
|
console.log('apiConfig:', apiConfig)
|
||||||
@ -62,6 +92,8 @@ export const getAutoLoadModelList = async () => {
|
|||||||
|
|
||||||
//获取放置区域
|
//获取放置区域
|
||||||
export const getPlacementZone = async (sku) => {
|
export const getPlacementZone = async (sku) => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
const response = await fetch(apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`));
|
const response = await fetch(apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`));
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.code === 200) {
|
if (result.code === 200) {
|
||||||
@ -104,7 +136,10 @@ export const getEvent = async (dropzone_data, sku) => {
|
|||||||
console.error(`查询SKU配置或替换模型失败:`, error);
|
console.error(`查询SKU配置或替换模型失败:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//点击放置区域执行事件 一般是换配件
|
||||||
export const executeEvent = async (dropzone_data, result) => {
|
export const executeEvent = async (dropzone_data, result) => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
const { wallName, index, transform } = dropzone_data;
|
const { wallName, index, transform } = dropzone_data;
|
||||||
const { position, rotation } = transform;
|
const { position, rotation } = transform;
|
||||||
@ -161,8 +196,9 @@ export const executeEvent = async (dropzone_data, result) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//换棚子
|
//一般是换棚子/换颜色/显示放置区域
|
||||||
export const executeEvent2 = async (result) => {
|
export const executeEvent2 = async (result) => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
// 检查是否有模型更换事件
|
// 检查是否有模型更换事件
|
||||||
const hasModelChange = result.data.events.some(e => e.event_type === 'change_model');
|
const hasModelChange = result.data.events.some(e => e.event_type === 'change_model');
|
||||||
@ -184,8 +220,7 @@ export const executeEvent2 = async (result) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { id, name, file_url, model_control_type, category, placement_zone } = target_data;
|
const { id, name, file_url, model_control_type, category, placement_zone } = target_data;
|
||||||
console.log('替换百叶模型:', event);
|
|
||||||
console.log('替换百叶模型类型:', category);
|
|
||||||
|
|
||||||
if (placement_zone) {
|
if (placement_zone) {
|
||||||
const { alpha, border_color, color, show_border, thickness, walls } = placement_zone
|
const { alpha, border_color, color, show_border, thickness, walls } = placement_zone
|
||||||
@ -232,8 +267,10 @@ export const executeEvent2 = async (result) => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//加载热点
|
||||||
export const getHotspot = async () => {
|
export const getHotspot = async () => {
|
||||||
|
const kernel = getKernel();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 从后端获取激活状态的热点列表
|
// 从后端获取激活状态的热点列表
|
||||||
const response = await fetch(apiConfig.getApiUrl('/api/hotspots?status=active&page=1&pageSize=100'));
|
const response = await fetch(apiConfig.getApiUrl('/api/hotspots?status=active&page=1&pageSize=100'));
|
||||||
@ -265,7 +302,7 @@ export const getHotspot = async () => {
|
|||||||
console.error('获取热点数据失败:', error);
|
console.error('获取热点数据失败:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//点击右侧按钮自动判断
|
||||||
export const getProductConfig = async (sku) => {
|
export const getProductConfig = async (sku) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`)}`);
|
const response = await fetch(`${apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`)}`);
|
||||||
|
|||||||
@ -168,9 +168,7 @@ export class AppModel extends Monobehiver {
|
|||||||
const newName = `${originalName}_${modelId}`;
|
const newName = `${originalName}_${modelId}`;
|
||||||
const clonedMaterial = originalMaterial.clone(newName);
|
const clonedMaterial = originalMaterial.clone(newName);
|
||||||
clonedMaterials.set(originalName, clonedMaterial);
|
clonedMaterials.set(originalName, clonedMaterial);
|
||||||
console.log(`[cloneMaterials] 克隆材质: ${originalName} -> ${newName}`);
|
|
||||||
console.log(`[cloneMaterials] 克隆后的材质名称: ${clonedMaterial.name}`);
|
|
||||||
console.log(`[cloneMaterials] 材质是否在场景中:`, scene.materials.includes(clonedMaterial));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用克隆的材质
|
// 应用克隆的材质
|
||||||
@ -178,8 +176,7 @@ export class AppModel extends Monobehiver {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`已为模型 ${modelId} 克隆 ${clonedMaterials.size} 个材质`);
|
|
||||||
console.log(`[cloneMaterials] 场景中的所有材质:`, scene.materials.map(m => m.name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 为网格设置阴影(投射和接收) */
|
/** 为网格设置阴影(投射和接收) */
|
||||||
@ -430,10 +427,18 @@ export class AppModel extends Monobehiver {
|
|||||||
* @returns 模型名称,未找到返回 undefined
|
* @returns 模型名称,未找到返回 undefined
|
||||||
*/
|
*/
|
||||||
findModelNameByMesh(mesh: AbstractMesh): string | undefined {
|
findModelNameByMesh(mesh: AbstractMesh): string | undefined {
|
||||||
|
console.log(111111111111);
|
||||||
|
|
||||||
const keys = this.modelDic.Keys();
|
const keys = this.modelDic.Keys();
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const meshes = this.modelDic.Get(key);
|
const meshes = this.modelDic.Get(key);
|
||||||
|
meshes.forEach(mesh => {
|
||||||
|
console.log(mesh.uniqueId);
|
||||||
|
console.log(mesh.name);
|
||||||
|
|
||||||
|
});
|
||||||
if (meshes?.some(m => m === mesh || m.uniqueId === mesh.uniqueId)) {
|
if (meshes?.some(m => m === mesh || m.uniqueId === mesh.uniqueId)) {
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,6 +452,7 @@ export class AppModel extends Monobehiver {
|
|||||||
*/
|
*/
|
||||||
remove(meshOrName: AbstractMesh | string): boolean {
|
remove(meshOrName: AbstractMesh | string): boolean {
|
||||||
let mesh: AbstractMesh | undefined;
|
let mesh: AbstractMesh | undefined;
|
||||||
|
|
||||||
|
|
||||||
// 判断传入的是对象还是字符串
|
// 判断传入的是对象还是字符串
|
||||||
if (typeof meshOrName === 'string') {
|
if (typeof meshOrName === 'string') {
|
||||||
@ -473,7 +479,8 @@ export class AppModel extends Monobehiver {
|
|||||||
* @param modelConfig 模型配置对象
|
* @param modelConfig 模型配置对象
|
||||||
*/
|
*/
|
||||||
async replaceModel(modelConfig: ModelMetadata): Promise<LoadResult> {
|
async replaceModel(modelConfig: ModelMetadata): Promise<LoadResult> {
|
||||||
console.log(modelConfig.modelId, this.modelDic);
|
|
||||||
|
|
||||||
|
|
||||||
this.removeByName(modelConfig.modelId);
|
this.removeByName(modelConfig.modelId);
|
||||||
return await this.addSingle(
|
return await this.addSingle(
|
||||||
@ -500,7 +507,8 @@ export class AppModel extends Monobehiver {
|
|||||||
this.modelDic.Remove(modelName);
|
this.modelDic.Remove(modelName);
|
||||||
this.modelMetadataDic.Remove(modelName);
|
this.modelMetadataDic.Remove(modelName);
|
||||||
this.mainApp.gameManager?.updateDictionaries();
|
this.mainApp.gameManager?.updateDictionaries();
|
||||||
console.log(`Model removed: ${modelName}`);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -509,7 +517,8 @@ export class AppModel extends Monobehiver {
|
|||||||
*/
|
*/
|
||||||
removeAll(): void {
|
removeAll(): void {
|
||||||
const modelNames = this.modelDic.Keys();
|
const modelNames = this.modelDic.Keys();
|
||||||
console.log(`开始清除所有模型,共 ${modelNames.length} 个模型`);
|
|
||||||
|
|
||||||
|
|
||||||
modelNames.forEach(modelName => {
|
modelNames.forEach(modelName => {
|
||||||
const meshes = this.modelDic.Get(modelName);
|
const meshes = this.modelDic.Get(modelName);
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export class GameManager extends Monobehiver {
|
|||||||
this.updateDictionaries();
|
this.updateDictionaries();
|
||||||
|
|
||||||
this.cacheRollerDoorMeshes();
|
this.cacheRollerDoorMeshes();
|
||||||
console.log('材质字典:', this.materialDic);
|
|
||||||
this.setRollerDoorScale("Box006.001", new Vector3(0.12, 0.02, 0.118));
|
this.setRollerDoorScale("Box006.001", new Vector3(0.12, 0.02, 0.118));
|
||||||
this.setRollerDoorScale("Box005.001", new Vector3(0.13, 0.02, 0.12));
|
this.setRollerDoorScale("Box005.001", new Vector3(0.13, 0.02, 0.12));
|
||||||
}
|
}
|
||||||
@ -760,9 +760,7 @@ export class GameManager extends Monobehiver {
|
|||||||
}): void {
|
}): void {
|
||||||
this.updateDictionaries();
|
this.updateDictionaries();
|
||||||
|
|
||||||
// 调试:打印所有材质名称
|
|
||||||
console.log('[applyMaterial] 查找材质:', options.target);
|
|
||||||
console.log('[applyMaterial] 当前所有材质:', this.materialDic.Values().map(m => m.name));
|
|
||||||
|
|
||||||
// 查找目标材质(支持精确匹配和前缀匹配)
|
// 查找目标材质(支持精确匹配和前缀匹配)
|
||||||
const targetMaterials: PBRMaterial[] = [];
|
const targetMaterials: PBRMaterial[] = [];
|
||||||
@ -777,7 +775,7 @@ export class GameManager extends Monobehiver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[applyMaterial] 找到材质:', targetMaterials.map(m => m.name));
|
|
||||||
|
|
||||||
// 应用材质属性到目标材质
|
// 应用材质属性到目标材质
|
||||||
targetMaterials.forEach(material => {
|
targetMaterials.forEach(material => {
|
||||||
@ -816,6 +814,6 @@ export class GameManager extends Monobehiver {
|
|||||||
material.markDirty();
|
material.markDirty();
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Material applied to ${targetMaterials.length} material(s): ${options.target}`, options);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
微信图片_20260518114457_432_3116.png
Normal file
BIN
微信图片_20260518114457_432_3116.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
Reference in New Issue
Block a user