宅匠 3D 室内设计是一款纯 JavaScript 实现的户型编辑与三维可视化工具,提供 24 种家具预设(含 3D 造型件描述)、参数化墙体系统(门窗沿墙定位)、8 种绘图模式(墙体/门窗/房间/家具/标注/文本/选择)、Three.js WebGL 3D 渲染 + Canvas 2D 等轴测软件渲染双引擎、鸟瞰与漫游双视角、撤销重做、文件存取与 PNG 导出等完整室内设计能力。本文记录在鸿蒙 PC 平台上,基于 Electron 壳方案从零构建一个具备 2D 户型编辑、3D 装配可视化、家具拖拽放置、双渲染引擎自动切换的完整过程------涵盖参数化墙体系统、画家算法软件渲染、层级命中检测、JSON 序列化校验、grid 同格叠加弹层、以及鸿蒙安全适配等核心功能的实现细节与踩坑经验。
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
AtomGit 仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_SweetHome3D
一、技术架构分析
1.1 整体架构
本项目采用 Electron Web 层 + 鸿蒙 HAP 壳工程的分层架构:
- Electron Web 层:运行于 ArkWeb 引擎,提供完整的户型数据建模、2D Canvas 平面渲染、Three.js 3D 场景装配、Canvas 2D 等轴测软件渲染、家具目录管理、撤销重做、文件操作等室内设计能力
- 鸿蒙 HAP 壳工程:通过 web_engine 模块加载 Web 应用,提供窗口管理、文件系统访问、原生对话框、PNG 导出、屏幕自适应等系统能力
这种架构的核心优势在于:业务代码与平台完全解耦,Electron 部分可以在任何平台独立运行,鸿蒙壳仅负责适配和打包。与采用 contextBridge 安全模型的 Electron 应用不同,本工程采用 nodeIntegration:true + contextIsolation:false 的直通模式------渲染进程直接访问 Node.js 的 fs、path、electron 的 ipcRenderer 等 API,主进程负责 dialog 对话框、文件读写与 PNG 导出 IPC,引擎逻辑全部在渲染进程内完成。
1.2 技术方案
| 对比项 | 传统室内设计软件(Sweet Home 3D / SketchUp) | 鸿蒙适配版(宅匠 3D) |
|---|---|---|
| 建模引擎 | Java + Java3D / C++ + OpenGL | 纯 JavaScript 数据模型 + Three.js |
| 3D 渲染 | 硬件加速 OpenGL | WebGL 主渲染 + Canvas 2D 等轴测软件兑底 |
| 2D 渲染 | Java2D / Cairo | Canvas 2D API 参数化绘制 |
| 家具库 | 商业 3D 模型库 | 24 种预设 + box/cyl/sph/cone 造型件描述 |
| 文件操作 | 专有格式 | JSON 序列化 + .sh3d/.json + PNG 导出 |
| 部署方式 | 安装包 | HAP 包(鸿蒙应用) |
| 运行环境 | Windows/Linux/macOS | 鸿蒙 PC(ArkWeb 引擎) |
1.3 核心功能清单
| 功能模块 | 具体能力 |
|---|---|
| 家具预设库 | 24 种家具(6 分类)+ 3D 造型件描述(box/cyl/sph/cone) |
| 参数化墙体 | 起点终点定义 + 厚度属性 + 门窗沿墙参数定位(0~1 位置) |
| 2D 平面渲染 | Canvas 绘制墙体/门窗/家具/房间/标注/文本 + 选择高亮 |
| 3D 装配数据 | buildSceneData 由数据模型产出 boxes/cyls/sphs/cones/floors |
| WebGL 渲染 | Three.js 场景 + MeshLambertMaterial + 阴影 + OrbitControls |
| 软件渲染兑底 | Canvas 2D 等轴测投影 + 画家算法 + 背面剔除 + 深度排序 |
| 交互模式 | 8 种模式(选择/墙体/门/窗/房间/家具/标注/文本) |
| 撤销重做 | 快照栈(50 步上限)+ JSON 序列化/反序列化 |
| 文件操作 IPC | 3 个通道(file:open + file:save + file:exportPng) |
| 弹层系统 | grid 同格叠加 + 文本输入弹层 + 关于对话框 |
| 鸿蒙安全 | 菜单移除 + 新窗口拦截 + 禁用硬件加速 + SwiftShader 开关 |
| 深色主题 | Catppuccin Mocha 配色 + CSS 变量体系 |
二、环境准备



