灵符lingfu

lingmodeler

↓ 0 downloads

LingModeler: 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

Windows · x86-64Linux · x86-64LingOS · x86-64LingOS · Pi 5 ARM64Web · WASM
Keywords 3d, modeling, vector, svg, gltf, obj, rigging, animation, textures, ling
lingfu install lingmodeler
LicenseApache-2.0 OR MIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (67)

ling/svg.ling
# ━━━ svg.ling — SVG import (paths, shapes, groups, transforms, styles) and export (static or SMIL-animated) ━━━
# Import converts everything into the .ling vector model (bezier nodes); export writes plain SVG 1.1.
# Limits: arcs become polylines, text / gradients / clip paths / filters are skipped, multi-contour paths become one
# item per contour (holes are filled as separate shapes).

# ── tokenising helpers ───────────────────────────────────────────────────────
fn sv_attr(tag, name) {
    bind keys = [" " + name + "=\"", " " + name + "='", "\n" + name + "=\"", "\t" + name + "=\""]
    bind i = 0.0
    while i < len(keys) {
        bind key = list_get(keys, i)
        bind p = str_find(tag, key)
        if p >= 0.0 {
            bind q = str_slice(tag, p + str_len(key), str_len(tag) - p - str_len(key))
            bind quote = sel(str_ends_with(key, "'"), "'", "\"")
            bind e = str_find(q, quote)
            if e >= 0.0 { return str_slice(q, 0.0, e) }
        }
        bind i = i + 1.0
    }
    return ""
}
fn sv_style(tag, name) {
    bind st = sv_attr(tag, "style")
    if st != "" {
        bind parts = str_split(st, ";")
        bind i = 0.0
        while i < len(parts) {
            bind kv = str_split(list_get(parts, i), ":")
            if len(kv) > 1.5 { if str_trim(list_get(kv, 0)) == name { return str_trim(list_get(kv, 1)) } }
            bind i = i + 1.0
        }
    }
    return sv_attr(tag, name)
}
fn sv_num(s, d) {
    bind t = str_trim(str_replace(str_replace(s, "px", ""), "pt", ""))
    if t == "" { return d }
    return to_number(t)
}
# numbers out of an attribute / path string (commas, spaces, minus signs as separators, exponents kept)
fn sv_numbers(s) {
    bind t = str_replace(s, ",", " ")
    bind t = str_replace(t, "\n", " ")
    bind t = str_replace(t, "\t", " ")
    bind t = str_replace(t, "\r", " ")
    bind t = str_replace(t, "-", " -")
    bind t = str_replace(t, "e -", "e-")
    bind t = str_replace(t, "E -", "E-")
    bind parts = str_split(t, " ")
    bind out = list_new()
    bind i = 0.0
    while i < len(parts) {
        bind p = list_get(parts, i)
        if p != "" && p != "-" {
            # "0.5.5" -> 0.5 .5
            bind dots = str_split(p, ".")
            if len(dots) > 2.5 {
                bind first = list_get(dots, 0) + "." + list_get(dots, 1)
                bind out = list_push(out, to_number(first))
                bind q = 2.0
                while q < len(dots) { bind out = list_push(out, to_number("0." + list_get(dots, q))) bind q = q + 1.0 }
            } else { bind out = list_push(out, to_number(p)) }
        }
        bind i = i + 1.0
    }
    return out
}
fn sv_color(s) {
    bind t = str_lower(str_trim(s))
    if t == "" || t == "none" || t == "transparent" { return 0.0 - 1.0 }
    if str_starts_with(t, "#") { return c_from_hex(t) }
    if str_starts_with(t, "rgb(") {
        bind n = sv_numbers(str_replace(str_replace(str_replace(t, "rgb(", ""), ")", ""), "%", ""))
        if len(n) > 2.5 { return c_rgb(list_get(n, 0), list_get(n, 1), list_get(n, 2)) }
    }
    bind names = ["black", "white", "red", "green", "blue", "yellow", "orange", "purple", "gray", "grey", "cyan", "magenta", "lime", "navy", "teal", "maroon", "olive", "silver", "pink", "brown", "gold"]
    bind vals = [0.0, 16777215.0, 16711680.0, 32768.0, 255.0, 16776960.0, 16753920.0, 8388736.0, 8421504.0, 8421504.0, 65535.0, 16711935.0, 65280.0, 128.0, 32896.0, 8388608.0, 8421376.0, 12632256.0, 16761035.0, 10824234.0, 16766720.0]
    bind i = 0.0
    while i < len(names) { if list_get(names, i) == t { return list_get(vals, i) } bind i = i + 1.0 }
    if str_starts_with(t, "url(") { return 8421504.0 }
    return 0.0
}
# 2D affine [a b c d e f]
fn sv_mul(m, n) {
    bind a = list_get(m, 0) bind b = list_get(m, 1) bind c = list_get(m, 2) bind d = list_get(m, 3) bind e = list_get(m, 4) bind f = list_get(m, 5)
    bind A = list_get(n, 0) bind B = list_get(n, 1) bind C = list_get(n, 2) bind D = list_get(n, 3) bind E = list_get(n, 4) bind F = list_get(n, 5)
    return [a * A + c * B, b * A + d * B, a * C + c * D, b * C + d * D, a * E + c * F + e, b * E + d * F + f]
}
fn sv_transform(s) {
    bind m = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
    if str_trim(s) == "" { return m }
    bind parts = str_split(s, ")")
    bind i = 0.0
    while i < len(parts) {
        bind p = str_trim(list_get(parts, i))
        bind o = str_find(p, "(")
        if o > 0.0 {
            bind name = str_lower(str_trim(str_replace(str_slice(p, 0.0, o), ",", "")))
            bind n = sv_numbers(str_slice(p, o + 1.0, str_len(p) - o - 1.0))
            bind t = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
            if name == "matrix" && len(n) > 5.5 { bind t = n }
            if name == "translate" { bind t = [1.0, 0.0, 0.0, 1.0, sel(len(n) > 0.5, list_get(n, 0), 0.0), sel(len(n) > 1.5, list_get(n, 1), 0.0)] }
            if name == "scale" { bind sx = sel(len(n) > 0.5, list_get(n, 0), 1.0) bind t = [sx, 0.0, 0.0, sel(len(n) > 1.5, list_get(n, 1), sx), 0.0, 0.0] }
            if name == "rotate" {
                bind a = mx_rad(sel(len(n) > 0.5, list_get(n, 0), 0.0))
                bind t = [cos(a), sin(a), 0.0 - sin(a), cos(a), 0.0, 0.0]
                if len(n) > 2.5 { bind t = sv_mul(sv_mul([1.0, 0.0, 0.0, 1.0, list_get(n, 1), list_get(n, 2)], t), [1.0, 0.0, 0.0, 1.0, 0.0 - list_get(n, 1), 0.0 - list_get(n, 2)]) }
            }
            if name == "skewx" { bind t = [1.0, 0.0, mx_tan(mx_rad(list_get(n, 0))), 1.0, 0.0, 0.0] }
            if name == "skewy" { bind t = [1.0, mx_tan(mx_rad(list_get(n, 0))), 0.0, 1.0, 0.0, 0.0] }
            bind m = sv_mul(m, t)
        }
        bind i = i + 1.0
    }
    return m
}
fn sv_apply_nodes(pts, m) {
    bind out = list_new()
    bind i = 0.0
    while i + 1.0 < len(pts) {
        bind p = xf_pt(m, list_get(pts, i), list_get(pts, i + 1.0))
        bind out = list_push(out, list_get(p, 0)) bind out = list_push(out, list_get(p, 1))
        bind i = i + 2.0
    }
    return out
}

