Files
gwangteo-underpass-block8/scripts/analyze_dxf.py
T

97 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json,pathlib,collections,math
ROOT=pathlib.Path(__file__).resolve().parents[1]
raw=(ROOT/'public/drawings/광터지하차도_일반도(BLOCK7).dxf').read_text(encoding='utf8',errors='replace').splitlines()
pairs=[(int(raw[i].strip()),raw[i+1].strip()) for i in range(0,len(raw)-1,2)]
entities=[]; current=None; section=''; block=None; blocks={}
for code,value in pairs:
if code==0:
if current:
if section=='ENTITIES': entities.append(current)
elif section=='BLOCKS' and block and current['type'] not in ('BLOCK','ENDBLK'): blocks.setdefault(block,[]).append(current)
current={'type':value,'data':[]}
if value=='ENDBLK': block=None
elif current:
current['data'].append((code,value))
if current['type']=='SECTION' and code==2: section=value
if current['type']=='BLOCK' and code==2: block=value
def get(e,c,d=None): return next((v for k,v in e['data'] if k==c),d)
def point(e,c=10): return [float(get(e,c,0)),float(get(e,c+10,0))]
def layer(e): return get(e,8,'')
out=ROOT/'docs/extracted';out.mkdir(exist_ok=True)
texts=[{'text':get(e,1,''),'p':point(e),'layer':layer(e)} for e in entities if e['type'] in ('TEXT','MTEXT')]
lines=[{'a':point(e),'b':point(e,11),'layer':layer(e),'handle':get(e,5)} for e in entities if e['type']=='LINE']
for name,data in [('texts',texts),('lines',lines),('blocks',blocks),('inserts',[{'name':get(e,2),'p':point(e),'sx':get(e,41,'1'),'sy':get(e,42,'1'),'rotation':get(e,50,'0'),'layer':layer(e)} for e in entities if e['type']=='INSERT'])]:
(out/(name+'.json')).write_text(json.dumps(data,ensure_ascii=False,indent=2),encoding='utf8')
print('Entities',collections.Counter(e['type'] for e in entities))
for t in texts:
if any(s in t['text'] for s in ('BLOCK7','106.675','107.307','101.636','101.069','102.436','101.869','299.010','318.990','선형','측구','20.100')): print(t)
for name,top in [('A',-74271),('B',-91211)]:
selected=[l for l in lines if l['layer'] in ('CS-CONC-MAJR','CS-CONC-MINR') and all(1118000<p[0]<1139000 and top-7500<p[1]<top+100 for p in (l['a'],l['b']))]
print(name,len(selected),collections.Counter(l['layer'] for l in selected))
(out/f'section-{name}.json').write_text(json.dumps(selected,ensure_ascii=False,indent=2),encoding='utf8')
# 도면 근거를 보존하는 모델 데이터와 원도면 검토 SVG 생성
sections=[]
for name,top,el,sta in [('A',-74271.37855593952,106.675,299.000),('B',-91211.37855593952,107.307,319.000)]:
selected=[l for l in lines if l['layer'] in ('CS-CONC-MAJR','CS-CONC-MINR') and all(1118000<p[0]<1139000 and top-7500<p[1]<top+100 for p in (l['a'],l['b']))]
sections.append({'name':name,'topCad':top,'topEL':el,'station':sta,'lines':selected})
def group(i):
if i<5 or i==100: return '底版'
if 5<=i<=15: return '좌측 측구·포장 경계'
if 16<=i<=26: return '우측 측구·포장 경계'
if 27<=i<=86: return '포장 반복선·내부선'
if 87<=i<=89: return '중앙 분리부'
if 90<=i<=94: return '좌측 벽체'
if 95<=i<=99: return '우측 벽체'
if 101<=i<=107: return '하부·외측 보조 윤곽'
return '바닥판 상면·세부선'
def norm(p,s): return [round((p[0]-1128486.218066491)/1000,7),round(s['topEL']+(p[1]-s['topCad'])/1000,7)]
parts=[]
for i,(a,b) in enumerate(zip(sections[0]['lines'],sections[1]['lines'])):
assert a['layer']==b['layer']
assert all(abs(a[k][0]-b[k][0])<0.01 for k in ['a','b'])
parts.append({'id':'line-'+a['handle'],'name':group(i).replace('底版','바닥판 하단')+' · '+str(i+1),'group':group(i).replace('底版','바닥판 하단'),'layer':a['layer'],'handles':[a['handle'],b['handle']],'a':[norm(a['a'],sections[0]),norm(a['b'],sections[0])],'b':[norm(b['a'],sections[1]),norm(b['b'],sections[1])],'repeat':27<=i<=86,'kind':'line','source':'일반도 14 / 단면 A-A·B-B / '+a['layer']})
# 삽입 블록의 반전과 실제 LINE/ARC 윤곽을 보존한다. 원호는 5도 간격으로 표시한다.
ductName='A$C2017021411375356'
for side,x,sx in [('좌측',1119736.218066468,1),('우측',1137236.218066513,-1)]:
for j,e in enumerate(blocks[ductName]):
if e['type']=='LINE': pts=[point(e),point(e,11)]
elif e['type']=='ARC':
center=point(e); r=float(get(e,40)); start=float(get(e,50)); end=float(get(e,51))
if end<=start:end+=360
pts=[[center[0]+r*math.cos(math.radians(t)),center[1]+r*math.sin(math.radians(t))] for t in [start+(end-start)*k/36 for k in range(37)]]
else: continue
profiles=[]
for s,y in zip(sections,[-77447.89255584464,-95669.3155558447]):profiles.append([norm([x+sx*p[0],y+p[1]],s) for p in pts])
parts.append({'id':f'duct-{side}-{j}','name':f'{side} 공동구·상부 상세 · {j+1}','group':f'{side} 공동구·상부 상세','layer':layer(e),'handles':[get(e,5)],'a':profiles[0],'b':profiles[1],'repeat':False,'kind':e['type'].lower(),'source':'일반도 14 / '+ductName+' / 원본 삽입 블록'})
model={'source':'광터지하차도_일반도(BLOCK7).dxf','units':'m','length':19.98,'sectionStations':[299,319],'longitudinalStations':[299.01,318.99],'sectionTops':[106.675,107.307],'sectionBottoms':[101.636,101.069],'centerEL':[None,None],'alignment':None,'status':'확인 필요','connection':{'mode':'straight','status':'확인 필요','authorization':'2026-10-20 사용자 검토용 직선 연결 허용','description':'종단 경계 299.010~318.990에 원본 단면 299.000/319.000을 대응시킨 검토용 연결. 실제 평면 위치와 중간 형상은 미검증.'},'parts':parts}
modelDir=ROOT/'src/model';modelDir.mkdir(exist_ok=True)
(modelDir/'block7.json').write_text(json.dumps(model,ensure_ascii=False,indent=2),encoding='utf8')
import html
previewDir=ROOT/'public/analysis';previewDir.mkdir(exist_ok=True)
views=[('단면A',1113000,-84300,1142000,-69500),('단면B',1113000,-102300,1142000,-85500),('평면검토',1020000,-96000,1105000,-64000),('종단검토',1040000,-113000,1085000,-95000),('측구상세',1165000,-85000,1185000,-77000)]
for title,x0,y0,x1,y1 in views:
content=[]
for l in lines:
if all(x0<p[0]<x1 and y0<p[1]<y1 for p in (l['a'],l['b'])):
color='#326c5d' if l['layer'] in ('CS-CONC-MAJR','CS-CONC-MINR') else '#8d969e'
content.append(f'<line x1="{l["a"][0]}" y1="{-l["a"][1]}" x2="{l["b"][0]}" y2="{-l["b"][1]}" stroke="{color}" stroke-width="15"><title>{l["handle"]} / {l["layer"]}</title></line>')
for t in texts:
x,y=t['p']
if x0<x<x1 and y0<y<y1:content.append(f'<text x="{x}" y="{-y}" font-size="200" fill="#28334b">{html.escape(t["text"])}</text>')
# 공동구 삽입 블록의 실제 선 포함
for e in entities:
if e['type']=='INSERT' and get(e,2)==ductName:
x,y=point(e);sx=float(get(e,41,1));sy=float(get(e,42,1))
if x0<x<x1 and y0<y<y1:
for q in blocks[ductName]:
if q['type']=='LINE':
a,b=point(q),point(q,11);content.append(f'<line x1="{x+sx*a[0]}" y1="{-y-sy*a[1]}" x2="{x+sx*b[0]}" y2="{-y-sy*b[1]}" stroke="#a97635" stroke-width="15"/>')
svg=f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{x0} {-y1} {x1-x0} {y1-y0}"><rect x="{x0}" y="{-y1}" width="{x1-x0}" height="{y1-y0}" fill="#fff"/>'+''.join(content)+'</svg>'
(previewDir/(title+'.svg')).write_text(svg,encoding='utf8')
print('모델 추출:',len(parts),'객체. 단면 원본 선 114쌍, 공동구 20쌍 × 좌우.')