Files
Pier_v1/도면작성/dxf.ts
T

102 lines
12 KiB
TypeScript

import { dimensionGraphics } from './dimension-graphics';
import { LAYERS, type DrawingDocument, type Entity, type Layer, type Point } from './types';
import { sheetFrame, viewPaperEntities } from './sheet';
import { CAD_LAYERS, CAD_STYLE, REFERENCE_ACI, referenceLayer } from './cad-style';
/** Native CAD entities in millimetres. Sheet scale affects annotations, never structural coordinates or measurements. */
export function exportDxf(doc: DrawingDocument,visible: ReadonlySet<Layer>): string {
let next=256;
const handle=()=> (next++).toString(16).toUpperCase();
const root=handle(),groups=handle(),layoutDictionary=handle(),modelLayout=handle(),paperLayout=handle(),modelRecord=handle(),paperRecord=handle(),textStyle=handle(),dimTextStyle=handle();
const pairs=(...values:(string|number)[])=>values.map((v,i)=>i%2?String(v).replace(/[\r\n]/g,' '):String(v)).join('\r\n')+'\r\n';
const section=(name:string,value:string)=>pairs(0,'SECTION',2,name)+value+pairs(0,'ENDSEC');
const table=(name:string,entries:string[],extra='')=>pairs(0,'TABLE',2,name,5,handle(),100,'AcDbSymbolTable',70,entries.length)+extra+entries.join('')+pairs(0,'ENDTAB');
const xdata=(e: Entity,view:string)=>pairs(1001,'PZ_GA',1000,e.source.pier.slice(0,100),1000,e.source.member,1071,e.source.index,1000,view);
const scales=[...new Set(doc.views.map(v=>v.scale))];
// Explicit model-unit text/arrow sizes; automatic DIMSCALE avoids a second viewport multiplier.
const dimstyle=(scale:number,name:string)=>pairs(0,'DIMSTYLE',105,handle(),100,'AcDbSymbolTableRecord',100,'AcDbDimStyleTableRecord',2,name,70,0,40,0,41,CAD_STYLE.arrow*scale,42,CAD_STYLE.extensionGap*scale,44,CAD_STYLE.extensionOverrun*scale,140,CAD_STYLE.dimensionText*scale,144,1,77,1,271,1,278,46,176,1,177,1,178,3,147,1.2*scale,342,dotRecord,340,dimTextStyle);
const dotRecord=handle();
const ltypes=[['CONTINUOUS',[]],['HIDDEN',[2,-1]],['CENTER',[1.25,-.25,.25,-.25]],['A-CENTR01',[1.25,-.25,.25,-.25]],['A-HIDDN01',[.25,-.125]],['A-ISONS01',[1,1]]] as [string,number[]][];
let tables=table('LTYPE',ltypes.map(([name,pattern])=>pairs(0,'LTYPE',5,handle(),100,'AcDbSymbolTableRecord',100,'AcDbLinetypeTableRecord',2,name,70,0,3,name,72,65,73,pattern.length,40,pattern.reduce((a,b)=>a+Math.abs(b),0))+pattern.map(v=>pairs(49,v,74,0)).join('')));
const layerNames=[...new Set(['0',...Object.keys(LAYERS),...Object.values(CAD_LAYERS),...Object.keys(REFERENCE_ACI),...sheetFrame(doc).map(e=>referenceLayer(e)),'CX-BORD-TEX3'])];
tables+=table('LAYER',layerNames.map(name=>{const semantic=Object.entries(CAD_LAYERS).find(([,v])=>v===name)?.[0]??name;return pairs(0,'LAYER',5,handle(),100,'AcDbSymbolTableRecord',100,'AcDbLayerTableRecord',2,name,70,0,62,REFERENCE_ACI[name]??7,6,semantic==='CENTER'?'CENTER':semantic==='HIDDEN'?'A-HIDDN01':name==='CS-CONC-MAJR'||name==='CS-DIML'||name==='CS-SYMB'?'A-ISONS01':'CONTINUOUS',370,-3);}));
const styleNames=['STANDARD','PZ_DIM','PZ_TEXT','PZ_EL','TB_TEXT','TB_WBTXT','ST1','굴림체','돋음'];
tables+=table('STYLE',styleNames.map((name,i)=>pairs(0,'STYLE',5,i===0?textStyle:i===1?dimTextStyle:handle(),100,'AcDbSymbolTableRecord',100,'AcDbTextStyleTableRecord',2,name,70,0,40,0,41,1,50,0,71,0,42,2.8,3,name==='TB_TEXT'?'BareunDotumPro1.ttf':name==='TB_WBTXT'?'BareunDotumPro3.ttf':['ST1','굴림체','돋음'].includes(name)?'Dotum.ttf':'gulim.ttc',4,'')));
tables+=table('APPID',['ACAD','PZ_GA'].map(name=>pairs(0,'APPID',5,handle(),100,'AcDbSymbolTableRecord',100,'AcDbRegAppTableRecord',2,name,70,0)));
tables+=table('DIMSTYLE',[dimstyle(1,'STANDARD'),dimstyle(50,'HS_050'),dimstyle(1,'1_1(typ1)'),...scales.map(s=>dimstyle(s,`PZ_${s}`))]);
let entities='',blocks='';
const blockRecords=[{name:'_Dot',id:dotRecord,layout:''},{name:'*Model_Space',id:modelRecord,layout:modelLayout},{name:'*Paper_Space',id:paperRecord,layout:paperLayout}];
const common=(e:Entity,owner:string,scale:number)=>pairs(5,handle(),330,owner,100,'AcDbEntity',8,referenceLayer(e),48,scale)+(e.cad?pairs(62,e.cad.color,370,e.cad.lineweight):'')+(owner===paperRecord?pairs(67,1,410,'Layout1'):'');
const translate=(p: Point,offset: Point): Point=>[p[0]+offset[0],p[1]+offset[1]];
const primitive=(e:Entity,offset:Point,owner:string,scale:number,view:string):string=>{
const base=common(e,owner,scale),data=xdata(e,view);
if(e.kind==='line'){const a=translate(e.a,offset),b=translate(e.b,offset);return pairs(0,'LINE')+base+pairs(100,'AcDbLine',10,a[0],20,a[1],30,0,11,b[0],21,b[1],31,0)+data;}
if(e.kind==='polyline')return pairs(0,'LWPOLYLINE')+base+pairs(100,'AcDbPolyline',90,e.points.length,70,e.closed?1:0)+e.points.map(p=>{const q=translate(p,offset);return pairs(10,q[0],20,q[1]);}).join('')+data;
if(e.kind==='circle'){const p=translate(e.center,offset);return pairs(0,'CIRCLE')+base+pairs(100,'AcDbCircle',10,p[0],20,p[1],30,0,40,e.radius)+data;}
if(e.kind==='hatch'){
const solid=e.pattern==='SOLID',angle=e.angle*Math.PI/180,color=e.color&&/^#[0-9a-f]{6}$/i.test(e.color)?parseInt(e.color.slice(1),16):0xcbd1d6;
const loops=[e.points,...e.holes??[]];
return pairs(0,'HATCH')+base+pairs(420,color,100,'AcDbHatch',10,0,20,0,30,0,210,0,220,0,230,1,2,e.pattern,70,solid?1:0,71,0,91,loops.length)+loops.map((loop,i)=>pairs(92,i?2:3,72,0,73,1,93,loop.length)+loop.map(p=>{const q=translate(p,offset);return pairs(10,q[0],20,q[1]);}).join('')+pairs(97,0)).join('')+pairs(75,0,76,1)+
(solid?'':pairs(52,0,41,1,77,0,78,1,53,e.angle,43,0,44,0,45,-Math.sin(angle)*e.spacing,46,Math.cos(angle)*e.spacing,79,0))+pairs(98,0)+data;
}
if(e.kind==='text'){
const p=translate(e.point,offset),align=e.align==='center'?1:e.align==='right'?2:0;
return pairs(0,'TEXT')+base+pairs(100,'AcDbText',10,p[0],20,p[1],30,0,40,e.height,1,e.text,50,e.rotation??0,7,e.cad?.textStyle&&styleNames.includes(e.cad.textStyle)?e.cad.textStyle:e.layer==='ELEVATION'?'PZ_EL':e.layer==='DIMENSION'?'PZ_DIM':'PZ_TEXT',72,align,11,p[0],21,p[1],31,0,100,'AcDbText',73,0)+data;
}
return '';
};
const startBlock=(name:string,id:string)=>pairs(0,'BLOCK',5,handle(),330,id,100,'AcDbEntity',8,'0',100,'AcDbBlockBegin',2,name,70,name.startsWith('*D')?1:0,10,0,20,0,30,0,3,name,1,'');
const endBlock=(id:string)=>pairs(0,'ENDBLK',5,handle(),330,id,100,'AcDbEntity',8,'0',100,'AcDbBlockEnd');
for(const record of blockRecords)blocks+=startBlock(record.name,record.id)+(record.name==='_Dot'?primitive({kind:'hatch',points:Array.from({length:16},(_,i)=>[.5*Math.cos(i*Math.PI/8),.5*Math.sin(i*Math.PI/8)] as Point),pattern:'SOLID',spacing:1,angle:0,color:'#ff0000',layer:'DIMENSION',source:{member:'dimension-dot',pier:doc.pier,index:0}},[0,0],record.id,1,'dimension-dot'):'')+endBlock(record.id);
const front=doc.views[0],side=doc.views[1];
const fw=front.bounds.maxX-front.bounds.minX,sw=side.bounds.maxX-side.bounds.minX;
let dimensionIndex=0;
for(const view of doc.views) {
const b=view.bounds;
const sectionWidth=doc.views.find(v=>v.id==='section')!.bounds.maxX-doc.views.find(v=>v.id==='section')!.bounds.minX;
const extraIndex=['heights','iso','keyplan'].indexOf(view.id);
const x=extraIndex>=0?(fw+sw+sectionWidth+20000)*(extraIndex+2):view.id==='side'||view.id==='footing'?fw+5000:view.id==='section'||view.id==='detail'?fw+sw+10000:view.id==='schedule'?fw+sw+sectionWidth+20000:0;
const y=['top','footing','detail','schedule'].includes(view.id)?-(b.maxY-b.minY)-5000:0;
const offset:Point=[x-b.minX,y-b.minY];
for(const e of view.entities) {
if(!visible.has(e.layer))continue;
if(e.kind!=='dimension'){entities+=primitive(e,offset,modelRecord,view.scale,view.id);continue;}
const id=handle(),name=`*D${++dimensionIndex}`;blockRecords.push({name,id,layout:''});
blocks+=startBlock(name,id)+dimensionGraphics(e).map(p=>primitive(p,offset,id,view.scale,view.id)).join('')+endBlock(id);
const a=translate(e.a,offset),b2=translate(e.b,offset);
const definition:Point=e.axis==='x'?[b2[0],e.position+offset[1]]:[e.position+offset[0],b2[1]];
const mid:Point=e.axis==='x'?[(a[0]+b2[0])/2,definition[1]+e.height]:[definition[0]-e.height,(a[1]+b2[1])/2];
entities+=pairs(0,'DIMENSION')+common(e,modelRecord,view.scale)+pairs(100,'AcDbDimension',2,name,10,definition[0],20,definition[1],30,0,11,mid[0],21,mid[1],31,0,70,32,1,e.label??'<>',3,`PZ_${view.scale}`,42,e.value,
100,'AcDbAlignedDimension',13,a[0],23,a[1],33,0,14,b2[0],24,b2[1],34,0,50,e.axis==='x'?0:90,100,'AcDbRotatedDimension')+xdata(e,view.id);
}
const source={member:'view-title',index:0,pier:doc.pier};
if(visible.has('TEXT'))entities+=primitive({kind:'text',point:[b.minX,b.maxY+4*view.scale],text:`${view.title} / model 1:1 mm`,height:3*view.scale,source,layer:'TEXT'},offset,modelRecord,view.scale,view.id);
}
// Layout1 matches the shortened general arrangement, while model space remains true-size.
const frame=sheetFrame(doc).filter(e=>visible.has(e.layer));
const border=frame.filter(e=>e.source.member==='reference-frame'||e.source.member==='reference-logo');
const fields=frame.filter((e):e is Extract<Entity,{kind:'text'}>=>e.kind==='text'&&e.source.member.startsWith('frame-field:'));
if(border.length){
const id=handle(),name='PZ_CXBLKA1';blockRecords.push({name,id,layout:''});
const attribute=(e:Extract<Entity,{kind:'text'}>,owner:string,definition:boolean)=>{
const text=primitive(e,[0,0],owner,1,'sheet');
const at=text.indexOf(pairs(100,'AcDbText',73,0));
return text.slice(0,at).replace(pairs(0,'TEXT'),pairs(0,definition?'ATTDEF':'ATTRIB'))+
pairs(100,definition?'AcDbAttributeDefinition':'AcDbAttribute',...(definition?[3,e.source.member.slice(12)]:[]),2,e.source.member.slice(12),70,0,73,0,74,0)+xdata(e,'sheet');
};
blocks+=startBlock(name,id)+border.map(e=>primitive(e,[0,0],id,1,'sheet')).join('')+fields.map(e=>attribute(e,id,true)).join('')+endBlock(id);
const insert=handle();entities+=pairs(0,'INSERT',5,insert,330,paperRecord,100,'AcDbEntity',8,'0',67,1,410,'Layout1',100,'AcDbBlockReference',66,fields.length?1:0,2,name,10,0,20,0,30,0,41,1,42,1,43,1,50,0)+pairs(1001,'PZ_GA',1000,doc.pier,1000,'reference-frame',1071,0,1000,'sheet');
if(fields.length)entities+=fields.map(e=>attribute(e,paperRecord,false)).join('')+pairs(0,'SEQEND',5,handle(),330,insert,100,'AcDbEntity',8,'0',67,1,410,'Layout1');
}
for(const e of frame)if(!border.includes(e)&&!fields.includes(e as Extract<Entity,{kind:'text'}>))entities+=primitive(e,[0,0],paperRecord,1,'sheet');
for(const view of doc.views)if(!['section','heights'].includes(view.id))for(const {entity} of viewPaperEntities(doc,view))
if(visible.has(entity.layer))entities+=primitive(entity,[0,0],paperRecord,1,view.id);
tables+=table('BLOCK_RECORD',blockRecords.map(r=>pairs(0,'BLOCK_RECORD',5,r.id,100,'AcDbSymbolTableRecord',100,'AcDbBlockTableRecord',2,r.name,70,4)+ (r.layout?pairs(340,r.layout):'')));
const dictionary=(id:string,owner:string,entries:[string,string][])=>pairs(0,'DICTIONARY',5,id,330,owner,100,'AcDbDictionary',281,1)+entries.map(([name,value])=>pairs(3,name,350,value)).join('');
const layout=(id:string,name:string,record:string,order:number)=>pairs(0,'LAYOUT',5,id,330,layoutDictionary,100,'AcDbPlotSettings',1,'',2,'',4,doc.paper.name,6,'',40,0,41,0,42,0,43,0,44,doc.paper.width,45,doc.paper.height,46,0,47,0,70,128,72,1,73,0,74,0,75,16,142,1,143,1,76,0,77,0,
100,'AcDbLayout',1,name,70,name==='Model'?1:0,71,order,10,0,20,0,11,doc.paper.width,21,doc.paper.height,12,0,22,0,32,0,14,0,24,0,34,0,15,100000,25,100000,35,0,146,0,13,0,23,0,33,0,16,1,26,0,36,0,17,0,27,1,37,0,76,0,330,record);
const objects=dictionary(root,'0',[['ACAD_GROUP',groups],['ACAD_LAYOUT',layoutDictionary]])+dictionary(groups,root,[])+dictionary(layoutDictionary,root,[['Model',modelLayout],['Layout1',paperLayout]])+layout(modelLayout,'Model',modelRecord,0)+layout(paperLayout,'Layout1',paperRecord,1);
const header=pairs(9,'$ACADVER',1,'AC1027',9,'$DWGCODEPAGE',3,'ANSI_949',9,'$INSUNITS',70,4,9,'$MEASUREMENT',70,1,9,'$DIMASSOC',280,1,9,'$LWDISPLAY',290,1,9,'$LTSCALE',40,1,9,'$HANDSEED',5,(next+20).toString(16).toUpperCase());
return section('HEADER',header)+section('TABLES',tables)+section('BLOCKS',blocks)+section('ENTITIES',entities)+section('OBJECTS',objects)+pairs(0,'EOF');
}