# ── path data -> list of [pts, closed] contours ──────────────────────────────
fn sv_path(d) {
    bind cmds = "MmLlHhVvCcSsQqTtAaZz"
    bind t = d
    bind i = 0.0
    while i < 20.0 { bind ch = str_slice(cmds, i, 1.0) bind t = str_replace(t, ch, "|" + ch + " ") bind i = i + 1.0 }
    bind segs = str_split(t, "|")
    bind contours = list_new()
    bind pts = list_new()
    bind closed = false
    bind cx = 0.0 bind cy = 0.0 bind sx = 0.0 bind sy = 0.0
    bind lcx = 0.0 bind lcy = 0.0
    bind lastc = ""
    bind i = 0.0
    while i < len(segs) {
        bind seg = str_trim(list_get(segs, i))
        if seg != "" {
            bind c = str_slice(seg, 0.0, 1.0)
            bind n = sv_numbers(str_slice(seg, 1.0, str_len(seg) - 1.0))
            bind rel = c == str_lower(c) && c != "z"
            bind C = str_upper(c)
            if C == "Z" {
                bind np = floor(len(pts) / 6.0)
                if np >= 2.0 {
                    bind l6 = (np - 1.0) * 6.0
                    if abs(list_get(pts, l6) - list_get(pts, 0)) + abs(list_get(pts, l6 + 1.0) - list_get(pts, 1)) < 0.0001 {
                        bind pts = list_set(pts, 2.0, list_get(pts, l6 + 2.0))
                        bind pts = list_set(pts, 3.0, list_get(pts, l6 + 3.0))
                        bind trimmed = list_new()
                        bind q2 = 0.0
                        while q2 < l6 { bind trimmed = list_push(trimmed, list_get(pts, q2)) bind q2 = q2 + 1.0 }
                        bind pts = trimmed
                    }
                }
                if len(pts) >= 6.0 { bind contours = list_push(contours, [pts, true]) }
                bind pts = list_new()
                bind cx = sx bind cy = sy
                bind lastc = "Z"
            } else if C == "M" {
                if len(pts) >= 6.0 { bind contours = list_push(contours, [pts, false]) }
                bind pts = list_new()
                bind k = 0.0
                while k + 1.0 < len(n) {
                    bind x = list_get(n, k) bind y = list_get(n, k + 1.0)
                    if rel { bind x = x + cx bind y = y + cy }
                    bind cx = x bind cy = y
                    if k == 0.0 { bind sx = x bind sy = y }
                    bind pts = sv_push_node(pts, x, y)
                    bind k = k + 2.0
                }
                bind lastc = "M"
            } else if C == "L" || C == "H" || C == "V" {
                bind step = sel(C == "L", 2.0, 1.0)
                bind k = 0.0
                while k + step - 1.0 < len(n) {
                    bind x = cx bind y = cy
                    if C == "L" { bind x = list_get(n, k) bind y = list_get(n, k + 1.0) if rel { bind x = x + cx bind y = y + cy } }
                    if C == "H" { bind x = list_get(n, k) if rel { bind x = x + cx } }
                    if C == "V" { bind y = list_get(n, k) if rel { bind y = y + cy } }
                    if len(pts) < 0.5 { bind pts = sv_push_node(pts, cx, cy) bind sx = cx bind sy = cy }
                    bind pts = sv_push_node(pts, x, y)
                    bind cx = x bind cy = y
                    bind k = k + step
                }
                bind lastc = "L"
            } else if C == "C" || C == "S" || C == "Q" || C == "T" {
                bind step = 6.0
                if C == "S" || C == "Q" { bind step = 4.0 }
                if C == "T" { bind step = 2.0 }
                bind k = 0.0
                while k + step - 1.0 < len(n) {
                    bind ox = sel(rel, cx, 0.0) bind oy = sel(rel, cy, 0.0)
                    bind x1 = 0.0 bind y1 = 0.0 bind x2 = 0.0 bind y2 = 0.0 bind x = 0.0 bind y = 0.0
                    if C == "C" {
                        bind x1 = list_get(n, k) + ox bind y1 = list_get(n, k + 1.0) + oy
                        bind x2 = list_get(n, k + 2.0) + ox bind y2 = list_get(n, k + 3.0) + oy
                        bind x = list_get(n, k + 4.0) + ox bind y = list_get(n, k + 5.0) + oy
                    }
                    if C == "S" {
                        bind x1 = cx bind y1 = cy
                        if lastc == "C" { bind x1 = 2.0 * cx - lcx bind y1 = 2.0 * cy - lcy }
                        bind x2 = list_get(n, k) + ox bind y2 = list_get(n, k + 1.0) + oy
                        bind x = list_get(n, k + 2.0) + ox bind y = list_get(n, k + 3.0) + oy
                    }
                    if C == "Q" || C == "T" {
                        bind qx = 0.0 bind qy = 0.0
                        if C == "Q" {
                            bind qx = list_get(n, k) + ox bind qy = list_get(n, k + 1.0) + oy
                            bind x = list_get(n, k + 2.0) + ox bind y = list_get(n, k + 3.0) + oy
                        } else {
                            bind qx = cx bind qy = cy
                            if lastc == "Q" { bind qx = 2.0 * cx - lcx bind qy = 2.0 * cy - lcy }
                            bind x = list_get(n, k) + ox bind y = list_get(n, k + 1.0) + oy
                        }
                        # quadratic -> cubic
                        bind x1 = cx + (qx - cx) * 2.0 / 3.0 bind y1 = cy + (qy - cy) * 2.0 / 3.0
                        bind x2 = x + (qx - x) * 2.0 / 3.0 bind y2 = y + (qy - y) * 2.0 / 3.0
                        bind lcx = qx bind lcy = qy
                    } else { bind lcx = x2 bind lcy = y2 }
                    if len(pts) < 0.5 { bind pts = sv_push_node(pts, cx, cy) bind sx = cx bind sy = cy }
                    # out-handle of the previous node, in-handle of the new node
                    bind j = len(pts) - 6.0
                    bind pts = list_set(pts, j + 4.0, x1) bind pts = list_set(pts, j + 5.0, y1)
                    bind pts = sv_push_node(pts, x, y)
                    bind j = len(pts) - 6.0
                    bind pts = list_set(pts, j + 2.0, x2) bind pts = list_set(pts, j + 3.0, y2)
                    bind cx = x bind cy = y
                    bind k = k + step
                }
                bind lastc = sel(C == "C" || C == "S", "C", "Q")
            } else if C == "A" {
                bind k = 0.0
                while k + 6.0 < len(n) {
                    bind x = list_get(n, k + 5.0) bind y = list_get(n, k + 6.0)
                    if rel { bind x = x + cx bind y = y + cy }
                    if len(pts) < 0.5 { bind pts = sv_push_node(pts, cx, cy) bind sx = cx bind sy = cy }
                    bind arc = sv_arc(cx, cy, list_get(n, k), list_get(n, k + 1.0), list_get(n, k + 2.0), list_get(n, k + 3.0), list_get(n, k + 4.0), x, y)
                    bind q = 0.0
                    while q + 1.0 < len(arc) { bind pts = sv_push_node(pts, list_get(arc, q), list_get(arc, q + 1.0)) bind q = q + 2.0 }
                    bind cx = x bind cy = y
                    bind k = k + 7.0
                }
                bind lastc = "A"
            }
        }
        bind i = i + 1.0
    }
    if len(pts) >= 6.0 { bind contours = list_push(contours, [pts, closed]) }
    return contours
}
fn sv_push_node(pts, x, y) {
    bind p = list_push(pts, x) bind p = list_push(p, y) bind p = list_push(p, x) bind p = list_push(p, y) bind p = list_push(p, x)
    return list_push(p, y)
}
# endpoint arc -> points (excluding the start), SVG implementation notes F.6
fn sv_arc(x1, y1, rx, ry, phi_deg, large, sweep, x2, y2) {
    bind rx = abs(rx) bind ry = abs(ry)
    if rx < 0.000001 || ry < 0.000001 { return [x2, y2] }
    bind phi = mx_rad(phi_deg)
    bind cp = cos(phi) bind sp = sin(phi)
    bind dx = (x1 - x2) * 0.5 bind dy = (y1 - y2) * 0.5
    bind x1p = cp * dx + sp * dy bind y1p = 0.0 - sp * dx + cp * dy
    bind lam = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry)
    if lam > 1.0 { bind rx = rx * sqrt(lam) bind ry = ry * sqrt(lam) }
    bind num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p
    bind den = rx * rx * y1p * y1p + ry * ry * x1p * x1p
    bind co = sqrt(max(0.0, num / max(0.000001, den)))
    if (large > 0.5) == (sweep > 0.5) { bind co = 0.0 - co }
    bind cxp = co * rx * y1p / ry bind cyp = 0.0 - co * ry * x1p / rx
    bind cx = cp * cxp - sp * cyp + (x1 + x2) * 0.5
    bind cy = sp * cxp + cp * cyp + (y1 + y2) * 0.5
    bind t1 = mx_atan2((y1p - cyp) / ry, (x1p - cxp) / rx)
    bind t2 = mx_atan2((0.0 - y1p - cyp) / ry, (0.0 - x1p - cxp) / rx)
    bind dt = t2 - t1
    if sweep > 0.5 && dt < 0.0 { bind dt = dt + 6.28318530718 }
    if sweep < 0.5 && dt > 0.0 { bind dt = dt - 6.28318530718 }
    bind steps = clampf(ceil(abs(dt) / 0.2), 2.0, 64.0)
    bind out = list_new()
    bind s = 1.0
    while s <= steps {
        bind t = t1 + dt * s / steps
        bind ex = rx * cos(t) bind ey = ry * sin(t)
        bind out = list_push(out, cp * ex - sp * ey + cx)
        bind out = list_push(out, sp * ex + cp * ey + cy)
        bind s = s + 1.0
    }
    return out
}

