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/panels.ling
# ━━━ panels.ling — the outliner (left) and the properties panel (right), per mode ━━━
# ── outliner ────────────────────────────────────────────────────────────────
fn sidebar(x, y, w, h) {
px_rect(x, y, w, h, C_BG_SIDE)
u_vsep(x + w - 1.0, y, h)
bind m = st_get("mode", 0.0)
if m < 4.5 { sidebar_scene(x, y, w, h) }
else if m < 6.5 { vc_sidebar(x, y, w, h) }
else { tx_sidebar(x, y, w, h) }
}
fn sidebar_scene(x, y, w, h) {
bind m = st_get("mode", 0.0)
u_header(x + 8.0, y + 8.0, w - 16.0, "Scene", ds_col("call"))
bind ids = me_ids()
bind rowh = 22.0
bind sel_o = me_sel_o()
bind list_h = sel(m >= 2.0 && m <= 4.0, floor((h - 30.0) * 0.4), h - 60.0)
bind content = len(ids) * rowh + 4.0
bind off = u_scroll_y("sb_scroll", x, y + 26.0, w, list_h, content, st_get("sb_off", 0.0))
st_set("sb_off", off)
u_clip(x, y + 26.0, w, list_h)
bind i = 0.0
while i < len(ids) {
bind o = list_get(ids, i)
bind ry = y + 28.0 + i * rowh - off
bind on = st_get(mk(o, "osel"), 0.0) > 0.5
bind icon = sel(me_nfaces(o) > 0.5, "cube", sel(me_has_bones(o), "bone", "vertex"))
bind r = u_row("sb_row" + num_str(o), x + 2.0, ry, w - 36.0, rowh, me_name(o), on, sel(o == sel_o, st_get(mk(o, "col"), C_BLUE), C_TEXT_DIM), icon)
if r > 1.5 { st_set("rename_o", o) modal_text("Rename object", "obj", me_name(o)) }
else if r > 0.5 {
if u_shift() { me_toggle_object(o) } else { me_select_object(o, false) }
if m == 1.0 { st_set("sel_o", o) }
}
# visibility eye
bind vis = st_get(mk(o, "vis"), 1.0) > 0.5
if u_hot(x + w - 30.0, ry + 2.0, 20.0, 18.0) { u_tip(sel(vis, "Hide", "Show")) if u_pressed() { st_set(mk(o, "vis"), sel(vis, 0.0, 1.0)) } }
icon_draw(sel(vis, "eye", "eye_off"), x + w - 20.0, ry + rowh * 0.5, 11.0, sel(vis, C_TEXT_DIM, C_LINE))
bind i = i + 1.0
}
u_unclip()
if len(ids) < 0.5 {
g_text_wrap(x + 10.0, y + 36.0, w - 20.0, 11.0, C_TEXT_DIM, "Empty scene. Edit > Add Primitive, Generate Asset, or File > Import.")
}
bind by = y + 26.0 + list_h + 6.0
if m >= 2.0 && m <= 4.0 {
u_hsep(x, by - 4.0, w)
rg_bone_list(x, by, w, h - (by - y) - 4.0)
} else {
bind bw = floor((w - 20.0) / 2.0)
if u_button("sb_add", x + 8.0, by, bw, 22.0, "+ Add", 0.0) { ctx_open(menu_sub_def("add"), x + 8.0, by + 24.0) }
if u_button("sb_gen", x + 12.0 + bw, by, bw, 22.0, "Generate", 0.0) { ctx_open(menu_sub_def("gen"), x + 12.0 + bw, by + 24.0) }
}
}
# ── properties ──────────────────────────────────────────────────────────────
fn props_panel(x, y, w, h) {
px_rect(x, y, w, h, C_BG_SIDE)
u_vsep(x, y, h)
bind m = st_get("mode", 0.0)
bind content = 1400.0
bind off = u_scroll_y("pp_scroll", x, y, w, h, content, st_get("pp_off", 0.0))
st_set("pp_off", off)
u_clip(x + 1.0, y, w - 1.0, h)
bind cy = y + 8.0 - off
bind px = x + 10.0
bind pw = w - 30.0
if m == 0.0 { bind cy = props_object(px, cy, pw) }
else if m == 1.0 { bind cy = props_edit(px, cy, pw) }
else if m == 2.0 { bind cy = rg_props(px, cy, pw) }
else if m == 3.0 { bind cy = wp_props(px, cy, pw) }
else if m == 4.0 { bind cy = ps_props(px, cy, pw) }
else if m == 5.0 { bind cy = vc_props(px, cy, pw) }
else if m == 6.0 { bind cy = an_props(px, cy, pw) }
else { bind cy = tx_props(px, cy, pw) }
u_unclip()
}
# shared: transform fields for the active object. returns the next y
fn props_transform(o, x, y, w) {
u_header(x, y, w, "Transform", ds_col("func"))
bind cy = y + 20.0
g_text(x, cy + 4.0, 11.0, C_TEXT_DIM, "Location")
bind r = u_vec3("pp_loc", x + 60.0, cy, w - 60.0, st_get(mk(o, "px"), 0.0), st_get(mk(o, "py"), 0.0), st_get(mk(o, "pz"), 0.0), 0.0 - 10000.0, 10000.0, 0.02)
if list_get(r, 0) >= 0.0 { if u_pressed() { me_undo_push() } st_set(mk(o, list_get(["px", "py", "pz"], list_get(r, 0))), list_get(r, 1)) st_set("dirty", 1.0) }
bind cy = cy + 26.0
g_text(x, cy + 4.0, 11.0, C_TEXT_DIM, "Rotation")
bind r = u_vec3("pp_rot", x + 60.0, cy, w - 60.0, mx_deg(st_get(mk(o, "rx"), 0.0)), mx_deg(st_get(mk(o, "ry"), 0.0)), mx_deg(st_get(mk(o, "rz"), 0.0)), 0.0 - 100000.0, 100000.0, 0.5)
if list_get(r, 0) >= 0.0 { if u_pressed() { me_undo_push() } st_set(mk(o, list_get(["rx", "ry", "rz"], list_get(r, 0))), mx_rad(list_get(r, 1))) st_set("dirty", 1.0) }
bind cy = cy + 26.0
g_text(x, cy + 4.0, 11.0, C_TEXT_DIM, "Scale")
bind r = u_vec3("pp_scl", x + 60.0, cy, w - 60.0, st_get(mk(o, "sx"), 1.0), st_get(mk(o, "sy"), 1.0), st_get(mk(o, "sz"), 1.0), 0.0001, 10000.0, 0.01)
if list_get(r, 0) >= 0.0 { if u_pressed() { me_undo_push() } st_set(mk(o, list_get(["sx", "sy", "sz"], list_get(r, 0))), list_get(r, 1)) st_set("dirty", 1.0) }
bind cy = cy + 30.0
if u_button("pp_apply", x, cy, 96.0, 22.0, "Apply", 0.0) { do_action("mesh.apply_xf") }
if u_button("pp_center", x + 102.0, cy, 96.0, 22.0, "Centre origin", 0.0) { do_action("mesh.center") }
return cy + 32.0
}
fn props_material(o, x, y, w) {
u_header(x, y, w, "Material", ds_col("bind"))
bind cy = y + 20.0
bind cur = st_get(mk(o, "col"), C_BLUE)
bind pal = [C_RED, C_ORANGE, C_YELLOW, C_GREEN, C_BLUE, C_INDIGO, C_VIOLET, 13158600.0, 8421504.0, 4473924.0, 16119285.0, 10921638.0, 14123060.0, 5343436.0]
bind i = 0.0
while i < len(pal) {
bind sx = x + (i % 7.0) * 24.0
bind sy = cy + floor(i / 7.0) * 24.0
if u_swatch("pp_sw" + num_str(i), sx, sy, 20.0, list_get(pal, i), cur == list_get(pal, i)) { me_undo_push() st_set(mk(o, "col"), list_get(pal, i)) st_set("dirty", 1.0) }
bind i = i + 1.0
}
bind cy = cy + 52.0
g_text(x, cy + 4.0, 11.0, C_TEXT_DIM, "Hex")
bind hx = u_textfield("pp_hex", x + 36.0, cy, 80.0, 22.0, sel(st_get("focus", "") == "pp_hex", st_get("pp_hex_t", c_hex(cur)), c_hex(cur)))
st_set("pp_hex_t", hx)
if st_get("focus", "") != "pp_hex" && hx != c_hex(cur) {
bind v = c_from_hex(hx)
if v >= 0.0 { me_undo_push() st_set(mk(o, "col"), v) st_set("dirty", 1.0) }
}
px_rect(x + 124.0, cy, 22.0, 22.0, cur)
px_outline(x + 124.0, cy, 22.0, 22.0, C_LINE)
bind cy = cy + 30.0
bind tx = st_get(mk(o, "tex"), 0.0 - 1.0)
bind names = tx_names_with_none()
g_text(x, cy + 5.0, 11.0, C_TEXT_DIM, "Texture")
bind nv = u_combo("pp_tex", x + 52.0, cy, w - 52.0, 22.0, names, tx + 1.0)
if nv >= 0.0 { me_undo_push() st_set(mk(o, "tex"), nv - 1.0) tx_bake_object(o) st_set("dirty", 1.0) }
bind cy = cy + 28.0
if u_check("pp_smooth", x, cy, "Smooth shading (export normals)", st_get(mk(o, "smooth"), 0.0) > 0.5) { st_set(mk(o, "smooth"), 1.0 - st_get(mk(o, "smooth"), 0.0)) st_set("dirty", 1.0) }
bind cy = cy + 24.0
if u_button("pp_bake", x, cy, 96.0, 22.0, "Bake colours", 0.0) { do_action("tex.bake") }
if u_button("pp_clearbake", x + 102.0, cy, 96.0, 22.0, "Clear bake", 0.0) { me_undo_push() st_set(mk(o, "fcol"), me_fill(me_nfaces(o), 0.0 - 1.0)) }
return cy + 32.0
}
fn props_object(x, y, w) {
bind o = me_sel_o()
bind cy = y
if o < 0.0 {
u_header(x, cy, w, "Object", ds_col("asm"))
g_text_wrap(x, cy + 24.0, w, 11.0, C_TEXT_DIM, "No object selected. Click one in the viewport or the outliner, or add a primitive.")
return cy + 60.0
}
u_header(x, cy, w, "Object", ds_col("asm"))
bind cy = cy + 20.0
bind nm = u_textfield("pp_name", x, cy, w, 22.0, sel(st_get("focus", "") == "pp_name", st_get("pp_name_t", me_name(o)), me_name(o)))
st_set("pp_name_t", nm)
if st_get("focus", "") != "pp_name" && nm != me_name(o) && str_trim(nm) != "" { st_set(mk(o, "name"), str_trim(nm)) st_set("dirty", 1.0) }
bind cy = cy + 28.0
g_text(x, cy, 11.0, C_TEXT_DIM, num_str(me_nverts(o)) + " vertices, " + num_str(me_nfaces(o)) + " faces, " + num_str(me_ntris(o)) + " triangles")
bind cy = cy + 22.0
bind cy = props_transform(o, x, cy, w)
bind cy = props_material(o, x, cy, w)
u_header(x, cy, w, "Mesh", ds_col("ctl"))
bind cy = cy + 20.0
bind bw = floor((w - 6.0) / 2.0)
if u_button("pp_shade", x, cy, bw, 22.0, "Smooth verts", 0.0) { do_action("mesh.smooth") }
if u_button("pp_subsurf", x + bw + 6.0, cy, bw, 22.0, "Subsurf", 0.0) { do_action("mesh.subsurf") }
bind cy = cy + 26.0
if u_button("pp_mirror", x, cy, bw, 22.0, "Mirror X", 0.0) { do_action("mesh.mirror_x") }
if u_button("pp_tri", x + bw + 6.0, cy, bw, 22.0, "Triangulate", 0.0) { do_action("mesh.tri") }
bind cy = cy + 26.0
if u_button("pp_recalc", x, cy, bw, 22.0, "Recalc normals", 0.0) { do_action("mesh.recalc") }
if u_button("pp_flip", x + bw + 6.0, cy, bw, 22.0, "Flip normals", 0.0) { do_action("mesh.flip") }
bind cy = cy + 26.0
if u_button("pp_join", x, cy, bw, 22.0, "Join selected", 0.0) { do_action("mesh.join") }
if u_button("pp_dup", x + bw + 6.0, cy, bw, 22.0, "Duplicate", 0.0) { do_action("edit.dup") }
bind cy = cy + 34.0
u_header(x, cy, w, "UV", ds_col("mod"))
bind cy = cy + 20.0
if u_button("pp_uvbox", x, cy, bw, 22.0, "Box map", 0.0) { do_action("uv.box") }
if u_button("pp_uvplanar", x + bw + 6.0, cy, bw, 22.0, "Planar (view)", 0.0) { do_action("uv.planar") }
bind cy = cy + 26.0
if u_button("pp_uvcyl", x, cy, bw, 22.0, "Cylinder", 0.0) { do_action("uv.cyl") }
if u_button("pp_uvsph", x + bw + 6.0, cy, bw, 22.0, "Sphere", 0.0) { do_action("uv.sphere") }
bind cy = cy + 30.0
if u_check("pp_uvshow", x, cy, "Show UV layout", st_get("show_uv", 0.0) > 0.5) { st_set("show_uv", 1.0 - st_get("show_uv", 0.0)) }
bind cy = cy + 24.0
if st_get("show_uv", 0.0) > 0.5 { uv_panel(o, x, cy, w, w) bind cy = cy + w + 8.0 }
u_header(x, cy, w, "Rig", ds_col("mod"))
bind cy = cy + 20.0
g_text(x, cy, 11.0, C_TEXT_DIM, sel(me_has_bones(o), num_str(len(st_get(mk(o, "bn_name"), []))) + " bones" + sel(st_get(mk(o, "bound"), 0.0) > 0.5, ", bound", ", not bound"), "No armature. Rig tab: Shift+A adds bones."))
bind cy = cy + 20.0
if u_button("pp_rig", x, cy, bw, 22.0, "Rig mode", 0.0) { set_mode(2.0) }
if u_button("pp_pose", x + bw + 6.0, cy, bw, 22.0, "Pose mode", 0.0) { set_mode(4.0) }
return cy + 40.0
}
fn props_edit(x, y, w) {
bind o = me_sel_o()
bind cy = y
u_header(x, cy, w, "Selection", ds_col("asm"))
bind cy = cy + 20.0
if o < 0.0 { g_text(x, cy, 11.0, C_TEXT_DIM, "No object to edit.") return cy + 20.0 }
bind sm = st_get("sel_mode", 0.0)
bind r = ds_selector("pp_selmode", x, cy, 12.0, ["VERT", "EDGE", "FACE"], sm, ds_col("lit"), "Selection mode")
if r >= 0.0 { st_set("sel_mode", r) }
bind cy = cy + 22.0
g_text(x, cy, 11.0, C_TEXT_DIM, num_str(me_sel_count(o)) + " / " + num_str(me_nverts(o)) + " vertices, " + num_str(len(me_selected_faces(o))) + " faces")
bind cy = cy + 20.0
bind bw = floor((w - 6.0) / 2.0)
if u_button("pp_sall", x, cy, bw, 22.0, "All (A)", 0.0) { do_action("sel.all") }
if u_button("pp_snone", x + bw + 6.0, cy, bw, 22.0, "None (Alt+A)", 0.0) { do_action("sel.none") }
bind cy = cy + 26.0
if u_button("pp_sinv", x, cy, bw, 22.0, "Invert", 0.0) { do_action("sel.invert") }
if u_button("pp_slink", x + bw + 6.0, cy, bw, 22.0, "Linked (L)", 0.0) { do_action("sel.linked") }
bind cy = cy + 26.0
if u_button("pp_sgrow", x, cy, bw, 22.0, "Grow", 0.0) { do_action("sel.grow") }
if u_button("pp_sshrink", x + bw + 6.0, cy, bw, 22.0, "Shrink", 0.0) { do_action("sel.shrink") }
bind cy = cy + 34.0
u_header(x, cy, w, "Tools", ds_col("ctl"))
bind cy = cy + 20.0
bind nv = u_param("pp_extr", x, cy, w, "Extrude distance (E)", st_get("op_extrude", 0.5), 0.0 - 5.0, 5.0, C_GREEN, 2.0)
if u_has(nv) { st_set("op_extrude", nv) }
bind cy = cy + 34.0
bind nv = u_param("pp_inset", x, cy, w, "Inset thickness (I)", st_get("op_inset", 0.2), 0.0, 2.0, C_GREEN, 2.0)
if u_has(nv) { st_set("op_inset", nv) }
bind cy = cy + 34.0
bind nv = u_param("pp_bevel", x, cy, w, "Bevel width (Ctrl+B)", st_get("op_bevel", 0.1), 0.001, 2.0, C_GREEN, 3.0)
if u_has(nv) { st_set("op_bevel", nv) }
bind cy = cy + 34.0
bind nv = u_param("pp_bsegs", x, cy, w, "Bevel segments", st_get("op_bevel_segs", 1.0), 1.0, 6.0, C_GREEN, 0.0)
if u_has(nv) { st_set("op_bevel_segs", round(nv)) }
bind cy = cy + 34.0
bind nv = u_param("pp_cuts", x, cy, w, "Loop cuts (Ctrl+R)", st_get("op_cuts", 1.0), 1.0, 10.0, C_GREEN, 0.0)
if u_has(nv) { st_set("op_cuts", round(nv)) }
bind cy = cy + 34.0
bind nv = u_param("pp_merged", x, cy, w, "Merge distance", st_get("op_merge_d", 0.001), 0.0001, 0.5, C_GREEN, 3.0)
if u_has(nv) { st_set("op_merge_d", nv) }
bind cy = cy + 34.0
bind nv = u_param("pp_smooth", x, cy, w, "Smooth strength", st_get("op_smooth", 1.0), 0.1, 10.0, C_GREEN, 1.0)
if u_has(nv) { st_set("op_smooth", nv) }
bind cy = cy + 36.0
bind labels = ["Extrude", "Inset", "Bevel", "Loop cut", "Subdivide", "Subsurf", "Merge", "By dist.", "Dissolve", "Fill", "Connect", "Split", "Separate", "Flip", "Recalc", "Triangulate", "To quads", "Smooth", "Mirror X", "Symm. X", "Spin", "Delete"]
bind acts = ["mesh.extrude", "mesh.inset", "mesh.bevel", "mesh.loopcut", "mesh.subdivide", "mesh.subsurf", "mesh.merge", "mesh.merge_dist", "mesh.dissolve", "mesh.fill", "mesh.connect", "mesh.split", "mesh.separate", "mesh.flip", "mesh.recalc", "mesh.tri", "mesh.quads", "mesh.smooth", "mesh.mirror_x", "mesh.symm", "mesh.spin", "edit.delete"]
bind i = 0.0
while i < len(labels) {
bind bx = x + (i % 2.0) * (bw + 6.0)
bind byy = cy + floor(i / 2.0) * 26.0
if u_button("pp_op" + num_str(i), bx, byy, bw, 22.0, list_get(labels, i), sel(list_get(acts, i) == "edit.delete", C_RED, 0.0)) { do_action(list_get(acts, i)) }
bind i = i + 1.0
}
bind cy = cy + ceil(len(labels) / 2.0) * 26.0 + 8.0
u_header(x, cy, w, "Vertex", ds_col("func"))
bind cy = cy + 20.0
bind av = st_get("act_v", 0.0 - 1.0)
bind av_ok = false
if av >= 0.0 && av < me_nverts(o) { if me_vert_selected(o, av) { bind av_ok = true } }
if av_ok {
g_text(x, cy + 4.0, 11.0, C_TEXT_DIM, "#" + num_str(av))
bind vx = st_get(mk(o, "vx"), []) bind vy = st_get(mk(o, "vy"), []) bind vz = st_get(mk(o, "vz"), [])
bind r = u_vec3("pp_vpos", x + 40.0, cy, w - 40.0, list_get(vx, av), list_get(vy, av), list_get(vz, av), 0.0 - 10000.0, 10000.0, 0.01)
if list_get(r, 0) >= 0.0 {
if u_pressed() { me_undo_push() }
bind k = list_get(r, 0)
if k == 0.0 { bind vx = list_set(vx, av, list_get(r, 1)) st_set(mk(o, "vx"), vx) }
if k == 1.0 { bind vy = list_set(vy, av, list_get(r, 1)) st_set(mk(o, "vy"), vy) }
if k == 2.0 { bind vz = list_set(vz, av, list_get(r, 1)) st_set(mk(o, "vz"), vz) }
st_set("dirty", 1.0)
}
} else { g_text(x, cy, 11.0, C_TEXT_DIM, "Click a vertex to edit its position.") }
return cy + 30.0
}