# topology_api/seed/seed_layout.py """Seed diagram layout and node positions for the engineering overview SVG.""" import json from topology_api.db import write_cursor THEME_KEY = "guanghan_light_engineering_v1" CANVAS_WIDTH = 1900 CANVAS_HEIGHT = 1900 CONTENT_X_OFFSET = 200 THEME_TOKENS = { "textColor": "#152033", "mutedTextColor": "#5e6f85", "metaColor": "#435a72", "hintColor": "#6f8296", } NODE_STYLES = [ ("node-card", "普通舱段", "#edf7ff", "#1677b8", 3, 16), ("core-card", "核心舱", "#fff3e2", "#c76b00", 5, 16), ("power-card", "电力模块分支", "#fff9d9", "#9b7900", 3, 16), ("power-node-card", "电力模块中心", "#fff2b8", "#9b7900", 5, 16), ("vehicle-card", "车辆", "#f7effb", "#8755a4", 3, 16), ] EDGE_STYLES = [ ("structural_mount", "结构直连", "#66788e", 6, None, "square", "structural", 10), ("pressurized_passage", "加压走道", "#168a55", 9, None, "square", "pressurized", 20), ("fuel", "燃料 / 供电", "#7a4fb3", 4, "13 10", "square", "utility", 30), ("power", "燃料 / 供电", "#7a4fb3", 4, "13 10", "square", "utility", 30), ("cooling", "冷却", "#27809a", 4, "10 8", "square", "cooling", 40), ("docking", "对接口", "#8755a4", 4, "10 8", "square", "docking", 50), ] DECORATION_STYLES = [ ( "radiator", "大型折叠散热面板", "#fff0a8", "#9b7900", 2, "散热器", "#6d5500", { "widthRatio": 0.55, "maxWidth": 130, "height": 16, "offsetXRatio": 0.55, "offsetY": -18, "ribCount": 3, }, 40, ), ] LEGEND_ITEMS = [ ("structural", "结构直连", "edge", "structural_mount", 10), ("pressurized", "加压走道", "edge", "pressurized_passage", 20), ("utility", "燃料 / 供电", "edge", "fuel", 30), ("radiator", "大型折叠散热面板", "decoration", "radiator", 40), ] def seed(): with write_cursor() as cur: # Get base cur.execute("SELECT id FROM bases WHERE code = 'guanghan'") base_id = cur.fetchone()["id"] cur.execute( "UPDATE diagram_themes SET is_default = false WHERE base_id = %(base)s", {"base": base_id}, ) cur.execute(""" INSERT INTO diagram_themes (id, base_id, theme_key, name, description, tokens, is_default) VALUES ( gen_random_uuid(), %(base)s, %(theme_key)s, '广寒浅色工程图', '适配 Wiki.js 与 Dark Reader 的浅色工程总图主题', %(tokens)s, true ) ON CONFLICT (base_id, theme_key) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description, tokens = EXCLUDED.tokens, is_default = true, updated_at = now() RETURNING id """, { "base": base_id, "theme_key": THEME_KEY, "tokens": json.dumps(THEME_TOKENS), }) theme_id = cur.fetchone()["id"] print(f"Theme: {theme_id}") # Upsert layout cur.execute(""" INSERT INTO diagram_layouts (id, base_id, layout_key, name, canvas_width, canvas_height, is_default, theme_id) VALUES (gen_random_uuid(), %(base)s, 'engineering_overview_v1', '工程总图 v1', %(canvas_width)s, %(canvas_height)s, true, %(theme)s) ON CONFLICT (base_id, layout_key) DO UPDATE SET name = EXCLUDED.name, canvas_width = EXCLUDED.canvas_width, canvas_height = EXCLUDED.canvas_height, is_default = true, theme_id = EXCLUDED.theme_id RETURNING id """, { "base": base_id, "canvas_width": CANVAS_WIDTH, "canvas_height": CANVAS_HEIGHT, "theme": theme_id, }) layout_id = cur.fetchone()["id"] print(f"Layout: {layout_id}") cur.execute("DELETE FROM diagram_legend_items WHERE theme_id = %(theme)s", {"theme": theme_id}) cur.execute("DELETE FROM diagram_decoration_styles WHERE theme_id = %(theme)s", {"theme": theme_id}) cur.execute("DELETE FROM diagram_edge_styles WHERE theme_id = %(theme)s", {"theme": theme_id}) cur.execute("DELETE FROM diagram_node_styles WHERE theme_id = %(theme)s", {"theme": theme_id}) for style_key, display_name, fill, stroke, stroke_width, corner_radius in NODE_STYLES: cur.execute(""" INSERT INTO diagram_node_styles (theme_id, style_key, display_name, fill_color, stroke_color, stroke_width, corner_radius, title_color, meta_color, hint_color) VALUES (%(theme)s, %(style_key)s, %(display_name)s, %(fill)s, %(stroke)s, %(stroke_width)s, %(corner_radius)s, %(title_color)s, %(meta_color)s, %(hint_color)s) """, { "theme": theme_id, "style_key": style_key, "display_name": display_name, "fill": fill, "stroke": stroke, "stroke_width": stroke_width, "corner_radius": corner_radius, "title_color": THEME_TOKENS["textColor"], "meta_color": THEME_TOKENS["metaColor"], "hint_color": THEME_TOKENS["hintColor"], }) for ( connection_type, display_name, stroke, stroke_width, dash_array, line_cap, legend_group, legend_order, ) in EDGE_STYLES: cur.execute(""" INSERT INTO diagram_edge_styles (theme_id, connection_type, display_name, stroke_color, stroke_width, dash_array, line_cap, legend_group, legend_order) VALUES (%(theme)s, %(connection_type)s, %(display_name)s, %(stroke)s, %(stroke_width)s, %(dash_array)s, %(line_cap)s, %(legend_group)s, %(legend_order)s) """, { "theme": theme_id, "connection_type": connection_type, "display_name": display_name, "stroke": stroke, "stroke_width": stroke_width, "dash_array": dash_array, "line_cap": line_cap, "legend_group": legend_group, "legend_order": legend_order, }) for ( decoration_key, display_name, fill, stroke, stroke_width, label, label_color, render_params, legend_order, ) in DECORATION_STYLES: cur.execute(""" INSERT INTO diagram_decoration_styles (theme_id, decoration_key, display_name, fill_color, stroke_color, stroke_width, label, label_color, render_params, legend_order) VALUES (%(theme)s, %(decoration_key)s, %(display_name)s, %(fill)s, %(stroke)s, %(stroke_width)s, %(label)s, %(label_color)s, %(render_params)s, %(legend_order)s) """, { "theme": theme_id, "decoration_key": decoration_key, "display_name": display_name, "fill": fill, "stroke": stroke, "stroke_width": stroke_width, "label": label, "label_color": label_color, "render_params": json.dumps(render_params), "legend_order": legend_order, }) for legend_key, label, item_type, style_ref, display_order in LEGEND_ITEMS: cur.execute(""" INSERT INTO diagram_legend_items (theme_id, legend_key, label, item_type, style_ref, display_order, is_visible) VALUES (%(theme)s, %(legend_key)s, %(label)s, %(item_type)s, %(style_ref)s, %(display_order)s, true) """, { "theme": theme_id, "legend_key": legend_key, "label": label, "item_type": item_type, "style_ref": style_ref, "display_order": display_order, }) # Component key → component_id mapping cur.execute("SELECT component_key, id FROM components WHERE base_id = %(base)s", {"base": base_id}) comp_map = {r["component_key"]: r["id"] for r in cur.fetchall()} # Layout and concise engineering labels. All visual semantics live here, # not in the Wiki page. nodes = [ # Core cross ("core_cabin", 630, 470, 240, 200, "core-card", None, 10, "四向安装节点", "北 / 东 / 南 / 西", {}, ["core_cabin"]), ("habitat_2", 630, 220, 240, 140, "node-card", "居住舱#2", 5, "核心舱北部安装口", "4铺位 + 2睡袋", {}, ["habitat_2"]), ("habitat_1", 630, 760, 240, 120, "node-card", "居住舱#1", 5, "核心舱南部安装口", None, {}, ["habitat_1"]), ("greenhouse", 330, 490, 220, 160, "node-card", "温室模块", 5, "核心舱西部安装口", "远端连接吴刚二号", {}, ["greenhouse"]), ("lab_module", 950, 490, 220, 160, "node-card", "实验舱", 5, "核心舱东部安装口", "三温区实验舱", {}, ["lab_module"]), ("wugang_2", 40, 495, 210, 150, "vehicle-card", None, 3, "Block 2 载人着陆器", "温室远端接口", {}, ["wugang_2"]), # Power cross ("power_module_hub", 650, 1260, 200, 180, "power-node-card", "电力模块", 8, "四向安装节点", "核电与资源中枢", {}, ["power_module_hub"]), ("power_storage_rack", 600, 990, 300, 160, "power-card", "北部模块", 5, "电力模块存储支架", "加压走道端", {"radiator": True}, ["power_storage_rack"]), ("nuclear_reactor", 620, 1540, 260, 150, "power-card", "南部模块", 5, "核反应堆", "30%功率", {"radiator": True}, ["nuclear_reactor"]), # Power west/east groups (multiple DB components per SVG node) ("power_west_group", 280, 1250, 260, 200, "power-card", "西部模块", 5, "ISRU · 采矿", "矿石 / 燃料存储", {"radiator": True}, ["isru_module", "mining_module", "ore_fuel_storage"]), ("power_east_group", 960, 1250, 260, 200, "power-card", "东部模块", 5, "核燃料存储 · 离心机", None, {"radiator": True}, ["fuel_storage_mod", "centrifuge_mod"]), # Bingluns ("binglun_1", 30, 1280, 210, 140, "vehicle-card", None, 2, "电力模块西侧", "燃料运输", {}, ["binglun_1"]), ("binglun_2", 645, 1740, 210, 120, "vehicle-card", None, 2, "电力模块南侧", None, {}, ["binglun_2"]), ("binglun_3", 1260, 1280, 210, 140, "vehicle-card", None, 2, "电力模块东侧", "Block 2", {}, ["binglun_3"]), ] # Delete existing nodes and re-insert cur.execute("DELETE FROM diagram_nodes WHERE layout_id = %(lid)s", {"lid": layout_id}) for ( node_key, x, y, w, h, style, label, z, display_meta, display_hint, decorations, member_keys, ) in nodes: primary_component_id = ( comp_map[member_keys[0]] if len(member_keys) == 1 else None ) cur.execute(""" INSERT INTO diagram_nodes (id, layout_id, component_id, node_key, x, y, width, height, style_key, label_override, z_index, display_meta, display_hint, decorations) VALUES (gen_random_uuid(), %(lid)s, %(cid)s, %(nkey)s, %(x)s, %(y)s, %(w)s, %(h)s, %(style)s, %(label)s, %(z)s, %(meta)s, %(hint)s, %(decorations)s) RETURNING id """, { "lid": layout_id, "cid": primary_component_id, "nkey": node_key, "x": x + CONTENT_X_OFFSET, "y": y, "w": w, "h": h, "style": style, "label": label, "z": z, "meta": display_meta, "hint": display_hint, "decorations": json.dumps(decorations), }) node_id = cur.fetchone()["id"] for display_order, member_key in enumerate(member_keys): cur.execute(""" INSERT INTO diagram_node_members (diagram_node_id, component_id, display_order) VALUES (%(node)s, %(component)s, %(display_order)s) """, { "node": node_id, "component": comp_map[member_key], "display_order": display_order, }) print(f"Nodes inserted: {len(nodes)}") if __name__ == "__main__": seed()