# ── import ───────────────────────────────────────────────────────────────────
fn sv_import(path) {
    if !file_exists(path) { return false }
    bind text = read_file(path)
    bind tags = str_split(text, "<")
    vc_push()
    bind w = 512.0 bind h = 512.0
    bind base = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
    # stacks for groups: matrix (6), fill, stroke, stroke-width, opacity
    st_set("sv_stack", [])
    bind m = base
    bind fill = 0.0 bind stroke = 0.0 - 1.0 bind sw = 1.0 bind op = 1.0
    bind count = 0.0
    bind first = true
    bind defs = 0.0
    bind i = 0.0
    while i < len(tags) {
        bind raw = list_get(tags, i)
        bind e = str_find(raw, ">")
        if e >= 0.0 {
            bind tag = " " + str_slice(raw, 0.0, e)
            bind tl = str_trim(tag)
            bind sp = str_find(tl + " ", " ")
            bind name = str_lower(str_slice(tl, 0.0, sp))
            bind name = str_replace(str_replace(name, "/", ""), "svg:", "")
            bind selfclose = str_ends_with(tl, "/")
            if str_starts_with(tl, "/") {
                bind nm2 = str_lower(str_trim(str_slice(tl, 1.0, str_len(tl) - 1.0)))
                if nm2 == "g" || nm2 == "svg:g" || nm2 == "a" {
                    bind stk = st_get("sv_stack", [])
                    if len(stk) >= 10.0 {
                        bind b = len(stk) - 10.0
                        bind m = [list_get(stk, b), list_get(stk, b + 1.0), list_get(stk, b + 2.0), list_get(stk, b + 3.0), list_get(stk, b + 4.0), list_get(stk, b + 5.0)]
                        bind fill = list_get(stk, b + 6.0) bind stroke = list_get(stk, b + 7.0) bind sw = list_get(stk, b + 8.0) bind op = list_get(stk, b + 9.0)
                        bind ns = list_new()
                        bind q = 0.0
                        while q < b { bind ns = list_push(ns, list_get(stk, q)) bind q = q + 1.0 }
                        st_set("sv_stack", ns)
                    }
                }
                if nm2 == "defs" { bind defs = max(0.0, defs - 1.0) }
            } else if name == "defs" && !selfclose { bind defs = defs + 1.0 }
            else if defs < 0.5 {
                if name == "svg" && first {
                    bind first = false
                    bind vb = sv_numbers(sv_attr(tag, "viewBox"))
                    bind vbw = 512.0 bind vbh = 512.0
                    if len(vb) > 3.5 { bind vbw = list_get(vb, 2) bind vbh = list_get(vb, 3) }
                    bind w = sv_num(sv_attr(tag, "width"), vbw)
                    bind h = sv_num(sv_attr(tag, "height"), vbh)
                    if str_ends_with(sv_attr(tag, "width"), "%") || w <= 0.0 { bind w = vbw }
                    if str_ends_with(sv_attr(tag, "height"), "%") || h <= 0.0 { bind h = vbh }
                    if len(vb) > 3.5 { if list_get(vb, 2) > 0.0 && list_get(vb, 3) > 0.0 {
                        bind m = [w / list_get(vb, 2), 0.0, 0.0, h / list_get(vb, 3), 0.0 - list_get(vb, 0) * w / list_get(vb, 2), 0.0 - list_get(vb, 1) * h / list_get(vb, 3)]
                    } }
                    vc_new_doc(round(w), round(h), st_get("tl_fps", 24.0))
                    st_set("vc_name", path_stem(path))
                    st_set("vc_bg", 0.0 - 1.0)
                } else {
                    # style of this element (inherits)
                    bind ef = fill bind es = stroke bind esw = sw bind eo = op
                    bind v = sv_style(tag, "fill") if v != "" { bind ef = sv_color(v) }
                    bind v = sv_style(tag, "stroke") if v != "" { bind es = sv_color(v) }
                    bind v = sv_style(tag, "stroke-width") if v != "" { bind esw = sv_num(v, 1.0) }
                    bind v = sv_style(tag, "opacity") if v != "" { bind eo = op * sv_num(v, 1.0) }
                    bind v = sv_style(tag, "fill-opacity") if v != "" { if sv_num(v, 1.0) < 0.01 { bind ef = 0.0 - 1.0 } }
                    bind em = sv_mul(m, sv_transform(sv_attr(tag, "transform")))
                    if name == "g" || name == "a" {
                        if !selfclose {
                            bind stk = st_get("sv_stack", [])
                            bind q = 0.0
                            while q < 6.0 { bind stk = list_push(stk, list_get(m, q)) bind q = q + 1.0 }
                            bind stk = list_push(stk, fill) bind stk = list_push(stk, stroke) bind stk = list_push(stk, sw) bind stk = list_push(stk, op)
                            st_set("sv_stack", stk)
                            bind m = em bind fill = ef bind stroke = es bind sw = esw bind op = eo
                        }
                    } else {
                        bind contours = list_new()
                        if name == "path" { bind contours = sv_path(sv_attr(tag, "d")) }
                        if name == "rect" {
                            bind x = sv_num(sv_attr(tag, "x"), 0.0) bind y = sv_num(sv_attr(tag, "y"), 0.0)
                            bind rw = sv_num(sv_attr(tag, "width"), 0.0) bind rh = sv_num(sv_attr(tag, "height"), 0.0)
                            bind contours = [[vc_rect_pts(x, y, x + rw, y + rh), true]]
                        }
                        if name == "circle" || name == "ellipse" {
                            bind cx = sv_num(sv_attr(tag, "cx"), 0.0) bind cy = sv_num(sv_attr(tag, "cy"), 0.0)
                            bind rx = sv_num(sv_attr(tag, sel(name == "circle", "r", "rx")), 0.0)
                            bind ry = sv_num(sv_attr(tag, sel(name == "circle", "r", "ry")), 0.0)
                            bind contours = [[vc_ellipse_pts(cx, cy, rx, ry), true]]
                        }
                        if name == "line" {
                            bind contours = [[vc_nodes_from_poly([sv_num(sv_attr(tag, "x1"), 0.0), sv_num(sv_attr(tag, "y1"), 0.0), sv_num(sv_attr(tag, "x2"), 0.0), sv_num(sv_attr(tag, "y2"), 0.0)]), false]]
                            if es < 0.0 { bind es = 0.0 }
                        }
                        if name == "polyline" || name == "polygon" {
                            bind contours = [[vc_nodes_from_poly(sv_numbers(sv_attr(tag, "points"))), name == "polygon"]]
                        }
                        bind q = 0.0
                        while q < len(contours) {
                            bind cpair = list_get(contours, q)
                            bind npts = sv_apply_nodes(list_get(cpair, 0), em)
                            bind closed = list_get(cpair, 1)
                            if name == "path" && !closed && ef >= 0.0 { bind closed = len(npts) >= 18.0 }
                            if len(npts) >= 6.0 {
                                bind kind = sel(name == "path", "path", name)
                                bind k = vc_add_item(kind, npts, closed)
                                st_set(vk(k, "fill"), sel(closed, ef, 0.0 - 1.0))
                                st_set(vk(k, "stroke"), es)
                                bind scale = sqrt(abs(list_get(em, 0) * list_get(em, 3) - list_get(em, 1) * list_get(em, 2)))
                                st_set(vk(k, "sw"), esw * scale)
                                st_set(vk(k, "op"), eo)
                                bind idv = sv_attr(tag, "id")
                                if idv != "" { st_set(vk(k, "name"), idv + sel(len(contours) > 1.0, "." + num_str(q), "")) }
                                bind count = count + 1.0
                            }
                            bind q = q + 1.0
                        }
                    }
                }
            }
        }
        bind i = i + 1.0
    }
    st_set("vc_zoom", 0.0)
    st_set("status", "Imported " + num_str(count) + " shapes from " + path_base(path))
    return count > 0.0
}

