POV-Ray : Newsgroups : povray.general : Impossible stl file conversion! : Re: Impossible stl file conversion! Server Time
15 Sep 2025 09:52:47 EDT (-0400)
  Re: Impossible stl file conversion!  
From: ingo
Date: 11 Sep 2025 14:30:00
Message: <web.68c31440a068c28c17bac71e8ffb8ce3@news.povray.org>
A quicky in Nim, not much tested:

---%<------%<------%<------%<---

#asciistl2pov.nim

import std/[math, sequtils, strformat, strutils]

type
  Vec3 = array[3, float]
  Face = array[3, int]

proc vec3(x,y,z: float): Vec3 = [x,y,z]

proc `+`(a,b: Vec3): Vec3 = [a[0]+b[0], a[1]+b[1], a[2]+b[2]]

proc `/`(a: Vec3, s: float): Vec3 = [a[0]/s, a[1]/s, a[2]/s]

proc dot(a,b: Vec3): float = a[0]*b[0]+a[1]*b[1]+a[2]*b[2]

proc length(v: Vec3): float = sqrt(dot(v,v))

proc normalize(v: Vec3): Vec3 =
  let l = length(v)
  if l > 1e-9: v / l else: vec3(0,1,0)


# Load STL, ASCII only
proc loadSTL(path: string): (seq[Vec3], seq[Face], seq[Vec3]) =
  var verts: seq[Vec3] = @[]
  var faces: seq[Face] = @[]
  var norms: seq[Vec3] = @[]
  var curVerts: seq[int] = @[]
  var curNormal = vec3(0,0,0)
  for line in lines(path):
    let l = line.strip()
    if l.startsWith("facet normal"):
      let p = l.splitWhitespace()
      curNormal = vec3(p[2].parseFloat, p[3].parseFloat, p[4].parseFloat)
    elif l.startsWith("vertex"):
      let p = l.splitWhitespace()
      let v = vec3(p[1].parseFloat, p[2].parseFloat, p[3].parseFloat)
      verts.add(v)
      curVerts.add(verts.len-1)
    elif l.startsWith("endfacet"):
      if curVerts.len == 3:
        faces.add([curVerts[0], curVerts[1], curVerts[2]])
        norms.add(curNormal)
      curVerts.setLen(0)
  return (verts, faces, norms)


proc vertexToFaces(faces: seq[Face], vertexCount: int): seq[seq[int]] =
  result = newSeqWith(vertexCount, newSeq[int]())
  for fIdx, f in faces:
    for vi in f:
      result[vi].add(fIdx)


proc computeVertexNormals(
  vertexCount: int, faceNormals: seq[Vec3], v2f: seq[seq[int]]
): seq[Vec3] =
  result = newSeqWith(vertexCount, vec3(0,0,0))
  for vIdx in 0..<vertexCount:
    var accum = vec3(0,0,0)
    for fIdx in v2f[vIdx]:
      accum = accum + faceNormals[fIdx]
    result[vIdx] = normalize(accum)


proc mesh2ToString(
  verts: seq[Vec3], faces: seq[Face], normals: seq[Vec3]
): string =
  var sb = newStringOfCap(verts.len * 100) # Preallocate rough size
  sb.add("#declare MSH = mesh2 {\n")

  # Vertex vectors
  sb.add("  vertex_vectors {\n")
  sb.add("    " & $verts.len & ",\n")
  for v in verts:
    sb.add(&"    <{v[0]}, {v[1]}, {v[2]}>,\n")
  sb.add("  }\n")

  # Normal vectors
  sb.add("  normal_vectors {\n")
  sb.add("    " & $normals.len & ",\n")
  for n in normals:
    sb.add(&"    <{n[0]}, {n[1]}, {n[2]}>,\n")
  sb.add("  }\n")

  # Face indices
  sb.add("  face_indices {\n")
  sb.add("    " & $faces.len & ",\n")
  for f in faces:
    sb.add(&"    <{f[0]}, {f[1]}, {f[2]}>,\n")
  sb.add("  }\n")

  sb.add("}\n")
  result = sb


proc writeMesh2(outPath: string, verts: seq[Vec3], faces: seq[Face], normals:
seq[Vec3]) =
  let s = mesh2ToString(verts, faces, normals)
  writeFile(outPath, s)


when isMainModule:
  let (verts, faces, faceNorms) = loadSTL("mesh_output.stl")
  let v2f = vertexToFaces(faces, verts.len)
  let vertexNorms = computeVertexNormals(verts.len, faceNorms, v2f)
  writeMesh2("model.inc", verts, faces, vertexNorms)
  echo "Converted STL to POV-Ray mesh2 with normals."

---%<------%<------%<------%<---

ingo


Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.