使用threejs实现stp预览

 

目录

 一、组件封装

二、创建scene与水印添加


 一、组件封装

<template>
  <div class="stp-viewer-wrapper" id="myCanvas">
  </div>
</template>
<script setup lang="ts">
import { onMounted, onBeforeUnmount, watch, ref } from "vue";
import type { PropType } from "vue";
import type { WaterMark } from '@/type';
import * as THREE from "three";
//@ts-ignore
import occtimportjs from 'occt-import-js'
// control help
import { TransformControls } from "three/examples/jsm/controls/TransformControls"
import { TrackballControls } from "three/examples/jsm/controls/TrackballControls"
import { renderScene, throttle } from '@/utils'
import { Xhr } from "@/request/http";


const props = defineProps({
  fileName: {
    type: String,
    required: true
  },
  fileIndex: {
    type: String,
    required: true
  },
  slug: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true
  },
  waterMark: {
    type: Object as PropType<WaterMark>,
    required: true
  }
})

let occtImportPromise: any = null
let fileBuffer: Uint8Array

// 定义渲染器
const renderer = ref<THREE.WebGLRenderer>(new THREE.WebGLRenderer({ antialias: true }))
// 定义摄像头
const camera = ref<THREE.PerspectiveCamera | null>(null)
// 定义场景
let scene: THREE.Scene

const emits = defineEmits(['on-request-error'])

watch(() => props.fileName, async (val, oldVal) => {
  if(!await downloadFile()) return
  await loadStep()
})

// view step 
onMounted(async () => {
  if(!await downloadFile()) return
  await loadStep()
  window.addEventListener("resize", () => resizeStp())
})

const resizeStp = throttle(() => {
  if(camera.value !== null && renderer.value !== null) {
    camera.value.aspect = window.innerWidth / window.innerHeight
    camera.value?.updateProjectionMatrix()
    renderer.value.setSize(window.innerWidth, window.innerHeight)
    if(props.waterMark.authname) renderScene(window.innerWidth, window.innerHeight, props.waterMark, scene);
  }
})

onBeforeUnmount(() => window.removeEventListener("resize", () => resizeStp()))

const downloadFile = async () => {
  try {
    const response = await Xhr.request('previewer', {
      method: 'GET',
      params: {
        slug: props.slug,
        index: props.fileIndex,
        password: props.password,
      },
      responseType: 'arraybuffer'
    })

    fileBuffer = new Uint8Array(response.data)
    return true
  } catch (e) {
    emits('on-request-error', (e as Error).message)
    return false
  }
}
// get occt
const loadOcctImport = (): Promise<any> => {
  if (occtImportPromise) {
    return occtImportPromise
  }
  const occt = occtimportjs()
  occtImportPromise = occt

  return occtImportPromise
}

const LoadGeometry = async (targetObject: THREE.Object3D) => {
  // init occt-import-js
  const occt = await loadOcctImport()

  const result = occt.ReadStepFile(fileBuffer)

  // process the geometries of the result
  const group = new THREE.Group();
  for (let resultMesh of result.meshes) {
    const { mesh, edges } = buildMesh(resultMesh, true);
    group.add(mesh);

    if (edges) group.add(edges);
  }
  targetObject.add(group);
}

