react-theree-fiber를 사용하여 JSX 3D 인쇄

기사 제작일자: 2019-12-29
react-three-fiberthree.js의 Exporter를 사용하여 JSX 3D 인쇄 성공

이번에 그 PoC를 기술합니다.

어떻게 된 거야?


react-three-fiber는 Three입니다.React에서 js를 처리하기 위한 라이브러리가 됩니다.
이 라이브러리는 특성상mesh, geometry 등 다각형을 구성하는 부분을 JSX로 기록할 수 있다.
function Thing() {
  return (
    <mesh>
      <boxBufferGeometry attach="geometry" args={[1, 1, 1]} />
      <meshNormalMaterial attach="material" />
    </mesh>
  )
}
그리고 3D 프린터에 사용되는 three.js의 데이터를 각 STL, OBJ,glTF로 변환해야 합니다.
이것도 there야.js에 STLExporter, GTLFexporter 등 각종 형식이 갖추어져 있습니다(일부 Exporter는undocumented로 불안정할 수 있습니다)
이를 조합하면 JSX의 3D 인쇄가 가능합니다.

Demo


https://codesandbox.io/s/react-three-fiber-to-stl-or-gltf-v5-lsvn2?fontsize=14&hidenavigation=1&module=%2Fsrc%2Fmodel%2FModel.tsx&theme=dark
Model.tsx를 편집하면 STL과glTF가 출력됩니다(약간 무거우니 주의하십시오)

디테일 부분


모델 생성


모델들은 이번에 이런 느낌으로 탄생했다.BufferGeometryGeometry 모두 사용 가능하며 끼워 넣어도 문제없습니다.
// Model.tsx
import { Canvas, useFrame, useThree } from "react-three-fiber"
import React from "react"

export const Model = () => {
  return (
    <mesh>
      <Model1 />
      <Model2 />
    </mesh>
  )
}
const Model1 = () => {
  return (
    <mesh position={[0, 0, 0]}>
      <cylinderBufferGeometry attach="geometry" args={[5, 5, 5]} />
      <meshNormalMaterial attach="material" />
    </mesh>
  )
}
const Model2 = () => {
  return (
    <mesh>
      <mesh position={[-5, -1.5, -3]}>
        <boxBufferGeometry attach="geometry" args={[6, 2, 5]} />
        <meshNormalMaterial attach="material" />
      </mesh>
      <mesh>
        <mesh position={[0, 3, -1]}>
          <octahedronBufferGeometry attach="geometry" args={[4]} />
          <meshNormalMaterial attach="material" />
        </mesh>
        <mesh position={[3, 0.5, 3]}>
          <sphereGeometry attach="geometry" args={[3, 10, 32]} />
          <meshNormalMaterial attach="material" />
        </mesh>
      </mesh>
    </mesh>
  )
}
그리고 이렇게 모형을 그리면 돼요.
const App = () => {
  const ref = useRef()
  const { gl } = useThree()
  gl.setClearColor("#ff99cc")

  return <Canvas>
    <Model />
  </Canvas>
}

exporter에 대응할 수 있도록 traverse scene


STL의 전환 자체는 앞서 말한 바와 같이 STLExporter만 사용하면 된다.useThree를 통해 scene 획득 가능
// ExportStl.tsx
import { STLExporter } from "three/examples/jsm/exporters/STLExporter"

export const ExportStl = () => {
  const { scene } = useThree()
  useEffect(() => {
    const stl = new STLExporter().parse(scene)
    console.log(stl)
  }, [scene])
  return <mesh></mesh>
}
Canvas에 동일하게 구성하면 됩니다.
const App = () => {
 // ...
  return <Canvas>
    <Model />
    <ExportStl />
  </Canvas>
}
단, 이용된geometry 등에 오류가 발생하기 쉽거나,geometry 간에merge가 발생하지 않도록 분산된 대상으로 토출되어 인쇄 데이터 제작에 불편하기 때문에 사전에 다음과 같이 변환한다.
import {
  Mesh,
  MeshBasicMaterial,
  Scene,
  Geometry,
  BufferGeometry,
  Object3D
} from "three"

