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,168 @@
|
||||
"""Re-read CAD wall station chains, replacing only verified inner-wall rows."""
|
||||
import argparse
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(root/'src/backend' if (root/'src/backend').is_dir() else root))
|
||||
|
||||
|
||||
def validate_candidate(group, outside, *, allow_drawing_quantity_review=False):
|
||||
paths = group['instance_paths_m']
|
||||
keys = {tuple(tuple(round(v, 8) for v in p) for p in bar) for bar in paths}
|
||||
if (outside or len(keys) != len(paths) or len(paths) != group['count']
|
||||
or not group['fabrication_measurement']['fixed_length_pass']):
|
||||
raise ValueError('independent length, uniqueness or containment gate failed')
|
||||
if group['count'] == group['schedule_count']:
|
||||
return
|
||||
comparison = group.get('quantity_comparison', {})
|
||||
proof = group.get('cad_cycle_assembly', {}).get('wall_station_sources', [])
|
||||
if (not allow_drawing_quantity_review or not proof
|
||||
or comparison.get('applied_authority') != 'placement_drawing'
|
||||
or comparison.get('status') != 'schedule_review_required'
|
||||
or comparison.get('drawing_count') != len(paths)
|
||||
or comparison.get('schedule_count') != group['schedule_count']
|
||||
or sum(len(r.get('selected_stations_mm', [])) for r in proof) != len(paths)):
|
||||
raise ValueError('source quantity review is unresolved or not authorized')
|
||||
# Preserve the true discrepancy and failed material-count check. This
|
||||
# permits a source-bound FEEDBACK model, never quantity/design approval.
|
||||
if group['fabrication_measurement'].get('count_pass') is not False:
|
||||
raise ValueError('material count mismatch must remain visible')
|
||||
|
||||
|
||||
def run(job_id, apply=False, allow_drawing_quantity_review=False, reviewed_ends=()):
|
||||
if reviewed_ends:
|
||||
raise ValueError('per-model endpoint exclusions are disabled; verify current CAD')
|
||||
import server
|
||||
from adaptive_block1_rebar import _frames
|
||||
from cad_rebar_recipe import load_sources
|
||||
from drawing_cycle_assembly import read_profile, generate_groups
|
||||
from local_inner_wall_stations import inner_wall_profile_roles, discover_inner_wall_stations
|
||||
from fresh_rebar_8097 import _compact_nonwing_rebar_taxonomy
|
||||
server.require_modeling_rules()
|
||||
if len(job_id) != 24 or any(c not in '0123456789abcdef' for c in job_id):
|
||||
raise ValueError('invalid job ID')
|
||||
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('job is running')
|
||||
sources = load_sources(job['sourceContext']['analysis'])
|
||||
assembly = plan['drawingEvidence'].get('cad_cycle_assembly_bindings')
|
||||
if not assembly:
|
||||
from local_rebar_pipeline import discover_bindings
|
||||
assembly = discover_bindings(plan, sources,
|
||||
plan['drawingEvidence'].get('cad_fabrication_inventory'), partial_skew=True)
|
||||
assembly = copy.deepcopy(assembly)
|
||||
pending = set(reviewed_ends)
|
||||
frames = {f['top']['id']: f for f in _frames(plan)}
|
||||
existing = {g.get('id'): g for g in plan['rebar_groups']}
|
||||
replacements = {}; reports = []
|
||||
for binding in assembly['groups']:
|
||||
frame = frames[binding['top_host']]
|
||||
parts = [{**binding, **p} for p in binding.get('domain_profiles', [{}])]
|
||||
ordinary = [p for p in parts if p['domain'] == 'ordinary']
|
||||
if len(ordinary) != 1:
|
||||
continue
|
||||
part = ordinary[0]
|
||||
try:
|
||||
profile = read_profile(sources[part['source_sha256']], part, sources=sources)
|
||||
except ValueError:
|
||||
continue
|
||||
if not inner_wall_profile_roles(profile, frame):
|
||||
continue
|
||||
for request in list(pending):
|
||||
host, role, edge = request.split(',')
|
||||
if host != binding['top_host']:
|
||||
continue
|
||||
if role not in ('left', 'right') or edge not in ('start', 'end'):
|
||||
raise ValueError('invalid reviewed wall end')
|
||||
proof = discover_inner_wall_stations(sources, frame, role, binding['cad_to_m'])
|
||||
lines = proof['chain']['dimension_lines']
|
||||
pair = lines[:2] if edge == 'start' else lines[-2:]
|
||||
binding.setdefault('inner_wall_station_exclusions', {})[role] = [dict(
|
||||
explicit_user_confirmation=True, source_sha256=proof['source_sha256'],
|
||||
adjacent_dimension_handles=[l['handle'] for l in pair],
|
||||
user_instruction='지정한 비반복 단부점 제외; 반복 간격 유지')]
|
||||
pending.remove(request)
|
||||
report = dict(block=frame['block'], mark=profile['material']['mark'], applied=False)
|
||||
reports.append(report)
|
||||
try:
|
||||
placements = [p for p in assembly['placements'] if p['top_host'] == binding['top_host']]
|
||||
built, _ = generate_groups(plan, sources, placements, [binding])
|
||||
group = built[0]; old = existing[group['id']]
|
||||
report.update(before=old['count'], after=group['count'], drawing=group['schedule_count'],
|
||||
wall_counts={r['host_role']: len(r['selected_stations_mm'])
|
||||
for r in group['cad_cycle_assembly']['wall_station_sources']})
|
||||
hosts = {c['id']: c for c in plan['concrete_components']}
|
||||
outside = 0
|
||||
for bar, owner in zip(group['instance_paths_m'], group['occurrence_instance_ids']):
|
||||
n, _ = server.tube_path_outside_component_union([bar], group['diameter_mm'],
|
||||
[hosts[k] for k in owner.split('+')])
|
||||
outside += n
|
||||
report['tube_outside_samples'] = outside
|
||||
validate_candidate(group, outside, allow_drawing_quantity_review=allow_drawing_quantity_review)
|
||||
report['material_count_matched'] = group['count'] == group['schedule_count']
|
||||
if not report['material_count_matched']:
|
||||
report['quantity_review'] = 'placement drawing retained; material schedule discrepancy remains visible'
|
||||
group['installation_line_review'] = True
|
||||
group['engineering_review_required'] = True
|
||||
group.setdefault('evidence', []).append(
|
||||
f"벽체 내측 치수열 기준 {group['count']}본 · 재료표 {group['schedule_count']}본. "
|
||||
"치수열 기준 배치 적용, 재료표 수량 차이는 별도 검토 대상이며 일치로 처리하지 않음.")
|
||||
group['section_projection_check'] = {'pass': True, 'tube_outside_samples': 0,
|
||||
'scope': 'own inner-wall stations and unchanged source vertical fabrication; not engineering approval'}
|
||||
group['cad_cycle_assembly'].update(containment_checked=True, tested_path_count=group['count'])
|
||||
group['placement_status'] = 'source_bound_feedback_pending_full_verification'
|
||||
group['display_authorized'] = False
|
||||
replacements[group['id']] = group
|
||||
report['applied'] = apply
|
||||
except (ValueError, KeyError) as error:
|
||||
report['reason'] = str(error)
|
||||
print(json.dumps(report, ensure_ascii=False), flush=True)
|
||||
if pending or (reviewed_ends and any('reason' in r for r in reports)):
|
||||
raise ValueError('reviewed end update was not fully verified; nothing published')
|
||||
if apply and replacements:
|
||||
result = copy.deepcopy(plan)
|
||||
result['drawingEvidence']['cad_cycle_assembly_bindings'] = assembly
|
||||
for binding in assembly['groups']:
|
||||
if binding.get('inner_wall_station_exclusions'):
|
||||
result.setdefault('innerWallStationReviews', {}).setdefault(binding['top_host'], {})[
|
||||
binding['family']] = copy.deepcopy(binding['inner_wall_station_exclusions'])
|
||||
groups = list(replacements.values()); _compact_nonwing_rebar_taxonomy(result, groups)
|
||||
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'])
|
||||
assert result['concrete_components'] == plan['concrete_components']
|
||||
result['innerWallStationRepair'] = dict(reports=reports, full_model_approved=False,
|
||||
source_hashes=sorted(sources), separate_skew_cycle_copies=False,
|
||||
drawing_quantity_review_authorized=allow_drawing_quantity_review)
|
||||
result['geometryRevision'] = 'inner-wall-stations-'+str(time.time_ns())
|
||||
from box_member_schema import member_model
|
||||
result['memberModel']=member_model(result)
|
||||
job.update(result=result,partialResult=copy.deepcopy(result),geometryRevision=time.time_ns())
|
||||
if path.read_bytes() != raw:
|
||||
raise ValueError('job changed during verification')
|
||||
backup = path.with_name(path.stem+'.before-inner-wall-'+str(time.time_ns())+'.json')
|
||||
backup.write_bytes(raw)
|
||||
with tempfile.NamedTemporaryFile(mode='w', dir=path.parent, delete=False, encoding='utf8') as output:
|
||||
json.dump(job, output, ensure_ascii=False, allow_nan=False); temporary = output.name
|
||||
os.replace(temporary, path)
|
||||
return reports
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('jobs', nargs='+'); parser.add_argument('--apply', action='store_true')
|
||||
parser.add_argument('--allow-drawing-quantity-review', action='store_true',
|
||||
help='Publish source-bound feedback with visible schedule discrepancy, never count approval')
|
||||
parser.add_argument('--reviewed-end', action='append', default=[],
|
||||
help='Explicit user-confirmed scalar endpoint removal: top_host,left|right,start|end')
|
||||
args = parser.parse_args()
|
||||
for job_id in args.jobs:
|
||||
print(json.dumps(dict(job=job_id, reports=run(job_id, args.apply, args.allow_drawing_quantity_review, args.reviewed_end)), ensure_ascii=False), flush=True)
|
||||
Reference in new issue
Block a user