const buildMesh = (geometryMesh: any, showEdges: any) => {
  const geometry = new THREE.BufferGeometry();

  geometry.setAttribute("position", new THREE.Float32BufferAttribute(geometryMesh.attributes.position.array, 3));

  if (geometryMesh.attributes.normal) 
    geometry.setAttribute("normal", new THREE.Float32BufferAttribute(geometryMesh.attributes.normal.array, 3));
  
  geometry.name = geometryMesh.name;

  const index = Uint32Array.from(geometryMesh.index.array);
  geometry.setIndex(new THREE.BufferAttribute(index, 1));

  const outlineMaterial = new THREE.LineBasicMaterial({ color: 0x000000 });
  const defaultMaterial = new THREE.MeshPhongMaterial({
    color: geometryMesh.color ? new THREE.Color(geometryMesh.color[0], geometryMesh.color[1], geometryMesh.color[2]) : 0xcccccc,
    specular: 0,
  });

  let materials = [ defaultMaterial ];
  const edges = showEdges ? new THREE.Group() : null;

  if (geometryMesh.brep_faces && geometryMesh.brep_faces.length > 0) {
    for (let faceColor of geometryMesh.brep_faces) {
      const color = faceColor.color ? new THREE.Color(faceColor.color[0], faceColor.color[1], faceColor.color[2]) : defaultMaterial.color;
      materials.push(new THREE.MeshPhongMaterial({ color: color, specular: 0 }));
    }
    const triangleCount = geometryMesh.index.array.length / 3;

    let triangleIndex = 0;
    let faceColorGroupIndex = 0;

    while (triangleIndex < triangleCount) {
      const firstIndex = triangleIndex;
      let lastIndex = null;
      let materialIndex = null;
      if (faceColorGroupIndex >= geometryMesh.brep_faces.length) {
        lastIndex = triangleCount;
        materialIndex = 0;
      } else if (triangleIndex < geometryMesh.brep_faces[faceColorGroupIndex].first) {
        lastIndex = geometryMesh.brep_faces[faceColorGroupIndex].first;
        materialIndex = 0;
      } else {
        lastIndex = geometryMesh.brep_faces[faceColorGroupIndex].last + 1;
        materialIndex = faceColorGroupIndex + 1;
        faceColorGroupIndex++;
      }
      geometry.addGroup(firstIndex * 3, (lastIndex - firstIndex) * 3, materialIndex);
      triangleIndex = lastIndex;

      if (edges) {
        const innerGeometry = new THREE.BufferGeometry();
        innerGeometry.setAttribute("position", geometry.attributes.position);

        if (geometryMesh.attributes.normal) innerGeometry.setAttribute("normal", geometry.attributes.normal);

        innerGeometry.setIndex(new THREE.BufferAttribute(index.slice(firstIndex * 3, lastIndex * 3), 1));

        const innerEdgesGeometry = new THREE.EdgesGeometry(innerGeometry, 180);
        const edge = new THREE.LineSegments(innerEdgesGeometry, outlineMaterial);

        edges.add(edge);
      }
    }
  }

  const mesh = new THREE.Mesh(geometry, materials.length > 1 ? materials : materials[0]);
  mesh.name = geometryMesh.name;

  if (edges) edges.renderOrder = mesh.renderOrder + 1;

  return { mesh, geometry, edges };
}
// 预览
const loadStep = async () => {
  const width = window.innerWidth;
  const height = window.innerHeight;
  // generate html element
  renderer.value.setSize(width, height);
  renderer.value.setClearColor(0xfafafa);
  const myCanvas = document.getElementById('myCanvas')
  if(!myCanvas) return
  const renderDom = renderer.value.domElement
  myCanvas!.appendChild(renderDom);

  camera.value = new THREE.PerspectiveCamera(50, width / height, 1.0, 100000.0);
  camera.value.position.set(5000.0, 15000.0, 10000.0);
  camera.value.up.set(0.0, 0.0, 1.0);
  camera.value.lookAt(new THREE.Vector3 (1, 0, 1));

  scene = new THREE.Scene();

  const ambientLight = new THREE.AmbientLight(0x444444);
  scene.add(ambientLight);

  const directionalLight = new THREE.DirectionalLight(0x888888);
  directionalLight.position.set(camera.value.position.x, camera.value.position.y, camera.value.position.z);
  scene.add(directionalLight);

  const trackBall = new TrackballControls(camera.value, renderDom);
  trackBall.update();
  trackBall.addEventListener('change', render => renderer.value.render(scene, camera.value as THREE.PerspectiveCamera));

  const control = new TransformControls(camera.value, renderDom);
  control.addEventListener('change', () => renderer.value.render(scene, camera.value as THREE.PerspectiveCamera));
  control.addEventListener('dragging-changed', function (event) {
      trackBall.enabled = !event.value;
  });

  const mainObject = new THREE.Object3D();
  LoadGeometry(mainObject);
  scene.add(mainObject);
  control.attach(mainObject);
  scene.add(control);

  if(props.waterMark.authname) renderScene(width, height, props.waterMark, scene);

  renderer.value.render(scene, camera.value);
  // renderer.setAnimationLoop((time) => {
  //   mainObject.rotation.x = time / 2000;
  //   mainObject.rotation.y = time / 1000;
  //   renderer.render(scene, camera);
  // });

  renderer.value.setAnimationLoop((time) => {
    trackBall.update();
    renderer.value.render(scene, camera.value as THREE.PerspectiveCamera);
  });
}

