Files
Pier_v1/RAG/design-model-box-sample_2/src/backend/cad_dowel_detail.py
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환
- Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용
- pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관)
- worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md)
- D:\Rhino-MCP 경로 참조를 새 위치로 치환

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:20:18 +09:00

141 lines
8.3 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.
"""Current-drawing dowel detail inventory, separate from body D-mark bars.
This module imports source evidence; it never infers a helix, an installation
phase, a bearing gap, or a host transform from a familiar mark or note.
"""
import copy
import math
import re
from cad_accessory_detail import detail_role, installed_detail_candidates
from cad_standard_section import _text_anchor
from local_assembly_discovery import selector
def parse_note(text):
text=re.sub(r'\s+','',text).upper()
bar=re.fullmatch(r'(?:H|S|D|Ø|Φ|⌀)(\d+(?:\.\d+)?)@(\d+(?:\.\d+)?)(?:MM)?',text)
if bar and min(float(v)for v in bar.groups())>0:
return {'kind':'dowel_bar_pitch','diameter_mm':float(bar[1]),'pitch_mm':float(bar[2])}
pipe=re.fullmatch(r'(?:Ø|Φ|⌀)(\d+(?:\.\d+)?)[X×](\d+(?:\.\d+)?)(?:MM)?',text)
if pipe and min(float(v)for v in pipe.groups())>0:
return {'kind':'pipe_dimensions','diameter_mm':float(pipe[1]),'length_mm':float(pipe[2])}
thick=re.fullmatch(r'T=(\d+(?:\.\d+)?)MM',text)
if thick and float(thick[1])>0:return {'kind':'layer_thickness','thickness_mm':float(thick[1])}
layers=re.fullmatch(r'(\d+)-?LAYERS?',text)
if layers and int(layers[1])>0:return {'kind':'layer_count','layers':int(layers[1])}
return None
def outlined_straight_bar(source, row, note, interfaces):
"""A drawn cylinder outline is not a bent four-leg bar.
Resolve its centreline only when its width is the annotated diameter,
its length is the original fabrication L, and the *dowel note leader*
reaches that actual closed outline. A nearby pipe cannot qualify.
"""
from local_assembly_discovery import leader_targets
shape=row.get('fabrication_local')or{}
if len(shape.get('points_m',[]))!=2:return []
material=row['material'];length=shape['length_m'];diameter=material['diameter_mm']/1000
if abs(note['diameter_mm']-material['diameter_mm'])>1e-6:return []
labels=[source.entity(r)for r in note['label_candidates']if re.search('다[웰월]바|DOWEL',r['text'],re.I)]
if len(labels)!=1:return []
label=labels[0];x,y=_text_anchor(label);h=label.get('text_height')or 0
lines=[e for e in source.entities if e.get('type')=='LINE' and len(e.get('points',[]))==2]
underlines=[e for e in lines if any(tag in e.get('layer','').upper()for tag in ('LEAL','DIM'))
and abs(e['points'][0][1]-e['points'][1][1])<1e-6
and 0<=y-e['points'][0][1]<=h
and min(p[0]for p in e['points'])-h<=x<=max(p[0]for p in e['points'])]
if len(underlines)!=1:return []
try:targets,leaders=leader_targets(source,label,1.,underline=underlines[0])
except ValueError:return []
bars=[e for e in lines if 'RBAR'in e.get('layer','').upper()]
key=lambda p:tuple(round(v,6)for v in p)
lookup={tuple(sorted((key(e['points'][0]),key(e['points'][1])))):e for e in bars}
results=[]
for interface in interfaces:
scale=interface['cad_to_m']
for target in targets:
a,b=target['points']
for axis in (0,1):
if abs(a[axis]-b[axis])>1e-6 or abs(math.dist(a,b)*scale-diameter)>.001:continue
# The leader reaches an end cap. Both side legs and the
# opposite cap must exist in the original source graph.
for sign in (-1,1):
c=a[:];d=b[:];c[axis]+=sign*length/scale;d[axis]+=sign*length/scale
edges=[]
for p,q in ((a,c),(c,d),(d,b)):
found=lookup.get(tuple(sorted((key(p),key(q)))))
if found is None:break
edges.append(found)
if len(edges)!=3:continue
centres=[[(a[k]+b[k])/2 for k in (0,1)],[(c[k]+d[k])/2 for k in (0,1)]]
results.append({'source_sha256':source.sha256,'source_region_id':getattr(source,'region_id',None),
'mark':material['mark'],'diameter_mm':material['diameter_mm'],'length_m':length,
'pitch_mm':note['pitch_mm'],'centreline_points_cad':centres,'cad_to_m':scale,
'outline_entities':[selector(e)for e in [target,*edges]],
'note':copy.deepcopy(note),'note_leaders':leaders,'note_underline':selector(underlines[0]),
'interface':copy.deepcopy(interface),'placement_authorized':False,'host_binding_complete':False})
unique={tuple(sorted(tuple(round(v,6)for v in p)for p in r['centreline_points_cad'])):r for r in results}
return list(unique.values())
def detail_inventory(sources, rows):
from cad_sheet_regions import all_regions
from source_rebar_catalog import sheet_block_witnesses
result=[]
for source in all_regions(sources):
titles=[e for e in source.entities if e.get('points') and detail_role(e.get('text',''))=='dowel']
if not titles:continue
all_titles=[e for e in source.entities if e.get('points') and
('상세'in e.get('text','') or 'DETAIL'in e.get('text','').upper())]
installed=installed_detail_candidates(source,roles=('dowel',))
from cad_approach_detail import approach_interfaces
interfaces=approach_interfaces(source.entities)
blocks=sorted({v['block']for v in sheet_block_witnesses(source.entities)})
for title in titles:
tx,ty=_text_anchor(title);height=title.get('text_height')or 0
if height<=0:continue
near=[]
for e in source.entities:
if not e.get('text') or not e.get('points') or e.get('type')=='ATTDEF':continue
x,y=_text_anchor(e)
if not(abs(x-tx)<24*height and 0<ty-y<32*height):continue
owners=[t for t in all_titles if 0<_text_anchor(t)[1]-y<32*(t.get('text_height')or 0)]
if owners and min(owners,key=lambda t:math.dist(_text_anchor(t),(x,y))) is not title:continue
near.append(e)
notes=[]
for e in near:
parsed=parse_note(e['text'])
if not parsed:continue
x,y=_text_anchor(e);h=e.get('text_height')or 0
owners=[v for v in near if v is not e and
re.search(r'다[웰월]바|DOWEL|ST\s*PIPE|스티로폼|TAR\s*PAPER',v.get('text',''),re.I)
and 0<_text_anchor(v)[1]-y<=2*h and abs(_text_anchor(v)[0]-x)<=2*h]
notes.append({**parsed,'source':{**selector(e),'text':e['text']},
'label_candidates':[{**selector(v),'text':v['text']}for v in owners],
'placement_authorized':False})
bars=[]
for item in installed:
if item['title']!=selector(title):continue
# Exact sheet ownership matters: body D1/D2/D3 and accessory
# D1/D2/D3 can have the same marks and even the same diameter.
matches=[r for r in rows if r['source_sha256']==source.sha256 and
r.get('source_region_id')==getattr(source,'region_id',None) and
r['material']['mark'].strip().upper()==item['mark'].strip().upper()]
straight=[]
if len(matches)==1:
for note in notes:
if note['kind']=='dowel_bar_pitch':straight.extend(outlined_straight_bar(source,matches[0],note,interfaces))
bars.append({'mark':item['mark'],'installation':copy.deepcopy(item),
'fabrication_rows':copy.deepcopy(matches),
'straight_outline_bindings':straight,
'row_binding_unique':len(matches)==1,'host_binding_complete':False,
'pending':'full detail/pipe/bearing relationship and physical station binding required'})
result.append({'source_sha256':source.sha256,'source_region_id':getattr(source,'region_id',None),
'title':{**selector(title),'text':title['text']},'blocks':blocks,'notes':notes,'reinforcement':bars,
'installation_bound':False,'placement_authorized':False,
'pending':'do not treat a pipe outline or break symbol as the straight dowel centreline'})
return {'details':result,'source_sha256s':sorted(sources),'external_calls':0,
'placement_authorized':False,'body_mark_binding_unchanged':True}