export const toRenderble = (scene: Scene): Scene => {
  let tmpGeometry = new Geometry()

  scene.clone().traverse((mesh) => {
    if (!isMesh(mesh)) return
    if (!mesh.geometry) {
      return
    }
    // 利用できるBufferGeometryをGemometryに揃える(後述)
    const appendGeom = toRenderableGeometry(mesh.geometry)
    if (!appendGeom) {
      return null
    }

    // 親のmeshを考慮する
    if (mesh.parent) {
      mesh.parent.updateMatrixWorld()
      mesh.applyMatrix(mesh.parent.matrixWorld)
    }

    mesh.geometry = appendGeom
    // meshをマージしていく
    tmpGeometry.mergeMesh(mesh)
  })

  // 最後、更にそれをBufferGeometryに変換する
  const outputScene = new Scene()
  const buf = new BufferGeometry().fromGeometry(tmpGeometry)
  const mesh = new Mesh(buf, new MeshBasicMaterial())
  outputScene.add(mesh)
  return outputScene
}
버퍼 Geometry를 Geometry로 한 번 바꿀 때 일부 바꿀 수 없는 버퍼 Geometry에 실패한 경우(react-three-fiber가 삽입되었을 수도 있음?)그래서 건너뛰고 있습니다.
const toRenderableGeometry = (
  geom: Geometry | BufferGeometry
): Geometry | null => {
  if (isGeometry(geom)) {
    return geom
  }
  if (geom.index === null && !geom.getAttribute("position")) {
    return null
  }

  // わりとfromBufferGeometryに失敗するパターンはありそうなので、失敗したらnullとしてしまう
  try {
    const buf = new Geometry().fromBufferGeometry(geom)
    return buf
  } catch (e) {
    console.warn(`skip: ${geom}`)
    return null
  }
}

그리고 아까 Component로 불러내면 돼요.결과적으로 이번에는 Context에 맡기도록 하겠습니다.
export const ExportStl = () => {
  const { scene } = useThree()
  const { setStl } = useExporterStore()
  useEffect(() => {
    const copyScene = toRenderble(scene)
    const stl = new STLExporter().parse(copyScene)
    setStl(stl)
  }, [scene])
  return <mesh></mesh>
}
도 다음과 같이 hooks화할 수 있다
export const useSTLExporter = () => {
  const { scene } = useThree()
  const [result, setResult] = useState()
  useEffect(() => {
    const copyScene = toRenderble(scene)
    const stl = new STLExporter().parse(copyScene)
    setResult(stl)
  }, [scene])
  return result
}
glTF의 상황은 다음과 같다.
const exportGltf = (scene, cb) => {
  return new GLTFExporter().parse(
    scene,
    (obj) => {
      cb(JSON.stringify(obj, null, 2))
    },
    { trs: true }
  )
}

export const ExportGltf = () => {
  const { scene } = useThree()
  useEffect(() => {
    const copyScene = toRenderble(scene)
    exportGltf(copyScene, (glTF) => {
      console.log(glTF)
    })
  }, [scene])
  return <mesh></mesh>
}

모델의 전환 결과를 밖으로 전달하기


위에서 설명한 바와 같이 Context를 활용한 말은react-three-fiber가 Reconceiler를 사용하여 사용자 정의 렌더링기<Canvas>가 되었기 때문에 일반적인 구성 요소에 비해 간단하게 사용할 수 없음useContex 등이다.
따라서 참고이것 괜찮아요?로 중간 인터페이스를 만들었다.

// App.tsx

const App = () => {
  return (
    <div>
      <ExporterStoreProvider> {/* ここはそのまま利用する */}
        <World />
      </ExporterStoreProvider>
    </div>
  )
}

// World.tsx

export const World = () => {
  const value = useExporterStore() // 一度値を取り出す
  return (
    <Canvas camera={{ position: [0, 0, 30] }}> 
      <ExportPassProvider value={value}> {/* Canvas内部で再度Providerにわたすことで中継させる */}
        <Model />
        <ExportStl />
      </ExportPassProvider>
    </Canvas>
  )
}

인쇄까지의 프로세스


STL 데이터는 이후에 React로 표시할 수 없기 때문에 Ultimaker cura 등의 방법으로 gcode로 전환한다.

그리고 이걸 3D로 출력하면 완성이 됩니다.
(인쇄가 그다지 예쁘지 않으니 용서해 주십시오.)

총결산


연기의 난점과 아직 고려되지 않은 패러다임 등이 있었지만, 선행적으로 목적을 달성했다.JSX 구조에서 이 기사 표지 이미지에 사용된 React의 로고처럼 규칙적인 것은 만들기 쉽다.이것들을 하나의 부품으로 다른 공구로 전체적으로 조립하면 좋을 가능성을 느낄 수 있다

좋은 웹페이지 즐겨찾기