SPipe 데모 v2 (2026-10-30) 서버 배포본
- SPipe_server_2026-10-30.zip 내용을 풀어서 등록 (압축 파일은 올리지 않음) - README에 데모 v2 서버 업데이트 순서 추가, 저장소에 없는 문서 목차 정리 - 패치내역을 docs/로 이동, _worklog.md 추가 - 임시 파일(backend/temp.*)과 API 키가 든 파일은 제외
This commit is contained in:
commit
21ebb16fb7
181 files changed
+57207
No files matched your search
@@ -0,0 +1,178 @@
|
||||
import type { NetworkModel } from './inpModel';
|
||||
import type { SimulationResult } from './types';
|
||||
|
||||
interface DxfExportOptions {
|
||||
model: NetworkModel;
|
||||
simulationResult?: SimulationResult | null;
|
||||
scenarioName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an AutoCAD ASCII DXF (R12 / AC1009) format drawing file
|
||||
* with organized CAD layers for pipes, nodes, text labels, and hydraulic results.
|
||||
*/
|
||||
export function generateDxf({
|
||||
model,
|
||||
simulationResult,
|
||||
scenarioName = 'Baseline',
|
||||
}: DxfExportOptions): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
const add = (...tokens: (string | number)[]) => {
|
||||
for (const t of tokens) {
|
||||
lines.push(String(t));
|
||||
}
|
||||
};
|
||||
|
||||
// Compute model extents to determine appropriate text height and circle radius
|
||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||
for (const n of Object.values(model.nodes)) {
|
||||
if (n.x < minX) minX = n.x;
|
||||
if (n.y < minY) minY = n.y;
|
||||
if (n.x > maxX) maxX = n.x;
|
||||
if (n.y > maxY) maxY = n.y;
|
||||
}
|
||||
if (minX === Infinity) {
|
||||
minX = 0; minY = 0; maxX = 100; maxY = 100;
|
||||
}
|
||||
|
||||
const modelSpan = Math.max(maxX - minX, maxY - minY) || 100;
|
||||
const textHeight = Math.max(0.5, Math.round((modelSpan * 0.015) * 10) / 10);
|
||||
const nodeRadius = Math.max(0.6, Math.round((modelSpan * 0.018) * 10) / 10);
|
||||
|
||||
// 1. HEADER SECTION
|
||||
add('0', 'SECTION');
|
||||
add('2', 'HEADER');
|
||||
add('9', '$ACADVER', '1', 'AC1009'); // AutoCAD Release 12 DXF standard
|
||||
add('9', '$INSUNITS', '70', '6'); // Meters (or Unitless)
|
||||
add('0', 'ENDSEC');
|
||||
|
||||
// 2. TABLES SECTION (Standard Engineering Layer Definitions)
|
||||
add('0', 'SECTION');
|
||||
add('2', 'TABLES');
|
||||
add('0', 'TABLE');
|
||||
add('2', 'LAYER');
|
||||
add('70', '7');
|
||||
|
||||
const defineLayer = (name: string, color: number) => {
|
||||
add('0', 'LAYER');
|
||||
add('2', name);
|
||||
add('70', '0');
|
||||
add('62', color); // AutoCAD ACI Color
|
||||
add('6', 'CONTINUOUS');
|
||||
};
|
||||
|
||||
defineLayer('PIPE', 4); // Cyan
|
||||
defineLayer('PIPE_TEXT', 3); // Green (ID, Dia, Len)
|
||||
defineLayer('PIPE_FLOW', 2); // Yellow (Q, V)
|
||||
defineLayer('NODE', 1); // Red
|
||||
defineLayer('NODE_TEXT', 7); // White/Black (ID, Elev)
|
||||
defineLayer('NODE_PRESS', 5); // Blue (P)
|
||||
defineLayer('FACILITY', 6); // Magenta (Reservoir, Tank)
|
||||
|
||||
add('0', 'ENDTAB');
|
||||
add('0', 'ENDSEC');
|
||||
|
||||
// 3. BLOCKS SECTION (Empty but required)
|
||||
add('0', 'SECTION');
|
||||
add('2', 'BLOCKS');
|
||||
add('0', 'ENDSEC');
|
||||
|
||||
// 4. ENTITIES SECTION
|
||||
add('0', 'SECTION');
|
||||
add('2', 'ENTITIES');
|
||||
|
||||
// Title Text entity
|
||||
add('0', 'TEXT');
|
||||
add('8', 'NODE_TEXT');
|
||||
add('10', minX, '20', maxY + textHeight * 3, '30', '0.0');
|
||||
add('40', textHeight * 1.5);
|
||||
add('1', `spipe Network Drawing - ${scenarioName}`);
|
||||
add('50', '0.0');
|
||||
|
||||
// A. Export Nodes (Junctions, Reservoirs, Tanks)
|
||||
for (const node of Object.values(model.nodes)) {
|
||||
const isFacility = node.type === 'RESERVOIR' || node.type === 'TANK';
|
||||
const layer = isFacility ? 'FACILITY' : 'NODE';
|
||||
const r = isFacility ? nodeRadius * 1.6 : nodeRadius;
|
||||
|
||||
// Node Circle entity
|
||||
add('0', 'CIRCLE');
|
||||
add('8', layer);
|
||||
add('10', node.x.toFixed(3), '20', node.y.toFixed(3), '30', '0.0');
|
||||
add('40', r.toFixed(3));
|
||||
|
||||
// Node ID and Elevation Text entity
|
||||
const elevStr = `EL.${node.elevation.toFixed(1)}`;
|
||||
add('0', 'TEXT');
|
||||
add('8', 'NODE_TEXT');
|
||||
add('10', (node.x + r * 1.2).toFixed(3), '20', (node.y + r * 0.4).toFixed(3), '30', '0.0');
|
||||
add('40', textHeight.toFixed(3));
|
||||
add('1', `${node.id} (${elevStr})`);
|
||||
add('50', '0.0');
|
||||
|
||||
// Hydraulic Pressure Text entity
|
||||
if (simulationResult) {
|
||||
const res = simulationResult.nodes[node.id];
|
||||
if (res) {
|
||||
const pressUnit = simulationResult.units.pressure;
|
||||
const pressStr = `P=${res.pressure.toFixed(1)}${pressUnit}`;
|
||||
add('0', 'TEXT');
|
||||
add('8', 'NODE_PRESS');
|
||||
add('10', (node.x + r * 1.2).toFixed(3), '20', (node.y - r * 1.0).toFixed(3), '30', '0.0');
|
||||
add('40', (textHeight * 0.9).toFixed(3));
|
||||
add('1', pressStr);
|
||||
add('50', '0.0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// B. Export Pipes and Pumps
|
||||
for (const pipe of Object.values(model.pipes)) {
|
||||
const n1 = model.nodes[pipe.node1];
|
||||
const n2 = model.nodes[pipe.node2];
|
||||
if (!n1 || !n2) continue;
|
||||
|
||||
// Pipe Line entity
|
||||
add('0', 'LINE');
|
||||
add('8', 'PIPE');
|
||||
add('10', n1.x.toFixed(3), '20', n1.y.toFixed(3), '30', '0.0');
|
||||
add('11', n2.x.toFixed(3), '21', n2.y.toFixed(3), '31', '0.0');
|
||||
|
||||
// Pipe Midpoint and Angle
|
||||
const midX = (n1.x + n2.x) / 2;
|
||||
const midY = (n1.y + n2.y) / 2;
|
||||
let angleDeg = Math.atan2(n2.y - n1.y, n2.x - n1.x) * (180 / Math.PI);
|
||||
if (angleDeg < -90) angleDeg += 180;
|
||||
if (angleDeg > 90) angleDeg -= 180;
|
||||
|
||||
// Pipe Annotation Text: ID, Diameter, Length
|
||||
const diaStr = `D${pipe.diameter}`;
|
||||
const lenStr = `L${Math.round(pipe.length)}`;
|
||||
add('0', 'TEXT');
|
||||
add('8', 'PIPE_TEXT');
|
||||
add('10', midX.toFixed(3), '20', (midY + textHeight * 0.4).toFixed(3), '30', '0.0');
|
||||
add('40', (textHeight * 0.85).toFixed(3));
|
||||
add('1', `${pipe.id}:${diaStr}-${lenStr}`);
|
||||
add('50', angleDeg.toFixed(1));
|
||||
|
||||
// Hydraulic Flow and Velocity Text entity
|
||||
if (simulationResult) {
|
||||
const res = simulationResult.links[pipe.id];
|
||||
if (res) {
|
||||
const flowStr = `Q=${res.flow.toFixed(1)} V=${res.velocity.toFixed(2)}`;
|
||||
add('0', 'TEXT');
|
||||
add('8', 'PIPE_FLOW');
|
||||
add('10', midX.toFixed(3), '20', (midY - textHeight * 1.2).toFixed(3), '30', '0.0');
|
||||
add('40', (textHeight * 0.75).toFixed(3));
|
||||
add('1', flowStr);
|
||||
add('50', angleDeg.toFixed(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add('0', 'ENDSEC');
|
||||
add('0', 'EOF');
|
||||
|
||||
return lines.join('\r\n');
|
||||
}
|
||||
Reference in new issue
Block a user