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/actions.ling
# ━━━ actions.ling — every menu / toolbar / keyboard command goes through do_action(id) ━━━
fn app_init() {
me_new_scene()
v3_init()
tx_init()
vc_init()
an_init()
tl_init()
st_set("mode", 0.0)
st_set("model_mode", 0.0)
st_set("status", "Ready - Edit > Add Primitive to start, F1 help")
# a starter cube so the viewport is not empty
bind o = pr_add("cube", [2.0, 1.0])
st_set("undo_n", 0.0)
st_set("dirty", 0.0)
}
fn need_obj() {
bind o = me_sel_o()
if o < 0.0 { st_set("status", "Select an object first") }
return o
}
fn need_edit_obj() {
bind o = me_sel_o()
if o < 0.0 { st_set("status", "Select an object first") return 0.0 - 1.0 }
if st_get("mode", 0.0) != 1.0 { set_mode(1.0) }
return o
}
fn do_action(a) {
if a == "" || a == "none" { return 0.0 }
if str_starts_with(a, "sub:") { return 0.0 }
if str_starts_with(a, "recent:") {
bind i = to_number(str_slice(a, 7.0, str_len(a)))
if i < st_llen("recent") { fi_open_path(st_lget("recent", i)) }
return 0.0
}
# test hooks: set:key=value mode:N add:kind
if str_starts_with(a, "set:") {
bind kv = str_split(str_slice(a, 4.0, str_len(a)), "=")
if len(kv) > 1.5 { st_set(list_get(kv, 0), to_number(list_get(kv, 1))) }
return 0.0
}
if str_starts_with(a, "mode:") { set_mode(to_number(str_slice(a, 5.0, str_len(a)))) return 0.0 }
if str_starts_with(a, "add:") { bind k = str_slice(a, 4.0, str_len(a)) bind p = pr_params(k) me_undo_push() pr_add(k, list_get(p, 1)) return 0.0 }
if str_starts_with(a, "open:") { fi_open_path(str_slice(a, 5.0, str_len(a))) return 0.0 }
if str_starts_with(a, "saveas:") { fi_save_path(str_slice(a, 7.0, str_len(a))) return 0.0 }
if str_starts_with(a, "export:") { fi_export_path(str_slice(a, 7.0, str_len(a))) return 0.0 }
if str_starts_with(a, "import:") { fi_open_path_merge(str_slice(a, 7.0, str_len(a))) return 0.0 }
if str_starts_with(a, "key:") { st_set("fake_key", str_slice(a, 4.0, str_len(a))) return 0.0 }
if a == "test.confirm" { v3_confirm_modal() return 0.0 }
if str_starts_with(a, "test.turntable:") { ra_turntable_to(str_slice(a, 15.0, str_len(a))) return 0.0 }
if str_starts_with(a, "test.vsheet:") { ra_vector_sheet(str_slice(a, 12.0, str_len(a)), 6.0, 96.0, 0.0) return 0.0 }
if str_starts_with(a, "test.texpng:") { tx_export(str_slice(a, 12.0, str_len(a))) return 0.0 }
if str_starts_with(a, "test.texsheet:") { tx_export_sheet(str_slice(a, 14.0, str_len(a)), 8.0, 64.0, 4.0, 1.0) return 0.0 }
if str_starts_with(a, "test.normal:") { tx_export_normal(str_slice(a, 12.0, str_len(a))) return 0.0 }
if str_starts_with(a, "test.svganim:") { sv_export(str_slice(a, 13.0, str_len(a)), true) return 0.0 }
if a == "test.scene" { println("SCENE objects=", me_count()) bind ids4 = me_ids() bind q4 = 0.0 while q4 < len(ids4) { bind o4 = list_get(ids4, q4) println(" ", me_name(o4), " v=", me_nverts(o4), " f=", me_nfaces(o4), " bones=", rg_count(o4), " bound=", st_get(mk(o4, "bound"), 0.0), " acts=", len(st_get(mk(o4, "act_names"), []))) bind q4 = q4 + 1.0 } return 0.0 }
if a == "test.new" { fi_new() return 0.0 }
if a == "vtest.shapes" { bind k1 = vc_add_item("rect", vc_rect_pts(60.0, 60.0, 220.0, 180.0), true) bind k2 = vc_add_item("ellipse", vc_ellipse_pts(330.0, 140.0, 90.0, 70.0), true) st_set(vk(k2, "fill"), C_ORANGE) bind k3 = vc_add_item("star", vc_poly_pts(256.0, 340.0, 110.0, 5.0, 0.45), true) st_set(vk(k3, "fill"), C_YELLOW) st_set(vk(k3, "sw"), 6.0) bind k4 = vc_add_item("path", [60.0, 450.0, 60.0, 450.0, 160.0, 380.0, 450.0, 450.0, 350.0, 520.0, 450.0, 450.0], false) st_set(vk(k4, "fill"), 0.0 - 1.0) st_set(vk(k4, "sw"), 5.0) st_set(vk(k4, "stroke"), C_VIOLET) vc_select(k3, false) return 0.0 }
if a == "vtest.anim" { tl_set_frame(0.0) an_key() tl_set_frame(24.0) bind s3 = vc_selected() st_set(vk(list_get(s3, 0), "rot"), 1.2) st_set(vk(list_get(s3, 0), "tx"), 80.0) an_key() tl_set_frame(12.0) return 0.0 }
if a == "vtest.print" { bind ids3 = vc_ids() println("VEC items=", len(ids3), " sel=", vc_sel_count(), " layers=", len(st_get("vc_layers", [])), " frame=", tl_frame()) bind q3 = 0.0 while q3 < len(ids3) { bind k5 = list_get(ids3, q3) println(" ", st_get(vk(k5, "name"), ""), " nodes=", len(st_get(vk(k5, "pts"), [])) / 6.0, " tx=", fmt2(st_get(vk(k5, "tx"), 0.0)), " rot=", fmt2(st_get(vk(k5, "rot"), 0.0)), " keys=", len(st_get(vk(k5, "keys"), [])) / 3.0, " fill=", st_get(vk(k5, "fill"), 0.0)) bind q3 = q3 + 1.0 } return 0.0 }
if str_starts_with(a, "gen:") { bind k = str_slice(a, 4.0, str_len(a)) bind p = gn_params(k) gn_make(k, list_get(p, 1)) return 0.0 }
if a == "test.autoweights" { rg_auto_weights() return 0.0 }
if str_starts_with(a, "test.selbone:") { bind o2 = me_sel_o() if o2 >= 0.0 { rg_select(o2, to_number(str_slice(a, 13.0, str_len(a))), false) } return 0.0 }
if str_starts_with(a, "test.frame:") { tl_set_frame(to_number(str_slice(a, 11.0, str_len(a)))) return 0.0 }
if str_starts_with(a, "test.cursor:") { bind p3 = str_split(str_slice(a, 12.0, str_len(a)), "/") st_set("cursor_x", to_number(list_get(p3, 0))) st_set("cursor_y", to_number(list_get(p3, 1))) st_set("cursor_z", to_number(list_get(p3, 2))) return 0.0 }
if str_starts_with(a, "test.pose:") { bind p4 = str_split(str_slice(a, 10.0, str_len(a)), "/") bind o2 = me_sel_o() bind b2 = to_number(list_get(p4, 0)) st_lset(mk(o2, "bp_rx"), b2, mx_rad(to_number(list_get(p4, 1)))) st_lset(mk(o2, "bp_ry"), b2, mx_rad(to_number(list_get(p4, 2)))) st_lset(mk(o2, "bp_rz"), b2, mx_rad(to_number(list_get(p4, 3)))) ps_mark(o2) return 0.0 }
if a == "test.printpose" { bind o2 = me_sel_o() bind d = ps_deformed(o2) bind v0 = "" if len(d) > 2.5 { bind v0 = " v0=" + fmt3(list_get(list_get(d, 0), 0)) + "," + fmt3(list_get(list_get(d, 1), 0)) + "," + fmt3(list_get(list_get(d, 2), 0)) } println("POSE bones=", rg_count(o2), " frame=", tl_frame(), " rx=", p_join(st_get(mk(o2, "bp_rx"), []), ","), " deformed=", len(d), v0) bind wb = st_get(mk(o2, "wb"), []) println("WB len=", len(wb), " v0=", p_join([list_get(wb, 0), fmt3(list_get(wb, 1)), list_get(wb, 2), fmt3(list_get(wb, 3))], ",")) return 0.0 }
if str_starts_with(a, "test.selvert:") { bind o2 = me_sel_o() if o2 >= 0.0 { me_sel_vert(o2, to_number(str_slice(a, 13.0, str_len(a))), true) st_set("act_v", to_number(str_slice(a, 13.0, str_len(a)))) } return 0.0 }
if str_starts_with(a, "test.seledge:") { bind o2 = me_sel_o() if o2 >= 0.0 { me_sel_edge(o2, to_number(str_slice(a, 13.0, str_len(a))), true) } return 0.0 }
if str_starts_with(a, "test.selface:") { bind o2 = me_sel_o() if o2 >= 0.0 { me_sel_face(o2, to_number(str_slice(a, 13.0, str_len(a))), true) } return 0.0 }
if str_starts_with(a, "test.print") { bind o2 = me_sel_o() if o2 >= 0.0 { println("MESH ", me_name(o2), " verts=", me_nverts(o2), " faces=", me_nfaces(o2), " tris=", me_ntris(o2), " sel=", me_sel_count(o2)) } else { println("MESH none") } return 0.0 }
bind o = me_sel_o()
# ── file ──
if a == "file.new" { if st_get("dirty", 0.0) > 0.5 { confirm_open("New scene", "Discard unsaved changes?", "file.new_go") } else { fi_new() } return 0.0 }
if a == "file.new_go" { fi_new() return 0.0 }
if a == "file.open" { fi_open() return 0.0 }
if a == "file.save" { fi_save() return 0.0 }
if a == "file.save_as" { fi_save_as() return 0.0 }
if a == "file.import_obj" { fi_import("obj") return 0.0 }
if a == "file.import_gltf" { fi_import("gltf") return 0.0 }
if a == "file.import_lmesh" { fi_import("lmesh") return 0.0 }
if a == "file.export_obj" { fi_export("obj") return 0.0 }
if a == "file.export_gltf" { fi_export("gltf") return 0.0 }
if a == "file.export_glb" { fi_export("glb") return 0.0 }
if a == "file.settings" { modal_open("settings") return 0.0 }
if a == "file.quit" { if st_get("dirty", 0.0) > 0.5 { confirm_open("Quit", "Discard unsaved changes and quit?", "file.quit_go") } else { st_set("quit", 1.0) } return 0.0 }
if a == "file.quit_go" { st_set("quit", 1.0) return 0.0 }
# ── edit ──
if a == "edit.undo" { if st_get("mode", 0.0) == 5.0 || st_get("mode", 0.0) == 6.0 { vc_undo() } else { me_undo() } return 0.0 }
if a == "edit.redo" { if st_get("mode", 0.0) == 5.0 || st_get("mode", 0.0) == 6.0 { vc_redo() } else { me_redo() } return 0.0 }
if a == "edit.toggle" { set_mode(sel(st_get("mode", 0.0) == 1.0, 0.0, 1.0)) return 0.0 }
if a == "edit.dup" {
bind m = st_get("mode", 0.0)
if m == 5.0 || m == 6.0 { vc_dup() return 0.0 }
if m == 1.0 && o >= 0.0 { me_undo_push() mo_dup_sel(o) v3_begin_modal("g") return 0.0 }
if m == 2.0 && o >= 0.0 { rg_dup() return 0.0 }
bind objs = me_selected_objects()
if len(objs) < 0.5 { st_set("status", "Nothing selected") return 0.0 }
me_undo_push()
bind i = 0.0
bind last = 0.0 - 1.0
while i < len(objs) { bind last = me_dup(list_get(objs, i)) bind i = i + 1.0 }
me_select_object(last, false)
bind i = 0.0
v3_begin_modal("g")
return 0.0
}
if a == "edit.delete" {
bind m = st_get("mode", 0.0)
if m == 5.0 || m == 6.0 { vc_delete() return 0.0 }
if m == 1.0 && o >= 0.0 { me_undo_push() mo_delete(o, st_get("sel_mode", 0.0)) return 0.0 }
if m == 2.0 { rg_delete() return 0.0 }
if m == 7.0 { tx_del() return 0.0 }
bind objs = me_selected_objects()
if len(objs) < 0.5 { st_set("status", "Nothing selected") return 0.0 }
me_undo_push()
bind i = 0.0
while i < len(objs) { me_remove(list_get(objs, i)) bind i = i + 1.0 }
return 0.0
}
# ── select ──
if a == "sel.all" {
bind m = st_get("mode", 0.0)
if m == 0.0 { bind ids = me_ids() bind i = 0.0 while i < len(ids) { st_set(mk(list_get(ids, i), "osel"), 1.0) bind i = i + 1.0 } return 0.0 }
if m == 1.0 && o >= 0.0 { me_sel_all(o, true) return 0.0 }
if (m == 2.0 || m == 4.0) && o >= 0.0 { rg_sel_all(o, true) return 0.0 }
if m == 5.0 || m == 6.0 { vc_select_all(true) return 0.0 }
return 0.0
}
if a == "sel.none" {
bind m = st_get("mode", 0.0)
if m == 0.0 { me_deselect_all_objects() return 0.0 }
if m == 1.0 && o >= 0.0 { me_sel_all(o, false) return 0.0 }
if (m == 2.0 || m == 4.0) && o >= 0.0 { rg_sel_all(o, false) return 0.0 }
if m == 5.0 || m == 6.0 { vc_select_all(false) return 0.0 }
return 0.0
}
if a == "sel.invert" { if o >= 0.0 { me_sel_invert(o) } return 0.0 }
if a == "sel.vert" { st_set("sel_mode", 0.0) return 0.0 }
if a == "sel.edge" { st_set("sel_mode", 1.0) return 0.0 }
if a == "sel.face" { st_set("sel_mode", 2.0) return 0.0 }
if a == "sel.grow" { if o >= 0.0 { me_sel_grow(o) } return 0.0 }
if a == "sel.shrink" { if o >= 0.0 { me_sel_shrink(o) } return 0.0 }
if a == "sel.linked" { if o >= 0.0 { me_sel_linked(o) } return 0.0 }
if a == "sel.loop" { st_set("status", "Alt+click an edge in edge mode to select its loop") return 0.0 }
if a == "sel.mat" { if o >= 0.0 { mo_select_by_color(o) } return 0.0 }
# ── add ──
if str_starts_with(a, "add.") {
bind k = str_slice(a, 4.0, str_len(a))
if k == "armature" { if o < 0.0 { me_undo_push() bind n = me_add_object("Armature", [], [], [], [], [], []) } set_mode(2.0) rg_add_bone() return 0.0 }
bind p = pr_params(k)
st_set("pm_live", 0.0)
params_open("Add " + k, "add." + k, list_get(p, 0), list_get(p, 1), list_get(p, 2), list_get(p, 3), list_get(p, 4))
return 0.0
}
if str_starts_with(a, "gen.") { gn_open(str_slice(a, 4.0, str_len(a))) return 0.0 }
# ── mesh ops ──
if str_starts_with(a, "mesh.") {
if a == "mesh.join" { mo_join(me_selected_objects()) return 0.0 }
if a == "mesh.apply_xf" { if o >= 0.0 { me_undo_push() mo_apply_xf(o) } return 0.0 }
if a == "mesh.center" { if o >= 0.0 { me_undo_push() mo_center(o) } return 0.0 }
if a == "mesh.shade" { if o >= 0.0 { st_set(mk(o, "smooth"), 1.0 - st_get(mk(o, "smooth"), 0.0)) } return 0.0 }
if a == "mesh.flip" { if o >= 0.0 { me_undo_push() mo_flip(o) } return 0.0 }
if a == "mesh.recalc" { if o >= 0.0 { me_undo_push() mo_recalc(o) } return 0.0 }
if a == "mesh.tri" { if o >= 0.0 { me_undo_push() mo_triangulate(o) } return 0.0 }
if a == "mesh.quads" { if o >= 0.0 { me_undo_push() mo_quads(o) } return 0.0 }
if a == "mesh.smooth" { if o >= 0.0 { me_undo_push() mo_smooth(o, st_get("op_smooth", 1.0)) } return 0.0 }
if a == "mesh.mirror_x" { if o >= 0.0 { me_undo_push() mo_mirror_x(o) } return 0.0 }
if a == "mesh.symm" { if o >= 0.0 { me_undo_push() mo_symmetrize(o) } return 0.0 }
if a == "mesh.subsurf" { if o >= 0.0 { me_undo_push() mo_subsurf(o) } return 0.0 }
if a == "mesh.subdivide" { if o >= 0.0 { me_undo_push() mo_subdivide(o) } return 0.0 }
if a == "mesh.merge_dist" { if o >= 0.0 { me_undo_push() mo_merge_dist(o, st_get("op_merge_d", 0.001)) } return 0.0 }
bind eo = need_edit_obj()
if eo < 0.0 { return 0.0 }
if a == "mesh.extrude" { me_undo_push() mo_extrude(eo, st_get("op_extrude", 0.5)) return 0.0 }
if a == "mesh.inset" { me_undo_push() mo_inset(eo, st_get("op_inset", 0.2)) return 0.0 }
if a == "mesh.bevel" { me_undo_push() mo_bevel(eo, st_get("op_bevel", 0.1), st_get("op_bevel_segs", 1.0)) return 0.0 }
if a == "mesh.loopcut" { me_undo_push() mo_loopcut(eo, st_get("op_cuts", 1.0)) return 0.0 }
if a == "mesh.merge" { me_undo_push() mo_merge(eo) return 0.0 }
if a == "mesh.dissolve" { me_undo_push() mo_dissolve(eo) return 0.0 }
if a == "mesh.fill" { me_undo_push() mo_fill(eo) return 0.0 }
if a == "mesh.connect" { me_undo_push() mo_connect(eo) return 0.0 }
if a == "mesh.split" { me_undo_push() mo_split(eo) return 0.0 }
if a == "mesh.separate" { me_undo_push() mo_separate(eo) return 0.0 }
if a == "mesh.spin" {
st_set("pm_live", 0.0)
params_open("Spin / lathe selection around the cursor (Y axis)", "mesh.spin", ["Steps", "Angle (degrees)"], [st_get("op_spin_steps", 12.0), st_get("op_spin_angle", 360.0)], [1.0, 1.0], [128.0, 360.0], [0.0, 0.0])
return 0.0
}
return 0.0
}
# ── uv ──
if a == "uv.box" { if o >= 0.0 { me_undo_push() uv_box(o) st_set("status", "Box UVs") } return 0.0 }
if a == "uv.planar" { if o >= 0.0 { me_undo_push() uv_planar(o) st_set("status", "Planar UVs from the view") } return 0.0 }
if a == "uv.cyl" { if o >= 0.0 { me_undo_push() uv_cyl(o) } return 0.0 }
if a == "uv.sphere" { if o >= 0.0 { me_undo_push() uv_sphere(o) } return 0.0 }
if a == "uv.reset" { if o >= 0.0 { me_undo_push() uv_reset(o) } return 0.0 }
if a == "uv.panel" { st_set("show_uv", 1.0 - st_get("show_uv", 0.0)) return 0.0 }
# ── rig / weights / pose ──
if a == "rig.add" { if st_get("mode", 0.0) != 2.0 { set_mode(2.0) } rg_add_bone() return 0.0 }
if a == "rig.extrude" { rg_extrude() return 0.0 }
if a == "rig.parent" { rg_parent() return 0.0 }
if a == "rig.unparent" { rg_unparent() return 0.0 }
if a == "rig.delete" { rg_delete() return 0.0 }
if a == "rig.symm" { rg_symmetrize() return 0.0 }
if a == "rig.rename" { if o >= 0.0 && rg_active(o) >= 0.0 { modal_text("Rename bone", "bone", rg_bone_name(o, rg_active(o))) } return 0.0 }
if a == "rig.bind" { rg_bind() return 0.0 }
if a == "rig.autoweights" { if o < 0.0 || !me_has_bones(o) { st_set("status", "Add bones first (Rig tab)") return 0.0 } if st_get(mk(o, "bound"), 0.0) < 0.5 { rg_bind() } modal_open("autoweights") return 0.0 }
if a == "rig.normalize" { wp_normalize_all() return 0.0 }
if a == "rig.clear" { wp_clear_all() return 0.0 }
if a == "rig.mirror_w" { wp_mirror_x() return 0.0 }
if a == "rig.smooth_w" { wp_smooth_all() return 0.0 }
if a == "pose.key" { ps_key() return 0.0 }
if a == "pose.unkey" { ps_unkey() return 0.0 }
if a == "pose.clear" { ps_clear() return 0.0 }
if a == "pose.apply_rest" { ps_apply_rest() return 0.0 }
if a == "pose.new_action" { modal_text("New action", "action", "Action") return 0.0 }
if a == "pose.bake" { ps_bake() return 0.0 }
# ── vector ──
if a == "vec.new" { st_set("vn_w", st_get("vc_w", 512.0)) st_set("vn_h", st_get("vc_h", 512.0)) st_set("vn_fps", st_get("an_fps", 24.0)) modal_open("vecnew") return 0.0 }
if a == "vec.import_svg" { fi_import("svg") return 0.0 }
if a == "vec.export_svg" { fi_export("svg") return 0.0 }
if a == "vec.save_ling" { fi_export("ling") return 0.0 }
if a == "vec.open_ling" { fi_import("ling") return 0.0 }
if a == "vec.layer_add" { vc_layer_add() return 0.0 }
if a == "vec.layer_del" { vc_layer_del() return 0.0 }
if a == "vec.group" { vc_group() return 0.0 }
if a == "vec.ungroup" { vc_ungroup() return 0.0 }
if a == "vec.fwd" { vc_reorder(1.0) return 0.0 }
if a == "vec.back" { vc_reorder(0.0 - 1.0) return 0.0 }
if a == "vec.flip_h" { vc_flip(true) return 0.0 }
if a == "vec.flip_v" { vc_flip(false) return 0.0 }
if a == "vec.reverse" { vc_reverse() return 0.0 }
if a == "vec.simplify" { vc_simplify() return 0.0 }
if a == "vec.extrude" {
if vc_sel_count() < 0.5 { st_set("status", "Select a closed shape in Vector mode first") return 0.0 }
st_set("pm_live", 0.0)
params_open("Extrude 2D shape to a mesh", "vec.extrude", ["Depth", "Scale (units per px)", "Bevel", "Curve segments"], [0.5, 0.01, 0.0, 6.0], [0.0, 0.001, 0.0, 1.0], [10.0, 1.0, 0.5, 24.0], [2.0, 3.0, 2.0, 0.0])
return 0.0
}
if a == "vec.lathe" {
if vc_sel_count() < 0.5 { st_set("status", "Select a profile path in Vector mode first") return 0.0 }
st_set("pm_live", 0.0)
params_open("Lathe 2D profile around its left edge", "vec.lathe", ["Segments", "Scale (units per px)", "Curve segments"], [16.0, 0.01, 6.0], [3.0, 0.001, 1.0], [128.0, 1.0, 24.0], [0.0, 3.0, 0.0])
return 0.0
}
if str_starts_with(a, "vec.tool:") { vc_set_tool(str_slice(a, 9.0, str_len(a))) return 0.0 }
# ── animation ──
if a == "anim.key" { an_key() return 0.0 }
if a == "anim.shapekey" { an_shapekey() return 0.0 }
if a == "anim.unkey" { an_unkey() return 0.0 }
if a == "anim.export_png" { st_set("sh_kind", "frames") st_set("sh_title", "Export animation frames (PNG sequence)") modal_open("sheet") return 0.0 }
if a == "anim.export_sheet" { st_set("sh_kind", "vecsheet") st_set("sh_title", "Export sprite sheet from the animation") modal_open("sheet") return 0.0 }
if a == "anim.export_svg" { fi_export("svganim") return 0.0 }
if a == "anim.play" { tl_toggle_play() return 0.0 }
# ── texture ──
if a == "tex.new" { tx_new() set_mode(7.0) return 0.0 }
if a == "tex.dup" { tx_dup() return 0.0 }
if a == "tex.del" { tx_del() return 0.0 }
if a == "tex.seed" { tx_seed() return 0.0 }
if a == "tex.apply" { tx_apply() return 0.0 }
if a == "tex.bake" { if o >= 0.0 { me_undo_push() tx_bake_object(o) st_set("status", "Baked texture colours into faces") } return 0.0 }
if a == "tex.export" { fi_export("png") return 0.0 }
if a == "tex.export_sheet" { st_set("sh_kind", "texsheet") st_set("sh_title", "Export animated texture sprite sheet") modal_open("sheet") return 0.0 }
if a == "tex.export_normal" { fi_export("normal") return 0.0 }
if a == "tex.export_all" { tx_export_all() return 0.0 }
if a == "tex.import_png" { fi_import("png") return 0.0 }
# ── view ──
if a == "view.sidebar" { st_set("show_sidebar", 1.0 - st_get("show_sidebar", 1.0)) return 0.0 }
if a == "view.props" { st_set("show_props", 1.0 - st_get("show_props", 1.0)) return 0.0 }
if a == "view.timeline" { st_set("show_timeline", 1.0 - st_get("show_timeline", 1.0)) return 0.0 }
if a == "view.wire" { st_set("v3_shade", 0.0) return 0.0 }
if a == "view.solid" { st_set("v3_shade", 1.0) return 0.0 }
if a == "view.xray" { st_set("v3_xray", 1.0 - st_get("v3_xray", 0.0)) return 0.0 }
if a == "view.cull" { st_set("v3_cull", 1.0 - st_get("v3_cull", 1.0)) return 0.0 }
if a == "view.grid" { st_set("v3_grid", 1.0 - st_get("v3_grid", 1.0)) return 0.0 }
if a == "view.normals" { st_set("v3_normals", 1.0 - st_get("v3_normals", 0.0)) return 0.0 }
if a == "view.weights" { st_set("v3_weights", 1.0 - st_get("v3_weights", 0.0)) return 0.0 }
if a == "view.front" { v3_set_view("front") return 0.0 }
if a == "view.right" { v3_set_view("right") return 0.0 }
if a == "view.top" { v3_set_view("top") return 0.0 }
if a == "view.persp" { v3_set_view("persp") return 0.0 }
if a == "view.frame" { v3_frame(false) return 0.0 }
if a == "view.frame_all" { v3_frame(true) return 0.0 }
if a == "view.hide" { bind objs = me_selected_objects() bind i = 0.0 while i < len(objs) { st_set(mk(list_get(objs, i), "vis"), 0.0) bind i = i + 1.0 } return 0.0 }
if a == "view.unhide" { bind ids = me_ids() bind i = 0.0 while i < len(ids) { st_set(mk(list_get(ids, i), "vis"), 1.0) bind i = i + 1.0 } return 0.0 }
if a == "view.turntable" { modal_open("turntable") return 0.0 }
if a == "view.shot" { p_shot("lingmodeler") st_set("status", "Screenshot saved to screenshots/") return 0.0 }
# ── help ──
if a == "help.about" { modal_open("about") return 0.0 }
if a == "help.shortcuts" { modal_open("shortcuts") return 0.0 }
if a == "help.help" { modal_open("help") return 0.0 }
if a == "help.formats" { modal_open("formats") return 0.0 }
st_set("status", "Unknown action: " + a)
}
# the text prompt finished
fn text_commit(kind, v) {
bind t = str_trim(v)
if t == "" { return 0.0 }
bind o = me_sel_o()
if kind == "obj" { bind ro = st_get("rename_o", o) if me_exists(ro) { st_set(mk(ro, "name"), t) st_set("dirty", 1.0) } return 0.0 }
if kind == "bone" { if o >= 0.0 { rg_rename(o, rg_active(o), t) } return 0.0 }
if kind == "action" { if o >= 0.0 { ps_new_action(o, t) } return 0.0 }
if kind == "layer" { vc_rename_layer(t) return 0.0 }
if kind == "item" { vc_rename_item(t) return 0.0 }
if kind == "texture" { tx_rename(t) return 0.0 }
}
# a parameter dialog was applied (kind = what it configures)
fn params_apply(kind, live) {
bind vals = st_get("pm_vals", [])
bind o = me_sel_o()
if str_starts_with(kind, "add.") {
if live { return 0.0 }
me_undo_push()
bind n = pr_add(str_slice(kind, 4.0, str_len(kind)), vals)
if st_get("mode", 0.0) == 1.0 { st_set("sel_o", n) }
st_set("status", "Added " + me_name(n))
return 0.0
}
if str_starts_with(kind, "gen.") { if !live { gn_make(str_slice(kind, 4.0, str_len(kind)), vals) } return 0.0 }
if kind == "mesh.spin" {
if live || o < 0.0 { return 0.0 }
st_set("op_spin_steps", list_get(vals, 0)) st_set("op_spin_angle", list_get(vals, 1))
me_undo_push()
mo_spin(o, list_get(vals, 0), list_get(vals, 1))
return 0.0
}
if kind == "vec.extrude" { if !live { vc_extrude_to_mesh(vals) } return 0.0 }
if kind == "vec.lathe" { if !live { vc_lathe_to_mesh(vals) } return 0.0 }
}
# ── keyboard ────────────────────────────────────────────────────────────────
fn keyboard() {
bind typing = st_get("focus", "") != "" || st_get("modal", "") != ""
if typing { return 0.0 }
bind ctrl = key_down("lctrl") || key_down("rctrl")
bind shift = key_down("lshift") || key_down("rshift")
bind alt = key_down("lalt") || key_down("ralt")
bind m = st_get("mode", 0.0)
if ctrl {
if key_pressed("z") { if shift { do_action("edit.redo") } else { do_action("edit.undo") } return 0.0 }
if key_pressed("y") { do_action("edit.redo") return 0.0 }
if key_pressed("s") { if shift { do_action("file.save_as") } else { do_action("file.save") } return 0.0 }
if key_pressed("o") { do_action("file.open") return 0.0 }
if key_pressed("n") { do_action("file.new") return 0.0 }
if key_pressed("q") { do_action("file.quit") return 0.0 }
if key_pressed("p") && m != 2.0 { do_action("file.settings") return 0.0 }
}
if key_pressed("f1") { set_mode(st_get("model_mode", 0.0)) return 0.0 }
if key_pressed("f2") && m != 2.0 { set_mode(2.0) return 0.0 }
if key_pressed("f3") { set_mode(3.0) return 0.0 }
if key_pressed("f4") { set_mode(4.0) return 0.0 }
if key_pressed("f5") { set_mode(5.0) return 0.0 }
if key_pressed("f6") { set_mode(6.0) return 0.0 }
if key_pressed("f7") { set_mode(7.0) return 0.0 }
if key_pressed("f12") { do_action("view.shot") return 0.0 }
if key_pressed("f11") { modal_open("shortcuts") return 0.0 }
if key_pressed("space") && (m == 4.0 || m == 6.0 || (m == 7.0 && st_get("tx_anim", 0.0) > 0.5)) { tl_toggle_play() return 0.0 }
if m < 4.5 { v3_keys(shift, ctrl, alt) return 0.0 }
if m < 6.5 { vc_keys(shift, ctrl, alt) return 0.0 }
tx_keys(shift, ctrl, alt)
}