# ── export ───────────────────────────────────────────────────────────────────
fn sv_hex(c) { if c < 0.0 { return "none" } return c_hex(c) }
fn sv_d(pts, closed) {
    bind n = floor(len(pts) / 6.0)
    if n < 1.0 { return "" }
    bind parts = list_new()
    bind parts = list_push(parts, "M" + mx_num(list_get(pts, 0)) + "," + mx_num(list_get(pts, 1)))
    bind segs = sel(closed, n, n - 1.0)
    bind i = 0.0
    while i < segs {
        bind j = (i + 1.0) % n
        bind ax = list_get(pts, i * 6.0) bind ay = list_get(pts, i * 6.0 + 1.0)
        bind bx = list_get(pts, i * 6.0 + 4.0) bind by = list_get(pts, i * 6.0 + 5.0)
        bind cx = list_get(pts, j * 6.0 + 2.0) bind cy = list_get(pts, j * 6.0 + 3.0)
        bind dx = list_get(pts, j * 6.0) bind dy = list_get(pts, j * 6.0 + 1.0)
        if abs(bx - ax) + abs(by - ay) + abs(cx - dx) + abs(cy - dy) < 0.0001 {
            bind parts = list_push(parts, "L" + mx_num(dx) + "," + mx_num(dy))
        } else {
            bind parts = list_push(parts, "C" + mx_num(bx) + "," + mx_num(by) + " " + mx_num(cx) + "," + mx_num(cy) + " " + mx_num(dx) + "," + mx_num(dy))
        }
        bind i = i + 1.0
    }
    if closed { bind parts = list_push(parts, "Z") }
    return p_join(parts, " ")
}
fn sv_xml(s) { return str_replace(str_replace(str_replace(s, "&", "&amp;"), "<", "&lt;"), "\"", "&quot;") }
fn sv_export(path, animated) {
    bind w = st_get("vc_w", 512.0) bind h = st_get("vc_h", 512.0)
    bind out = list_new()
    bind out = list_push(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
    bind out = list_push(out, "<!-- Created with LingModeler " + APP_VERSION + " (from a .ling vector document) -->")
    bind out = list_push(out, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" + mx_num(w) + "\" height=\"" + mx_num(h) + "\" viewBox=\"0 0 " + mx_num(w) + " " + mx_num(h) + "\">")
    if st_get("vc_bg", 0.0 - 1.0) >= 0.0 { bind out = list_push(out, "  <rect width=\"100%\" height=\"100%\" fill=\"" + sv_hex(st_get("vc_bg", 0.0)) + "\"/>") }
    bind ids = vc_ids()
    bind layers = st_get("vc_layers", [])
    # animation samples (every frame of the timeline)
    bind f0 = st_get("tl_start", 0.0) bind f1 = max(f0 + 1.0, st_get("tl_end", 48.0))
    bind fps = tl_fps()
    bind nfr = f1 - f0 + 1.0
    bind dur = (f1 - f0) / fps
    if animated {
        an_save()
        bind f = f0
        while f <= f1 {
            an_eval(f)
            bind i = 0.0
            while i < len(ids) {
                bind k = list_get(ids, i)
                bind pv = vc_pivot(k)
                st_lpush("sva_t" + num_str(k), mx_num(st_get(vk(k, "tx"), 0.0)) + " " + mx_num(st_get(vk(k, "ty"), 0.0)))
                st_lpush("sva_r" + num_str(k), mx_num(mx_deg(st_get(vk(k, "rot"), 0.0))) + " " + mx_num(list_get(pv, 0)) + " " + mx_num(list_get(pv, 1)))
                st_lpush("sva_s" + num_str(k), mx_num(st_get(vk(k, "sx"), 1.0)) + " " + mx_num(st_get(vk(k, "sy"), 1.0)))
                st_lpush("sva_o" + num_str(k), mx_num(st_get(vk(k, "op"), 1.0)))
                st_lpush("sva_d" + num_str(k), sv_d(st_get(vk(k, "pts"), []), st_get(vk(k, "closed"), 0.0) > 0.5))
                bind i = i + 1.0
            }
            bind f = f + 1.0
        }
        an_restore()
    }
    bind l = 0.0
    while l < len(layers) {
        bind out = list_push(out, "  <g id=\"layer" + num_str(l + 1.0) + "\" data-name=\"" + sv_xml(list_get(layers, l)) + "\"" + sel(vc_layer_visible(l), "", " display=\"none\"") + ">")
        bind i = 0.0
        while i < len(ids) {
            bind k = list_get(ids, i)
            if st_get(vk(k, "layer"), 0.0) == l {
                bind closed = st_get(vk(k, "closed"), 0.0) > 0.5
                bind style = "fill=\"" + sel(closed, sv_hex(st_get(vk(k, "fill"), 0.0 - 1.0)), "none") + "\" stroke=\"" + sv_hex(st_get(vk(k, "stroke"), 0.0 - 1.0)) + "\" stroke-width=\"" + mx_num(st_get(vk(k, "sw"), 1.0)) + "\" stroke-linejoin=\"round\" stroke-linecap=\"round\""
                bind op = st_get(vk(k, "op"), 1.0)
                bind idt = "id=\"" + sv_xml(lf_ident(st_get(vk(k, "name"), "item"))) + "\""
                bind keyed = len(st_get(vk(k, "keys"), [])) > 2.5 || len(st_get(vk(k, "skf"), [])) > 0.5
                if animated && keyed {
                    bind kn = num_str(k)
                    bind anim = " dur=\"" + mx_num(dur) + "s\" repeatCount=\"indefinite\""
                    bind out = list_push(out, "    <g transform=\"translate(" + mx_num(st_get(vk(k, "tx"), 0.0)) + " " + mx_num(st_get(vk(k, "ty"), 0.0)) + ")\">")
                    bind out = list_push(out, "      <animateTransform attributeName=\"transform\" type=\"translate\" values=\"" + p_join(st_get("sva_t" + kn, []), ";") + "\"" + anim + "/>")
                    bind out = list_push(out, "      <g>")
                    bind out = list_push(out, "        <animateTransform attributeName=\"transform\" type=\"rotate\" values=\"" + p_join(st_get("sva_r" + kn, []), ";") + "\"" + anim + "/>")
                    bind pv = vc_pivot(k)
                    bind out = list_push(out, "        <g transform=\"translate(" + mx_num(list_get(pv, 0)) + " " + mx_num(list_get(pv, 1)) + ")\"><g>")
                    bind out = list_push(out, "          <animateTransform attributeName=\"transform\" type=\"scale\" values=\"" + p_join(st_get("sva_s" + kn, []), ";") + "\"" + anim + "/>")
                    bind out = list_push(out, "          <g transform=\"translate(" + mx_num(0.0 - list_get(pv, 0)) + " " + mx_num(0.0 - list_get(pv, 1)) + ")\">")
                    bind out = list_push(out, "            <path " + idt + " d=\"" + sv_d(st_get(vk(k, "pts"), []), closed) + "\" " + style + " opacity=\"" + mx_num(op) + "\">")
                    bind out = list_push(out, "              <animate attributeName=\"opacity\" values=\"" + p_join(st_get("sva_o" + kn, []), ";") + "\"" + anim + "/>")
                    if len(st_get(vk(k, "skf"), [])) > 0.5 { bind out = list_push(out, "              <animate attributeName=\"d\" values=\"" + p_join(st_get("sva_d" + kn, []), ";") + "\"" + anim + "/>") }
                    bind out = list_push(out, "            </path>")
                    bind out = list_push(out, "          </g></g></g>")
                    bind out = list_push(out, "      </g>")
                    bind out = list_push(out, "    </g>")
                } else {
                    bind m = vc_xf(k)
                    bind tr = ""
                    if abs(list_get(m, 0) - 1.0) + abs(list_get(m, 1)) + abs(list_get(m, 2)) + abs(list_get(m, 3) - 1.0) + abs(list_get(m, 4)) + abs(list_get(m, 5)) > 0.00001 {
                        bind tr = " transform=\"matrix(" + mx_num(list_get(m, 0)) + " " + mx_num(list_get(m, 1)) + " " + mx_num(list_get(m, 2)) + " " + mx_num(list_get(m, 3)) + " " + mx_num(list_get(m, 4)) + " " + mx_num(list_get(m, 5)) + ")\""
                    }
                    bind out = list_push(out, "    <path " + idt + " d=\"" + sv_d(st_get(vk(k, "pts"), []), closed) + "\" " + style + sel(op < 0.999, " opacity=\"" + mx_num(op) + "\"", "") + tr + "/>")
                }
                if animated {
                    bind kn = num_str(k)
                    st_set("sva_t" + kn, []) st_set("sva_r" + kn, []) st_set("sva_s" + kn, []) st_set("sva_o" + kn, []) st_set("sva_d" + kn, [])
                }
            }
            bind i = i + 1.0
        }
        bind out = list_push(out, "  </g>")
        bind l = l + 1.0
    }
    bind out = list_push(out, "</svg>")
    write_file(path, p_join(out, "\n") + "\n")
    return true
}

← Back to index