lingmodeler
↓ 0 downloadsLingModeler: box modeling with quads and tris, rigging + weight painting, 2D vector illustration and animation, procedural textures and game assets; .lmesh / .ling native formats, OBJ, glTF and SVG import / export
Keywords 3d, modeling, vector, svg, gltf, obj, rigging, animation, textures, ling
lingfu install lingmodelerCode / Files (67)
ling/objfmt.ling
# ━━━ objfmt.ling — Wavefront OBJ + MTL import / export (polygons kept, UVs per corner, materials as colours / textures) ━━━
fn ob_mtl_parse(path) {
# returns flat [name, colour, texpath, ...]
bind out = list_new()
if !file_exists(path) { return out }
bind lines = str_split(read_file(path), "\n")
bind cur = ""
bind col = 13421772.0
bind tex = ""
bind i = 0.0
while i < len(lines) {
bind ln = str_trim(str_replace(list_get(lines, i), "\r", ""))
if str_starts_with(ln, "newmtl ") {
if cur != "" { bind out = list_push(out, cur) bind out = list_push(out, col) bind out = list_push(out, tex) }
bind cur = str_trim(str_slice(ln, 7.0, str_len(ln) - 7.0))
bind col = 13421772.0
bind tex = ""
} else if str_starts_with(ln, "Kd ") {
bind n = sv_numbers(str_slice(ln, 3.0, str_len(ln) - 3.0))
if len(n) > 2.5 { bind col = c_rgb(list_get(n, 0) * 255.0, list_get(n, 1) * 255.0, list_get(n, 2) * 255.0) }
} else if str_starts_with(ln, "map_Kd ") {
bind tex = str_trim(str_slice(ln, 7.0, str_len(ln) - 7.0))
}
bind i = i + 1.0
}
if cur != "" { bind out = list_push(out, cur) bind out = list_push(out, col) bind out = list_push(out, tex) }
return out
}
fn ob_import(path) {
if !file_exists(path) { return false }
bind lines = str_split(read_file(path), "\n")
bind dir = fs_parent(path)
# global pools
bind gx = list_new() bind gy = list_new() bind gz = list_new()
bind tu = list_new() bind tv = list_new()
bind mtl = list_new()
# current object
bind oname = path_stem(path)
bind remap_keys = list_new()
st_set("ob_n", 0.0)
bind lvx = list_new() bind lvy = list_new() bind lvz = list_new()
bind fi = list_new() bind fs = list_new() bind fc = list_new() bind uv = list_new() bind fcol = list_new()
bind map = list_new()
bind curcol = 0.0 - 1.0 bind curtex = ""
bind objcol = 0.0 - 1.0 bind objtex = ""
bind made = 0.0
bind i = 0.0
while i <= len(lines) {
bind ln = ""
if i < len(lines) { bind ln = str_trim(str_replace(list_get(lines, i), "\r", "")) }
bind flush = i == len(lines) || str_starts_with(ln, "o ") || (str_starts_with(ln, "g ") && len(fc) > 0.5)
if flush && len(fc) > 0.5 {
bind o = me_add_object(oname, lvx, lvy, lvz, fi, fs, fc)
st_set(mk(o, "uv"), uv)
# single-colour objects keep the colour as the object colour
bind uniform = true
bind q = 1.0
while q < len(fcol) { if list_get(fcol, q) != list_get(fcol, 0) { bind uniform = false } bind q = q + 1.0 }
if uniform && len(fcol) > 0.5 { if list_get(fcol, 0) >= 0.0 { st_set(mk(o, "col"), list_get(fcol, 0)) } }
else { st_set(mk(o, "fcol"), fcol) }
if objtex != "" {
bind tp = sel(file_exists(objtex), objtex, fs_join(dir, objtex))
if file_exists(tp) && p_has_images() { if tx_import_image(tp) { st_set(mk(o, "tex"), tx_cur()) } }
}
me_edges_dirty(o)
bind made = made + 1.0
bind lvx = list_new() bind lvy = list_new() bind lvz = list_new()
bind fi = list_new() bind fs = list_new() bind fc = list_new() bind uv = list_new() bind fcol = list_new()
bind map = list_new()
bind objtex = ""
}
if str_starts_with(ln, "o ") || str_starts_with(ln, "g ") { bind nm = str_trim(str_slice(ln, 2.0, str_len(ln) - 2.0)) if nm != "" { bind oname = nm } }
else if str_starts_with(ln, "v ") {
bind n = sv_numbers(str_slice(ln, 2.0, str_len(ln) - 2.0))
if len(n) > 2.5 { bind gx = list_push(gx, list_get(n, 0)) bind gy = list_push(gy, list_get(n, 1)) bind gz = list_push(gz, list_get(n, 2)) }
}
else if str_starts_with(ln, "vt ") {
bind n = sv_numbers(str_slice(ln, 3.0, str_len(ln) - 3.0))
if len(n) > 1.5 { bind tu = list_push(tu, list_get(n, 0)) bind tv = list_push(tv, list_get(n, 1)) }
}
else if str_starts_with(ln, "mtllib ") { bind mtl = ob_mtl_parse(fs_join(dir, str_trim(str_slice(ln, 7.0, str_len(ln) - 7.0)))) }
else if str_starts_with(ln, "usemtl ") {
bind nm = str_trim(str_slice(ln, 7.0, str_len(ln) - 7.0))
bind curcol = 0.0 - 1.0 bind curtex = ""
bind q = 0.0
while q + 2.0 < len(mtl) { if list_get(mtl, q) == nm { bind curcol = list_get(mtl, q + 1.0) bind curtex = list_get(mtl, q + 2.0) } bind q = q + 3.0 }
if curtex != "" { bind objtex = curtex }
}
else if str_starts_with(ln, "f ") {
bind parts = str_split(str_slice(ln, 2.0, str_len(ln) - 2.0), " ")
bind corners = list_new() bind cu = list_new()
bind q = 0.0
while q < len(parts) {
bind p = list_get(parts, q)
if p != "" {
bind ix = str_split(p, "/")
bind vi = to_number(list_get(ix, 0))
if vi < 0.0 { bind vi = len(gx) + vi } else { bind vi = vi - 1.0 }
# global -> local vertex (sparse map stored in st)
bind key = "obm_" + num_str(made) + "_" + num_str(vi)
bind li = 0.0 - 1.0
if st_has(key) > 0.5 { bind li = st_get(key, 0.0) }
if li < 0.0 {
bind li = len(lvx)
st_set(key, li)
bind map = list_push(map, key)
bind lvx = list_push(lvx, list_get(gx, vi)) bind lvy = list_push(lvy, list_get(gy, vi)) bind lvz = list_push(lvz, list_get(gz, vi))
}
bind corners = list_push(corners, li)
bind ux = 0.0 bind uy = 0.0
if len(ix) > 1.5 { if list_get(ix, 1) != "" {
bind ti = to_number(list_get(ix, 1))
if ti < 0.0 { bind ti = len(tu) + ti } else { bind ti = ti - 1.0 }
if ti >= 0.0 && ti < len(tu) { bind ux = list_get(tu, ti) bind uy = list_get(tv, ti) }
} }
bind cu = list_push(cu, ux) bind cu = list_push(cu, uy)
}
bind q = q + 1.0
}
if len(corners) >= 2.0 {
bind fs = list_push(fs, len(fi)) bind fc = list_push(fc, len(corners)) bind fcol = list_push(fcol, curcol)
bind q = 0.0
while q < len(corners) { bind fi = list_push(fi, list_get(corners, q)) bind q = q + 1.0 }
bind q = 0.0
while q < len(cu) { bind uv = list_push(uv, list_get(cu, q)) bind q = q + 1.0 }
}
}
bind i = i + 1.0
}
return made > 0.5
}
fn ob_export(path) {
bind ids = me_ids()
bind dir = fs_parent(path)
bind mtlname = path_stem(path) + ".mtl"
bind out = list_new()
bind mtl = list_new()
bind out = list_push(out, "# LingModeler " + APP_VERSION + " OBJ export")
bind out = list_push(out, "mtllib " + mtlname)
bind vbase = 1.0 bind tbase = 1.0 bind nbase = 1.0
bind matn = 0.0
bind j = 0.0
while j < len(ids) {
bind o = list_get(ids, j)
if st_get(mk(o, "vis"), 1.0) > 0.5 && me_nfaces(o) > 0.5 {
bind name = lf_ident(me_name(o))
bind out = list_push(out, "o " + name)
bind m = me_matrix(o)
bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
bind def = ps_deformed(o)
if len(def) > 2.5 { bind vx = list_get(def, 0) bind vy = list_get(def, 1) bind vz = list_get(def, 2) }
bind k = 0.0
while k < len(vx) {
bind p = m_apply(m, [list_get(vx, k), list_get(vy, k), list_get(vz, k)])
bind out = list_push(out, "v " + mx_num(list_get(p, 0)) + " " + mx_num(list_get(p, 1)) + " " + mx_num(list_get(p, 2)))
bind k = k + 1.0
}
bind fi = st_get(mk(o, "fi"), []) bind fs = st_get(mk(o, "fs"), []) bind fc = st_get(mk(o, "fc"), [])
bind uv = st_get(mk(o, "uv"), []) bind fcol = st_get(mk(o, "fcol"), [])
bind k = 0.0
while k < len(fi) { bind out = list_push(out, "vt " + mx_num(list_get(uv, k * 2.0)) + " " + mx_num(list_get(uv, k * 2.0 + 1.0))) bind k = k + 1.0 }
bind smooth = st_get(mk(o, "smooth"), 0.0) > 0.5
bind nrm = me_vertex_normals(o)
bind nmx = list_get(nrm, 0) bind nmy = list_get(nrm, 1) bind nmz = list_get(nrm, 2)
bind nc = 0.0
if smooth {
bind k = 0.0
while k < len(vx) {
bind n = v3_norm(m_apply_dir(m, [list_get(nmx, k), list_get(nmy, k), list_get(nmz, k)]))
bind out = list_push(out, "vn " + mx_num(list_get(n, 0)) + " " + mx_num(list_get(n, 1)) + " " + mx_num(list_get(n, 2)))
bind k = k + 1.0
}
bind nc = len(vx)
} else {
bind f = 0.0
while f < len(fc) {
bind n = v3_norm(m_apply_dir(m, me_face_normal(vx, vy, vz, fi, list_get(fs, f), list_get(fc, f))))
bind out = list_push(out, "vn " + mx_num(list_get(n, 0)) + " " + mx_num(list_get(n, 1)) + " " + mx_num(list_get(n, 2)))
bind f = f + 1.0
}
bind nc = len(fc)
}
bind out = list_push(out, sel(smooth, "s 1", "s off"))
# materials: the object colour (+ texture) and one per distinct face colour
bind base = st_get(mk(o, "col"), 13421772.0)
bind tex = st_get(mk(o, "tex"), 0.0 - 1.0)
bind texfile = ""
if tex >= 0.0 && p_has_images() {
bind texfile = lf_ident(name) + "_tex.png"
bind size = st_get(tk(tx_id(tex), "size"), 128.0)
bind img = tx_render_image(tex, size, size, 0.0)
if st_get(tk(tx_id(tex), "type"), "") == "image" { bind img = st_get(tk(tx_id(tex), "img"), 0.0 - 1.0) }
if img >= 0.0 { p_img_save(img, fs_join(dir, texfile)) } else { bind texfile = "" }
}
bind cols = list_new() bind names = list_new()
bind lastmat = ""
bind f = 0.0
while f < len(fc) {
bind c = base
if f < len(fcol) { if list_get(fcol, f) >= 0.0 { bind c = list_get(fcol, f) } }
bind mi = 0.0 - 1.0
bind q = 0.0
while q < len(cols) { if list_get(cols, q) == c { bind mi = q } bind q = q + 1.0 }
if mi < 0.0 {
bind mi = len(cols)
bind cols = list_push(cols, c)
bind mn = name + "_mat" + num_str(mi)
bind names = list_push(names, mn)
bind mtl = list_push(mtl, "newmtl " + mn)
bind mtl = list_push(mtl, "Kd " + mx_num(c_r(c) / 255.0) + " " + mx_num(c_g(c) / 255.0) + " " + mx_num(c_b(c) / 255.0))
bind mtl = list_push(mtl, "Ka 0 0 0")
bind mtl = list_push(mtl, "Ks 0.1 0.1 0.1")
bind mtl = list_push(mtl, "d " + mx_num(1.0))
if texfile != "" && c == base { bind mtl = list_push(mtl, "map_Kd " + texfile) }
bind mtl = list_push(mtl, "")
}
bind mn = list_get(names, mi)
if mn != lastmat { bind out = list_push(out, "usemtl " + mn) bind lastmat = mn }
bind s = list_get(fs, f) bind c2 = list_get(fc, f)
if c2 >= 3.0 {
bind parts = list_new()
bind k = 0.0
while k < c2 {
bind v = list_get(fi, s + k)
bind ni = sel(smooth, nbase + v, nbase + f)
bind parts = list_push(parts, num_str(vbase + v) + "/" + num_str(tbase + s + k) + "/" + num_str(ni))
bind k = k + 1.0
}
bind out = list_push(out, "f " + p_join(parts, " "))
} else if c2 == 2.0 {
bind out = list_push(out, "l " + num_str(vbase + list_get(fi, s)) + " " + num_str(vbase + list_get(fi, s + 1.0)))
}
bind f = f + 1.0
}
bind vbase = vbase + len(vx)
bind tbase = tbase + len(fi)
bind nbase = nbase + nc
}
bind j = j + 1.0
}
write_file(path, p_join(out, "\n") + "\n")
write_file(fs_join(dir, mtlname), "# LingModeler materials\n" + p_join(mtl, "\n") + "\n")
return true
}