2.1 开发环境要求
| 项目 | 版本/信息 |
|---|---|
| 操作系统 | Windows 10/11 |
| 核心框架 | Electron (Node.js + Chromium) |
| 技术栈 | HTML5/Vanilla JavaScript |
| 3D 引擎 | Three.js(MIT 协议) |
| 渲染引擎 | WebGL + Canvas 2D API |
| 目标设备 | 鸿蒙 PC |
| 目标架构 | arm64-v8a |
| 开发工具 | DevEco Studio(鸿蒙官方 IDE) |
2.2 项目结构
暂时无法在飞书文档外展示此内容
2.3 依赖说明
| 依赖 | 版本 | 用途 |
|---|---|---|
| electron | 系统内置 | 桌面应用框架 |
| Three.js | r120+ | 3D WebGL 渲染引擎 |
| OrbitControls | 配套 | 3D 视角轨道控制器 |
| Sweet Home 3D | 参考设计 | 四面板架构与交互设计思想 |
2.4 应用身份信息
| 字段 | 值 | 说明 |
|---|---|---|
| bundleName | com.wei.zhaijiang3d | 应用唯一标识 |
| vendor | wei | 开发者 |
| versionName | 1.0.0 | 应用版本 |
| 应用名称 | 宅匠 3D 室内设计 | 中文显示名 |
| 英文名 | zhaijiang3d | package.json name |
| 许可证 | Apache-2.0 | 开源协议 |
2.5 构建与部署
构建流程遵循鸿蒙 HAP 壳工程的标准步骤:
- 在 DevEco Studio 中打开 ohos_hap 工程
- 确保 web_engine/src/main/resources/resfile/resources/app/ 部署目录包含所有业务文件
- 选择 arm64-v8a 目标架构,构建 HAP 包
- 安装到鸿蒙 PC 设备运行
2.6 部署目录同步
每次修改 Electron 层代码后,需将文件同步到部署目录:
暂时无法在飞书文档外展示此内容
2.7 鸿蒙权限配置
module.json5 中根据应用实际需要声明权限。宅匠作为室内设计工具,主要使用文件系统相关权限用于户型图文件存取与 PNG 图片导出。
三、核心适配流程
3.1 主进程:窗口创建与安全配置
主进程负责窗口管理、文件操作 IPC 和 PNG 导出,采用 nodeIntegration:true 直通模式:
暂时无法在飞书文档外展示此内容
关键配置说明:
- disableHardwareAcceleration:禁用硬件加速,避免鸿蒙 GPU 兼容性问题
- enable-unsafe-swiftshader:允许软件 WebGL 回退,供 Three.js 在无 GPU 环境创建渲染上下文
- 菜单双保险:Menu.setApplicationMenu(null) + mainWindow.setMenu(null)
- 窗口尺寸:主显示器 workAreaSize 的 95% 宽 × 90% 高
- 新窗口拦截:setWindowOpenHandler 拒绝一切新窗口创建
- 安全模型:nodeIntegration:true + contextIsolation:false,渲染进程直接访问 Node.js API
3.2 文件操作 IPC:3 个通道
主进程提供 3 个 IPC 通道处理文件操作:
暂时无法在飞书文档外展示此内容
三个通道各有分工:file:open 支持 .sh3d 和 .json 两种扩展名;file:save 默认文件名取"户型图.sh3d";file:exportPng 接收 Canvas dataUrl 并剥离 base64 头部后写入文件。
四、家具预设库与 3D 造型件系统
4.1 家具分类与预设结构
home-engine.js 定义了 6 个家具分类和 24 种预设:
暂时无法在飞书文档外展示此内容
每种预设包含 2D 属性(label/cat/width/height/color/icon)和 3D 造型件描述 d3。d3 数组中每个元素描述一个基本几何体:
暂时无法在飞书文档外展示此内容
4 种几何体类型:box(盒体)、cyl(圆柱)、sph(球体)、cone(圆锥)。每种造型件使用局部坐标(cm,y 向上),pos 为几何中心位置,size 为宽高深。
4.2 家具数据操作
createHomeData 返回包含 7 类元素数组的数据模型:
暂时无法在飞书文档外展示此内容
addFurniture 根据预设类型创建家具实例:
暂时无法在飞书文档外展示此内容
updateFurniture 支持批量属性更新(位置、旋转、尺寸等),通过 Object.keys 遍历 props 逐字段赋值。
4.3 墙体与门窗系统
墙体由起点终点定义,厚度默认 10cm。addWall 创建墙体,removeWall 删除墙体时级联清理依附的门窗:
暂时无法在飞书文档外展示此内容
门窗通过 wallId 关联宿主墙,position 为 0~1 的沿墙参数位置。getWallPoint 函数将参数位置转换为实际坐标:
暂时无法在飞书文档外展示此内容
findNearestWall 使用 pointToSegmentDist 计算点到线段距离,找到鼠标附近最近的墙体及其参数位置,用于门窗放置。
五、2D 平面渲染引擎
5.1 渲染管线
renderFloorPlan 是 2D 渲染入口,按层级顺序绘制所有元素:
暂时无法在飞书文档外展示此内容
9 层渲染顺序确保家具覆盖墙体、门窗嵌入墙体、选择高亮在最上层。
5.2 门与窗的绘制
门绘制为四分之一圆弧(表示开启方向)加一条垂直线段,窗绘制为两条平行线加半透明填充矩形:
暂时无法在飞书文档外展示此内容
门/窗绘制先通过 translate 移动到墙上参数位置,再 rotate 旋转到墙体方向,最后绘制局部图形。
5.3 层级命中检测
hitTest 按优先级检测鼠标命中的元素:家具 > 门 > 窗 > 文本 > 房间 > 墙体。
暂时无法在飞书文档外展示此内容
家具使用矩形命中区域,门/窗使用圆形命中区域(半径 = 宽度/2),文本使用 20px 半径圆形区域,房间使用射线法点包含检测,墙体使用点到线段距离检测。
5.4 房间面积计算
roomArea 使用鞋带公式计算多边形面积,从 cm² 转换为 m²:
暂时无法在飞书文档外展示此内容
pointInPolygon 使用射线法判断点是否在多边形内,用于房间命中检测。
六、3D 渲染引擎:WebGL 与软件双路径
6.1 Three.js 场景初始化
initThree 创建 WebGL 渲染器、透视相机、光照系统和轨道控制器:
暂时无法在飞书文档外展示此内容
detectSoftwareGL 通过 WEBGL_debug_renderer_info 扩展检测渲染器名称,匹配 swiftshader/llvmpipe/software 时降级画质。
6.2 3D 装配数据生成
buildSceneData 将数据模型转换为 3D 几何体描述:
暂时无法在飞书文档外展示此内容
rotateOffset 处理家具旋转后的局部坐标变换:
暂时无法在飞书文档外展示此内容
6.3 WebGL 场景重建
render3D 根据装配数据重建 Three.js 动态组:
暂时无法在飞书文档外展示此内容
matColor 使用 MeshLambertMaterial 替代 MeshStandardMaterial,对光照要求更低,颜色显示更稳定。
6.4 软件渲染兑底
当 WebGL 不可用时,initSoft 创建 Canvas 2D 等轴测渲染兑底:
暂时无法在飞书文档外展示此内容
softProject 实现等轴测投影:世界坐标 (wx, wy, wz) 经旋转、倾斜、缩放后映射到屏幕坐标。depth 值用于画家排序。
renderSoft 按画家算法渲染:先收集所有面的绘制项,按深度远→近排序后依次绘制:
暂时无法在飞书文档外展示此内容
盒体分解为 5 个可见面(底面不可见省略),每面 4 个顶点经旋转后世界坐标即时展开存入数组(不能存闭包,var 被后续迭代覆写会导致全部面画到最后一件家具处)。美术化明暗:顶面最亮 0.96,底面 0.42,侧面按朝向屏幕方向分档。
七、交互模式与编辑工具
7.1 八种绘图模式
setMode 切换当前绘图模式,同时重置临时状态:
暂时无法在飞书文档外展示此内容
7.2 鼠标交互处理
onCanvasMouseDown 根据当前模式分发处理:
暂时无法在飞书文档外展示此内容
7.3 键盘快捷键
bindKeyboardEvents 处理全局键盘事件:
暂时无法在飞书文档外展示此内容
7.4 双视角系统
setViewMode 切换鸟瞰与漫游视角:
暂时无法在飞书文档外展示此内容
鸟瞰模式启用 OrbitControls 自由交互,漫游模式禁用控制器,通过 updateVisitCamera 将相机定位到访客位置和朝向。
八、文件操作与撤销重做
8.1 撤销重做机制
采用快照栈实现,每次修改前推入当前状态序列化快照:
暂时无法在飞书文档外展示此内容
撤销栈上限 50 步,超过则丢弃最早的快照。每次 undo/redo 都通过 afterRestore 统一刷新属性面板、家具列表和 3D 视图。
8.2 JSON 序列化与反序列化
serialize 直接 JSON.stringify,deserialize 包含完整的元素校验和悬空引用清理:
暂时无法在飞书文档外展示此内容
pickValid 过滤结构不完整的元素,validNum 校验数值有效性。悬空引用清理确保门窗的 wallId 指向存在的墙体。nextId 取持久化值与现存最大 id+1 的较大者,避免删除高 id 元素后导入导致 id 复用。
8.3 PNG 导出
exportPlanPng 和 export3dPng 分别导出 2D 平面图和 3D 视图截图:
暂时无法在飞书文档外展示此内容
导出前清除选择高亮,导出后恢复。3D 导出根据当前渲染路径选择 WebGL 或软件渲染 Canvas。
九、常见问题与解决方案
Q1:软件 WebGL 环境下 Three.js 渲染全黑
问题现象: 禁用硬件加速后,Three.js 场景全部渲染为黑色,无法看到任何家具和墙体。
根因分析: 软件 WebGL 实现(SwiftShader/llvmpipe)不支持 PBR 材质(MeshStandardMaterial)所需的光照计算,导致所有表面反射为零。
解决: 改用 MeshLambertMaterial 替代 MeshStandardMaterial,并检测软件渲染环境后关闭阴影和高开销画质项:
暂时无法在飞书文档外展示此内容
MeshLambertMaterial 使用漫反射光照模型,对 GPU 要求极低,软件 WebGL 下颜色显示稳定。detectSoftwareGL 通过 WEBGL_debug_renderer_info 扩展匹配渲染器名称。
Q2:软件渲染兑底时家具面绘制顺序错误导致视觉穿模
问题现象: 软件渲染模式下,家具的某些面穿透其他面显示,视觉上出现穿模现象。
根因分析: 早期软件渲染未实现背面剔除,所有面都参与绘制,导致被遮挡的面仍然可见。
解决: 在收集绘制项时计算面法线与视线方向的点积,大于等于零则为背面,跳过不绘制:
暂时无法在飞书文档外展示此内容
盒体分解为 5 个可见面(底面不可见省略),背面剔除后画家排序不会出现穿模。
Q3:撤销重做栈无限增长导致内存溢出
问题现象: 长时间编辑后应用逐渐变慢,最终内存溢出崩溃。
根因分析: 每次操作都推入快照到撤销栈,未设置上限,大量 JSON 字符串累积导致内存溢出。
解决: 撤销栈设置 50 步上限,超过则丢弃最早的快照:
暂时无法在飞书文档外展示此内容
50 步上限平衡了撤销深度与内存占用,新操作清空重做栈符合用户预期。
Q4:反序列化旧版本文件时渲染崩溃
问题现象: 打开早期版本保存的 JSON 文件时,应用崩溃或渲染异常。
根因分析: 旧版本 JSON 缺少 rooms/dimensions/texts 数组,反序列化后这些字段为 undefined,渲染循环访问 undefined.length 抛异常。
解决: deserialize 入口补齐缺失数组,并对所有元素执行结构校验:
暂时无法在飞书文档外展示此内容
pickValid 过滤结构不完整的元素,悬空引用清理确保门窗的 wallId 指向存在的墙体。
Q5:导出 PNG 时选择高亮残留在图片中
问题现象: 导出的平面图 PNG 中包含选中元素的绿色虚线框。
根因分析: 导出前未清除 selectedId,renderOnce2D 绘制了选择高亮。
解决: 导出前临时保存并清除 selectedId,导出后恢复:
暂时无法在飞书文档外展示此内容
两次 renderOnce2D 分别用于导出无选择图片与恢复界面显示。
Q6:修改了代码,但 HAP 安装后没有生效
问题现象: 修改了 renderer.js、home-engine.js 等文件,重新构建 HAP 安装后,应用行为没有任何变化。
根因分析: 鸿蒙壳工程的实际运行文件从 web_engine/src/main/resources/resfile/resources/app/ 部署目录加载,而非开发目录。修改后必须手动同步。
解决: 每次修改后执行同步命令:
暂时无法在飞书文档外展示此内容
验证方法: 同步后用文件哈希对比确认一致性。
Q7:AGC 审核驳回------requestPermissions 声明了受限 ACL 权限
问题现象: AGC 审核驳回,指出 web_engine/src/main/module.json5 中声明了受限 ACL 权限,但应用隐私政策中未声明对应用途。
根因分析: Electron 壳工程模板默认声明了大量系统权限,宅匠作为室内设计工具不需要位置、相机、麦克风等权限。
解决: 精简权限声明,只保留应用实际使用的系统权限,移除 FILE_ACCESS_PERSIST、READ_WRITE_DESKTOP_DIRECTORY、READ_PASTEBOARD、CAMERA、MICROPHONE 等不需要的权限。宅匠无剪贴板功能,READ_PASTEBOARD 旁路不适用。
十、总结
本文完整记录了在鸿蒙 PC 平台上适配宅匠 3D 室内设计的全过程。通过 Electron 壳方案,我们实现了一个纯 JavaScript 的户型编辑与三维可视化工具,核心特性包括:
| 特性 | 实现方案 |
|---|---|
| 家具预设库 | 24 种家具(6 分类)+ box/cyl/sph/cone 3D 造型件描述 |
| 参数化墙体 | 起点终点定义 + 厚度属性 + 门窗沿墙参数定位(0~1 位置) |
| 2D 平面渲染 | Canvas 9 层绘制(网格/房间/墙体/门窗/家具/标注/文本/选择) |
| 3D 装配数据 | buildSceneData 由数据模型产出 boxes/cyls/sphs/cones/floors |
| WebGL 渲染 | Three.js + MeshLambertMaterial + 阴影 + OrbitControls |
| 软件渲染兑底 | Canvas 2D 等轴测投影 + 画家算法 + 背面剔除 + 深度排序 |
| 交互模式 | 8 种模式(选择/墙体/门/窗/房间/家具/标注/文本) |
| 撤销重做 | 快照栈(50 步上限)+ JSON 序列化/反序列化 |
| 文件操作 IPC | 3 个通道(open + save + exportPng)支持 .sh3d/.json/.png |
| 弹层系统 | grid 同格叠加 + 文本输入弹层 + 关于对话框 |
| 鸿蒙安全 | 菜单移除 + 新窗口拦截 + 禁用硬件加速 + SwiftShader 开关 |
| 深色主题 | Catppuccin Mocha 配色 + CSS 变量体系 |
适配过程中最关键的三个点:
- Three.js 双渲染引擎------WebGL 主渲染路径使用 MeshLambertMaterial 替代 MeshStandardMaterial 降低 GPU 要求,detectSoftwareGL 检测软件 WebGL 实现后关闭阴影和高开销画质项;Canvas 2D 软件渲染兑底路径实现等轴测投影 + 画家算法 + 背面剔除,盒体分解为 5 个可见面按深度远→近排序绘制
- 参数化墙体与门窗系统------墙体由起点终点定义,门窗通过 wallId 关联宿主墙,position 为 0~1 的沿墙参数位置,getWallPoint 将参数位置转换为实际坐标;removeWall 级联清理依附的门窗,findNearestWall 使用 pointToSegmentDist 计算鼠标附近最近墙体
- JSON 序列化校验与悬空引用清理------deserialize 入口补齐缺失数组兼容旧版本,pickValid 过滤结构不完整的元素,悬空引用清理确保门窗的 wallId 指向存在的墙体,nextId 取持久化值与现存最大 id+1 的较大者避免 id 复用
这些问题在标准 Electron 环境中不会出现或表现不同,是鸿蒙 ArkWeb 引擎特有的兼容性挑战。通过系统性的排查和修复,最终实现了稳定可用的鸿蒙版纯 JavaScript 户型编辑与三维可视化工具。