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/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, "&", "&"), "<", "<"), "\"", """) }
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
}