318 lines
13 KiB
Python
318 lines
13 KiB
Python
# 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()
|