Three.js 场景导出 SVG 文件实现指南

Three.js 场景导出 SVG 文件实现指南

1. 功能概述

本文档详细介绍如何将 Three.js 3D 场景转换为 SVG 格式的文件,并实现对特定材质(LineMaterial)的过滤。这个功能通常用于:

  • 场景快照保存

  • 矢量图导出

  • 2D 文档生成

2. 核心实现流程

2.1 基础环境准备

首先需要获取必要的 Three.js 组件和渲染尺寸:

// 获取必要的组件
const renderer = viewer.renderer;  // Three.js 渲染器
const scene = viewer.threeScene;   // Three.js 场景
const camera = viewer.camera;      // Three.js 相机
​
// 获取实际渲染尺寸
const width = renderer.domElement.width;
const height = renderer.domElement.height;
​
// 保存相机原始状态
const originalAspect = camera.aspect;
const originalMatrix = camera.matrix.clone();
const originalProjectionMatrix = camera.projectionMatrix.clone();

2.2 场景对象过滤

在导出之前,需要处理场景中的对象可见性:

// 保存和设置场景对象可见性
const originalVisibility = new Map();
scene.traverse((object) => {
  // 保存原始可见性状态
  originalVisibility.set(object, object.visible);
  // 隐藏使用 LineMaterial 的线段
  if (object.material && object.material.isLineMaterial) {
    object.visible = false;
  }
});

2.3 SVG 渲染设置

使用 Three.js 的 SVGRenderer 进行渲染:

// 创建 SVG 渲染器
const svgRenderer = new SVGRenderer();
svgRenderer.setSize(width, height);
​
// 更新相机参数以匹配渲染尺寸
camera.aspect = width / height;
camera.updateProjectionMatrix();

2.4 生成 SVG 文件

将场景渲染为 SVG 并创建文件:

// 渲染场景到 SVG
svgRenderer.render(scene, camera);
​
// 获取 SVG 内容并添加必要的头信息
const svgContent = svgRenderer.domElement.outerHTML;
const finalSvgContent = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
${svgContent}`;
​
// 创建文件对象
const blob = new Blob([finalSvgContent], { type: 'image/svg+xml' });
const file = new File([blob], 'plotLayer.svg', { type: 'image/svg+xml' });

2.5 清理和状态恢复

确保场景状态被正确恢复:

// 恢复场景中物体的可见性
originalVisibility.forEach((visible, object) => {
  object.visible = visible;
});
​
// 恢复相机状态
camera.aspect = originalAspect;
camera.matrix.copy(originalMatrix);
camera.projectionMatrix.copy(originalProjectionMatrix);
camera.updateProjectionMatrix();
​
// 清理 SVG 渲染器
if (svgRenderer.domElement && svgRenderer.domElement.parentNode) {
  svgRenderer.domElement.parentNode.removeChild(svgRenderer.domElement);
}

3. 完整实现代码

export function getScreenPlotEntity() {
  return new Promise(async (resolve, reject) => {
    try {
      let viewer = useViewer();
      if (!viewer) {
        throw new Error('Viewer not initialized');
      }
​
      const renderer = viewer.renderer;
      const scene = viewer.threeScene;
      const camera = viewer.camera;
​
      if (!renderer || !scene || !camera) {
        throw new Error('Required Three.js components not initialized');
      }
​
      // 获取实际渲染尺寸
      const width = renderer.domElement.width;
      const height = renderer.domElement.height;
​
      // 保存相机状态
      const originalAspect = camera.aspect;
      const originalMatrix = camera.matrix.clone();
      const originalProjectionMatrix = camera.projectionMatrix.clone();
​
      try {
        // SVG 渲染器设置
        const svgRenderer = new SVGRenderer();
        svgRenderer.setSize(width, height);
​
        // 更新相机参数
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
​
        // 处理场景对象可见性
        const originalVisibility = new Map();
        scene.traverse((object) => {
          originalVisibility.set(object, object.visible);
          if (object.material && object.material.isLineMaterial) {
            object.visible = false;
          }
        });
​
        // 渲染和创建文件
        svgRenderer.render(scene, camera);
        const svgContent = svgRenderer.domElement.outerHTML;
        const finalSvgContent = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
${svgContent}`;
​
        const blob = new Blob([finalSvgContent], { type: 'image/svg+xml' });
        const file = new File([blob], 'plotLayer.svg', { type: 'image/svg+xml' });
        
        resolve(file);
​
      } finally {
        // 恢复场景状态
        camera.aspect = originalAspect;
        camera.matrix.copy(originalMatrix);
        camera.projectionMatrix.copy(originalProjectionMatrix);
        camera.updateProjectionMatrix();
​
        // 恢复场景中物体的可见性
        originalVisibility.forEach((visible, object) => {
          object.visible = visible;
        });
      }
    } catch (error) {
      console.error('SVG export failed:', error);
      reject(error);
    }
  });
}

4. 使用方法

4.1 基本使用

// 导出 SVG 文件
getScreenPlotEntity()
  .then(file => {
    console.log('SVG file generated:', file);
    // 处理生成的文件
  })
  .catch(error => {
    console.error('Failed to generate SVG:', error);
  });

4.2 文件下载示例

getScreenPlotEntity()
  .then(file => {
    const url = URL.createObjectURL(file);
    const link = document.createElement('a');
    link.href = url;
    link.download = file.name;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
  });

代码地址

GitHub - chaoyang915506/three-learningContribute to chaoyang915506/three-learning development by creating an account on GitHub.icon-default.png?t=O83Ahttps://github.com/chaoyang915506/three-learning在线预览

Three.js Demoicon-default.png?t=O83Ahttps://chaoyang915506.github.io/three-learning/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值