</script>
<style scoped lang="less">
.stp-viewer-wrapper {
  .set-height-width(100%, 100%);
}
</style>

二、创建scene与水印添加

import type { WaterMark } from '@/type'
import * as THREE from 'three'

/**
 * 创建水印
 * @param waterMark 水印内容
 * @returns HTMLCanvasElement
 */

export const waterMarkPattern = (waterMark: WaterMark): HTMLCanvasElement => {
  const canvas = document.createElement("canvas") as HTMLCanvasElement
  canvas.width = 200 
  canvas.height = 100

  const ctx = canvas.getContext("2d")! as CanvasRenderingContext2D 
  ctx.rotate((-18 * Math.PI) / 180)
  ctx.font = "20px Vedana"
  ctx.fillStyle = "rgba(0, 0, 0, .5)"
  ctx.textAlign = "left"
  ctx.textBaseline = "middle"
  ctx.fillText(waterMark.authname, 10, 60)

  return canvas
}

/**
 * 在目标元素上渲染水印
 * @param targetElementId 目标元素id
 * @param width 目标元素宽度
 * @param height 目标元素高度
 * @param waterMark 水印内容
 */
export const renderWatermark = ( targetElementId: string, width: number, height: number, waterMark: WaterMark) => {
  const canvas: HTMLCanvasElement = document.getElementById(targetElementId) as HTMLCanvasElement
  if(!canvas) return
  const ctx = canvas.getContext("2d")!
  // 平铺水印
  const pattern = ctx.createPattern(waterMarkPattern(waterMark), "repeat") as CanvasPattern
  ctx.rect(0, 0, width, height)
  ctx.fillStyle = pattern
  ctx.fill()
}

/**
 * 创建空canvas
 * @param width 
 * @param height 
 * @param fillStyle 
 * @returns 
 */
export const emptyCanvas = (width: number, height: number, fillStyle: string): HTMLCanvasElement => {
  const canvas = document.createElement("canvas") as HTMLCanvasElement // 默认size 300px * 150px
  const ctx = canvas.getContext("2d")!
  
  canvas.width = width 
  canvas.height = height

  ctx.fillStyle = fillStyle
  ctx.fillRect(0, 0, width, height); // First rectangle

  return canvas
}

/**
 * 创建纹理图片
 * @param width 纹理图片宽度
 * @param height 纹理图片高度
 * @param waterMark 水印
 */
export const create3DTexture = (width: number, height: number, waterMark: WaterMark): string => {
  const canvas = emptyCanvas(width, height, 'skyblue')
  const ctx = canvas.getContext("2d")!

  // 平铺水印
  const pattern = ctx.createPattern(waterMarkPattern(waterMark), "repeat") as CanvasPattern
  ctx.rect(0, 0, width, height)
  ctx.fillStyle = pattern
  ctx.fill()

  return canvas.toDataURL("image/jpeg", 1.0)
}

/**
 * 3d 添加背景
 * @param scene three.scene
 */
export const renderScene = (width: number, height: number, waterMark: WaterMark, scene: THREE.Scene) => {
  const texture = new THREE.Texture()
  const image = new Image()
  image.src = create3DTexture(width, height, waterMark)

  texture.image = image
  image.onload = () => texture.needsUpdate = true

  scene.background = texture
}
/**
 * 水印信息
 */
export type WaterMark = {
  /**
   * eg. 张三1234
   */
  authname: string;
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值