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,93 @@
|
||||
"""Publish a fresh-source body tie candidate only if its change is rigid pose."""
|
||||
import argparse
|
||||
import copy
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import time
|
||||
import sys
|
||||
root=Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0,str(root/'src/backend'if (root/'src/backend').is_dir()else root))
|
||||
from server import component_surface_z_at_xy
|
||||
from mesh_surface_aliases import prism_surface_view
|
||||
|
||||
|
||||
def merge_pose(current, candidate):
|
||||
result=copy.deepcopy(current)
|
||||
old={g['id']:g for g in current['rebar_groups']if g.get('source_body_tie_binding')}
|
||||
new={g['id']:g for g in candidate['rebar_groups']if g.get('source_body_tie_binding')}
|
||||
if old.keys()!=new.keys():raise ValueError('body tie occurrences changed')
|
||||
if current['concrete_components']!=candidate['concrete_components']:raise ValueError('concrete changed')
|
||||
changed=[];removed=0
|
||||
hosts={c['id']:prism_surface_view(c)for c in current['concrete_components']}
|
||||
station_key=lambda s:json.dumps([s['station_m'],s['domain'],s['section_projection']],sort_keys=True)
|
||||
for key,g in old.items():
|
||||
n=new[key]
|
||||
for field in ['diameter_mm','unit_length_m','host_component_ids']:
|
||||
if g[field]!=n[field]:raise ValueError('not a pose-only change: '+field)
|
||||
for field in ['section','host_role']:
|
||||
if g['source_body_tie_binding'][field]!=n['source_body_tie_binding'][field]:
|
||||
raise ValueError('source station binding changed: '+field)
|
||||
binding=n['source_body_tie_binding']
|
||||
if not binding['pose_rule'].startswith('body-source-wrap-v5:'):raise ValueError('unexpected pose rule')
|
||||
previous={station_key(s):(p,i)for i,(s,p)in enumerate(zip(g['source_body_tie_binding']['stations'],g['instance_paths_m']))}
|
||||
following={station_key(s)for s in binding['stations']}
|
||||
excluded={station_key(s)for s in binding.get('excluded_unpaired_candidates',[])
|
||||
if s['cycle_pair'].get('outside_pair_domain')and not s['cycle_pair']['pass']}
|
||||
if following-set(previous)or set(previous)-following-excluded:raise ValueError('unexplained station addition/removal')
|
||||
if n['count']!=len(n['instance_paths_m'])or len(binding['stations'])!=n['count']:raise ValueError('count metadata')
|
||||
if len(binding['engagement'])!=n['count']:raise ValueError('missing per-bar audit')
|
||||
if n['occurrence_instance_ids']!=[n['host_component_ids'][0]]*n['count']:raise ValueError('occurrence mismatch')
|
||||
removed+=len(previous)-len(following)
|
||||
for s,q,e in zip(binding['stations'],n['instance_paths_m'],binding['engagement']):
|
||||
p,old_index=previous[station_key(s)]
|
||||
if len(p)!=len(q):raise ValueError('detail topology changed')
|
||||
for i in range(len(p)):
|
||||
for j in range(i):
|
||||
if abs(math.dist(p[i],p[j])-math.dist(q[i],q[j]))>1e-8:
|
||||
raise ValueError('non-rigid fabrication change')
|
||||
centre=lambda path:[(min(v[i]for v in path)+max(v[i]for v in path))/2 for i in range(3)]
|
||||
old_c=centre(p);new_c=centre(q)
|
||||
if math.dist(old_c,new_c)>1e-7:
|
||||
pair=e['cycle_pair']
|
||||
if binding['axis_proof']['axis']!='return_legs':raise ValueError('open-U moved')
|
||||
anchor=pair.get('centre_station_m',s['station_m'])
|
||||
if abs(new_c[0]-s['portal_slope']*new_c[1]-anchor)>1e-7 or abs(new_c[1]-old_c[1])>1e-7:
|
||||
raise ValueError('not the authored pair centre')
|
||||
host=hosts[n['host_component_ids'][0]]
|
||||
old_z=component_surface_z_at_xy(host,'top_z_m',*old_c[:2]);new_z=component_surface_z_at_xy(host,'top_z_m',*new_c[:2])
|
||||
old_e=g['source_body_tie_binding'].get('engagement',[])
|
||||
shift_keys=('diameter_face_centre_shift_m','source_model_centre_shift_m')
|
||||
old_shift=sum(old_e[old_index]['cycle_pair'].get(k,0)for k in shift_keys)if old_e else 0
|
||||
shift=sum(pair.get(k,0)for k in shift_keys)-old_shift
|
||||
if old_z is None or new_z is None or abs(new_c[2]-old_c[2]-(new_z-old_z)-shift)>1e-6:
|
||||
raise ValueError('unexplained height shift')
|
||||
if g['instance_paths_m']!=n['instance_paths_m']:changed.append(key)
|
||||
result['rebar_groups']=[copy.deepcopy(new[g['id']])if g['id']in new else g for g in result['rebar_groups']]
|
||||
result['drawingEvidence'].setdefault('preview_geometry',{})['rebar_groups']=copy.deepcopy(result['rebar_groups'])
|
||||
result['drawingEvidence']['body_tie_pose_audit']={'rule':'body-source-wrap-v5','changed_groups':changed,
|
||||
'concrete_and_other_rebar_preserved':True,'detail_lengths_preserved':True,
|
||||
'excluded_unpaired_end_candidates':removed,'centres_bound_to_authored_cycle_pairs':True,
|
||||
'engineering_approval':False}
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
p=argparse.ArgumentParser(description=__doc__);p.add_argument('job',type=Path);p.add_argument('candidate',type=Path)
|
||||
args=p.parse_args();raw=args.job.read_bytes();job=json.loads(raw)
|
||||
if job.get('status')not in ('completed','awaiting_feedback','failed'):raise ValueError('active job')
|
||||
result=merge_pose(job['result'],json.loads(args.candidate.read_text())['result'])
|
||||
if args.job.read_bytes()!=raw:raise ValueError('concurrent job update')
|
||||
backup=args.job.with_name(args.job.stem+f'.before-body-face-return-{time.time_ns()}.json')
|
||||
with backup.open('xb')as stream:stream.write(raw)
|
||||
job.update(result=result,partialResult=copy.deepcopy(result),geometryRevision=time.time_ns())
|
||||
with tempfile.NamedTemporaryFile(mode='w',dir=args.job.resolve().parent,delete=False)as stream:
|
||||
json.dump(job,stream,ensure_ascii=False,allow_nan=False);temporary=stream.name
|
||||
if args.job.read_bytes()!=raw:raise ValueError('concurrent job update before publish')
|
||||
os.replace(temporary,args.job.resolve())
|
||||
print(json.dumps({'backup':str(backup),'audit':result['drawingEvidence']['body_tie_pose_audit']},ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__=='__main__':main()
|
||||
Reference in new issue
Block a user