Skip to content

Instantly share code, notes, and snippets.

@oxyflour
Last active July 5, 2020 16:27
Show Gist options
  • Select an option

  • Save oxyflour/148a029fa5eb62e9b4b3a50ef3e086ba to your computer and use it in GitHub Desktop.

Select an option

Save oxyflour/148a029fa5eb62e9b4b3a50ef3e086ba to your computer and use it in GitHub Desktop.
unfold stl
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
const stl = require('stl'),
fs = require('fs')
const Vec = {
add(a, b) {
return a.map((v, i) => v + b[i])
},
sub(a, b) {
return a.map((v, i) => v - b[i])
},
dot(a, b) {
return a.reduce((s, v, i) => s + v * b[i], 0)
},
len(s) {
return Math.sqrt(this.dot(s, s))
},
angle(a, b) {
return Math.acos(Vec.dot(a, b) / Vec.len(a) / Vec.len(b))
},
cross(a, b) {
return [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
},
normal(a, b, c) {
return Vec.cross(Vec.sub(a, b), Vec.sub(b, c))
}
}
/**
*
* @param { number[] } p0
* @param { number[] } p1
* @param { number[] } p2
* @param { number[] } pa
* @param { number[] } pb
* @returns { number[] }
*/
function remap(p0, p1, p2, pa, pb, flip = false) {
const [a, b] = [Vec.sub(p2, p0), Vec.sub(p1, p0)],
s = Vec.len(a),
[x, y] = Vec.sub(pb, pa),
t = Math.atan2(y, x) - Vec.angle(a, b) * (flip ? -1 : 1)
return Vec.add(pa, [s * Math.cos(t), s * Math.sin(t), 0])
}
/**
*
* @param { number[] } p0
* @param { number[] } p1
* @param { number[] } p2
* @param { number[] } p3
*/
function flipped(p0, p1, p2, p3) {
return Vec.dot(Vec.normal(p0, p1, p2), Vec.normal(p3, p2, p1)) < 0
}
class Mesh {
/** @type { { [k: string]: number } } */
idx = { }
/** @type { { xyz: number[], uvw: number[], id: number }[] } */
pts = []
/** @param { number[] } xyz */
addPt(xyz) {
const k = xyz.map(v => v.toFixed(5)).join(',')
if (this.idx[k] === undefined) {
const id = this.idx[k] = this.pts.length
this.pts[id] = { xyz, id, uvw: [] }
}
return this.idx[k]
}
/** @type { { ij: number[], id: number, fcs: number[] }[] } */
eds = []
/** @param { number[] } ij */
addEd(ij) {
const k = ij.sort().join(':')
if (this.idx[k] === undefined) {
const id = this.idx[k] = this.eds.length
this.eds[id] = { ij, id, fcs: [] }
}
return this.idx[k]
}
/** @type { { eds: number[], ijk: number[], fixed?: boolean }[] } */
fcs = []
/** @param { [number[], number[], number[]] } pts */
addFc(pts) {
const [i, j, k] = pts.map(xyz => this.addPt(xyz)),
eds = [[i, j], [j, k], [k, i]].map(ij => this.addEd(ij)),
ijk = [i, j, k],
f = this.fcs.length
for (const e of eds) {
this.eds[e].fcs.push(f)
}
this.fcs.push({ eds, ijk })
}
/**
* @param { number } f
* @param { (f: number, p: number) => void } cb
*/
walk(f, cb, p = -1) {
if (p < 0) {
for (const fc of this.fcs) {
fc.fixed = false
}
}
const fc = this.fcs[f]
if (!fc.fixed) {
cb(f, p)
fc.fixed = true
for (const e of fc.eds) {
for (const n of this.eds[e].fcs) {
this.walk(n, cb, f)
}
}
}
return this
}
unfold() {
for (const pt of this.pts) {
pt.uvw.length = 0
}
const f = Math.floor(Math.random() * this.fcs.length),
[p0, p1] = this.fcs[f].ijk.map(i => this.pts[i])
p0.uvw = p0.xyz.slice()
const d = Vec.sub(p1.xyz, p0.xyz),
s = Vec.len(d),
t = Math.atan2(d[1], d[0])
p1.uvw = Vec.add(p0.uvw, [s * Math.cos(t), s * Math.sin(t), 0])
return this.walk(f, (f, p) => {
const [p0, p1, p2] = this.fcs[f].ijk.map(i => this.pts[i]),
[p3] = p >= 0 ? this.fcs[p].ijk.filter(i => !this.fcs[f].ijk.includes(i)).map(i => this.pts[i]) : []
if (!p0.uvw.length && p1.uvw.length && p2.uvw.length) {
p0.uvw = remap(p1.xyz, p2.xyz, p0.xyz, p1.uvw, p2.uvw)
if (p3 && p3.uvw.length && flipped(p0.uvw, p1.uvw, p2.uvw, p3.uvw)) {
p0.uvw = remap(p1.xyz, p2.xyz, p0.xyz, p1.uvw, p2.uvw, true)
}
} else if (p0.uvw.length && !p1.uvw.length && p2.uvw.length) {
p1.uvw = remap(p0.xyz, p2.xyz, p1.xyz, p0.uvw, p2.uvw)
if (p3 && p3.uvw.length && flipped(p1.uvw, p0.uvw, p2.uvw, p3.uvw)) {
p1.uvw = remap(p0.xyz, p2.xyz, p1.xyz, p0.uvw, p2.uvw, true)
}
} else if (p0.uvw.length && p1.uvw.length && !p2.uvw.length) {
p2.uvw = remap(p0.xyz, p1.xyz, p2.xyz, p0.uvw, p1.uvw)
if (p3 && p3.uvw.length && flipped(p2.uvw, p1.uvw, p0.uvw, p3.uvw)) {
p2.uvw = remap(p0.xyz, p1.xyz, p2.xyz, p0.uvw, p1.uvw, true)
}
} else {
console.warn(`[WARN] ignore face`)
console.warn(p0)
console.warn(p1)
console.warn(p2)
}
})
}
/** @param { string } file */
write(file, description = '...') {
const facets = this.fcs
.filter(fc => fc.ijk.every(i => this.pts[i].uvw.length))
.map(fc => ({ verts: fc.ijk.map(i => this.pts[i]).map(p => p.uvw.length ? p.uvw : p.xyz) }))
fs.writeFileSync(file, stl.fromObject({ description, facets }))
}
}
const mesh = new Mesh()
for (const face of stl.toObject(fs.readFileSync('faces.stl')).facets) {
mesh.addFc(face.verts)
}
mesh.unfold().write('unfold.stl')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment