- 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>
80 lines
6.2 KiB
Python
80 lines
6.2 KiB
Python
"""Publish explicitly authorized drawing-N distributions after rigid checks."""
|
|
import argparse,copy,json,math,os,subprocess,sys,tempfile,time
|
|
from pathlib import Path
|
|
root=Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0,str(root/'src/backend' if (root/'src/backend').is_dir() else root))
|
|
|
|
|
|
def run(job_id,node,apply=False):
|
|
import server
|
|
from cad_rebar_recipe import load_sources
|
|
from cad_sheet_regions import source_regions
|
|
from local_fabrication_shape import build_fabrication_local
|
|
from quantity_body_ties import distribute,target_segments
|
|
from body_tie_engagement import actual_bar_contacts
|
|
from body_tie_geometry import source_body_tie_domain
|
|
server.require_modeling_rules()
|
|
if len(job_id)!=24 or any(c not in '0123456789abcdef' for c in job_id):raise ValueError('invalid job')
|
|
path=Path('/tmp/drawing-model-studio-8097-jobs')/(job_id+'.json');raw=path.read_bytes();job=json.loads(raw);plan=job['result']
|
|
if job.get('status')=='running':raise ValueError('active job')
|
|
sources=load_sources(job['sourceContext']['analysis']);inventory=plan['drawingEvidence']['cad_fabrication_inventory']['rows']
|
|
kernel=root/'model_kernel.js'
|
|
if not kernel.is_file():kernel=root/'src/frontend/src/model_kernel.js'
|
|
replacements={};reports=[]
|
|
for old in plan['rebar_groups']:
|
|
if not old.get('source_body_tie_binding') or old['fabrication_measurement'].get('count_pass'):continue
|
|
report={'id':old['id'],'before':len(old['instance_paths_m']),'applied':False};reports.append(report)
|
|
try:
|
|
block=int(old['rebar_block'].replace('BLOCK',''))
|
|
rows=[r for r in inventory if r.get('blocks')==[block] and r['material']['mark']==old['mark'] and r.get('fabrication_local')]
|
|
if len(rows)!=1:raise ValueError('own source fabrication row not unique')
|
|
row=rows[0];source=sources[row['source_sha256']]
|
|
if row.get('source_region_id'):
|
|
source=next(r for r in source_regions(source) if r.region_id==row['source_region_id'])
|
|
detail=build_fabrication_local(source,row);expected=[math.dist(a,b) for a,b in zip(detail['points_m'],detail['points_m'][1:])]
|
|
count=row['material']['count'];report['drawing']=count
|
|
new=distribute(plan,old,count,authorized=True)
|
|
for p in new['instance_paths_m']:
|
|
lengths=[math.dist(a,b)for a,b in zip(p,p[1:])]
|
|
if len(lengths)!=len(expected) or min(max(abs(a-b)for a,b in zip(lengths,order))for order in (expected,expected[::-1]))>.001:
|
|
raise ValueError('fabrication leg length changed')
|
|
script="const fs=require('fs');require(process.argv[1]);const g=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(JSON.stringify(g.instance_paths_m.map((p,i)=>ModelKernel.bendPath(p,g,i))));"
|
|
bends=json.loads(subprocess.run([node,'-e',script,str(kernel)],input=json.dumps(new),capture_output=True,text=True,check=True).stdout)
|
|
domain=source_body_tie_domain(plan,old['host_component_ids'][0])
|
|
outside,first=server.tube_path_outside_component_union(new['instance_paths_m'],new['diameter_mm'],domain,sample_step_m=.025)
|
|
rounded_outside,rounded_first=server.tube_path_outside_component_union([b['path']for b in bends],new['diameter_mm'],domain,sample_step_m=.025)
|
|
segments=target_segments(plan,new);rounded_contact=[]
|
|
for b,record in zip(bends,new['quantity_distribution']['records']):
|
|
witnesses=record['contact']['contacts']
|
|
rounded_contact.append(actual_bar_contacts(b['path'],segments,witnesses,new['diameter_mm']))
|
|
report.update(after=new['count'],outside=outside,rounded_outside=rounded_outside,
|
|
rounded_contact_pass=sum(c['pass']for c in rounded_contact),warnings=sum(bool(b['warnings'])for b in bends))
|
|
if outside or rounded_outside or report['warnings'] or report['rounded_contact_pass']!=count:raise ValueError('geometry/contact validation failed')
|
|
# Raw and rounded contact are independently retained; no approval is implied.
|
|
new['quantity_distribution']['rounded_contact']=rounded_contact
|
|
new['placement_geometry_check']={'pass':True,'tube_outside_samples':0,'rounded_tube_outside_samples':0,'scope':'own source concrete union; straight and viewer-rounded paths'}
|
|
new['source_review_authorization']={'explicit_request':True,'scope':'drawing_quantity_distribution','export_allowed':False}
|
|
new['placement_status']='quantity_distributed_feedback_pending_engineering_review'
|
|
new['section_projection_check']={'pass':False,'reason':'user-authorized quantity distribution is not a literal drawing-station reconstruction'}
|
|
replacements[old['id']]=new;report['applied']=apply
|
|
except (ValueError,KeyError,StopIteration)as error:report['error']=str(error)
|
|
print(json.dumps(report,ensure_ascii=False),flush=True)
|
|
if apply and replacements:
|
|
result=copy.deepcopy(plan);result['rebar_groups']=[replacements.get(g.get('id'),g)for g in result['rebar_groups']]
|
|
preview=result.get('drawingEvidence',{}).get('preview_geometry')
|
|
if isinstance(preview,dict):preview['rebar_groups']=copy.deepcopy(result['rebar_groups'])
|
|
result['bodyTieQuantityDistribution']={'explicit_user_authorization':True,'reports':reports,'full_model_approved':False}
|
|
result['geometryRevision']='quantity-distribution-'+str(time.time_ns())
|
|
job.update(result=result,partialResult=copy.deepcopy(result),geometryRevision=time.time_ns())
|
|
if path.read_bytes()!=raw:raise ValueError('job changed during validation')
|
|
path.with_name(path.stem+'.before-tie-quantity-'+str(time.time_ns())+'.json').write_bytes(raw)
|
|
with tempfile.NamedTemporaryFile(mode='w',dir=path.parent,delete=False,encoding='utf8')as out:
|
|
json.dump(job,out,ensure_ascii=False,allow_nan=False);temp=out.name
|
|
os.replace(temp,path)
|
|
return reports
|
|
|
|
|
|
if __name__=='__main__':
|
|
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('job');parser.add_argument('--node',required=True);parser.add_argument('--apply',action='store_true')
|
|
args=parser.parse_args();run(args.job,args.node,args.apply)
|