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>
This commit is contained in:
commit
964a841de1
3978 files changed
+58013356
No files matched your search
@@ -0,0 +1,115 @@
|
||||
"""Read-only audit: fresh CAD fabrication, installed legs, and viewer bends."""
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
BACKEND=ROOT/'src/backend' if (ROOT/'src/backend').is_dir() else ROOT
|
||||
sys.path.insert(0,str(BACKEND))
|
||||
|
||||
|
||||
def lengths(path):
|
||||
return [math.dist(a,b) for a,b in zip(path,path[1:])]
|
||||
|
||||
|
||||
def audit(job_id,node):
|
||||
import server
|
||||
from cad_rebar_recipe import load_sources
|
||||
from cad_sheet_regions import source_regions
|
||||
from adaptive_block1_rebar import _frames
|
||||
from local_fabrication_shape import build_fabrication_local
|
||||
server.require_modeling_rules()
|
||||
if len(job_id)!=24 or any(c not in '0123456789abcdef' for c in job_id):
|
||||
raise ValueError('invalid job identity')
|
||||
path=Path('/tmp/drawing-model-studio-8097-jobs')/(job_id+'.json')
|
||||
original=path.read_bytes();job=json.loads(original);plan=job['result']
|
||||
sources=load_sources(job['sourceContext']['analysis'])
|
||||
inventory=plan['drawingEvidence']['cad_fabrication_inventory']['rows']
|
||||
frames=_frames(plan)
|
||||
groups=[g for g in plan['rebar_groups'] if g.get('source_body_tie_binding') or
|
||||
any(r.get('fabrication_pose') for r in g.get('cad_cycle_assembly',{}).get('profile_records',[]))]
|
||||
kernel=ROOT/'model_kernel.js'
|
||||
if not kernel.is_file():kernel=ROOT/'src/frontend/src/model_kernel.js'
|
||||
script="""const fs=require('fs');require(process.argv[1]);
|
||||
const groups=JSON.parse(fs.readFileSync(0,'utf8'));
|
||||
process.stdout.write(JSON.stringify(groups.map(g=>g.instance_paths_m.map((p,i)=>{
|
||||
const b=ModelKernel.bendPath(p,g,i);
|
||||
return {source_m:b.sourceLengthM,rounded_m:b.lengthM,warnings:b.warnings};
|
||||
}))));"""
|
||||
rounded=json.loads(subprocess.run([node,'-e',script,str(kernel)],input=json.dumps(groups),
|
||||
text=True,capture_output=True,check=True).stdout)
|
||||
rows=[]
|
||||
for g,bends in zip(groups,rounded):
|
||||
record={'id':g['id'],'mark':g['mark'],
|
||||
'role':'body_tie' if g.get('source_body_tie_binding') else 'connection',
|
||||
'model_count':len(g['instance_paths_m']),'source_verified':False}
|
||||
rows.append(record)
|
||||
owners=set(g.get('host_component_ids',[]))
|
||||
owned=[f for f in frames if owners.intersection(f[r]['id'] for r in ('top','bottom','left','right'))]
|
||||
if len(owned)!=1:
|
||||
record['error']='not uniquely owned by one physical block';continue
|
||||
block=owned[0]['block'];record['block']=block
|
||||
# Mark is only a same-source, same-block fabrication-table join.
|
||||
candidates=[r for r in inventory if r.get('blocks')==[block] and
|
||||
r['material']['mark']==g['mark'] and r.get('fabrication_local')]
|
||||
if len(candidates)!=1:
|
||||
record['error']='no unique own-block fabrication row';continue
|
||||
row=candidates[0]
|
||||
try:
|
||||
source=sources[row['source_sha256']]
|
||||
if row.get('source_region_id'):
|
||||
regions=[s for s in source_regions(source) if s.region_id==row['source_region_id']]
|
||||
if len(regions)!=1:raise ValueError('own fabrication sheet region is not unique')
|
||||
source=regions[0]
|
||||
fabrication=build_fabrication_local(source,row)
|
||||
expected=lengths(fabrication['points_m']);declared=row['detail_label_length_m']
|
||||
deviations=[]
|
||||
for p in g['instance_paths_m']:
|
||||
actual=lengths(p)
|
||||
if len(actual)!=len(expected):raise ValueError('installed topology differs from fabrication')
|
||||
deviations.append(min(max(abs(a-b) for a,b in zip(actual,order))
|
||||
for order in (expected,list(reversed(expected)))))
|
||||
raw=[sum(lengths(p)) for p in g['instance_paths_m']]
|
||||
if not raw:raise ValueError('no installed paths')
|
||||
record.update(source_verified=True,source_sha256=source.sha256,
|
||||
source_length_text=source.entity(row['detail']['source_entities']['unit_length'])['text'],
|
||||
drawing_length_m=declared,drawing_leg_lengths_mm=[v*1000 for v in expected],
|
||||
drawing_count=row['material']['count'],
|
||||
count_pass=len(raw)==row['material']['count'],
|
||||
source_leg_sum_pass=abs(sum(expected)-declared)<=.001000001,
|
||||
installed_length_pass=max(abs(v-declared) for v in raw)<=.001000001,
|
||||
installed_leg_lengths_pass=max(deviations)<=.001000001,
|
||||
max_installed_leg_error_mm=max(deviations)*1000,
|
||||
raw_length_range_m=[min(raw),max(raw)],raw_total_m=sum(raw),
|
||||
rounded_length_range_m=[min(b['rounded_m'] for b in bends),max(b['rounded_m'] for b in bends)],
|
||||
rounded_total_m=sum(b['rounded_m'] for b in bends),
|
||||
bend_deduction_range_mm=[min((b['source_m']-b['rounded_m'])*1000 for b in bends),
|
||||
max((b['source_m']-b['rounded_m'])*1000 for b in bends)],
|
||||
bend_warnings=[dict(instance=i,warnings=b['warnings']) for i,b in enumerate(bends) if b['warnings']])
|
||||
except (KeyError,ValueError) as error:
|
||||
record['error']=str(error)
|
||||
if path.read_bytes()!=original:raise ValueError('job changed during audit; discard stale result')
|
||||
return {'job':job_id,'model_modified':False,'full_model_approved':False,
|
||||
'length_basis':'raw dimensioned corner path versus viewer arc centreline; distinct quantities',
|
||||
'rows':rows}
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('jobs',nargs='+');parser.add_argument('--node',default='node')
|
||||
parser.add_argument('--output',type=Path)
|
||||
args=parser.parse_args();results=[]
|
||||
for job in args.jobs:
|
||||
result=audit(job,args.node);results.append(result)
|
||||
print(json.dumps({'job':job,'groups':len(result['rows']),
|
||||
'source_unresolved':[r['id'] for r in result['rows'] if not r['source_verified']],
|
||||
'length_failures':[r['id'] for r in result['rows'] if r['source_verified'] and not all(
|
||||
r[k] for k in ('source_leg_sum_pass','installed_length_pass','installed_leg_lengths_pass'))],
|
||||
'count_mismatch_groups':sum(r.get('count_pass') is False for r in result['rows'])}),flush=True)
|
||||
if args.output:
|
||||
with args.output.open('x',encoding='utf8') as stream:
|
||||
json.dump(results,stream,ensure_ascii=False,indent=2,allow_nan=False)
|
||||
else:print(json.dumps(results,ensure_ascii=False,indent=2))
|
||||
Reference in new issue
Block a user