PNCAD (포인캐드 · 구 poincad2) — 작업 규칙
규칙만 둔다. 상태·이력은
handoff.md, 완료이력은archive.md.
🆕 신규 (2026-10-25 — UI-2)
- [코디] 26.10.25 툴바 버튼 아랫줄 단축키 =
.sk자식 span → 버튼 줄바꿈(WRAP) 검사·elementFromPoint는 kbd/.sk 자식을 버튼으로 쳐야 한다(_e2e_3dui9n-2) · 렌더 버튼은.open클래스 토글만(1클릭 펼침·2클릭phPress) — E2E 가 렌더를 누를 땐 접힘이면 한 번 더 클릭. 손 3-way 병합 시 pfs app.html = CRLF →git merge-file전에 셋 다 LF 로(안 하면 파일 통째 충돌) · 해결 스크립트 선례resolve.py(ours=AI 연출 줄 보존 + theirs 렌더 블록).
🆕 신규 (2026-10-24 — 부재 선택 P1·P2 테스트 반영)
- [코디] 26.10.25 pncad → PFS(테스트·deploy-live) 이식 =
C:\dev\_work\pncad-partsel\r5\sync3way.py <base=직전 이식 pncad 커밋> <theirs> <dst web\pncad>(sync FILES 전부 merge-file 3-way · dst==base 는 복사 · PFS/운영 고유 블록 자동 보존 · 충돌만 손). R5 에서 테스트 충돌 0 · 운영 1곳. E2E 환경변수 함정: 옛 E2E·_e2e_r4b mock은PNCAD_ROOT=<worktree>+PNCAD_SIDE_OPEN=1,_e2e_ui3d는 SIDE_OPEN «없이»(접힘 기본을 검사한다). - [코디] 26.10.24 pfs
web\pncad\app.html은 pncadapp.html과 갈라져 있다(photo-ai·이미지 분기 181줄) —tools\sync_to_pfs.py로 통째 복사 금지(12파일 덮어쓰기 직전까지 감 · 실측) → 기능 diff 만 적용 + 새 파일만 복사(--dst <pfs worktree>). 운영본은 더 갈라져 있어 패치 hunk 일부 거부 → 앵커 손 이식. 테스트 실주소 E2E =PNCAD_TEST=1 PYTHONUTF8=1 python C:\dev\pncad\tools\_e2e_<이름>.py(로그인 · 서버 쓰기 차단 · 0 단언). 선택 UX 회귀 묶음 =tools\_regress_partsel.py· imos 대조 비교 =tools\_cmp_verify2.py. - [코디] 26.10.24 CP 해석을 바꾸는 코드는
node tools/_family_equiv.mjsGATE PASS +node verify2.mjs --family --json=…이 기준선과 IDENTICAL 을 확인한다(P3 패밀리 엔진). 마스터 필드 = 설계 필드(패밀리가 합성) + 관측치(n·alt·typ·fixW·fixL — 이름별 그대로 덧붙임 · alt 가 18T 몸통 강제 때 자재 선택에 쓰여 빼면 3 아티클 달라짐 = 변이 시험 실측). - [코디] 26.10.24 P4 함정: 슬롯 이름의 두께가 마스터 두께보다 먼저 쓰인다(천판_18T_Rv49 = 마스터 30T 인데 산출 18T) · 두께→자재 규칙이 schema·family 두 벌 →
_family_equiv.mjs게이트 ⑧ 이 둘의 일치를 지킨다(한쪽만 고치지 마라). 🔴[페페] 26.10.24 칸막이 ≠ 고정선반(용도·보링 방식 다름 · 측판 계열 패밀리) · 두께·자재 모순 = 최종 두께 정본, 자재를 두께에 맞춘다(23T 면 23T_PB).
🆕 신규 (2026-10-24 — 파트 별도엣지)
- 🔴 [페페] 26.10.24 엣지 = 칼라만 고른다(두께 선택 넣지 마라 — 「선택사항이 많으면 작업속도가 떨어진다」) · 기본 = 4면 작업본 몸통엣지(
SCENE.bodyEdge, 비면 동색) · 파트 별도엣지 칼라 입력 = 실제 4변 전부 → 면 체크로 뺀다(측판 전후상하 · 가로판 전후좌우) · 규격표 = 바뀐 부재만 꼬리표. [코디] CP 슬롯edge{color,on}(변번호 = 축*2+방향, 앱 트리밍 AX_DIR 축) ·xedge·edgeNote· 덧판층 행엔 안 붙인다 · 3D 띠 =gEdge(실사 hide 목록). 테스트tools/_e2e_edge.py. pncad552a0c3· master1e40c0ad· 운영758b4285. - ⚠ [코디] 26.10.24 pfs master 와 deploy-live 의
web/pncad/app.html·spec.html이 갈라져 있다(master 에 미배포 변경) → master 커밋 cherry-pick 이 충돌난다. 처방 = 앵커 치환 패치 스크립트를 deploy-live 트리에 따로 적용 → 그 트리를PNCAD_ROOT로 E2E → push. 운영판에 없는 줄(앱 규격표 머리 줄 등)은 선택 앵커로 건너뛴다.
🆕 신규 (2026-10-24 — 2겹 파트 두께 = 총 두께)
- 🔴 [페페] 26.10.24 2겹(덧판) 파트의 「파트 두께」(thkOv) = 총 두께 · 기본층 = 총 − 덧판 — 18T + 덧판 9 = 9+9(종전 18+9 = 27 은 오류). 비워 두면 종전대로 기본두께 + 덧판(외경 유지·내경 감소). [코디]
schema.js baseThkOf(총 ≤ 덧판이면 종전 = 쓸 수 없는 값, UI 가 거부) · matOf 기본층 두께 · 파트 패널·편집기 = 덧판 이상 총두께 거부 · 체크 켤 때 총 18 이하면 반씩. pncad125f42e·9664575· pfs master06acccab· 운영 deploy-livee02b8485.
🆕 신규 (2026-10-24 — imos EP류·오픈장 카탈로그 편입)
- [코디] 26.10.24 카탈로그에 imos 아티클을 더할 땐 convert.mjs 재변환 →
tools/reseed_catalog.py <test|prod>미리보기(insert N · update 0 · gone 0) →--go순서. 정본이 PFS DB 라 파일 배포만으로는 앱에 안 뜬다(서버 시딩은 테이블이 빌 때만). 라우트 =/api/pncad/reseed(catalog 접두 없음). - [코디] 26.10.24 node 전개 점검은 앱 현장 기본(
backThk 3·vars.기둥목폭 60)으로 돌린다 — 이걸 빼면 «기둥목으로 새는 CSIDE»(오픈장 측판 폭 60)·«등판 3T 로 바뀌는 18T 뒤판» 이 안 보인다(tools/_check_ep_open.mjs). - [코디] 26.10.24 라이브판 schema.js 는 isPostCP 가 없고 측판 루프에 inline
post다 — 각목 판정 수정은 두 곳(master isPostCP · live inline) 따로 이식.
🆕 신규 (2026-10-21 — 유리도어 상부장 18T · 뒤판각목)
- 🔴 [페페] 26.10.21 유리도어 상부장 몸통 = 전부 18T(장별 bodyThk 18) · 뒤판만 등판 두께 — 종전 26.10.2 「18T 장에서도 뒤판각목 15T 고정」 폐기. [코디]
schema.js BODY_FORCE_PC에 '07'(뒤판각목) 추가 → 장별 18T(bodyForce · 오픈장 경로 포함) 때 뒤판각목도 18T_PB · 현장 스위치(bodyForce 없음)·verify2/3 불변. pncaddf3d27b· pfs master0c1c06e4(미푸시) · 운영 deploy-livee9f44de2. 부재 이름 role 은 CP 마스터 그대로 「15T 뒤판각목」으로 찍힌다(두께 칸 18) → 네스팅 행은 name1 「18T 뒤판각목」 으로 고쳐 올린다. 적용 도구 =C:\dev\_work\enex-gyeryong-1021\resave_glass18.py(대상 = 이름에 「유리」+「상부」 · 유리장식장 제외).
🆕 신규 (2026-10-20 — 첫 화면 작업건 더블클릭)
- [코디] 26.10.20 click 핸들러가 목록을 innerHTML 로 다시 그리면 그 목록의
ondblclick은 절대 안 뜬다(두 클릭의 대상 노드가 달라짐 — 페페 크롬 실측 mousedown/click ×2, dblclick 0). 더블클릭 동작은 click 안에서e.detail >= 2로 처리한다(#sceneList수정 pncad2b21af7). Playwrightdblclick()도 같은 증상을 재현하니 E2E 는#lOk대신 실제 dblclick 으로 검사한다.
🆕 신규 (2026-10-19 — 파트 자재 2겹 · 덧판)
- [코디] 26.10.19
thkOf= 형상 두께(기본+덧판cp.lam.thk). 자재명·층 판정은baseThkOf/layersOf. 산출 부재(out.parts)는 덧판이면 2매(layerbase/lam) → 새 산출 경로는p.layer/p.lam을 본다(19T 제외·칼라·종류). 덧판층은 도어 rows 금지(accgenopenGroup). 새 cp 필드는CP_KEEP한 곳에 넣으면resolveCP가 따라간다(26.10.19 부터 CP_KEEP 순회).
🆕 신규 (2026-10-17 — 벽·바닥 GLM)
- [코디] 26.10.19 GLM 헤드리스 런을 클로드 구독으로 넘길 땐 GLM 세션을
--resume하지 말고 새 세션 + «git status·diff 로 진행분 확인 후 이어서» — 다른 공급자 thinking 서명이 섞인 transcript 는 거부될 수 있다. env 는ANTHROPIC_*전부 제거(API 키 남으면 종량과금). 구현 =C:\dev\_work\pncad-walls-1017\run_glm.py· 이미 도는 옛 코드 프로세스는 다리 스크립트(chain_claude.py)로 끊고 재가동. - [코디] 26.10.18 GLM
claude -p가 백그라운드로 띄운 프로세스(R4 = SSM 포트포워드 13308)가 살아 있으면 결과(result 이벤트)를 낸 뒤에도 프로세스가 안 끝나 대기열이 멈춘다 → 브리프 끝에 «띄운 서버·터널·브라우저는 전부 닫고 끝내라», 감시는 log 의"type":"result"를 보고 남은claude -p트리를 kill. - [코디] 26.10.18 Z.ai quota API 는 새 5h 창(사용 0)이면
nextResetTime키를 빼고 준다 →.get()필수(19:52 KeyError 로 R3b 미실행). 대기열을 «중단»할 땐 자식run_glm.py도 kill(안 그러면 리셋 뒤 혼자 깨어나 실행). - [코디] 26.10.17 GLM 헤드리스 에이전트(claude -p + Z.ai env)는 «변경 전 기준»을 얻으려고
git stash를 쓴다 — 미커밋 남의 변경이 있는 리포면 브리프에 «stash 금지 · 기준은 git worktree + PNCAD_ROOT» 를 박는다(R1 stash/pop 무손실이었지만 위험).
🆕 신규 (2026-10-16 — 2차② 오픈장)
- [코디] 26.10.16 새 «몸통 산출» 경로를 만들면 반드시
schema.js isDoorRoute를 탄다(allParts·renderBom·spec digest) — 안 타면 오픈장이 몸통·도어 이중 생산. - [코디] 26.10.16 운영판 spec.html 은 master 보다 한참 옛 판 → hunk 이식 안 맞음, 명시 앵커(
mk_live_open.pySPEC_MANUAL)로.
🆕 신규 (2026-10-16 — 3D 부속 2차 배포)
- [코디] 26.10.16 pfs 본 트리 master 에 남의 미push 커밋이 있으면 그대로 push 금지 → origin/master 기준 작업트리(
C:\dev\_wt\…)에서 내 커밋만 cherry-pick 해 push. - [코디] 26.10.16 E2E 스크립트가
finally에서sys.exit하면 예외가 삼켜져 rc=0 거짓 통과 — traceback 출력 후 FAIL 로 남긴다(_e2e_acc.py·_e2e_gen.pyd4f4b08). - [코디] 26.10.16 3D 패널 버튼은 값 칸 blur→change→재렌더로 첫 클릭이 사라진다 → 패널 button mousedown preventDefault + 확정 전 flush. E2E 는 실제 마우스 클릭으로 재현해야 잡힌다(패널 숨김 우회 금지).
🆕 신규 (2026-10-15 — 인라인 검색 Enter best 전수)
- 🔴 [코디] 26.10.15 5차
web/pncad/droplist.js를 고치면 pfspython tools/partner_nesting/build.py재빌드 →web/nesting/partner/asset/droplist.js·BUILD.json커밋·배포까지 한 작업 — 협력사(월드) 네스팅 사본은 빌드 산출물이라 손으로 고치지 않는다. 안 돌리면 협력사 화면은 옛 사본(10-08 빌드가 1·2차 변경을 놓친 채 5차까지 갔다). 확인 =build.py --check«최신». - 🔑 [코디] 26.10.15 3차 [페페] pncad 작업본 저장 = 목록 밖 칼라 자동 등록(「항상 자동으로 저장되게」) — 장치는 PFS
POST /api/poin/colors(poin_color_controller · 네스팅 저장 registerNewColors 와 공용 · 없는 이름만 INSERT · 대소문자 무시 · 50 · 32자 · 반환 = 평문 배열). 새 등록 장치를 만들지 마라. app.htmlcolSaved/colAutoAdd한 곳 · 실패면 경고만(저장 성공 유지) · E2E 원격은 POST abort = 쓰기 0. - 🔴 [코디] 26.10.15
/production/pncad/droplist.js는 PNCAD 전용이 아니다 — 네스팅(web/nesting/nesting.html은 모든 data-drop 에 best 자동 부착)·자재/부자재 발주서·업무패널·견적v2 가 같은 파일을 import 한다. droplist 의 «보이는» 변경은 옵트인으로 막는다(선례 =initDrops({ bestMark: true })를 부른 화면만 ↵·«목록에 없음»). best 점수 규칙을 바꾸면 그 화면들 Enter 결과가 같이 바뀐다. - 🔴 [코디] 26.10.15 pfs
web/pncad/app.html·editor.js가 pncad master 보다 앞서 있을 수 있다(AI 연출 브랜치 테스트분 · pfs 직접 QA 수정 bec0ccd6) →sync_to_pfs.py전에diff --strip-trailing-cr로 «pfs 쪽에만 있는 줄»부터 본다. 있으면 전체 복사 금지 — pncad 커밋 diff 를git apply --directory=web/pncad --ignore-whitespace로 얹고, 얹은 뒤 pncad↔pfs 차이 줄 수가 전과 같은지 확인. - 🔑 [코디] 26.10.15 운영 해시 대조 =
PNCAD_HASH_LOCAL=C:\dev\pfs_deploy_wt\web\pncad python tools/_deploy_hash.py prod— 기본(pfs master 사본)은 선별 포워드 운영과 app·spec 이 원래 다르다. 로컬에 없는 자산(live 엔 실사 모듈 없음)은 자동 제외.
🆕 신규 (2026-10-14 — 소요한남 LPM 상하분리)
- 🔴 [코디] 26.10.14 네스팅 앱은 2440 넘는 부재가 있으면 그 칼라에 1220×2700 원장을 자동으로 붙여 조용히 넘어간다 → LPM(4×8 · 실사용 ≤2400)·4.5T(4×8만)는 작업본 단계에서 PB·MDF 2440 초과를 직접 검사하고, 한샘 도면의 상·하 2몸통(이음판은 1장만 그림)은 장 2개로 나누되 도면이 판을 안 그린 쪽 천/지판을 트리밍 0(
C:\dev\_work\sohan-lpm-split-1014\split.mjs).
🆕 신규 (2026-10-14 — 84㎡ 주방 샘플 #140)
- [코디] 26.10.14 TL_SD_가전소물은 높이 1600 미만 금지(부재 18mm 돌출 · 뒤판 길이 0) → 낮은 가전 키큰장은 B2D + 측판·칸막이 + W_SD 로 쌓는다.
- [코디] 26.10.14 헤드리스 Playwright 기본은 소프트웨어 렌더라 «렌더 시작»이 막힌다 →
--use-angle=d3d11 --enable-gpu --ignore-gpu-blocklist로 780M GPU 를 잡는다. - [코디] 26.10.14 테스트 8001 은 테일스케일 IP 직접 접속 = 빈 응답(curl 52) · Host 헤더(
pfs-test.poin.co.kr) 있어야 200 → 안내 주소 =http://pfs-test.poin.co.kr:8001. - [코디] 26.10.14 ㄷ자 평면 = 벽 1개 + 장 90°/270° 회전 + 앱에서 외곽 상자 재서 좌표 맞춤(
C:\dev\_work\pncad-kitchen84\_scene.py재사용).
🆕 신규 (2026-10-13 — AI 연출 U0·U3·U4 미니PC 워커)
- [코디] 26.10.13 공유 CDP 크롬(9334)을 무인 작업이 쓸 때는 «전용 작업 창 + 그 탭 target_id 만» — imagegen 기본
_page는 처음 보이는 Gemini 탭을 집어 페페 탭을 가져간다. 작업 창은desk.py park로 데스크톱 2, 끝나면 창 닫고 cdplease 반납(크롬은 안 끔). - [코디] 26.10.13 자주 다시 쓰는 JSON 장부(엔진 상태 등)는 읽기 때 쓰지 말고, 쓸 때
os.replace재시도 — 윈도우 일시 잠금으로 워커 rc=1 플래키 1회(C:\dev\tools\pncad-stage-worker\enginestate.py). - [코디] 26.10.13 ChatGPT 웹 편집은 보존 지시가 약하면 가구를 다시 그려 30~50px 이탈 → 프롬프트 st2 유지 + compose
align실패 판정 필수(U0 실측C:\dev\_work\pncad-ai-staging\u0\results.md). 띠 자산 재생성 = 해시 변경 →compose.BAND_SHA256과 서버PNCAD_STAGE_BAND_SHA동시 수정.
🆕 신규 (2026-10-13 — AI 연출 U2 서버 API)
- 🔑 [코디] 26.10.13 pfs 컨트롤러를 «실제 앱 + Flask test client» 로 시험하려면 시험 venv
C:\dev\_work\pncad-ai-staging\u2\venv38(py3.8 ·requirements-u2test.txt= pfs 판 그대로 + Werkzeug 0.15.6 — 0.15.2 는 py3.8+ 에서 routing CodeType 으로 죽고 0.15.5 가 그 한 줄만 고침) →web.create_app()이 452 라우트로 뜬다. DB 는mysql_database.current_app = SimpleNamespace(config=…)로 임시 MariaDB 스크래치 스키마에 + 최소user·company·audit_log를 깔면 실제 before_request 문·api_gate 게이트(외부계정 403)까지 탄다(tests_cody\test_pncad_stage_controller.py머리). - 🔴 [코디] 26.10.13 화면 계약상 403 = «로그인 만료» (photo-ai.js
call()) → 사용자가 받을 수 있는 거절(Origin 불일치 등)은 403 으로 내지 마라(400origin). 남의 잡 403 은 정상 화면 흐름에 안 나오니 유지. - 🔴 [코디] 26.10.13 「결과를 받을 수 없는 설정」이면 claim·잡 작성부터 막는다 — 띠 해시 env 가 비면 결과가 전부 422 인데 claim 은 엔진 하루 수량을 예약하고 슬롯이 unknown 으로 굳는다(vff 반박 P1). 검증 게이트(fail-closed)를 만들면 «일을 받는 문»에도 같은 조건을 건다(
_cfg_problem→ pingready·usageenabled:false). - ⚠ [코디] 26.10.13 모듈 캐시 «검사 중» 시각을 검사 앞에서 적지 마라 —
ready()가_READY.at을 SHOW TABLES 전에 적어, 첫 검사가 도는 동안 같은 프로세스의 동시 요청이 503 off 를 받았다(동시 6요청 중 2건 · 실측). 결과와 시각을 검사 «뒤»에 같이 적는다. - ⚠ [코디] 26.10.13 늦은 회수·재전송 멱등은 «잡 행»의 임대 값으로 판정하면 안 된다 — 재큐(
_end_attempt)가 worker_id·lease_token 을 지운다 → 슬롯 flags 에 임대 증명(by= 토큰 이름 ·lease= lease_token sha256 앞 32)을 claim 때 남기고 대조한다(워커 flags 의 예약 키는 버린다). 원문 저장 0. - 🔑 [코디] 26.10.13 변이 시험은 코드 파일을 안 고치고 pytest 플러그인으로 —
pytest_collection_finish에서 컨트롤러·DAO 함수를 바꿔 끼우고(-p mutplug· env 로 변이 선택) 12종 모두 실패가 나는지 본다(origin·owner·route·room·admin·inputsha·claimkey·proof·verify·cfg·recoverlease·reserved).
🆕 신규 (2026-10-13 — AI 연출 U1 서버 원장·대기열)
- 🔴 [코디] 26.10.13 pfs DB 동시성 시험은 «가짜 트랜잭션»으로 증명이 안 된다 → 로컬 임시 MariaDB 로 실제 InnoDB 에서 잰다 —
python C:\dev\_work\pncad-ai-staging\u1\mdb.py init|start|stop(별도 datadir · 127.0.0.1:13399 · portgate 등록 · DB 스터디 3306 금지) →PNCAD_STAGE_TEST_DB=127.0.0.1:13399 python -m pytest tests_cody/test_pncad_stage_db.py(운영 판 pymysql 0.9.3 =C:\dev\pfs\.venv\Scripts\python.exe <시험파일>단독 실행기). 🔴--innodb-snapshot-isolation=OFF필수 — MariaDB 11.6+ 기본 ON 은 잠금 읽기가 스냅샷 뒤 바뀐 행을 만나면 1020 오류를 내 MySQL 8 에 없는 실패로 시험이 엇나간다. 잠금을 빼는 변이로 시험 검출력을 꼭 확인(사용자·claim·접수 잠금 제거 → 실패 확인됨). - 🔴 [코디] 26.10.13 pymysql 은 CLIENT.FOUND_ROWS 를 안 켠다 →
rowcount= «바뀐 행 수» — 같은 값으로 다시 쓰는 UPDATE(같은 초 lease_until 재기록 등)는 0 이 나온다. 영향 행으로 판정하는 UPDATE 는 상태 전이처럼 값이 반드시 바뀌게 쓰고, 임대 검사는SELECT … FOR UPDATE로 한다(MySQLTransactionUtil.execute_count머리 주석). - 🔑 [코디] 26.10.13 이름 잠금(GET_LOCK)은 트랜잭션 «첫 문장» — RR 읽기 뷰는 첫 테이블 읽기에서 생기므로 잠금 뒤 뷰가 앞 보유자의 커밋을 본다. 해제는 commit/rollback 이 커넥션을 닫는 것에 기댄다(커밋 «전» RELEASE 금지 = 경쟁) — 커넥션 풀을 들이면
RELEASE_ALL_LOCKS()를 커밋 뒤에. 사용자 잠금만으론 사용자 «사이» 공용 한도·대기열 경쟁을 못 막는다 → 전역 접수 잠금(pncad_stage_admit)을 사용자 잠금 뒤에. 임대·만료 판정 시각은 행 잠금을 잡은 «뒤» 다시 구한다(잠금 대기 중 만료 = 부활). - ⚠ [코디] 26.10.13 마이그레이션 번호는 설계 문서 이름을 믿지 말고 확인 —
git log --all --name-only --format= -- 'migrations/patch6*'+ 전 worktreemigrations\대조(AI 연출 설계의 patch62 = 이미 자재 입고일 →patch63_pncad_stage.sql→ 26.10.14 또 다른 세션 patch63 과 충돌해patch64_pncad_stage.sql· 미푸시 로컬 커밋 브랜치까지 본다).
🆕 신규 (2026-10-13 — AI 연출 U6 화면 photo-ai.js)
- 🔴 [코디] 26.10.13 Playwright
wait_for_function에 async 술어를 넘기면 즉시 통과한다(Promise 는 truthy) —import('./photo-ai.js')로 상태를 읽는 대기가 전부 헛통과해 «잡 번호 받음» 전에 다음 단계로 갔다. 대기 술어는 동기로(window.PNCAD_AI.state()훅) ·evaluate는 async 가 된다. - ⚠ [코디] 26.10.13 Playwright
click은aria-disabled="true"버튼을 «비활성» 으로 보고 30초 기다리다 실패한다 → 막힌 CTA 를 «눌러도 무동작» 검사할 땐click(force=True). - 🔴 [코디] 26.10.13 API 응답 파서는 «성공 응답 안의 err 필드»(잡 실패 사유 — 예
err:{code:'safety'})를 HTTP 오류로 읽으면 안 된다 — 폴링이 계속 실패로 돌아 E-NET2 가 떴다. 오류 코드는 «실패 응답» 또는 RestResponse 최상위code만(photo-ai.js call()). - 🔑 [코디] 26.10.13 photo.js 와 같은 자리(#view 좌하단)를 쓰는 진행 UI 는 #rbar 를 빌리지 말고 전용 바(#aiBar) — #rbar 버튼·Esc(guardKey)·ResizeObserver 는 photo.js 내부
U.bar상태로 돈다(코덱스 상의). 대신 1단계 시작 때 photo.js 안내 바(느림·완료)가 떠 있으면#rbClose로 닫는다(헤드리스 SwiftShader 는 «느림» 바가 거의 늘 떠 있어 겹침). - 🔑 [코디] 26.10.13 #aiPane keydown 은 Tab·Esc 포함 전 키 stopPropagation(Ctrl+숫자만 preventDefault) — 버튼 포커스에서 Ctrl+7(실사 끔→패널 닫힘)·Ctrl+D·Delete·I/H/F 가 3D 로 샌다(web-designer 재확인 F). 3D 키 핸들러는 window 버블이라 패널에서 끊으면 된다.
- ⚠ [코디] 26.10.13 새 #vbar 버튼·#main 자식을 넣으면 구조 고정 E2E 3곳이 깨진다 —
_e2e_3dui.py9-2(버튼 id 목록)·9-4(aria-pressed 목록)·5-1(#main 자식) ·_e2e_photo.py1-3(#vbar 뒤쪽 순서). 의도된 추가면 기대값을 같이 고친다. - ⚠ [코디] 26.10.13
_e2e_photo.py런7 7-7(«질감 로드 끝 = 칩 숨김»)이 손 안 댄 master 5e92b5a 에서도 2회 연속 타임아웃(tex.state === 'ready'30초) → 그 뒤 런8 이 안 돈다. U6 회귀 판정은--only 1,2,3,4,5,6,8+ 런7 단독으로 따로 잰다. 원인 미조사. - 🔑 [코디] 26.10.13 멱등키 = 같은 논리 요청이면 성공 응답까지 같은 키(전송 실패 [다시 보내기] · again/retry 응답 유실 뒤 재클릭) · 전송 중 취소는 fetch 를 끊지 말고 잡 번호를 받은 뒤 서버 취소(서버가 이미 받았을 수 있다) — 코덱스 반박검토.
crypto.randomUUID는 비보안 컨텍스트(테일스케일 http)에 없다 →getRandomValues.
🆕 신규 (2026-10-13 — 세션 터미널 이관)
- 🔑 [코디] 26.10.13 VS Code(Remote-SSH) 세션 끊김 방지 = 미니PC 콘솔 터미널
claude --resume <id> --remote-control재기동 → 본문 =C:\dev\seedchain\rules\runbook.md「세션 릴레이」 맨 위 🔑 줄.
🆕 신규 (2026-10-13 — 실사 v3 그림자 고품질화)
- 🔑 [코디] 26.10.13 헤드리스 Playwright 도 실 GPU 를 잡는다 —
chromium.launch(args=['--use-angle=d3d11','--ignore-gpu-blocklist','--enable-gpu'])면 Bash(Session 0)에서도ANGLE (AMD Radeon 780M … D3D11)(기본은 SwiftShader). 실기 화질·속도 측정에 schtasks /IT 가 필요 없다 · 도구 =C:\dev\_work\pncad-photo-v3\diag.py --gpu(같은 뷰 3모드 캡처 + 그림자 mm/텍셀 로그). - 🔴 [코디] 26.10.13 three-gpu-pathtracer 0.0.17 은
FEATURE_MIS 0이면 직접광 표본(NEE)을 통째로 뺀다 → 방향광·작은 면광 기여 0(델타 광원은 BSDF 광선이 못 맞힌다). v2 의 «경로추적 그림자가 넓게 퍼짐» 원인. MIS 켬이 기본 · 780M D3D11 순백 없음(설계 §5 순백 기록은 재현 안 됨) · 순백이면 photo-pt 가 32×32 다운샘플로 감지해 MIS 끄고 다시. - ⚠ [코디] 26.10.13 경로추적 진행은 GPU 울타리(fenceSync)로 잰다 — CPU 가 update 를 앞질러 쌓으면 진행률이 거짓으로 빠르고 끝에서 수십 초 멈춘다(헤드리스 780M: 30초 상한이 45초).
KHR_parallel_shader_compile이면 첫 update 가 바로 돌아오니 상한 시계는 첫 울타리가 풀릴 때부터. - ⚠ [코디] 26.10.13 실사 «얼룩·퍼짐» 의 주범은 그림자 맵이 아니라 GTAO 였다(반경 250~300mm · thickness 450mm · 혼합 1 = 가구 아래·뒤 검은 점묘 헤일로 · 같은 뷰 AO on/off 로 확정) → 반경 110/130 ·
TUNE.aoThick60 ·TUNE.aoInt.55. AO 는 직사광 자리까지 곱해지니 혼합 1 금지. - 🔑 [코디] 26.10.13 그림자 카메라 = «보이는 받는 면» 맞춤(tight) — 화면 격자 광선 + 화면 안 상자 꼭짓점 → 전체 맞춤과 교집합 · 움직이는 동안(시점 변경·장면 변경)은 전체 맞춤 · bias/normalBias 는 텍셀(mm)에서 자동(normalBias 상한 6mm = 18T 빛샘). 근접 뷰 2048 4.48→1.14mm/텍셀.
- ⚠ [코디] 26.10.13
tools/_deploy_hash.py의 로컬 기준은 본 트리C:\dev\pfs\web\pncad고정 — 본 트리가 origin 보다 뒤면(타 세션 미커밋 파일로 pull 못 함) 배포가 맞아도 «불일치»로 나온다. pfs worktree(origin/master 기준)로 배포했으면C:\dev\_work\pncad-photo-v3\hash_wt.py <worktree> test로 대조(경로만 바꿔 실행). sync 도 같은 이유로sync_wt.py <worktree>(autocrlf 체크아웃이라 CR 만 다른 파일은 복사하지 말 것). - 🔑 [코디] 26.10.13 스틸 누적은 선형광으로(sRGB 값 평균 = 지터 반그림자가 밝은 쪽으로 쏠림) · 지터 표본(halton 5·7)은 AA 지터(2·3)와 다른 기저.
🆕 신규 (2026-10-13 — 3D 표시색 칼라별 구분)
- 🔑 [코디] 26.10.13 3D 재질을 바꾸는 새 코드는
cmPaint규약을 따른다 — 선택 발광 중인 메시(m.userData.base있음)는base를 새 재질로 바꾸고m.material = hiMat(새 재질)· 안 그러면 hiOff 때 옛 재질로 돌아간다. 범례 강조(.15)는 재질만 바꾸고 group visible(숨김·아이솔레이션)은 안 건드린다 · 실사(photo.js)는 재질을 통째로 바꾸니 실사 켜기 전에 강조를 푼다. - 🔑 [코디] 26.10.13 자동색 풀 판정 = 부재 역할(
isBackPanel= CABBACK)이지 코드 문자열이 아니다 — 몸통 풀은 «몸통에 한 번이라도 쓰인 코드»만 정렬해 배정하므로 등판 전용 코드가 늘거나 줄어도 몸통 색은 안 밀린다(E2E I3). - ⚠ [코디] 26.10.13 rebuild 가 부르는 함수는 모듈 뒤쪽
const요소 참조를 쥐지 마라(TDZ) —renderCmBadge는 매번 getElementById. / E2E 에서 모달 닫은 직후 Esc 는 activeElement 가 닫힌 모달 안 버튼이라 3D 키 핸들러가 무시한다 →document.activeElement.blur()후 누른다. - 🔑 [코디] 26.10.13 deploy-live 판 app.html =
git merge-file(base 직전 pncad · ours origin/deploy-live · theirs 새 판) 스크립트로 만들고 master 전용 참조(photoOn·phLoad)가 안 남았는지 assert —C:\dev\_work\pncad-color-1013\mk_live.py. deploy-live 기준 3dui 는 141/148(실사·우클릭 메뉴 7건 = master 전용)이 정상 기준선.
🆕 신규 (2026-10-13 — 소요한남 통합 · 작업본 숨김)
- 🔴 [코디] 26.10.13 장별 칼라(
inst.color)는 등판까지 덮는다(26.10.2 「동일칼라」) — 몸통 칼라만 다른 장(다른 방 마감)을 한 작업본에 합칠 땐inst.backMat(장별 등판 넘버, 우선순위 1위)를 같이 넣어야 등판이 원래 넘버로 남는다. 통합 검증 = 원본 작업본들을 앱에서 열어PC2.nestRows()합 ↔ 통합본nestRows()(타입 칸 제외) 완전 일치(C:\dev\_work\sohan-merge-1013\dry_compare.py). - 🔑 [코디] 26.10.13 작업본 숨김 = 목록 «기본 제외»만 —
select_one·PUT·select_nest_rows(네스팅 합산)·?scenes=링크는 숨김 무관. 숨김 PATCH 는modify_date=modify_date로 수정시각 불변(네스팅 «작업본이 저장본보다 새것» 판정·최근순 보존). 목록을 새로 부르는 화면은 «목록에서 빠진 번호를 선택에서 정리» +?scenes=진입은 숨김까지 받아 맞춘다. - ⚠ [코디] 26.10.13 pfs master 의
web/pncad/app.html·spec.html에 운영 미배포분(실사·세션 B)이 쌓여 있어 cherry-pick 이 충돌한다 — 충돌은 «deploy-live 쪽 구조 유지 + 내 줄만» 으로 풀고, 그 deploy-live 후보 파일을PNCAD_ROOT=C:\dev\pfs_deploy_wt\web\pncad python tools/_e2e_hide.py local처럼 로컬 E2E 로 먼저 돌린다(master 전용 함수 hideErr·openScene 참조가 섞이면 운영 pageerror). - ⚠ [코디] 26.10.13 E2E 에서 작업 현장 행 더블클릭 열기는 안 잡힌다(첫 클릭이 목록을 다시 그려 대상이 바뀜) → 행 클릭 +
#lOk로 연다(기존 E2E 와 같은 방식).
🆕 신규 (2026-10-13 — 실사 v2 경로추적)
- 🔑 [코디] 26.10.13
#rqp라디오는[data-q](품질)·[data-m](렌더 방식) 두 그룹이다 —[role=radio]전체를 도는 코드(syncQp·roving·클릭·E2E 셀렉터)를 쓰면 품질 선택이 깨진다. 항상 그룹 속성으로 거른다. - 🔑 [코디] 26.10.13 three-gpu-pathtracer 첫
update()= 셰이더 컴파일(메인 스레드 정지) → 30초 상한·ETA 는 그 뒤부터(warm). 배경은backgroundMap단색(TUNE.bg· ACES 역계산untone), env 는 조명 전용. - 🔑 [코디] 26.10.13 경로추적 씬 복제는
hiGuard안에서 한다(선택 발광 클론 hiMat 이 PNG 에 구워진다). 재질은userData.base ?? material. - ⚠ [코디] 26.10.13 헤드리스 SwiftShader 도 CDN 모듈을 받아 경로추적을 실제로 돈다(컴파일 0.25초 · 14샘플/30초) — 화질 판정은 못 해도 pageerror·전이·복원 E2E 는 된다. API run 샌드박스도 테일스케일 8871 에 붙어 E2E 를 돌릴 수 있었다(네트워크 전면 차단 아님 — CDN 은 차단).
- ⚠ [코디] 26.10.13
notify.py출력 꼬리가\r\n\r\n이라tail -1은 빈 줄 —tail -3으로 본다(빈 출력 = 실패 오판 2회) →C:\dev\seedchain\rules\notify.md. - ⚠ [코디] 26.10.13 텍스처 정규화는 평균만 맞추는 곱이면 밝은 결이 먼저 255 로 날아간다(옛 ×5 도 oak 20.6%·grey_wood 14.7% 클리핑) → 곱+감마
y=min(1,k·x^γ). 대가 = oak 결 대비 0.72→0.34.
🆕 신규 (2026-10-13 — 세션 B 보완: E2E PNCAD_ROOT · 규격표 실측 · 병렬 병합)
- 🔑 [코디] 26.10.13 E2E 는
tools/_e2e_common.py use_pncad_root의 PNCAD_ROOT 규약을 쓴다(새 E2E 도 동일). route glob 이/api/pncad/…까지 잡으니 predicate 로 거를 것.fulfill로 낸 문서는 이후/api요청이 PNA/CORS 에 걸려, 자체/apihandler 가 없는 E2E 는api404=True로 둔다. - ⚠ [코디] 26.10.13 규격표 쪽 분할 실측은 인쇄 상태(
.pmeasure)로 잰다 — 화면 상태로 재면 합친 행이 20px 과대. print 에뮬 검사 뷰포트 폭은 1073px. - ⚠ [코디] 26.10.13 대량
.v판정은 읽기 일괄 → 쓰기 일괄(강제 레이아웃 방지). 칸 이동 Enter 는 재렌더와 겹치면 글자가 소실되니.v포커스 중엔 재렌더를 보류한다. - 🔴 [코디] 26.10.13 세션 병렬 병합 순서 = «내 worktree 에 master 를 merge → 검증 → master ff-only»,
sync_to_pfs는 ff 뒤에만. master 작업트리에 타 세션 미커밋 파일이 있으면git merge가 거기서 깨지고 sync 가 충돌 파일을 복사한다. - ⚠ [코디] 26.10.13
cody_agent run --repo는 bash 에서 백슬래시 경로가 깨진다 → 슬래시(C:/dev/pncad).ask기본 예산이 $1 이라--budget필수. - 🔑 [코디] 26.10.13 API 실행기 패치는
git apply --cached+git apply로 작업트리에 넣으면 다른 에이전트의 미커밋 변경과 섞이지 않는다.
🆕 신규 (2026-10-12 — 실사 모드 v1)
- 🔑 [코디] 26.10.13 fold · qa1009 · partsel · instqty · trim E2E 5개는
8799/pncad/app.html하드코딩이라 env 로 worktree 를 못 가리킨다(photo · 3dui 만PNCAD_LOCAL_BASE/PNCAD_LOCAL_APP지원) → 임시C:\dev\_work\pncad-photo-1012\_run_patched.py로 URL 치환해 돌렸다. 근본 처방 = 5개에 env 지원 추가(미적용). - ⚠ [코디] 26.10.13 질감 재질
envMapIntensity기본 1 이면TUNE.env(.4) 기준에서 우드가 하얗게 뜬다 —photo.js는 질감 재질의 env 만TUNE.env로 덮고 roughness 는 아키타입 값을 유지한다(retune도 같은 규칙). 새 질감 재질을 만들면 env 를 같이 덮을 것. - ⚠ [코디] 26.10.13 Edit 도구가 CRLF 파일을 LF 로 바꾼다(
photo.js전체가 LF 로 바뀜) → CRLF 파일을 Edit 한 뒤엔 커밋 전에 CRLF 로 통일(diff 가 전 줄 변경으로 부푼다). - 🔑 [코디] 26.10.13 라운드 · 마이터 부재(비 BoxGeometry)는 질감을 안 입히고 결 없는 단색 짝 재질을 쓴다 — 박스 투영 UV 가 곡면에서 늘어나는 걸 막는다(
photo-tex.js). - 🔑 [코디] 26.10.13
finish-map.json에 없는 칼라 = 편집 화면 색(cmHex) 그대로(질감 없음) · 매핑된 칼라만finish-map색을 쓴다 — 매핑 없는 회색 칼라가 흰색으로 바뀌던 버그(적대검토 mid③)의 처방. 선택 발광 클론(hiMat)에도 질감을 같이 붙이고,finish-map로드 실패는 재시도로 복구한다. - 🔴 [코디] pncad 에 새 자산(.js · 텍스처 등)을 넣으면
tools/sync_to_pfs.py·tools/_deploy_hash.py·tools/_verify_deploy.pyFILES 세 곳에 같이 넣는다 — 빠지면 pfs 에서photo.js404 → 실사 토글 무반응 · 「실사 모듈을 불러오지 못했다」(26.10.12 실사 모드 v1 에서 재확인 · 위 26.10.9 jointui.js 선례와 같은 사고). - 🔑 [코디] 기존 E2E 가
/pncad/app.html(8799)을 하드코딩 — worktree 의 파일을 검사하려면 envPNCAD_LOCAL_BASE·PNCAD_LOCAL_APP(_e2e_3dui.py·_e2e_photo.py)로 돌리고, Git Bash 에선MSYS_NO_PATHCONV=1을 붙인다. 검사 =C:\dev\pncad\tools\_e2e_photo.py(72항목) ·_e2e_3dui.py(147항목). - ⚠ [코디]
tools/_e2e_prod.png·_e2e_test.png는 git 추적 파일이다 — E2E 뒤mv _e2e_*.png같은 정리 glob 에 걸리면 추적 파일이 이동·삭제된다. 정리는 이름을 지정하거나 새 산출물만 대상으로. - 🔑 [코디] 실사 모드에서 픽킹용 메시(gZone opacity 0)는
visible=false로 둔다 — Raycaster 는visible을 보지 않으므로 픽킹은 그대로 되고, 그림자·AO 는overrideMaterial로 그려서 opacity 0 을 무시하기 때문에 안 숨기면 그림자·AO 에 나타난다. 도어(.82 반투명)는 실사에서 불투명. - 🔑 [코디] 실사 재질 교체 = 원본 재질 불변 + WeakMap 에 원본 보관 → 끌 때 복귀(
photo.js). 원본 재질 객체를 직접 고치면needsUpdate함정과 복귀 누락이 생긴다. 실사 켬/끔은 저장하지 않고(항상 꺼진 채 시작) 품질만 localStoragepncad.realq(pncad.fold는 불리언 전용 — 넣지 마라) · PMREM·톤매핑은 실사 켠 동안만(편집 화면 바이트 동일).
🆕 신규 (2026-10-10 2차 — 이동 축 · 배치 목록 칼럼 · 도어 기본 꺼짐)
- 🔴 [코디] 장 드래그 이동 축 =
moveDrag의 «커서 방향과 가장 나란한 화면 축»(axScreen) + 축선 최근접점(axisParam) — 옛 «카메라 지배축이 법선인 평면 두 축»은 고도 ≈42° 이상에서 높이(Z)가 후보에서 빠지고, Ctrl 깊이는 «화면 위 = 벽에서 멀어짐» 고정이라 커서와 반대였다. 화면 길이가 최장 축의 20% 미만인 축(시선과 나란)은 후보 제외(AX_MIN— 절대 px 기준은 줌에 따라 틀린다) · 높이·깊이가 둘 다 세로로 보이면 높이 우선(높이 길이 ≥ 깊이 × .5). 검사 =tools/_e2e_3dui.py [local|test|prod] 6(6s-6 = 5뷰 × 다중 2장 × 화면 투영 부호). - 🔑 [코디] 도어 기본 꺼짐(
app.door= true 일 때만 켬) — E2E 에서click('#bDoor')로 «끄기» 하던 곳은 전부 상태 맞춤(if on → click)으로 바꿨다(joints·divswap·partsel·instqty·trim·pncad). 새 E2E 도 토글 클릭 대신 상태를 맞춘다. Raycaster 는 visible=false 를 안 거르니 숨긴 도어 픽킹은 코드가showDoor로 거른다. - [코디] 배치 목록 =
#cat안 세로 3칸(#catTop·#catSplit·#instPane) · 비율 =pncad.split(숫자, foldPut 금지) · 접힘 =#cat.instShut(위 칸 flex-grow 1 강제 — 비율 < 1 이 남으면 빈 칸) ·#instRail·#cntRail삭제. ⚠오른쪽 끝이#side가 돼 PFS QA 헬퍼 토글(총관리자 화면)이 «선택 아티클» 머리줄 오른쪽 끝을 덮는다.
🆕 신규 (2026-10-10 — 기준점 큐브 · 선택선 1px)
- 🔑 [코디]
gSel에는 선(LineSegments2)과 기준점 점(THREE.Points ·SEL_DOTShaderMaterial)이 같이 들어 있다 — gSel.children 을 순회해linewidth·color를 읽는 새 코드는isLineSegments2로 거른다(디버그 훅selLines가 그렇게 바뀌었다 · 점 좌표 =PC2.selDots()). ShaderMaterial 색은THREE.Color를 쓰지 말고 sRGB 값을 셰이더에 박는다(선형 변환돼 어두워진다). - [코디] 기준점 큐브 사선 투영 깊이 한 칸 = (+28, −13)px — 처방안 (+27, −19)는 «후» 점과 «중» 점이 12.6px 로 붙는다(27점 최소 간격 전수 탐색 24.7px). 선택선 굵기를 바꾸면
selPulse()복귀값도 같이 바꾼다(안 바꾸면 펄스 뒤 옛 굵기로 돌아간다). E2E =tools/_e2e_3dui.py [local|test|prod] 5.
🆕 신규 (2026-10-09 — 접합 B안·3D UI)
- 🔴 [코디] 내부 T 모드
thru(통) = «상대 판이 통과» → 칸막이·선반 끝(줄기)은 «사이에 끼움» ·split= 줄기가 통과·상대 판이 두 조각. 화면 문구를 파트 입장으로 쓸 땐jointui.js jointStance()를 쓴다(엔진 대조 검사tools/_test_jointstance.mjs). 처방 문서가 반대로 적었던 적 있음. - 🔴 [코디] 세션 재시작 직후 백그라운드 worker 에 SendMessage 로 재개하면 같은 agent id 로 «쌍둥이» 2개가 돌 수 있다(26.10.9 실측 — 한쪽이 용어 커밋, 다른 쪽이 같은 파일 편집). 배포 단계는 락 폴더(
mkdir …\deployN.lock) + push·deploy-live 직전git fetch로 원격 예상값 확인을 지시문에 넣는다. - 🔑 [코디] 화면 용어(26.10.9 [페페]): 덧댐 → 덧방 · 측판통 → 측판 덧방 · 천지통/천판통 → 천판 덧방. 저장값(sideThru·topThru·overlay)은 불변 — 새 문구도 이 용어로.
- [코디] 뒤판 변별 접합 키 =
back|L/R/T/B(+back= 4변 기본) · 해석은 단계 단위(배치 > 아티클 > auto) · 4변 같으면back하나로 접어 저장 · 성립 판정 =schema.js backCornerConflicts. - [코디] 3D 이름 라벨 =
placeLabels()점수 배치(몸통 1000·치수 100·반대쪽 40·라벨 60·뷰밖 400·계단 뒤집힘 20). 계단(먼 장이 위)은 처리 순서만으론 안 맞는다 → 뒤집힘 감점 필수 · 순서 히스테리시스는 카메라 이동 중만(멈춘 프레임에 재계산). 지표 =tools/_probe_lblplace.py(합격 몸통·라벨끼리·뷰밖 0 · 치수 ≤2). - 🔴 [코디] 화면 오른쪽 위(오른쪽 64px × 위 60~120px)에 버튼을 두지 마라 — PFS QA 헬퍼 토글(fixed top:70 right:12 44×44, 총관리자·test 화면)이 덮는다. local(8799)엔 QA 헬퍼가 없어 로컬 E2E 는 통과하고 test 에서만 터진다(26.10.9 배치목록 접기). 검사 = elementFromPoint 로 버튼 중심이 그 버튼인지(
_e2e_3dui.py5-3q·5-4q, local 은 44×44 더미 주입). - [코디]
node --check는 ESM 중복 선언을 못 잡는다 → 모듈은node -e "import('./x.js')"로 검사. worktree 파일 E2E =PNCAD_ROOT=<폴더> tools/_e2e_joints.py local.
🆕 신규 (2026-10-09 — 규격표 멍장 좌대류·칼라 칩)
- 🔑 [코디] 규격표 섹션은
bomSection(행)— PRODCODE 보다sect표식이 먼저다. 아티클 단위로 섹션을 묶어야 하는 부재(멍장류 vbox)는 prodcode 를 바꾸지 말고 sect 를 단다(prodcode 는 bodyForce 두께·네스팅이 쓴다). - 🔑 [코디] 인쇄 표(table-layout:fixed)의 첫 행이 colspan 머리면 둘째 행 th width 는 안 먹는다 →
<colgroup>으로 열 폭을 준다. 장 표기용 칩은 편집칸(vc) «밖» 형제로 — OV 키·저장값에 섞지 않는다.
🆕 신규 (2026-10-09 — 접합 2단계)
- 🔴 [코디] ES 모듈 최상위 상수 이름을 브라우저 전역(CSS·Image·Option·Node·Text·Event 등)과 같게 짓지 마라 — jointui.js
const CSS = ...가 window.CSS 를 가려CSS.escape가 TypeError(목록 세그먼트·키보드 선택 무반응). node 자체검사로는 안 잡히고 브라우저 E2E(pageerror)로만 잡힌다. - 🔑 [코디] pncad 에 새 자산 파일(.js)을 추가하면
tools/sync_to_pfs.py·tools/_deploy_hash.py·tools/_verify_deploy.py세 곳 FILES 에 같이 넣는다 — 빠지면 pfs 에 안 실려 모듈 링크 실패로 화면 전체가 빈다(jointui.js 선례). pfs 는 /production/pncad/* 일반 서빙이라 화이트리스트는 없다. - 🔑 [코디] 접합 규격표 끝 기호는
hasJointCfg(item, inst)일 때만 jointInfo 를 켠다 — 설정 없는 장은 종전 바이트(스냅샷 동일). 접합 E2E =tools/_e2e_joints.py [local|test](prod 모드 없음, 운영은 해시 대조로).
🆕 신규 (2026-10-09 오전 — 페페 QA 5건: 순서·라벨·표시색·열기 가드)
- [코디] 26.10.9 PNCAD 화면 수정을 pfs 사본(C:\dev\pfs\web\pncad)에 직접 위임하면 정본 C:\dev\pncad 와 갈라진다 — 오늘 app.html·editor.js 4커밋이 pfs 직행이라 dev-97 세션이 3-way 병합(a28e76b)으로 되돌려 맞췄다. 위임 --repo 는 항상 C:\dev\pncad → sync_to_pfs.py --check → 배포. 같은 날 여러 세션이 pncad 를 만지면 cross-session 메시지로 «schema.js 먼저 커밋 → 상대 rebase · 동기화·deploy-live 는 순차» 를 합의한다(26.10.9 dev-97↔dev-64 선례).
- 🔴 [코디] 모달 안에서 알릴 말은
msg()가 아니라toast(text, host)—msg는 사이드#msg라 열린 모달에 가려 «아무 반응 없음»이 된다(작업 현장 「열기」 무반응의 원인). 고를 자리 강조는nudge(el)(0.5초×2 · 재클릭 리플로우 · reduced-motion = 정적 1초). - 🔴 [코디] 배치 3D 재질 =
colorMat(hex, door)(캐시 키 = 색) · 색 =cmHex(colorNoOf 코드)— 칼라 목록이 다 모인 뒤 rebuild 끝에서 칠한다(자동 배정이 정렬순이라). 목재색matOf는 카탈로그 썸네일 전용으로 남았다.SCENE.colorMap은 작업본 data JSON 안(schema validateScene 은 모르는 키를 안 막는다 → 스키마 변경 0). 「새 현장으로 시작」 keep 목록에도 colorMap 이 있다. - 🔑 [코디] 배치 순서를 바꾸면 «인스턴스 번호»를 쥔 상태를 전부 같이 맞바꾼다 —
selInst· 파트 주소partSig(인스턴스|pkey) 인selPart·selMulti·라운드 초안RA.sig. 안 맞추면 rebuild 재탐색이 옆 장 부재를 잡는다. pkey 안에|가 있으니 첫 칸만 바꾼다. - 🔑 [코디] 이름 라벨 겹침 판정은
updateDimLabels()가 돌려주는 치수 사각형(put, 장 번호i포함)으로 한다 — 그래서 치수를 먼저 놓고 라벨을 놓는다. 빽빽한 전체화면에선 «옆 장» 치수와의 겹침이 남는다(0 불가) → 자기 장 치수 회피를 우선(점수 100:1). 검사 =tools/_probe_lblover.py(옛판 대비 집계) ·_e2e_qa1009.pyE1.
🆕 신규 (2026-10-09 — 라운드 설정 · 포인 자체 아티클)
- 🔴 [코디] 라운드 2차(S·E·곡선 종류) — 모서리 값 해석·저장·형상은 schema.js 한 곳: 해석
roundCorner· 저장 최소형roundVal/roundOwn(a=b 타원·접선 원호는 옛r로 접는다 = 기존 작업본·라벨·BOM 키 불변) · 윤곽roundOutline(앱 3D·규격표 그림 공용). 화면 입력 S·E 는 «그 변 반대쪽 끝에서» 잰 값이고 저장은 모서리 기준 a = W−S · b = D−E 다 — 새 출력 경로도 corners 의 a·b·s·e 를 읽고 다시 환산하지 마라. R 지정 원호는 R ≥ rIn(판 안 최소 R) 이어야 외접 재단 사각형 안에 든다(roundArc). - 🔑 [코디] 같은 날 여러 세션이 pncad 를 만질 땐 C:\dev\pncad 안 중첩 worktree(
git worktree add -b <br> _wt_x HEAD)로 격리 개발 → devplan 8799/pncad/_wt_x/app.html로 그대로 서빙(정션은 404 · 폴더는 OK) → 상대 커밋이 오면 stash·ff·pop → 끝에 ff 머지. E2E 는page.gotoURL 만/pncad/_wt_x/로 바꿔 돌리면 상대 모듈 경로가 따라온다. 운영 작업본 «바꾸기 전 vs 후» =_snap_scenes.py prod@/pncad/<폴더>(코드 = 로컬 판 · 데이터 = 운영 GET). - 🔴 [코디] 라운드 좌우는
schema.js roundLR한 함수로만 푼다 — 저장은 오픈/닫힘 상대 비트(inst.round), 3D 윤곽·BOM 라벨·규격표 비고·화면 위젯이 전부part.round.corners[].lr(= roundLR 결과)를 읽는다. 새 출력 경로(CNC·스티커 등)도 corners 를 읽고 오픈쪽을 다시 계산하지 마라. 개별 덮어쓰기 키는 pkey(존|면|순번)라 구조가 바뀌면 밀린다 → own 에kind를 같이 박고(다르면 무시) 고아 키는out.round.orphans로 보고 · 그 장을 고칠 때 정리. bom 키에 라운드 라벨이 붙는다(없으면 종전 키 그대로 = 기존 작업본 불변). - 🔴 [코디] 포인 자체 아티클은
data/poin-articles.json(convert 생성물 밖) — 앱·spec.html 이 «없는 id 만» 병합한다(DB 에 같은 id 가 생기면 DB 가 이긴다). sync_to_pfs·_deploy_hash FILES 에 같이 있다. ⛒Poin_오픈장프리셋 CP(오픈장_측판·천판·지판·뒤판_18T)는 PRODCODE 41/43/43.2/47 = 몸통규격표(≤10) 밖 마감 라인이라 규격표에서 통째로 빠지고, 오픈장_AS/FS·오픈장 뒤판 18T는 마스터 두께가 비어 주방 현장 15T·등판 스위치 3T 로 나온다 → 몸통 오픈장은 일반가구 18T CP 로 짠다. - 🔴 [코디] 26.10.9 타 세션이 pfs
web/pncad/app.html을 직접 고쳐 운영까지 냈다(3커밋) — pncad 원본과 pfs 사본이 같은 날 두 번 갈렸다. sync 전diff --strip-trailing-cr방향 확인은 «작업 시작 때 한 번»으로 모자란다 → pfs 커밋 직전에git fetch+git log -3 -- web/pncad/를 다시 보고, 앞서 있으면 pncad 에 3-way 병합(git merge-file, base = 역복사 시점)한 뒤 sync 한다(이번 = 충돌 0). 테스트 DB 엔 PNCAD 작업본이 0건 → test E2E 의 기존 작업본은 운영 GET 사본(C:\dev\_work\pncad-spec-1008\live_scene_*.json)을 물린다.
🆕 신규 (2026-10-08 밤 — 사이드메뉴 접기)
- 🔑 [코디] 접기 상태 localStorage
pncad.fold는 앱(app.html)과 편집기(editor.js)가 같이 쓴다 — 통째setItem(JSON.stringify(내 객체))금지, 매번 읽어서 그 키만 고친다(foldSet). 안 그러면 한쪽 저장이 다른 쪽 키를 지운다. 3D 뷰(#view·#edGraphic) 안에 버튼을 새로 띄우면pointerdown을stopPropagation한다 — 안 끊으면 클릭이 3D 픽킹(down→pointerup)으로 새서 선택이 풀린다. 검사 =tools/_e2e_fold.py [local|test|prod] [WxH].
🆕 신규 (2026-10-08 저녁 — 서랍 앞판 도어토글 · 서랍 옵션)
- 🔴 [코디] 앞면(도어·서랍 앞판) 판정은 역할 이름이 아니라
p.emit === false가 기준이다 — 서랍 앞판 역할은 CP 마스터상마이다라/도어|프론트|앞판/에 안 걸려 편집기 「도어」 토글·카드 썸네일에서 안 숨었다(메인 화면 gDoor 는 emit 기준이라 정상). 새 표시·필터를 만들면 editor.jsisFront()같은 판정을 쓴다. - 🔑 [코디] 서랍 fill 의
n(단 수)은 텐덤(·$변수) 경로에서만 먹는다 — 언더레일·볼레일은drawerBox1벌(트롤리001 볼레일 n=5 도 1벌). 텐덤은 D·H·T 를 안 보고 모델 마스터 치수. 편집기 서랍 패널이 이 구분대로 칸을 비활성한다. 서랍 정의 이름 → 레일·D·H·T =schema.js parseDrawerName(convert parseRail 과 동일 규칙).
🆕 신규 (2026-10-08 오후 — 분할 선반 부재 교체)
- 🔴 [코디] 분할재(선반·칸막이) 부재의
zone은 슬롯 주인이 아니다 — 일반 분할은 «자식 존»(0.2.1), 박스형(좌대)은 «자기 존» 주소로 나와 «끝 마디 떼기» 추정은 박스형·중첩 분할에서 남의 슬롯을 집는다. 슬롯 =part.divZone(schemaemit이 적는다, pkey 무관). 앱partSlot·편집기selectFromPart/paint가 이걸 쓴다 — 분할재를 다루는 새 코드도 divZone 으로. - 🔴 [코디] CP 교체 =
schema.js swapCP한 곳(자리 속성CP_KEEP= ref·inset·thkOv·trim·offs·hi 보존). 손으로{...parseCP(v), …}를 다시 짜지 마라 — 26.10.8 이전 편집기 교체가hi를 빠뜨려 우경첩 도어가 좌경첩이 됐다. ⚠resolveCP($변수 교체)는 아직offs를 안 넘긴다(verify2 영향 확인 전이라 미수정). - 🔑 [코디]
tools/_deploy_hash.py는 26.10.8 오후까지 editor.js 를 안 쟀다 — 편집기만 바뀐 배포를 «일치»로 통과시켰다. 지금 대조 = app·spec·schema·editor·droplist·lindiv·catalog·drawers. 배포 자산이 늘면 여기 FILES 도. - 🔑 [코디] 운영 E2E 에서 «저장» 경로를 보려면 PUT 을 본문만 캡처하고 abort → 그 본문을 spec.html 의 GET 응답으로 물린다(
tools/_e2e_divswap.pyhandler) — 서버 도달 0 으로 저장 포맷·규격표 반영을 같이 본다. 메인 앱 3D 에서 선반을 집으려면 도어를 끄고(#bDoor), 편집기에선 먼저 그 칸(공간)을 골라 밖 부재를 고스트(픽킹 통과)로 만든다.
🆕 신규 (2026-10-08 — 전역 변수 누락 · 좌우 측판 · OV 키 고아)
- 🔴 [코디] CP 파라미터가 참조하는
$변수(전역 마스터)는 작업본 vars 에 없다 —pv()는 «미정의 변수»를 조용히 기본값(0)으로 삼킨다. verify2 는 imos IDBVAR 스냅샷을 물려 맞았는데 앱·규격표는 이동선반 = 내경(imos 내경−3)이었다(학동매장). 고친 것 =schema.js CP_VAR_DEFAULTS(AS_LR여백·찬넬깊이·뒤판깊이 · 정본 data/vars.json 과 자체검사 대조). ⛒기둥목폭02는 넣지 마라 —postWidth()가 현장기둥목폭보다 먼저 봐 모든 기둥목이 68 이 된다. cp.json 에 새$변수참조가 생기면 이 표에 넣을지 본다. - 🔴 [코디] 엔진이 부재 치수를 바꾸면 규격표 수동수정값(OV
p|idx|부재|LxWxT)이 고아가 된다 — 키에 치수가 들어 있어서다(26.10.8 학동 #29 이동선반 8칸). 수정 보고에 «고아 키 목록 + 이관 계획»을 붙이고, 이관은 운영배포 뒤 승인 받아 한다(배포 전 이관 = 지금 운영에서 반대로 고아). 도구 =C:\dev\_work\pncad-spec-1008\_ov_rekey_plan.mjs(수정 전/후 엔진 pkey 대응) →_ov_rekey_apply.py [prod] [--save](DRY 기본·백업·재GET). - 🔑 [코디] 좌·우 측판 구분 =
schema.js sideLR(장 단위 L/R 치수 묶음 비교) — 부재명은 안 바꾸고 bom 행lr만 단다(본표 열·저장 키·nest_rows 불변). 규격표 칸 =cellLabel좌570*1766(공백 넣으면 칸 88px 에서176/6으로 쪼개진다 — web-designer 실측), 앱 항목 =roleLabel측판(좌).
🆕 신규 (2026-10-06 — 몸통/도어 경계)
- 🔴 [코디] 몸통(PNCAD 작업본)에서 «노출면 도어 동일마감» 판은 뺀다 — 홈바 니치 천판 · 파우더장 상부 니치(숨은EP·천판·거울판) · 신발장 노출측판/노출뒷판(=하부 FIX) · 복도장 FIX 440 · 수납 걸레받이 등 도면에 「도어 동일마감」·사양서 「휠라·EP = 18T MDF PET」 로 적힌 판은 도어(마감재) 쪽 부재다. 몸통에 같이 넣으면 몸통 원장(HS0868GP)과 도어 원장(IV075MT)에 이중 생산된다 → 몸통에서 빼고 도어 대조표(
C:\dev\_work\gs-daejeon-byeondong\door_result.md§4)로 넘긴다. 단 문틀 본틀 · 홈바 걸레받이 · 후면 케이싱은 몸통 수동행에만 두고 도어엔 안 넣는다(중복 0). 26.10.6 GS 대전 변동 #41~44 / 도어 #22.
🆕 신규 (2026-10-02 — 장별 몸통두께 · 이력 게이트 · 저장된 nest_rows)
- 🔴 [코디]
thkOf이력 게이트(alt)는 «현장 스위치»를 imos 와 맞추는 장치다 — 장별 두께(inst.bodyThk)에 그대로 태우면 이력 적은 CP 가 안 바뀐다. 26.10.2 GS 안산 유리도어 상부장 고정선반주방_FS_Rv39(이력 9건, 18T 없음)가 18T 장에서 15T 로 남아 네스팅에 「15T PB <별도칼라>」 원장. 고친 것 =expand(opts.bodyForce)→ 몸통 판재 PRODCODE 01~05 만 게이트 없이(matOf도18T_PB). 앱instOpts·spec.html digest가bodyForce: !!inst.bodyThk를 같이 넘긴다 — 새 전개 경로를 만들면 이 옵션도 넘긴다. verify2/3 는 안 넘겨 회귀 0. 검사 =node schema.js(6항) ·tools/_e2e_bodyforce.py [local|test|prod] [scene]. - 🔴 [코디] 원장(칼라) 넘버 규칙은
schema.js colorNoOf한 곳이다 — 몸통·등판 모두 장별 칼라 > 현장 넘버 > 현장 색상(페페 결정 26.10.2 「동일칼라로 진행」: 유리도어 상부장 등판 = 그 장 칼라, 두께는 현장 등판두께). 앱matNoOf는 이 함수만 부른다 — 새 산출 경로(TSV·발주 등)를 만들면 직접SCENE.backMat를 읽지 말고 이걸 쓴다. 뒤판각목은 18T 장에서도 15T + 장별 칼라(페페 결정 = 그 칼라 15T 원장을 산다). - 🔴 [코디] 네스팅은 «저장 시점»
nest_rows를 읽는다 — 엔진(schema.js)을 고쳐도 운영 작업본은 재저장 전까지 옛 행이다. 엔진 수정 보고에 «영향 작업본 목록 + 재저장 필요»를 붙인다. 판정 =C:\dev\pfs\scripts\_pncad_nest18_probe_LIVE.py(ssm_run · SELECT 만) 처럼 운영 nest_rows 를 직접 읽는다(/api/nesting/pncad-parts는 POST 라 운영 E2E 에선 피한다). 재저장은 승인 후C:\dev\pncad\tools\_scene_resave2.py(backup→dry→save→verify · 판정 = data·메타 불변 + 허용 변경만) 를 쓴다.
🆕 신규 (2026-10-02 — 도면→작업본 POST 2차: GS 원주 단구)
- 🔴 [코디] 장별 칼라(
inst.color)는 규격표 본문 어디에도 안 찍힌다(머리말 「색상」 하나뿐) → 칼라·두께가 현장 기본과 다른 장은 POST 전에 제품명에 「18T」·「(PQFA2T)」 를 넣는다(POST 뒤엔 PUT = 승인 필요 · 26.10.2 #38·#39 의 PQFA2T 7장은 이름에 칼라가 빠져 ⏳). 🔑한샘 GS 도면은 도면폼 블록의 「TYPE TITLE」 텍스트 위치로 서브시트(12728.5×9000)를 자동 분할하면 시트 35장이 한 번에 잘린다(C:\dev\_work\gs-wonju-dangu\pncad_1002\_sheets.py all· ezdxf 로거를 ERROR 로 내려야 렌더가 안 느리다) · 검증은 도면 입면 위에 장 박스를 겹쳐 그린다(_elev_compare.py). - 🔴 [코디] 네스팅 합산 숫자를 보고할 땐 «부재 매수(조각)»와 «원장(장)»을 갈라 적는다 — 26.10.2 「15T PB 276 (매수)」를 페페가 원장으로 읽어 「곱셈 중복」 의심 → 재검산 결과 중복 0(직접 센 부재 = nest_rows 8/8,
C:\dev\_work\gs-wonju-dangu\pncad_1002\_qtycheck.mjs), 원장은 네스팅 실행값 49장(_nestrun.py= 운영 네스팅 화면 읽기 실행, 저장 안 함). (양단면 「단면」 고정은 같은 날 아래 줄로 해결) - 🔴 [페페 확정 26.10.2] 몸통자재는 등판 MDF 포함 전부 양면 계산 — 단면 예외 없음 —
app.html nestRows의 양단면 =sideOf() → '양면'(19T 도어류만 원래대로 제외). 네스팅 발주 규격 「민자양면」(PB·MDF 모두)·시트길이 = 원장×길이×2 · 원장 장수·총 매수 불변. [코디] pncad9038ca0· pfs master84eea4f9· 운영1cbf13f3(같은 날 1차eedd6640은 등판 단면을 남겼다가 페페 지시로 예외 제거). ⚠저장된 nest_rows 는 저장 시점 값 → 배포 전 저장본은 다시 저장해야 양면이 된다(26.10.2 원주 #37~39·네스팅 #13 만 반영, 나머지 #2·3·27~36 · 네스팅 #8·#12 는 단면 그대로 — 영향 목록C:\dev\_work\gs-wonju-dangu\pncad_1002\_impact_list.py).
🆕 신규 (2026-10-01 — 규격표 «로딩 안 됨» = 모듈 캐시 버전 어긋남)
- 🔴 [코디] 「배포 직후 화면이 빈 채로 안 뜬다 · API 호출 0」 = 브라우저가 쥔 옛
schema.js와 새 HTML 의 export 불일치부터 의심한다. 26.10.1 학동매장: HTML 은 관리자에게no-store(QA 주입기)라 늘 새것인데schema.js·lindiv.js·editor.js·droplist.js·data/*.json은 Flask 기본public, max-age=43200(12h)이라 서버에 묻지도 않았다 →does not provide an export named 'useDrawers'. 판정 = 운영 nginx 로그에서 그 IP 의spec.html200 뒤/api/pncad/*0건(C:\dev\pfs\scripts\_pncad_spec_load_logprobe2.py를ssm_run.py로) + Playwright route 로 옛 schema.js 주입 재현. ⛒고친 것 =C:\dev\pfs\web\pfs\default\controller\default_controller.py_send_revalidate()(PNCAD 페이지·자산·네스팅 =Cache-Control: no-cache, 304 재검증) — pfsc6dfd7f9/운영001a4d69. ⚠헤더를 바꿔도 이미 캐시된 옛 응답은 옛 수명대로 산다 → 배포 당일엔Ctrl+Shift+R안내. 새 send_from_directory 화면(ES module)을 만들면 이 헬퍼를 탄다. 검사 =C:\dev\pncad\tools\_e2e_cache_reval.py [test|prod] [scene](헤더 + 같은 프로필로 두 번 열어 schema.js 304 · 운영 10/10).
🆕 신규 (2026-10-01 — 텐덤 서랍 후판·바닥판 · 분할폴백 폐지)
- 🔴 [코디] imos
dranglelem오프셋은 식 칸(STAOFFFOR·ENDOFFFOR·BSHOFFFOR·TOPOFFFOR·BOTOFFFOR)이 정본, 숫자 칸(STARTOFFS…)은 옛 캐시다. 그라스 V8 = 숫자 −37.5 · 식 −16.5 → imos 실부재 Wz−33(식). 세바 디올·그라스 스칼라·헤펠레도 같다. 숫자 칸으로 뽑으면 텐덤 대조 49% · 식 칸으로 79%.anglelem(존 면)에도 같은*FOR칸이 있다 — 다른 오프셋을 옮길 때도 식 칸부터 본다. 재료 =tools/pull_drawer_geom.py→tools/mkdrawers.py→data/drawers.json. - 🔴 [코디] LINDIV 세그먼트보다 자식이 많으면 «내용이 있어도» 자른다(imos 동작) — convert 「분할폴백」(LINDIV 버리고 균등분할)은 폐지. 폴백 7종 전부 상승·회귀 0(B2D 67.5→79.3 · 15T_멍장 6.7→33.3 · 전체 61.5→63.5%). B2D 가 서랍 3단+빈칸으로 나오던 원인.
- 🔑 [코디] 텐덤 모델을 모르면 임시 파트로라도 판을 뽑는다 = imos 전역 기본값
텐덤_미확정_H65mm(schema.js 내장본 — 마스터 미로드여도 나온다). 임시 파트만 존 깊이에 맞춰 박스 깊이를 480/430/380/330/280 사다리로 줄인다(모델 지정 시엔 imos 처럼 안 건드림).part.provisional·part.drawer로 표시. - ⚠ [코디] 엔진을 고쳐도 네스팅(
/api/nesting/pncad-parts)은 안 바뀐다 — 작업본의nest_rows는 «저장 시점» 계산본이다. 부재가 늘거나 바뀌는 수정 뒤엔 기존 작업본을 앱에서 다시 저장해야 네스팅에 반영된다(26.10.1 scene 32~36 해당). - 🔑 [코디] 배포 해시 대조 =
tools/_deploy_hash.py [test|prod] --wait N— 서버가 HTML 끝에 qa_helper<link>·<script>2줄을 주입해 그대로 재면 app/spec 이 늘 불일치로 나온다(대조기가 그 두 줄을 빼고 잰다). 배포 직후 로그인 502 는 uWSGI 재시작(재시도).
🆕 신규 (2026-10-01 — 도면→작업본 API 생성)
- 🔑 API 로 작업본을 POST 하면 nest_rows = [] → 운영 앱에서
PC2.storeScene(true)(저장 버튼과 같은 함수)로 저장해야 앱 계산값이 들어간다. 실례 =C:\dev\_work\gs-ansan-seongpo\pncad_0930\_post_scenes.py(#32~36). - 🔴 네스팅 「💾 저장」은 칼라 마스터에 없는 이름을
poin_colors에 자동 INSERT 한다(nesting.htmlregisterNewColors) → 미등록 칼라가 섞인 작업본은 칼라 등록 승인 전에 nesting_job 을 저장하지 마라. - ⚠ 장별 18T(bodyThk)는 규격표 부재 칸에 두께가 안 찍힌다(머리말은 15T) → 15T 로 오재단할 수 있으니 제품명에 「18T 칼라」를 붙이거나 표시 방법을 따로 정한다(미결). [코디]
🆕 신규 (2026-09-29 — 규격표 스티커 출력)
- 🔴 [코디]
spec.html은 26.9.28 부터 pfs 사본(C:\dev\pfs\web\pncad\spec.html)이 앞서 있었다 — 그날 커밋 5건이 pfs 에서 직접 고쳐져C:\dev\pncad\spec.html이 46줄 낡은 채였다(여기서sync_to_pfs.py를 돌렸으면 합치기·하단메모가 운영에서 사라졌다). 26.9.29 pfs→pncad 로 역복사해 맞췄다(97edae3). ⛒sync 전에diff --strip-trailing-cr로 방향부터 본다. 같은 날--check에app.html·schema.js도 어긋남으로 나온다(내 작업 아님 — 확인 후 처리). - 🔑 [코디] 규격표 페이지에서 100mm 스티커를 인쇄할 땐
@page크기와 «여백»까지 주입 CSS 로 덮는다 — 규격표 자체@page{A4 landscape; margin:5mm 2mm 5mm 11mm}가 남으면 앱 PrintSettings 여백(5mm)을 덮거나 A4 를 라벨에 맞춰 축소한다. 주입<style>을 head 끝에 붙이면 뒤 선언이 이긴다. 검사 = Playwrightpage.pdf(prefer_css_page_size=True)의 mediabox 283pt(=100mm)·쪽수(_e2e_specsticker.py). - 🔑 [코디] 몸통 스티커 양식 정본 = imos RDL
002 몸통 바코드— 사무실PCBINDATA(INTERNTYPE='RDL')에서 뽑은 원본 =C:\dev\pncad\reference\imos-rdl\(001 규격표·002 몸통 바코드·003 부자재 바코드 +rdl_dump.py). 뽑는 법 =ssh poinimos01+ PowerShellSqlClient(NAME LIKE '002%'로 한글 리터럴 회피) →%TEMP%에 바이트 저장 → scp. 002 는 실물 1개당 1장(커서로 TOTAL 만큼 복제, 규격 끝(k/N))이다. 🔴스티커 칸 글자 맞춤은 «가운데 정렬 flex» 라scrollWidth가 왼쪽 넘침을 못 본다 → 자식 줄 폭으로 재고, 자식 글자는em으로 둬야 칸 축소가 먹는다.k|키 = site·type2·name·ura·dim·note(26.9.29 첫판의 type·pos·items 는 폐기 키). - 🔑 [코디] 한샘 스티커 로고(26.9.29) =
company_logo/1.jpg가 이제 HANSSEM — 파레트 전용company_logo/pallet/1.jpg와 같은 파일이 됐다(sticker_controller.py의 「기본 로고가 HANSSEM NEXUS」 주석은 옛 얘기). 옛 로고 =C:\dev_attic\2026-09-29\pfs-company-logo-hanssem-nexus\1.jpg. 두 로고가 1376×170 으로 크기가 같아 검사는 «오른쪽 18% 띠 잉크 0» 으로 가른다. - 🔑 [코디] 스티커 수정값은 규격표 OV 에
k|<배치idx>|site·type·pos·items로 저장 — 새 테이블 없이 되돌리기·수정목록·서버저장을 공짜로 얻는다. 기본값은h|·a|수정값이 먼저 먹고,k|가 있으면 그게 더 세다.
🆕 신규 (2026-09-28 — 규격표 측판 칸 순서)
- 🔴 [코디] CP 의 PART_V(
vertical) 플래그를 규격표 칸 순서에 그대로 쓰면 측판이 뒤집힌다.주방가구_측판_15T만 PART_V(n=2990)라cellText가 측판을 LW = 높이깊이(734*570)로 냈다 — 열 라벨 정본(Textbox50)은 측판 = 깊이높이. 우리 측판은 l=높이·w=깊이라 측판은 PART_V 여도 WL*(schema.js cellText, 자체검사 2줄). app.html 부재치수 모달의 복제cellOf는 지우고cellText하나로 통일. 기존 수동수정값(좌/우 접두어) 17칸(scene 29)은C:\dev\pfs\scripts\_pncad_side_ov_flip.py(o 가 옛 순서인 측판 키만 WL 로, DRY 기본)로 이관. 운영3575e78(26.9.28(월) 08:3x KST). - 🔑 [코디] 규격표 수량 줄 = 제 부재 치수 블록의 «마지막 아랫줄»(페페 규칙 26.9.28). 부재마다
.pb(치수 칸 = 줄+메모)로 감싸고alignQty()가 짝 수량.pb에 같은 min-height(flex-end), 칸전체 메모 몫은 수량 꼬리.pb.tl. 호출 = paint·input·focusout·메모버튼·beforeprint(.pmeasure로 no-print 빼고 재측정)/afterprint. ⚠min-height 는 지우고 잰다(누적). ⚠좁은 수량 칸은 고친 값 뒤 ↺ 가 접혀 한 줄 늘어난다(화면만) →td.pc:has(> .pb.tl) .ln{white-space:nowrap}. 구조가 바뀌어_e2e_specmemo.py의:scope > .ln은:scope > .pb > .ln. 검증 =C:\dev\pncad\tools\_e2e_specqty.py [test|prod] [scene](저장 안 함). 운영6ac7484+b855f2a. - 🔴 [코디] 규격표 OV 키
역할|LxWxT는 유일하지 않다 — bom 집계키는 CP·자재까지 보지만 저장 키는 치수만 봐서, 같은 칸에 치수가 같은 부재 둘(운영 scene 29 천판863x60x18등)이 메모·수정값을 공유했다(페페 「아래 칸에 위 메모가 복사」 26.9.28). ⛒dimIds()로 두 번째부터~2·~3접미사, 첫째는 옛 키 유지(저장값 보존) — 본표·부자재 공통. 새 편집키를 만들 땐 한 시트 안data-k중복 0 을 검사(_e2e_specqty.py「편집칸 키 중복 0」). 운영8c88d30. - 🔑 [코디] 규격표 로드 7.1초의 94% = 머리말용 전체 현장목록
/api/field/(1.4MB·6.6초) → 쓰는 현장만/api/field/<no>(45ms) 단건 + 부트스트랩 4개Promise.all= 0.4~0.6초(운영 scene 29,C:\dev\pncad\tools\_perf_spec.py). 렌더 자체는 43ms 라 DOM 최적화는 불필요했다. 운영07c8d29. - 🔴 [코디] spec_override 저장은 «통째 PUT»이다 — 열린 탭이 있으면 서버에서 넣은 값이 그 탭의 다음 저장에 지워진다(26.9.28 실측: scene 29
~2밴드 복원 3건이 페페 탭 저장 한 번에 소멸 → 재적용). DB 로 OV 를 고칠 땐 페페에게 먼저 탭을 닫거나 새로고침하게 하고 넣는다. 근본 해결 = 키 단위 PATCH(병합) — 미착수. - 🔑 [코디] 규격표 배치 합치기 = 규격표 OV 전용 가상 아티클(페페 그릴링 26.9.28: 행만 합치고 치수는 손으로 · 이름/치수 「A + B」 · 번호칸 체크+상단바 「합치기 (n)」 · ⤺ 풀기). 묶음 = OV
x|merge(JSON [[idx,…]]), 합친 행 idx =1+2문자열이라 모든 키 빌더(a|·p|·n|·s|)가 자동으로 별도 네임스페이스 — 원래 배치 수정값과 안 섞이고 풀면 그대로 복귀. 합친 행 초기값 = 각 배치의 «OV 반영» 값(effParts 는 mainHtml·subsHtml 과 같은 dimIds 키 규칙으로 읽는다 — 규칙을 바꾸면 여기도). 부재명+치수+두께꼬리표 같고 수량이 정수면 합산. 메모도 orig 로 물려받는다. 씬 불변. 최하단 메모 =.subs안.foot(f|memo)라 slice 가 같이 잰다(부자재 0개여도.subs는 항상 나온다). 검증 =C:\dev\pncad\tools\_e2e_specmerge.py(테스트 전용 20항목). 운영e511f28. - 🔴 [코디] 정렬 검사는 «줄(.ln) 바닥»이 아니라 «글자(.v·.tt·메모) 바닥»으로 잰다 — 화면에서 ↺ 가 혼자 다음 줄로 접혀 빈 줄이 부재 블록 마지막 줄이 됐는데(수량이 ↺ 줄에 붙음)
.ln바닥 검사는 0px 로 통과했다(페페 재확인으로 발견 26.9.28). 처방 = ↺ 를position:absolute오른쪽 위 뱃지로. ⛒padding 으로 자리를 비우면 폭이 줄어 두께 꼬리표가 대신 다음 줄로 밀린다(운영 실측). 허용 4px = 같은 줄 9pt 꼬리표·11pt 값 차이. 치수를 비우면 꼬리표도 숨김.v:empty ~ .tt. 운영040d848. - 🔑 [코디] 규격표 A4 활용(26.9.28) — 운영 scene 29 는 5번째 행이 2.6px 모자라 다음 장으로 밀리고 하단 메모(85px)가 3쪽을 통째로 먹었다. 여유를 깎던 두 상수:
CONT_H(「계속 →」 흐름 한 줄 23px → 0,.cont.pgc를 시트 아래 여백 4mm 안 절대배치) ·SAFE(9.4px → 1.9px). 행 없는 장은 빈 표머리 생략. 화면.sheet{min-height:20cm}. 결과 3장→2장. 검증은 «시트 수 = 인쇄 PDF 쪽수»(Playwrightpage.pdf로 운영 상위 8개 작업본 넘침 0) — SAFE 를 더 줄이거나 여백을 바꿀 땐 이걸 다시 돌린다. 운영a3c2aba.
🆕 신규 (2026-09-19 — 트리 행 복사·붙여넣기)
- 🔴
editor.js맨 위 CSS 블록은 «템플릿 리터럴 안»이다 — 주석에 백틱을 쓰면 거기서 문자열이 끊긴다. 부재정의 이름을 백틱으로 감싼 주석 한 줄에 모듈 전체가Unexpected identifier로 죽었다. 증상이 고약하다 — 화면이 백지가 되는 게 아니라 그냥 안 뜨고(window.PC2미설정) E2E 는wait_for_function30초 타임아웃으로만 보인다. ⛒파일을 고칠 때마다 문법검사 한 줄을 먼저 돌린다:node --input-type=module -e "…import('data:text/javascript;base64,'+…)"(import/export 줄을 지우고 base64 로 실어 평가). 눈으로는 절대 안 잡힌다. [코디] - 🔴
initCatalogUI는 1,800줄짜리 한 스코프다 — 새 상수 이름은 반드시 먼저 grep 한다.KIND_NAME을 새로 선언했더니 뒤쪽 묶음백업 목록이 이미 쓰고 있어already been declared로 죽었다(→ROW_KIND). 위 문법검사가 이것도 같이 잡는다. [코디] - 🔑 편집기 안에서
ctx.msg()로 알려도 사람은 못 본다 —#msg는 우측 사이드바라 편집기 모달(#edWrap)이 덮는다. 편집기 안 피드백은state(txt)(헤더#edState)를 쓴다. 기존 「부재정의 마스터에 없다」 경고도 같은 이유로 안 보이고 있었다. [코디] - 🔑 덮어쓰기 기능에 확인창 대신 «1단계 스냅샷»을 둔다(페페 26.9.19 건 · 코덱스·web-designer 같은 결론).
structuredClone(ED.item)한 판이면 충분하고, 다른 편집이 한 번 들어오면 버린다(touch()안에서UNDO = null) — 안 버리면 되돌리기가 그 뒤에 손댄 값까지 같이 날린다. 되돌리기 버튼은 그 행에서만 노출해야 일반 편집 취소로 오인하지 않는다. [코디] - ⚠ 행에 붙이는 아이콘은 절대배치로 넣는다(
.row{position:relative}+.racts{position:absolute;right:2px}). flex 로 넣으면 행 폭을 밀어 트리 가로스크롤이 생긴다. 비활성은 nativedisabled속성만 —pointer-events:none을 주면 «왜 안 되는지» 툴팁이 안 뜬다. 아이콘 뜻은 PFS 관행 = ⎘ 복사(원본을 뜬다) · ⧉ 붙여넣기(복사해서 어딘가에 넣는다)(견적btn-copy-item· 자재관리loc-copy). [코디] - ⚠ 로컬 8799 는 PFS API 가 없어
/api/*가 전부 404 — 콘솔 에러 검사에서 걸러야 오탐이 없다. 카탈로그 없이도 편집기는 뜨므로 트리를 직접 시드하면 배포 전 로컬 검증이 된다(_e2e_rowcopy.py의SEED). [코디]
🆕 신규 (2026-09-18 2차 — 도어 좌우 · 인스턴스 그룹 변환 · 치수 배치)
- 🔑 「좌우가 있는 것」은 이름이 아니라 «위치»가 정본이다. 더블도어 세트는 CP 이름이 하나라
두 짝을 못 가른다(실측: DDO 179면 중 좌우가 이름·HI 로 적힌 건 3면). 좌짝=좌경첩 · 우짝=우경첩을
emitDoorSet이 짝을 낼 때 박는다. 이름 꼬리표(_L/_R)는 단문에서만 근거다. - 🔴 imos 컬럼을 «안 실어서» 생긴 오표시가 있다 — 새 표시 요구가 오면 원본 덤프부터 센다.
anglelem.DOORHISIDE(1=좌 2=우)가 도어면 73개에 있는데cp.json엔 좌우 필드가 0이었다. 이름 꼬리표만 보던 판정이 도어면 623 중 438(70%)을 좌경첩으로 떨어뜨렸고 그중 35면은 실제로 우경첩. ⛒조사 먼저(tools/_probe_door_lr.mjs), 고치기는 그 다음. 「자료들이 그 부분을 따르고 있다」는 페페 말이 단서다. - ⚠ 그 컬럼은 도어면에만 뜻이 있다 —
DOORHISIDE는 측판·선반 행에도 값이 들어 있다.PARTTYPE='D'로 게이트를 안 걸면 엉뚱한 부재에 좌우가 붙는다. - 🔴
isDoor()(PRODCODE 21 경계)와 「역할 이름에 도어가 들었나」가 어긋나는 CP 가 있다 (TL_SDO_L_HND— 도어로 그려지는데 부재로도 잡힌다). 화면 표시는 그려지는 쪽 기준으로 채우고 (mkPart한 곳),isDoor는 건드리지 마라 — 그건emit을 정해서 BOM 이 움직인다. - 🔑 CP 를 오더변수로 갈아끼우는
resolveCP는 「면이 들고 있던 값」을 명시적으로 옮겨야 한다 (inset·trim·thkOv·hi). 빠뜨리면 변수 하나 바뀌는 순간 우경첩이 조용히 좌로 돌아간다. - 🔴 인스턴스에 회전을 넣으려면 부재 좌표를 월드 → 그룹 로컬로 내려야 한다(26.9.18 실행).
그룹 원점 = 발자국 중앙·바닥이라야 회전이 제 몸 가운데를 돈다(코너를 원점으로 두면 90° 돌릴 때 멀리 날아간다).
⚠ 그러면
gBody.children이 부재가 아니라 인스턴스 그룹이 된다 —.children.find(o => o.isMesh …)꼴을 쓰던 곳이 전부 조용히undefined를 문다(E2E 4본이 그렇게 깨졌다). 부재 전량은PC2.parts()로 받는다.m.position도 로컬이라 화면좌표를 낼 땐localToWorld/getWorldPosition를 거친다. - ⚠ 레이어 토글(
gDoor.visible)은 부모가 아니라 «레이어 그룹»에 걸린다 → 「진짜 보이나」 판정은parent.visible이 아니라 조상까지 훑는shown(). 한 겹 늘어난 순간 툴팁이 도어를 끈 뒤에도 떴다. - 🔑 자석은 치수(
dim.w/h)가 아니라 월드 AABB 로 문다. 회전한 장은 폭·높이가 그대로가 아니다. (코덱스 상의 결론도 같다 — AABB 로 후보를 내고, 정밀 간섭판정이 필요할 때만 OBB.) - 🔑 카메라 따라 바뀌는 표시는 «6면으로 이산화 + 히스테리시스»가 정답(코덱스 상의 확인). 매 프레임 재계산은 낭비고, 경계에서 상태가 떨릴 때 치수가 깜빡인다(0.06 마진). 치수는 그 장의 로컬 축에 투영해 면을 고른다 — 그래야 회전한 장도 제 기준으로 잡힌다.
- 🔑 치수 겹침은 「단(段) 배치」로 풀고, 되풀이는 「한 번만」으로 푼다(제도 관례 · furniture-expert). ①치수 요청을 전부 모았다가 한 번에 단 배정(장 하나만 보고는 못 푼다) ②짧은 구간부터 안쪽 단 ③같은 벽·같은 구간·같은 값은 한 벌만, 남기는 건 가장 바깥 것. ②만 넣으면 하부장 3통에 「672」가 3벌 겹쳐 찍힌다(E2E 는 통과했고 스크린샷에서 잡혔다 — 치수 배치는 눈으로 한 번 봐야 한다).
- ⚠ 뷰큐브의 평면·저면은 방위각을 못박아야 한다 — 시선이 up 과 겹쳐 roll 이 안 정해진다.
(0.001, 1, 0.001)처럼 x·z 를 같이 주면 방위각 45° 가 돼 글자가 마름모로 눕는다 → 앞쪽 한 축만 실눈만큼. - ⚠ 뷰큐브는 「지금 보이는 면」만 눌린다(뒷면·모로 선 면은 못 누른다 = 공통 성질).
기본 3/4 각에서 보이는 건 우·평면·정면뿐 → 「⤢ 맞춤」·「⇄ 반대면」 두 버튼을 같이 둬야 6면에 다 닿는다.
E2E 에서 정축 뷰의 옆면 클릭이
intercepts pointer events로 30초 타임아웃 나는 게 이 성질이다. - ⚠ 치수 라벨 폰트를 올리면 겹침이 늘어난다 — 11.5→13px 은 엇갈림 배치와 한 벌로 나가야 한다
(폰트만 올리면 지금보다 더 지저분해진다 · web-designer 경고). 라벨 폭은 글자로만 정해지니 한 번만 재서 캐시한다
(매 프레임
offsetWidth를 읽으면 레이아웃을 강제한다).
🆕 신규 (2026-09-18 — 규격표 메모(셀값과 별도) · 「규격표 로드」)
- 🔑 「셀값과 별도로 메모」는 OV 에 접두어 하나만 더하면 끝난다. 저장(
PUT …/spec)·되돌리기·#edits수정목록·세로 페이지 분할이 전부 키-값 맵 한 벌을 보고 돌기 때문에, 메모 키(n|아티클|부재명|치수· 칸 전체는n|…|*)를 새로 내고vc(no, key, '', lab, 'mv')로 칸을 내면 새 배선이 0이다. ⛒치수·수량 칸(p|)에 전달사항을 적게 두면 규격 자체가 바뀐다 — 그래서 별도 키가 목적이고, 기존 「빈 칸 메모」(m|)는 그대로 둔다(셋의#edits라벨을 「부재메모 / 칸 전체 메모 / 메모」로 갈라야 목록에서 구분된다). - 🔴 빈 칸 클릭용
td .v:empty규칙이 (0,2,1)이라.mv:empty(0,2,0)를 이긴다 — 메모 숨김은td .v.mv:empty로 한 급 세게 써야 한다. 안 그러면 빈 메모칸이 본표 전 칸에 회색 박스로 깔린다(실측 84칸). 이 파일에 CSS 를 더할 땐 기존 셀렉터의 «요소 선택자 1개»를 먼저 센다. - ⚠
display:block컨트롤을 칸에 넣으면 행 높이가 바뀐다 → 실측 2패스 장 분할이 어긋난다. 칸 하단 메모 버튼·메모 한 줄이 둘 다 해당 → 토글 클릭과 메모 입력 뒤에redraw()를 다시 태운다(fCols.onchange와 같은 패턴). 토글을 켠 채 인쇄하면 실측이 버튼을 포함해 넉넉하게 잡히므로 넘칠 일은 없다. - 🔑 메모는 「장식 태그」가 아니라 재단자가 읽는 본문이다 → 두께 꼬리표(
.tt9pt)와 달리 10pt(13.3px)로 13px 하한을 지킨다. 값과의 구분은 색이 아니라 기호(::before{content:'※ '}+ 이탤릭) — 흑백 프린터·색약에서도 산다.::before는textContent에 안 섞이므로 저장값이 더러워지지 않는다(꼬리표를 편집칸 «밖»에 두는 것과 같은 이유). - ⚠
vc()가 딸려 내는 되돌리기↺는 메모에 뜻이 없다(원본이 늘 빈 값) →.v.mv + .rst{display:none}. 지우는 법 = 글자를 비우면setOv가 키를 지운다(별도 ✕ 버튼을 만들지 마라). - ℹ 메모 키가
p|와 같은 「부재 치수 조합」이라 같은 부재명·같은 치수가 한 아티클에 둘 이상이면 키가 겹친다 — 기존 치수 오버라이드(p|)가 원래 갖고 있던 성질을 그대로 물려받은 것이다. 고칠 거면 둘을 같이 고친다(pkey순번축 도입). - 🔵 미적용(13px 하한 옛 위반) =
#pick표 12px ·#edits표 12px · 본표 머리행th9pt/8pt(imos RDL 원본). 지시 범위 밖이라 손대지 않았다 — 페페 판단.
🆕 신규 (2026-09-17 3차 — 규격표 줄 간격 · 배치 경계 페이지 분할 · 빈칸 메모 · 페이지 표시)
- 🔑 「헤더가 장마다 반복되나」는 «구조»로 이미 정해진다 — 시트마다
headHtml()을 부르면 공짜다. 세로 분할을 넣을 때 그 구조를 지켰기 때문에 추가 작업이 0이었다. 다만 전에는 그걸 재는 검사가 없었다 → E2E 에 「장마다.hd· 현장명 값 ·thead」 세 줄을 넣어 두면 다음에 구조를 건드릴 때 바로 걸린다. - ⛒
.hd .who(담당·시각·작업본번호·페이지) 는 머리말 칸(.b, top .42cm) «위» 띠에 얹혀 있고overflow:hidden이 없다. 세로로 키우면 칸을 덮고(실측 여유 1.4px), 가로로 길어지면 잘리는 게 아니라 옆으로 흘러넘친다(실측 295/295px = 꽉 참). 여기에 뭘 더 얹기 전에 두 값을 먼저 잰다.justify-content:flex-end라 넘치면 왼쪽(담당)부터 밀린다 — 중요한 건 오른쪽 끝에 둔다. - ⚠ 글자를 키울 땐
line-height를 같이 내려라 —.pgno8pt→10pt 만 올리면 기본 줄높이(1.45)가 14.7px 를 만들어 칸을 0.4px 덮는다.line-height:1로 13.3px 가 되어 1.4px 남는다. 인쇄 서식에서 폰트 한 급은 곧 줄높이 문제다. - ℹ 「표시되게 해줘」 = 지금 안 보인다는 뜻일 때가 있다. 페이지 번호는 원래 있었는데 8pt 로
(1/2)라 페페가 못 봤다 → 자리는 그대로 두고 한 급 키우고 굵게, 괄호 빼고1/2. 그리고pages > 1조건을 뺐다 — 「번호 없음 = 1장」을 사람이 매번 판정하게 두지 않는다.
🆕 신규 (2026-09-17 3차 — 줄 간격 · 배치 경계 페이지 분할 · 빈칸 메모)
- 🔴 인쇄 서식의 「세로 페이지 분할」은 CSS 로 못 한다 — 머리말이 표 «밖» 형제
div이기 때문이다. 인쇄 헤더 반복은 진짜<thead>(display:table-header-group)에만 걸리는 메커니즘이라.hd(현장·거래처·결재칸)는 CSS 로는 절대 장마다 안 찍힌다 → 종이가 흩어지면 어느 작업본 건지 알 수가 없다. JS 2패스가 정답 = ①행 전량 렌더 ②await document.fonts.ready뒤 행마다getBoundingClientRect().height누적(폰트 붙기 전에 재면 높이가 바뀌고,border-collapse라offsetHeight합은 표 높이와 안 맞는다) ③예산 넘기 직전에 컷 → 재렌더. 2차는 열 폭·글자크기가 같아 행 높이가 안 변한다(autoFit 도 1차에서 이미 결정된다). 안전망으로table.mx tr{break-inside:avoid}를 같이 건다. - 🔑 한 장 예산 =
(21 − 0.5 − 0.5 − 0.2 − 0.4)cm × 37.7953px− 머리말 − 표 위여백 −thead실측 − 「계속 →」 − 여유 0.25cm. 앞의 상수는 A4 가로 21cm −@page상하 여백 −.pad상하 패딩이다.thead·머리말·부자재는 상수로 박지 말고 DOM 에서 재라(부자재 높이는 부재 수마다 다르고, 머리말 좌표는 또 바뀐다). - ⛒ 열 분할과 행 분할을 넣으면 「마지막 장」에 매달린 것 셋을 전부 다시 계산해야 한다 — ①장 번호
(n/N)②「계속 →」 ③부자재 블록이 붙는 장. 안 고치면 「3/2」가 뜨고, 행으로 갈린 중간 장에 「계속 →」이 빠지고, 부자재가 엉뚱한 장에 붙는다. 번호·「계속」은 잘라 놓고 «나중에» 작업본 단위로 한 번에 매기는 게 안 틀린다(s.cont = s.pageNo < s.pages). - 🔴 「빈 칸인가」를
textContent로 재지 마라 — 숨은 되돌리기 아이콘(↺)이 셀 텍스트에 잡힌다.display:none이어도textContent에는 들어간다 → E2E 가 「빈 칸 0개」라고 계속 거짓 보고했다(26.9.17 실측). 판정은 키 접두어로 한다(.v[data-k^="m|"]= 부재 없는 칸에 낸 메모칸). - 🔑 부재가 없는 칸은
hits.map().join('')이 빈 문자열을 내<td></td>가 된다 — 입력칸 자체가 없어 클릭이 안 먹었다(운영 실측 576칸 중 228칸 = 39.6%). 빈 칸에도vc()한 개를 강제로 내고 클릭 타겟은td .v:empty{display:block}로 칸을 채운다. - ⚠ 화면에만 보일 표시는
@media print로 지우지 말고 처음부터@media screen안에 둔다 — 인쇄에 규칙 자체가 안 걸려서 「상쇄 빠뜨림」 사고가 날 자리가 없다. - ⚠ 줄 간격은 본표(
table.mx)만 벌린다 — 부자재(.subs)까지 벌리면 마지막 장의 부자재 예산만 깎여 표 쪼개기가 더 자주 발동한다(web-designer 판단 26.9.17). 본표 = 세로 패딩2px+ 줄높이1.4(행 38→49.8px). - ℹ 운영 E2E 를 「장이 많은 작업본」에 걸 땐 목록의
item_cnt를 믿지 마라 — 학동매장 #28 은 목록 18장인데 실제 전개는 12장이었다(전개 실패 0건 = 목록 값이 낡았다).
🆕 신규 (2026-09-17 후속 — 몸통규격표 5건)
- 🔴
contenteditable값을textContent로 저장하는 화면에서 줄바꿈 =<br>이 아니라 «개행문자»다.spec.html은focusout에서el.textContent를 읽어 OV 에 넣고 다시el.textContent = v로 되쓴다 → 브라우저 기본 Shift+Enter 가 꽂는<br>은 다음 blur 에 통째로 증발한다.execCommand('insertText', false, '\n')로 개행문자를 넣고white-space:pre-line으로 보인다. ⛒같이 안 고치면 무조건 되돌아가는 곳 = 정규화replace(/\s+/g,' ')(\s가\n을 먹는다 → 가로 공백만 접게[^\S\n]+) · 한 줄짜리 요약표(#edits)는 표시용으로\n → ' / '. - 🔑 「글자가 많으면 자동 축소」의 판정은 «폭»이 아니라 «줄 수»다. 긴 값은 가로로 넘치지 않는다 — 제 칸 안에서 접히고 머리말 칸은
overflow:hidden이라 세로로 잘린다(그래서scrollWidth > clientWidth로 재면 영원히 안 걸린다, 26.9.17 실측 FAIL).el.scrollHeight > lineHeight × (의도한 줄 수) + 2로 재고, 의도한 줄 수 = 값에 든\n개수 + 1(Shift+Enter 로 일부러 넣은 줄을 넘침으로 오판하면 안 된다). 하한 13px(=9.75pt) 아래로는 안 내린다 — 그 밑은 축소가 아니라 사람이 고칠 일이다. - ⚠ imos RDL 좌표를 옮긴 인쇄물에서 칸 높이를 맞출 땐 «총높이»를 지켜라. 머리말 아랫선을 제일 긴 칸(2.99cm)에 맞추면
.hd가 3.3→3.7cm 로 커지고 본표 시작이 밀려 페이지당 담기는 행 수가 바뀐다(시트 장수 증가). 제일 흔한 값(2.59cm)으로 줄여 맞추면 총높이가 그대로다. 결재칸을 키울 땐 그 아래.ship(상차일) 시작점을 같이 밀어야 겹치지 않는다. - ⚠ 「선이 안 보인다」를 인쇄 CSS 탓으로 먼저 의심하지 마라 — 결재칸은
print-color-adjust도border-collapse도 아니고 선을 그릴 요소가 아예 없었다(직함 텍스트 한 줄뿐). 구조부터 본다. - ⚠ 규격표 셀에 붙이는 표식(두께 꼬리표 등)은 편집칸(
vc) «밖» 형제로 둔다 — 안에 넣으면 사용자가 그 칸을 고칠 때 같이 지워지고, 서버로 가는 override 문자열에18T가 섞여 데이터가 더러워진다(되돌리기↺아이콘이 이미 형제로 빠져 있는 것과 같은 이유). - 🔴 운영 E2E 는 쓰기를 하지 마라 —
_e2e_spec계열은 임시 작업본을 만들고 셀을 고친다(= 운영 DB 쓰기). 운영에선 이미 있는 작업본 하나를 읽기만 하는 분기로 돌린다(RW = WHICH != 'PROD'). 그리고 운영 데이터로 단언을 쓰면 오판한다 — 「기본 15T 엔 꼬리표가 없다」는 주방 씬 전제라, 일반 라인(기본 18T) 작업본에서는 15T 에 꼬리표가 붙는 게 정상이다.
🆕 신규 (2026-09-17 — 파트 4면 트리밍 · 3D 두께 표시 · 배치 제품명)
- 🔑 배치별로 부재 치수를 덮을 땐
emit()한 곳 + 「부재 주소」다. 모든 부재가 지나는 단일 통로가schema.js emit(out, part, at)이고(mkPart는 존·면을 모른다), 주소 =`${zone}|${side}|${순번}`(part.pkey). 순번이 필요한 이유 = 선반 n매·서랍 부재·양문 도어가 같은 존·같은 면으로 나온다. ⛒순번은 placeholder·0치수 필터보다 «먼저» 매긴다 — 변수값 때문에 빠지는 부재가 있으면 뒤 부재 주소가 밀려 트리밍이 남의 판으로 옮겨 붙는다. CP 필드(thkOv처럼)에 안 붙이므로 아티클 트리에 자리가 없는 서랍 부재도 된다(두께 오버라이드는 여기서 막힌다). - 🔴
fillZone의 서랍 측판 2매는 «같은size객체»를 공유한다(P(sidep, …)를 두 번 부른다) — 제자리에서 깎으면 맞은편 측판까지 줄어든다. 부재 치수를 고치는 코드는size·box를 복사하고 시작한다. 자체검증에 이 케이스를 넣어 뒀다(복사를 빼면 죽는 것까지 확인). - ⚠
size.l/w가 어느 3D 축인지는 부재마다 다르다(측판 l=높이 · 천판 l=폭 · 세운 밴드 w=높이) — 표를 만들지 말고 트림 «전» box 3축 중size.t와 같은 축을 두께축으로 빼고, 남은 둘을size.l·size.w값으로 되짚는다. 두께축을 못 가리면(정사각 단면) 치수는 손대지 않는다. - 🔴 파트 재식별 키에 좌표를 섞지 마라 — 종전
partSig가box.x/z를 썼는데 4면 트리밍이 바로 그 좌표를 바꾼다 → 트림을 넣는 순간 rebuild 뒤 재탐색이 실패해 고른 파트가 풀렸다. 엔진 주소(pkey)로 갈았다. - ⚠ 트림으로 치수가 0 이하가 되면
emit의 0치수 게이트에 걸려 부재가 사라진다 — 사라지면 3D 에서 다시 못 집으니 속성패널에 「↺ 트리밍 지우기」(장 단위)를 같이 둔다. - 🔴
inst.ref는 카탈로그 조회 키다(CAT.items[inst.ref]) — 「이름 고치게 해줘」를 그 칸에 그대로 물리면 구조가 사라지거나 다른 아티클로 뒤바뀐다. 표시용inst.name을 따로 두고ref는 불변, 원본은 툴팁에. 나가는 곳 = 앱 규격표·TSV·spec.html본표/부자재 제품명.spec.html셀 수동수정(OV)이 더 세다(vc()가ov ? ov.v : orig라orig만 갈면 끝, 되돌리기는 새 이름으로 온다). - 🔴 표 인라인 편집은 Enter 로 닫을 때 «재진입»을 막아야 한다 —
renderInst가innerHTML통째 교체라 닫으면서 떼어낸 입력칸이 blur 를 또 쏘고, 그 blur 핸들러가 다시 렌더를 불러The node to be removed is no longer a child of this node가 난다(콘솔에만 뜬다 = E2E 없으면 못 본다). 커밋은 타이핑마다(blur 에 기대면 3D 드래그·rebuild 가 입력칸을 떼갈 때 치던 글이 증발). - ⚠ 3D 오버레이 라벨을 부재 수만큼 띄우면 숫자가 서로 덮는다 — 화면 투영 크기로 거른다(멀리 40px / 고른 장 20px 하한). 파트마다 project 두 번 하지 말고 카메라 거리로 환산(
px = mm × k ÷ 거리,k = 화면높이 ÷ 2tan(fov/2)). 신규 오버레이 요소는 13px 하한을 지킨다(기존.lbl11px·.dlbl11.5px 는 옛 위반).
🆕 신규 (2026-09-16 — 변수 CP 목록 · 3D 파트 선택)
- 🔑 「변수 설정이 안 된다」는 엔진부터 재라 —
B_DD_AS는 엔진이 멀쩡했고(resolveCP) 후보 목록이 없었을 뿐이다. CP참조 변수 판정 =varKind(tpl)==='text' && CPM[tpl]. 그 변수가 낳은 부재는cp.ref === '$변수'를 쥐고 나온다(치수 역추적 키). - 🔴 메인 3D 머티리얼은 자재별 공유(
matsMap) — 선택 강조로emissive를 직접 켜면 같은 자재 부재가 전부 빛난다. 선택 메시만 클론으로 바꿔 끼우고 해제 때 원복. - 🔴 Raycaster 는
visible=false를 안 거른다 — 도어를 끈 상태의 파트 픽은showDoor로 후보 그룹을 직접 뺀다.gBody에는 엣지LineSegments가 섞여 있어isMesh로 거른다. - ⚠
editor.js providers.cp는 편집기 슬롯(data-cp/rootface) 기준이라 다른 화면 칸에 붙이면 빈 값일 때 그룹이 안 선다 → 메인 패널은 별도 공급자cpvar. placeholder 를 공용 공급자에 먹이면 편집기 신규 CP 칸(placeholder예: 주방가구_측판_15T)의 그룹이 바뀐다. - ⚠ 우측 패널
.r행은 입력칸 뒤 115px 뿐(CP명 102px) — 입력칸 「우측」 표기 요청은 힌트 줄(오른쪽 정렬)로 받는다. 상단바도 남은 폭 12px 라 새 토글은 3D 뷰 오버레이로. - 🔴 인스턴스 수량(
inst.qty)은allParts()한 곳에서 곱한다 — 산출 요약·nest_rows·자재 소요량이 전부 거기서 나온다. 인스턴스별로bom(l.r.parts)를 따로 도는 곳(renderBom·spec.htmldigest)만 qty 를 따로 곱한다. 새 산출 경로를 만들면 이 둘 중 어디를 타는지 먼저 본다. - ⚠ 앱 부팅 중
rebuild()→renderInst가 모듈 뒤쪽 헬퍼보다 먼저 돈다 — 뒤에 둔 헬퍼는const f = () =>가 아니라function선언(호이스팅)으로(TDZ). - 🔴 편집기를 배치 수정 모드로 열면 「확정 저장」을 숨긴다 — id 가 카탈로그 ref 그대로라 덮어쓰기 confirm 한 번에 공유 원본이 바뀐다. 모드 초기화는
openWith첫머리(다른 진입에서 버튼 잔상 방지). - 🔴 부재 → 트리 자리 역추적은
part.zone·part.side로 한다 — 단 분할재(divider)의 zone 은 「자식 경로」다(${path}.${i}). 분할재 CP 는 부모 존split.divider에 있으니 zone 끝.i를 떼야 한다. 밴드 앞뒤 2장은 side 가top.F/top.B. - ⚠ 두께를 바꾸는 기능은
matOf도 같이 봐야 한다 —thkOf만 고치면 18T 부재에15T_PB·9T 등판에3T_HDF가 찍힌다(규격표 자재칸·원장 발주 어긋남). - ⚠
_e2e_spec.py는 작업본을 만들고 지운다 — 운영에서 돌리지 않는다. - ⚠
_e2e_home.py는 작업건 CRUD 를 한다 — 운영 검증은 읽기만 하는 partsel·vars·droplist·mouse·pncad 5본으로 한다._e2e_pncad/_home은 인자local이 없다(test 기본).
🆕 신규 (2026-09-04 저녁 — 첫 화면 로딩 최적화)
- 🔑 첫 화면이 느린 진짜 원인은 3D 가 아니라
/api/field/였다 — 운영 실측 6,621ms · 1.4MB, 전체 7,342ms 의 90%. 현장 마스터 전량을 부팅 top-levelawait로 받고 있었다. → 지연 로딩(loadFields(), 현장 선택 팝업 첫 열림에 1회) 하나로 7,342ms → 613ms. ⛒ 「느리다」를 3D·카탈로그 탓으로 넘겨짚지 말고 Resource Timing 부터 찍는다(계측 30초). - ℹ 카탈로그(
/api/pncad/catalog488KB·282ms)는 지연 못 시킨다 — 부팅 top-level 에서varFreq → VAR_KEYS → DEF()로 씬 기본 변수 세트를 만든다. 늦추면 빈 씬조차 못 만든다. - 🔴 PFS 응답에 gzip 이 아예 안 걸려 있다(실측 운영·테스트 동일) —
catalog488KB ·cp.json289KB ·field1.4MB 전부Content-Encoding없음. nginxgzip on+gzip_types application/json하나로 4~6배 준다. 운영 인프라 설정이라 별도 승인 건. - 🔴 직전 배치를 자동으로 열지 않는다(페페 지시) — 데이터는 작업건을 고른 뒤에 들어온다.
⚠ 대신
save()가 빈 배치를 localStorage 에 쓰면 안 된다 — 부팅rebuild()가 부르는save()가 복구본을 빈 씬으로 덮어 지운다(실측: 새로고침 한 번에 소멸). 첫 화면 복구 배너는 파싱만 하고 누를 때만 전개한다(3D 비용 0). 저장상태(STO)도 복원하지 않는다 — 빈 화면에 「저장됨」이 뜬다. - ⚠ 검색·이름 칸은
autocomplete="off"+ 고유name—type=search만으로는 크롬이 값을 채운다 (#fq·#lq·#jName). PFS 전역 규약과 같다(projects\pfs\rules.md).
🆕 신규 (2026-09-04 — 첫 화면 = 작업 현장 CRUD)
- 🔴 첫 화면은 3D 가 아니라 「작업 현장」 목록이다(페페 지시 26.9.4) — 부팅 끝에서
homeOpen(). 3D 는 뒤에 그대로 서 있고 「닫기 (3D 화면)」로 지금 배치로 들어간다. 현장은pncad_scene행에 붙어서만 존재한다(작업건 0건인 현장은 DB 에 자리가 없다) → 「현장 추가」는 PFS 현장 선택 → 첫 작업건 만들기까지 한 흐름이다. PNCAD 는 현장을 새로 만들지 않는다(마스터는 PFS 것뿐). 타입·K/F 는 저장분에서 고르기만 한다 — CRUD 화면에서 새 타입을 치게 두면 오타로 같은 평형이 둘로 갈린다. - 🔴 오버레이를 하나 더 띄우려면
z-index를 먼저 본다..ov는position:fixed; inset:0라 화면을 통째로 덮고, 같은 z-index 면 DOM 뒤엣것이 이긴다 → 현장 선택 팝업(11)이 현장 목록(11) 밑에 깔려 「현장 추가」가 아예 안 눌렸다. 현재 층 = 목록 11 · 배치 12 · 작업건 폼 13 · 현장 선택 14. - 🔴 목록 행 클릭에서
innerHTML통째 재렌더 금지 — 더블클릭이 죽는다. 첫 클릭이 대상 행을 지워 두 번째가 새 요소에 떨어져dblclick이 발화하지 않는다(현장 선택의 「더블클릭 = 즉시 확정」이 그래서 계속 안 먹고 있었다). 선택 표시는 클래스만 토글한다. - 🔴 목록에서 고친 값은 메타 전용
PATCH /api/pncad/scene/<no>로 보낸다. 목록은 씬data를 안 싣는다(수 MB) → 이름 하나 고치자고GET → PUT왕복을 하면 그 사이 남이 저장한 배치를 덮어쓴다. - ⚠ 첫 화면 오버레이가 E2E 클릭을 전부 막는다 — 기존 E2E 10본은 앱을 열자마자 3D 를 조작한다 →
앱 goto 직후
document.getElementById('listWrap')?.classList.remove('on')한 줄이 필요하다 (넣는 자리는 로그인 goto 가 아니라 pncad 앱 goto 뒤). - ⚠ 죽은 E2E 체크는 조용히 계속 FAIL 한다 —
_e2e_pncad.py의 색상칸 검사가 26.9.3 datalist 전폐 뒤에도#colorList option을 세고 있었다(그날부터 계속 1건 실패). UI 규약을 바꾸면 그 규약을 보는 체크도 같이 고친다.
🆕 신규 (2026-09-04 — 밴드 앞뒤 2장 · 들여넣기)
- 🔴 밴드(TRAVERSE)는 한 부재정의가 2장을 낳을 수 있다. 정본 =
VORNE(앞)/HINTEN(뒤) 두 플래그이고 둘 다 1 이면 앞·뒤 2장이다(176종 중 90종, imos 실인스턴스는 예외 없이 2매).data\cp.json=bandPos:'F'|'B'|'FB'. 이름의_F/_B는 관례일 뿐 판정 근거가 아니다 —주방가구_밴드_15T_B는 진짜로 뒤 1장이고, 그 짝 앞밴드는 형제 존의 별도 CP_15T_F다. 두 장이 먹는 존 높이는 한 장분(나란히 놓이지 겹쳐 쌓이지 않는다). - 🔴 들여넣기(Reveal) =
CONDISFRON/CONDISBACK— 그 면에서 장 안쪽으로 미는 mm (양수=들여넣기). 이름의Rv##숫자가 곧 이 값이다(51종 일치). 수식판SCONDFRON/SCONDBACK이 있으면 그쪽이 정본 —-$찬넬깊이= 서랍 레일깊이+1 이라CH깊이40 오더에서 41 이 된다. 리터럴CONDIS는CH깊이37 로 굳은 캐시값이라 그것만 믿으면 3mm 틀어진다. 치수엔 안 들어간다 (위치만 민다) →verify2(치수 대조)로는 절대 안 걸리는 부류다. 검증은 DWG 좌표(verify3) 몫. - 🔴
reference\catalog-2026-08-17\cp-traverse.json은 11컬럼짜리 축약본이다 —VORNE/HINTEN/CONDIS*/SCOND*가 통째로 빠져 있다. TRAVERSE 정본은reference\imos-full\cp_traverse_full.json(전컬럼).mkcp2.py가 이제 full 을 먼저 읽는다. 다른 클래스도 축약본을 쓰기 전에 컬럼 수를 세라. - ⚠ 부재정의 편집칸은 「값이 있는 필드」만 뜬다(
editor.js renderElemDetail의edit()이cur == null && ov == null이면 빈 문자열 반환) → 빈 필드엔 새 값을 넣을 수가 없다. 신규 CP 생성 경로도 없다. 부품 수정 모달을 만들 때 이 두 개가 실제 요건이다(페페 지시 26.9.4).
🆕 신규 (2026-09-03 — 아티클 편집기 5건 보완: 고스트 렌더·자체 드롭리스트)
- 🔴 모든 목록·자동완성은 크롬 양식 금지(페페 지시 26.9.3). ⬆2026-09-14 전역 규칙으로 승격 — 정본은
C:\dev\CLAUDE.md§3 +projects/web-designer/kb/08-forms-inputs.md§1-0(ruleguardchrome-native-dropdown). 여기 남은 건 pncad 고유 함정이다. datalist 는 전폐 — 콤보는<input data-drop="타입">+droplist.js공급자(providers[타입]), select 는 요소를 값 저장용으로 남기고 mousedown/keydown 만 가로채 자체 팝업을 띄운다. 이 방식이라 Playwrightselect_option·기존onchange가 전부 무수정 생존한다(요소를 숨기면 E2E 3본이 깨진다). 새 목록 UI 를 만들 때도 이 경로만.droplist.js는tools/sync_to_pfs.pyFILES 에 등록돼 있다 — 새 JS 파일은 FILES 누락 = 운영 백지. - 🔴 datalist 의 「목록이 안 뜬다」 원인 = 값이 차 있으면 부분일치 필터가 전부 걸러서다. 자체 콤보는 열 때 항상 전체를 보여주고 타이핑부터 거른다. Enter 는 화살표 내비 이후에만 선택 — 안 그러면 손으로 친 새 이름을 목록 첫 항목이 덮는다.
- 🔴 드롭리스트 함정 3(코덱스+리뷰 실측): ①선택은 blur 보다 먼저 오는 pointerdown 에서
②한글 IME 조합 중 Enter 는
isComposing/keyCode 229 로 무시 ③재렌더 후 포커스 복원이 focusin 으로 팝업을 다시 연다 → 복원 포커스엔data-dropSkip마커. ④(추가) select 문자키는 네이티브 type-ahead 가 change 를 쏴 재렌더→앵커 detach→고아 팝업+이중 change — 문자키에서 팝업을 닫고, pick 은anchor.isConnected를 검사한다. ⑤팝업 DOM 은 MAX_ROWS 400 캡(CP 마스터 1672행 통그리기 금지). - 🔴 공간 강조 개념(26.9.3 개정) — 2026-08-18 「발광만 얹는다」 규칙 중 공간(zone) 선택의 전체 발광은 뺐다: 하위 공간을 고르면 ①경계선은 선택 공간 하나만(잎이면 채움) ②밖 부재는 고스트 (opacity .1 · depthWrite 끔 · 엣지 페어링해 같이 흐림) ③분할 노드 선택 때만 직계 자식 1단 교대 채움. 잎 전체를 그리면 다층 장에서 경계선이 겹쳐 못 읽는다(페페 지적). 파트·분할재 발광은 그대로다.
- ⚠ 고스트는 픽킹·회전 피벗을 통과해야 한다 —
hitAt과cursorPoint가 같은 필터 (find(h => !h.object.userData.ghost))를 써야 한다. 한쪽만 고치면 Shift+휠 회전 피벗이 보이지 않는 고스트 표면에 스냅된다(리뷰에서 잡힘). - ⚠ E2E 로컬(8799)은 PFS API 가 없다 —
/api/pncad/var·/api/poin/colors·qa_helper 404 는 정상. 배리어블 픽커 검사는 test/prod 에서만 돈다(tools/_e2e_droplist.py가 스스로 SKIP). - 🔴 더블도어 = imos
DOOR.NOD=2(한 세트가 도어짝 2장·DOORSINGL/R). 판정은 포인 명명규칙_DDO/_DDI(survey2 drawers-doors-fronts §4, DOOR 232건 검증 — cp.json 엔 NOD 가 안 뽑혀 있다). 세트 폭 반 갈라 2장, 양문 간격FUGE_2T= 포인 전 도어 0(실측).schema.js emitDoorSet한 곳. 🔑 도어는 emit=false 라 BOM·verify2 를 구조적으로 못 움직인다(emit 이 out.parts 에서 거른다) — 도어 표시를 고칠 땐 대조기 걱정 없이 render 만 보면 된다. 도어 옵션 전수 조사표 = devplanpncad-editor-fix5.html(온셋/인셋·플랩 FDO 갭 16·힌지·손잡이·슬라이딩은 별도 마스터). - 편집화면 마우스(26.9.3 페페 지시) = 좌드래그 이동 · 우드래그 회전 · 휠드래그 이동 · Shift+휠드래그 커서중심 회전 · 클릭(5px 이내) 선택. 본화면 규약과 이제 다르다 — 편집기만이다.
🆕 신규 (2026-08-31 밤 — 장별 몸통두께 · 자재명 근본수정)
- 🔴 두께 스위치가 자재명을 안 바꾸고 있었다(오래된 버그·운영 반영분).
matOf가 CP 마스터에 박힌cp.mat을 맨 먼저 반환해서 두께만 15→18 로 바뀌고 이름은15T_PB로 남았다 → 규격표 「자재」 칸과 원장 발주 키가 실물과 어긋난다(장별 지정뿐 아니라 기존 일반가구 장 전부 해당). 🔑 이름은 지어내지 않는다 — 그 두께의 imos 실측본alt.mat을 쓴다.thkOf스위치 게이트가 이미 「그 두께로 나온 이력(alt)이 있을 때만」이라 이름은 항상 있고, 게이트가 막은 CP(뒤판각목 등)는 두께가 안 바뀌니 이름도 그대로다.alt78건이{thk, mat}쌍을 갖고 있다. ⚠ 등판(ctx.backThk) 때 겪은 함정과 같은 것 — 두께 바꾸는 코드를 짜면matOf를 반드시 같이 본다. ⚠ verify2 대조키는 치수(l×w×t) 라 자재명 변경에 안 움직인다 → 자재명 회귀는 대조기가 안 잡는다. 자체검사(node schema.js)에 박아야 한다. - 장별 몸통두께 =
items[i].bodyThk(숫자) — 「주방인데 이 상부장만 18T」(페페 지시). 계열 두 칸(주방·일반)을 같이 덮는다 — 고른 대상이 계열이 아니라 「이 장」이고, 한 장 안에 다른 계열 CP 가 섞여도 한 두께로 나와야 한다. 엔진은 이미 지원하던 경로라schema.js변경 0(ctx 만 갈아끼운다). - ⚠ 두께가 실제로 바뀌는 부재 범위를 UI 에 적어라 — 측판·천지판·선반만 바뀌고 뒤판(3T)·뒤판각목(15T 고정)·
도어(18T MDF)·박스형 부자재(PRODCODE 12·13)는 안 따라간다. 안 적으면 「18T 골랐는데 뒤판각목이 15T」로
버그 신고가 온다. 실측 =
tools/_probe_thk.mjs(W_SD/W_DD/B_SD 두께·자재 대조). - ⚠
spec.html은 앱과 같은 ctx 를 써야 한다 — 화면과 종이가 갈리면 규격표가 거짓말을 한다. 전개 옵션을 늘릴 때app.html expandInst와spec.html digest를 한 번에 고친다.
🆕 신규 (2026-08-31 — 오더헤더 자재 넘버 · 등판재 분리 · 운영배포)
- 🔴 오더헤더 정본은 추측하지 말고
PROADMIN을 읽는다. 실오더 30건 실측 —COLOUR2=타입(84A·59B) ·COLOUR3=K/F ·COLOUR4=몸통자재 넘버(PTA420·LE3-BP045·PTA261 / TLG0129MU처럼 2종 병기도 있다) ·COLOUR5="15T/4.5T"(몸통두께/등판두께). ⛒spec.html이 종전에 몸통재 칸에 색상명을 찍고 타입을 비고칸에 넣고 있었다 — RDL 표현식(=First(Fields!COLOUR2...))만 보고 컬럼 값을 안 봐서 생긴 오배치다. 등판 넘버 칸은 imos 에 없다(포인 신규) → 몸통재 칸 아래 줄에 작게 병기한다. - 🔴 등판두께 3T/4.5T 가 표시용 장식이었다 — 씬에 값만 있고 엔진에 안 들어가 4.5T 를 골라도 부재는 3T 로 나왔다.
이제
ctx.backThk가cls==='CABBACK'부재의 두께와 자재(3T_HDF↔MDF045)를 덮는다(schema.js isBackPanel). ⚠matOf는cp.mat을 맨 먼저 반환하므로 두께만 바꾸면 자재명이3T_HDF로 남아 원장이 잘못 갈린다 — 두 함수(thkOf·matOf) 를 같이 고쳐야 한다. ⚠ 뒤판각목(CSIDE)·서랍바닥은 등판이 아니다. 클래스로만 가른다(이름·PRODCODE 로 가르면 같이 딸려온다). 🔑ctx.backThk를 안 주면 종전 그대로라verify2전량 대조는 59.1% 무변화(회귀 0) — 엔진 기본값을 바꾸는 대신 ctx 를 옵션으로 두는 게 대조 자산을 지키는 방법이다. - 🔴 원장 발주 키 = 색상명이 아니라 자재 넘버다.
nestRows()의 컬러 칸을등판이면 backMat · 아니면 (장별 색상 > bodyMat > 색상)으로 바꿨다 → 네스팅 「자재 소요량 합계」가 두께+넘버로 갈라 등판 소요량이 저절로 따로 나온다(네스팅 쪽 코드 변경 0). - ⚠ PNCAD 안의 소요량 장수는 「면적 환산 이론 최소치」다 — 로스·커프·결방향을 안 본다. 확정 장수는 네스팅 길로틴이 낸다. 화면·TSV 에 그렇게 적어 뒀다(안 적으면 발주 수량으로 쓰인다).
- ⚠ 작업본 목록에 실어야 하는 값은 컬럼으로 끌어올린다 —
data는 통짜 JSON(수 MB)이라 목록에 못 싣는다.body_mat·back_mat·back_thk3컬럼 추가(scene_type·scene_line과 같은 이유). 운영 DDL 은 배포 전에scripts/_pncad_mat_schema.py(컬럼별 검사·추가)로 먼저 넣는다 — 3개를 한ALTER로 묶으면 하나만 이미 있어도 전체가 실패하고, DAO 의try/except가 그 실패를 삼켜 INSERT 에서 500 이 난다. - ⚠ Playwright 는
confirm()을 기본 dismiss 한다 — 덮어쓰기 확인을 낀 기능은 E2E 에서pg.on('dialog', lambda d: d.accept())를 안 걸면 「아무 일도 안 일어남」으로 조용히 FAIL 한다. - ⚠ 낡은 단언은 고친다 —
_e2e_pncad.py③ 이 「몸통재 칸이 없어야 한다」를 지키고 있었다. 2026-08-17 에 없앤 건 색상명 중복칸이고 지금 넣은 건 자재 넘버라 별개다.
🆕 신규 (2026-08-19 밤 — 몸통규격표 출력 화면 spec.html)
- 🔴 몸통규격표 본표는 리스트가 아니라 매트릭스다 — RDL
Tablix2의 행 그룹 =POSSTR(아티클) · 열 그룹 =NAME1(부재명), 한 칸에 (높이*길이, 수량) 두 열이 붙는다. 행 머리 3칸(번호·제품명·수량)의 머리글은 2줄로 쪼갠 글자다(번/호 · 제품명/W*D*H · 수/량) — 별개 컬럼이 아니다. 열 머리 밑줄 축 라벨은Textbox50식이 정본: 측판=깊이*높이 · 뒤판=길이*높이 · 서랍재/뒤판각목=높이*길이 · 그 외=깊이*길이. - 🔴 A4 가로 한 장에 부재명 8열(행머리 4.39cm + 8×2.88cm). 그 이상이면 장을 나눈다 — imos 도 같은 폭이다.
종이 규격 =
29.7×21cm· 여백상하 5mm / 좌 11mm / 우 2mm(RDLPage). - 🔴 규격표 셀 수정본도 「사람이 고친 것만 DB」 규약을 그대로 쓴다. 원본은 저장하지 않는다 —
엔진(
schema.js)이 늘 다시 만드니까 되돌리기 = 그 키를 지우는 것이다. 저장소 =pncad_scene.spec_override(작업본당 JSON 한 칸,PUT /api/pncad/scene/<no>/spec). ⚠ 씬 저장(update)은 이 컬럼을 안 건드린다 — 배치를 다시 저장해도 규격표 수정본이 살아야 한다. - ⚠ JS 객체 리터럴에
걸레받이/문틀재같은 전각 기호(/)를 키로 쓰면 문법 오류다(식별자 문자가 아니다). 섹션 이름은 배열 하나로 두고Object.fromEntries로 만든다. - 출력용 화면은 앱과 분리한다(
spec.html,/production/pncad/spec.html) — 인쇄 CSS 가 3D 앱 레이아웃과 싸운다. 진입 = app.html 규격표 창의 「🖨 출력용 규격표」, 인자?scenes=1,2또는?field=<현장명>. - 테스트 DB 에는 PNCAD 작업본이 0건이다 — 화면 E2E 는 임시 작업본을 직접 만들고(POST) 끝나면 지운다(
tools/_e2e_spec.py).
🆕 신규 (2026-08-19 밤 — qahelper 사이드바 뒤 3D 뷰 폭 안 돌아오던 버그)
- 🔴 PFS qahelper 는
body { padding-right: 380px }를 넣었다 뺀다 — 창 크기는 안 바뀌므로window resize가 안 뜬다. 캔버스/차트처럼 px 로 크기를 박는 요소는addEventListener('resize', …)로는 못 따라간다 → 컨테이너를ResizeObserver로 직접 본다. PNCAD 증상 = 패널 열린 동안 카탈로그 토글 등으로resize()가 한 번 돌면 캔버스가 380px 줄어든 채 굳고, 패널을 접어도#view의 배경 그라디언트만 남아 "배경색은 같은데 화면만 좁아진 것" 처럼 보인다(원인 추적이 어렵다). 수정 =app.htmlnew ResizeObserver(resize).observe(view)(editor.js 그래픽 호스트가 이미 쓰던 방식과 통일). 회귀체크 =cd C:\dev\pncad && PYTHONUTF8=1 python tools/check_view_resize.py(자체 http 서버 + Playwright, 열림 -380 / 닫힘 복구 assert). - PNCAD 는 PFS 사이드바가 없는
target=_blank단독 화면이라, 우측에 뭔가 뜨면 그게 곧 본문 폭을 먹는다. 우측 오버레이를 새로 붙일 땐 폭 복구를 같이 확인한다.
🆕 신규 (2026-08-19 밤 — imos DWG 실좌표 대조기 verify3 · 배치 규칙 근본수정)
- 🔴 부재 좌표의 정본 = 오더 DWG 다. DB 에는 없다.
IDBGPL(86컬럼)·IDBINFO어디에도 부재 XYZ 가 없고,IDBGEOM(19,435행)은 아티클 삽입점이다(IDBINFO.ID와 조인,IDBGPL과는 0건). 실좌표는poinimos01:D:\ProgramData\imos AG\Factory\Imorder\<오더>\*.DWG를 받아 AutoCAD 2024 COM 으로 뜯는다(tools/dwg_parts.py→tools/mk_dwgjob.py→verify3.mjs). - 🔴 좌표 규약(실측 확정) — 아티클 로컬 원점 = 앞·아래·왼쪽.
+x오른쪽 ·+y앞→뒤 ·+z위. 월드 =IDBGEOM.IP + 로컬(OR_ = 0). PNCADpart.box와 축·원점이 완전히 같다* — 변환 없이 1:1. - 🔴 imos ELEMID = 면 주소이고 앞0 → 우1 → 뒤2 → 좌3 시계방향이다.
convert.mjs FACE_KEY가 좌우 뒤집혀 있었다. 대칭 장에선 치수가 같아 안 보이고 코너 기둥목처럼 좌우가 다른 부재에서만 드러난다. ⚠ 라벨을 바꾸면 트림 결합표schema.js PLAN_JOIN도 같이 미러링해야 한다 — 한쪽만 고치면 치수가 조용히 틀어진다(58.3%→57.9%, 미러링 후 복귀). - 🔴
SIZREF 'O'(Outer)는 앞판 평면뿐 아니라 몸통 존 자체를 장 외곽에 재기준한다. 종전엔fbase만 돌려 덧방은 맞고 기둥목만 왼쪽 끝에 박혔다(Δx 최대 +570). ※ imos 에 깊이 분할축은 없다 —DIVDIR= V/H/I/A 뿐이고LINDIVZ는 10,604행 전부 빈값. - 🔴 LINDIV 혼합칸 —
1+($CH높이)MM= 「66mm 고정」이 아니라 비율 1 + 65mm 가산이다. 단, 단위 없는 항이 맨숫자 리터럴일 때만 비율이다 —$밑장깊이-170mm처럼 변수면 그건 치수라 식 전체가 고정칸이다(안 가르면 도어 폭이 통째로 틀어진다). 종전엔 단위가 하나라도 붙으면 통째 고정으로 뭉개서1-30MM이 −29mm 짜리 칸이 됐고, 자체검사가 그 오답을 정답으로 박제하고 있었다. - 🔴 「밴드는 두께+1 을 먹는다」는 증상보정이었다. 진짜 −1 은 그 부재 자신의 TOPOFFS/BOTOFFS(−0.5×2)다. 존을 깎으면 같은 존을 쓰는 이동선반까지 같이 틀어진다. 존은 두께만 먹고, 세로 부재(L/R·앞면 구조재)가 자기 오프셋만큼 짧아지고 그만큼 떠 있게 한다.
- 🔴
anglelem.INSET= 그 면의 바깥쪽 법선 방향 밀어내기. 앞면이면 −y, 뒷면이면 +y. 도어 −20 · 코너 덧방 −4.5 · 뒤판각목 −18/−19/−20. 카탈로그에 이미 있었는데 좌표엔 안 쓰고 있었다. - 🔴 천/지판(CABIN 02·03)의 깊이 기준은 「전면」이다(페페 질문의 답).
UEBERST_V(ueb.v) 하나가 정한다 — v=0 이면 앞면 플러시, v<0(Rv 리빌)이면 그만큼 뒤로.ueb.h(HINTEN)는 크기만 줄이고 위치엔 무관. full depth 면 종전 뒤기준 식과 대수적으로 같아 하부장은 무변화다. - 🔴 밴드 깊이는 오더변수
$밴드폭이 CPfixW보다 우선한다(60/56). 이름에 mm 박힌 CP(_F_40mm)만 예외. - 🔴 밴드가 눕었나 세웠나 =
TRAVERSE.LAGE(0 눕힘 · 2 세움 · 1 VV · 3 mixed). 이름의_V는 관례일 뿐이다. 세운 밴드는 깊이↔높이가 뒤집히고(깊이=두께·높이=밴드폭), 깊이 기준이 장 외곽 깊이다(뒤판 뒤쪽을 채우므로). 뒤판이 깊이를 먹은 장은 뒤끝에서 1mm 띄운다. - 🔴 구조재 앞면은 오버레이가 아니다 — 존 앞면에서 안쪽으로 채운다(inset 0). 도어·덧방만 장 앞으로 나온다.
- ⚠ E2E 픽스처에 면 라벨(L/R)을 박지 마라 — ELEMID 매핑이 바뀌면 같은 부재가 L↔R 로 바뀐다.
전개된 부재에서
zone·side를 읽어 써라(_e2e_editor2가 그렇게 깨졌다). - ⚠ PFS 배포 후 「↻ imos 기준선 갱신」을 안 돌리면 화면은 안 바뀐다 — 화면 카탈로그는 DB 시딩판이다.
CLI =
PYTHONUTF8=1 python tools/_reseed.py [test|prod] --apply(미리보기는--apply없이). - ⚠ pfs
deploy-live는 전체 머지 금지 — 「선별 포워드」가 이 리포의 방식이다. master 를 통째 머지하면 PNCAD 와 무관한print-agent·orderpaper-sync까지 운영에 들어간다 (실측:agent.py−588줄).git checkout master -- <바꿀 파일만>으로 가져와 커밋한다.
🆕 신규 (2026-08-19 — CP 오버레이 · 부재목록 편집 · imos 기준선 갱신)
- 🔴 정본 규약 = 「imos 생성물은 파일 · 사람이 고친 것만 DB」. CP·아티클·배리어블 셋 다 같은 패턴이다.
DB 로 통째 옮기면
mkcp2/convert.mjs를 다시 돌릴 때마다 충돌하고, 파일만 두면 화면에서 못 고친다. CP =data/cp.json+pncad_cp_override(필드 단위) →schema.applyCpOverride()로 병합. 화면과 node 대조기가 같은 함수를 쓴다 — node 쪽은tools/pull_overrides.py로 오버레이를 받아 둔다. 🔑 필드 단위인 이유 = 사람은thk만, imos 는ueb만 바뀌는 경우가 실재한다. 레코드째 덮으면 그런 CP 는 imos 갱신을 영영 못 받는다. - 🔴
applyCpOverride의 타입 기준은 반드시 imos 원값(base)이다. DB 는 값을 문자열로 준다 — 이미 덮인 값을 기준 삼으면 한 번 문자열이 된 숫자칸이 영영 문자열로 굳어 계산이 조용히 틀어진다.node schema.js「오버레이 3」에 「두 번 덮어도 숫자」 케이스로 박아 뒀다. - 🔴
cp.json을 그 자리에서 덮으면 imos 원값이 사라진다 — 화면의 「imos 원값 병기」가 병합본을 가리킨다.app.html이CPBASE = structuredClone(CPM)로 사본을 먼저 뜬다(구현 중 발견·수정). - 🔴
_seed_if_empty()는pncad_article이 비었을 때만 돈다 — convert.mjs 를 고쳐도 PFS 화면은 최초 시딩판 그대로다(단독 8799 는 파일을 읽어 새 값이 나오니 로컬만 보면 못 잡는다). → 「↻ imos 기준선 갱신」(/api/pncad/reseed). 손 안 댔는지 판정 = 가장 최근kind='seed'백업판과의 정규형 대조다.origin='imos'는 편집해도 안 바뀌어(컨트롤러 규약) 판정에 못 쓴다. - 🔴 재시딩에 지우는 동작을 넣지 마라. 손 안 댄 행만 UPDATE · 없던 것만 INSERT · imos 에서 사라진
아티클은 보고만 한다. 통짜
DELETE하는replace_all()은 백업 복원 전용이다(재시딩이 그걸 쓰면 사용자 제작 아티클이 통째로 날아간다). - ⚠
thk오버레이는verify2를 안 움직인다 — 오더변수바디자재_주방가구/일반가구(ctx.bodyThk)가 CP 두께보다 우선한다. 오버레이가 대조기까지 먹히는지 확인하려면 밴드fixW같이 ctx 가 안 덮는 칸으로 해라 (60→80 주입 시 58.3%→58.0%). - ⚠ E2E 의 PFS 경로는
/production/pncad/다(/pncad/는 단독 8799 전용). 로그인도 폼 채우기가 아니라/api/auth/loginfetch 로 한다 — 로그인 페이지 마크업에 안 묶인다. - imos 보링 체인 실측(다음 단계 재료) — 2026 가공 123,175행인데 워크그룹 27종·(WGNAME,직경,깊이,
방향,가공구분) 조합 57개뿐이고 상위 3종이 77%다.
IDBWG는(ORDERID, ID)로IDBGPL과 부재 단위로 맞물려IP_X/IP_Y/IP_Z(부재 자체 좌표계)·OR_*·DIA·DE·CNT를 준다 → verify2 와 같은 좌표 대조기가 가능하다.IDBWG.NAME1은 부재명이 아니라 PRODCODE 다(IDBGPL.NAME1 과 뜻이 다르다). 페페 결정 = 9단 체인 이식이 아니라 실측 역공학 우선.
🆕 신규 (2026-08-18 심야 — imos 아티클 생성 구조 완전복사 1~3단계)
- 🔴 트림코드(
anglelemSTARTTRIM/ENDTRIM/TOPTRIM/BOTTRIM)가 부재쌍 결합의 정본이다. 평면 윤곽은 F→L→B→R 순환이고 결합쌍은(F.END,L.START) (L.END,B.START) (B.END,R.START) (R.END,F.START).L=관통(이웃을 덮고 지나간다) ·S=끼움 ·P=CP 파라미터가 결정(뒤판 RGRDE) ·D=관통변형 ·M=중앙. 🔑 깎는 조건은 「내가 S」가 아니라 「내가 S 이고 이웃이 L」이다 — 양쪽 다S면 관통재가 없다. 앞면 도어가 정확히 그 케이스(도어SSSS↔ 측판SLLL/LSLL)라, 이 가드가 없으면 풀오버레이 도어가 측판 두께만큼 좁아진다. 여기에 같은 변의STARTOFFS/ENDOFFS/TOPOFFS/BOTOFFS(mm)를 항상 더한다. 실측 정본 =일반가구_2단서랍_날개측판 =D − 날개두께18 − 2(STARTOFFS)→ imos 458/458. 전량 대조 56.8% → 58.3%(측판 깊이 + 앞판 폭 + 뒤판 좌우여유). 자체검사 =node schema.js트림 3건. - 오프셋 수식(
STAOFFFOR등)은 안 파싱한다 — 2,270행 중 2,257행이 숫자 리터럴이고 숫자 컬럼이 그 캐시값이다. 비숫자 수식은68-550·88-$밑장깊이류 18행뿐 → 캐시된 숫자가 이미 답이다. - ⚠ 편집기에서 부재정의를 바꾸면 트림·오프셋이 조용히 날아갔다(
bindZone이parseCP로 CP 를 통째로 새로 만들었다). 면 CP 를 재조립하는 코드는ref·inset·trim·offs를 반드시 물려줘야 한다. - 🔴 아래 3개는 imos 컬럼이 있어도 규칙으로 먹이면 안 된다(전량 대조가 내려간다 — 근거는 코드 주석에 남겼다):
DIVTYPE='S'(부자재)로 선형 판정 → 58.3%→54.5%.S에 pc11(속대·보조목·누적자재)처럼 깊이를 쓰는 게 섞여 있다. PRODCODE 12·13 이 정본.SIZREF5슬롯의 첫·끝을 양 끝 앵커로 분리 적용 → 58.3%→58.1%, 회귀는 전부 우수 코너장(BC_R_001계). 슬롯 방향이 좌우 대칭에서 뒤집히는 듯 → 방향 규칙을 실측으로 확정하기 전엔 불리언outer가 정본.- 가상면(
FUNCT='V')INSET을 내경에 반영 → 부호를 어느 쪽으로 줘도 58.2%. 카탈로그 165종에 46존뿐. → 셋 다 카탈로그에는 원문 보존(divtype·sizref·vinset). 편집기 표시·후속 조사용이다. - 🔴
anglzone.LEFTHINGE/RIGHTHINGE는 힌지 방향이 아니다.RIGHTHINGE는906126224같은 초기화 안 된 정수가 들어 있고, 둘 다 켜진 존이 10,604 중 8,796인데 도어 면을 가진 존은 614뿐이다(상관 없음).anglzone.DOORHISIDE는 도어존 614개가 전부 0. 도어 방향은 CP 이름이 쥔다(하부도어_SDO_R·_DDO·_FDO). → 「힌지 10,344건 확보」는 이 쓰레기값을 센 것이었다. 힌지 기능은 CP 이름 파싱으로 다시 설계할 것. - 🔴 이동선반(
DIVTYPE='A') 수량은 아티클에서 안 나온다 — 오더에서 사람이 정한다.신발장_DD124인스턴스에서$Q_AS01/$Q_AS02가 전부5{1}(=8매)인데 imos 실부재는 0·3·4·5·6·7·8·10 으로 갈리고 같은 H(1970)에서도 3·4·7·8 이 공존한다. 전량 대조 「잉여 이동선반 1,085매」의 정체가 이것 → 규칙으로 못 맞춘다. 대조 상한선을 볼 때 이 몫(≈4%p)을 빼고 읽어라. 재현 =tools/_probe_shelf_qty.mjs. BASECP(받침)은 카탈로그 165종 전부BA_filler_1000_H080mm인데BAHEIGHT/BAINSET/BAINSERT/BATOEKICK이 전부 0 = 받침이 꺼져 있다. 「87.7% 채워짐」은 기본 CP 이름일 뿐 부재가 안 나온다.- imos DB 조회는
tools/imosdb.py공용 헬퍼로 한다(q()·dump()).pull_*.py마다 dbquery 배선을 복사하지 마라. 받아둔 것 =cp_rest_full.json(DOOR 232·KMS 490) ·drawer_full.json(DR_MAIN 926·ZONE 626·SING 806·SIZE 16,480) ·pd_full.json(kmsprof 2,062 + MPP 4종) ·hardware_rules.json(CONNSELATTR 26,251). 전부reference/imos-full/. - 몸통 프리셋 = imos
KORP_VOR(Korpus-Vorgabe). 포인이 쓰는 줄은Poin_kitchen·Poin_furniture·Poin_오픈장3종뿐(나머지는 imos 기본Corpus_*·STANDARD). 컬럼은 독일어 약어 —KORP=측판OB_B=천판UN_B=지판RUEC=뒤판MITS=칸막이KO_B=고정선반EL_B=이동선반SOCK=받침. 생성 =tools/mkpresets.py→data/presets.json. - 배리어블 분류는
IMOS.FAMILY(찬넬·서랍·도어·01몸통부자재 … 72종, 424개 중 421개 채워짐).CATEGORY는 206개뿐이라 보조. 생성 =tools/mkvars2.py→data/vars-meta.json. 🔑 PFSpncad_var에 컬럼을 안 늘렸다 — 값은 DB 가, 분류는 imos 마스터가 쥐는 읽기전용 참고열이라 파일로 나른다. - 🔴
convert.mjs를 고쳐도 PFS 화면은 안 바뀐다 —_seed_if_empty()는pncad_article이 비었을 때만 돈다. 단독(8799)은data/catalog.json을 직접 읽어 바로 반영되지만, PFS 안에서는 DB 에 든 옛 판이 계속 나온다. 재시딩 경로가 아예 없다(있는 건 백업/복원뿐,replace_all은 사용자 제작 아티클까지 지운다). → 필요한 것 = origin='imos' 행만 갱신하는 머지 시딩. 착수 전 그릴링 대상(파괴 범위·트리거 주체).
🆕 신규 (2026-08-18 밤 — 아티클 편집기 2차 · 기둥목 겹판 · 부재목록)
- 🔴 imos 는 부재 좌표를 저장하지 않는다 — anglzone/anglelem/IDBGPL/IDBGEOM 어디에도 좌표 컬럼이 없고 매 렌더 때 존트리로 재계산한다(실측 2026-08-18). 배치가 어긋난 버그는 DB 로 정답을 못 찾는다 → 실물 구법(가구전문가·KB)·imos 화면 캡처가 정본이다.
- 🔴 코너 기둥목 2본 = 15T 면대면 30T 겹판(경첩 인발하중 보강 — 실물 구법, KB 「코너대기둥목 제작깊이 68mm」·DISTFRONT=37=System32 첫홀 정합). 존트리는 68mm 포켓 좌·우 벽에 1본씩 기록해 그대로 그리면 38mm 뜬다.
schema.js snapPostPairs()= 같은 CP 각목 2본·같은 y·z·간격 60mm 이내 → 장 모서리쪽 앵커에 붙임(치수·수량 불변, 3D만). ⚠기둥목폭은 상수 아님 — 68=$기둥목폭02(BC/WC_SD 001계) · 60=$기둥목폭(DD계), 오더별 45~117 산발. ⚠예외 = BC_DD_R_001 3본 · BC*_CH_001_TK 의주방장_기둥목_0000_SCR별도 2본. - 🔴 밴드(TRAVERSE) 전후면 =
VORNE/HINTEN플래그가 정본(이름_F/_B는 관례) →cp.json bandPos. 앞밴드만 앞면 정렬(horizY분기), 뒤밴드는 종전 후면(장 외곽 깊이 기준). 깊이 폴백사슬 = fixW(실부재 최빈) → bandDepth(위치 플래그쪽 TIEFE 리터럴) → CABIN식 — fixW 없는 저빈도 밴드(_F_68mm류)가 CABIN 폴백으로 새면 장 전체깊이 판이 된다. mkcp2 의TIEFE_VORN or TIEFE_HINT병합은 HINTEN 밴드에서 오값이라 플래그 분기로 교체. ⚠LAGE=2(수직_V)·3(mixed) 밴드는 미구현 — 다룰 때 별도 규칙. - 부재목록(
▤ bElems) =schema.cpMaster()읽기전용 조회(imos EM 역할). CP 정본이 convert 생성물이라 편집은 서버 테이블 신설 후(phase2 — 필드 목록은 session-2026-08-18-article-editor2-imos-full.md §4). - ⚠ 편집기 E2E 존 경로 — BC_R_001 기둥목존은
0.0.0.0.0.0(6단). 한 단계 얕게 짚으면 빈 면이 잡혀 에러 없이 성공처럼 보인다(설치면 이동은 빈 면 가드). - ⚠ 공간 투명표시는 아티클 편집기 전용(페페 지시 26.8.18) — 메인 3D
paintZones()는 존 색을 안 입힌다(픽킹만 유지). 메인에 존 강조를 되살리지 마라. - imos 원본 전컬럼 =
reference/imos-full/zone_full.json(anglprim/anglzone/anglelem 17,015행, 재실행tools/pull_zone_full.py). 트림코드 4변 100%·힌지 10,344건 확보 — convert.mjs 소비가 다음 본작업.
🆕 신규 (2026-08-18 오후 — 좌대·멍박스·멍장 근본수정)
- 🔴
#이름LINDIV = DESCRIPTOR 조건분할이다 — 분할식이DESCRIPTORVALUES(NAME, NODENUM, CONDITIONID, LINDIV)에 있고, NODENUM 순으로 훑어 처음 참이 되는 조건의 LINDIV 을 쓴다(CONDITIONID=0= DEFAULT). 조건은 전량X <op> 숫자의 AND 이고 X = 나누는 축의 길이(실측 135조건 ·LEFTVALUE전량'X'·OPERATIONTYPE전량 0). 정본 =data/descriptors.json(54종·186분기, 생성tools/pull_descriptors.py). 종전엔 카탈로그 자식 수만큼 균등분할로 폴백해 칸 수가 스냅샷에 굳어 있었다(좌대 전량 잉여의 정체). ⚠ 소비자마다useDescriptors()를 따로 물려야 한다(app.html·verify2.mjs·verify.mjs·convert.mjs) —useCPMaster와 같은 구조. - 🔴 박스형 부자재(좌대·멍박스)는 구성원칙이 반대다 — 긴변(F/B)이 존 폭을 통으로 가로지르고 짧은변(L/R·분할재)이 그 사이에 끼운다. imos 정본 =
anglelem.STARTTRIM/ENDTRIM의'L'(장) /'S'(단). 실측(2026 인스턴스 199건 전량 · 편차 0) = 긴변W − max(inset, 0.5) 양쪽· 짧은변D − insetF − 앞뒤 두께· 길이는 넷 다 존 높이. 판정 = 네 면 CP 가 전부 CSIDE PRODCODE 12. 15T 판으로 바뀌면 짧은변도 같이 줄어 맞는다(18T_좌대 15T 2건 확인).schema.js walkBox(). - 🔑
max(inset, 0.5)가 핵심 — 18T_좌대(inset 1)는W−2, 15T_좌대(inset 0)는W−1이다. inset 이 없어도 양쪽 0.5씩 물러난다(CABINUEBERST −0.5와 같은 관행). - 🔴 세로 박스형(멍장류) = 좌·우가 박스형 CP 이고 위·아래 경계 부재가 있을 때 — 천/지판이 존 폭을 통으로(
W − max(inset,0.5)×2), 측판이 그 사이에(H − 천지판 두께 2장 − 1). 종전엔 천지판이내경 − 1(= W−37) 이라 전량 어긋났다. - 🔴 박스형·선형 부자재(PRODCODE 12·13)는 몸통 두께 스위치를 안 받는다 — 이름이 곧 자재 지정이다.
18T_문틀재실부재 486매 중 15T 는 6매뿐인데alt이력이 있다는 이유로 주방 오더에서 전량 15T 로 바뀌어15T_문틀재_VA가 0% 였다.alt유무만으로 스위치를 걸면 안 된다(뒤판각목 사고와 같은 계열). - ⚠ 박스형 부자재(12)는 F면에서도 오버레이가 아니다 — 앞판 평면(
fb)이 아니라 그 존 박스를 딱 채운다(갭 0).struct판정에서 12를 빼야 멍장 칸막이 F/B 가 존 폭 그대로 나온다. - ⚠
convert.mjs의 「분할폴백」은 LINDIV 을 통째로 버린다 — 남는 자식에 내용이 있으면 균등분할로 떨어지는데, 15T_멍장이 그 케이스라 80mm 고정칸이 78mm 균등칸으로 뭉개진다(imos 실측은 LINDIV 이 맞다). 폴백 목록은data/convert-report.json의분할폴백7건. - ⏳ 남은 원인(다음 세션) = ①텐덤 서랍 — 부재는 후판 1 + 바닥판 1(측판 없음, 금속 박스가 측판 역할). 오프셋이 박스 종류별로 갈린다(같은 W600 에
Wz−89와Wz−43이 공존) →텐덤_<브랜드>_<모델>_D<>_H<>CP 이름 파싱이 선행. 누락 459+469매. ②좌대 분할 수는 디스크립터로 76% 까지만 맞는다 — 나머지는 오더에서 손으로 고친 것(같은 W1500 에 2칸·3칸이 공존). ③멍장 칸막이 개수도 같은 성격(멍장측판개수디스크립터가 따로 있으나 루트 LINDIV 은 리터럴).
🆕 신규 (2026-08-18 낮 — 편집기를 imos Article Designer 구조로)
- 🔴 아티클 편집기는 imos Article Designer 를 그대로 베낀다(페페 지시 — "그래야 적응하고 수정 지시를 낼 수 있다"). 화면 구성 정본 =
catalog-article-center.md:341의 뷰 이름 —imosCADArticleSizeSnapInView(상단 치수 바) ·imosCADArticleDesignerSnapInTreeView(좌측 정의 트리) ·imosArticleDesignerGraphicView(중앙 그래픽). 뷰 프리셋 3D/정면/평면/측면 = imos 명령 103 1/2/3. - 🔴 트리 노드 타입 =
Ind.dll TreeEnums.TypeE(article-variables.md§12.2) —Root · Dimensions(200) · Zone(400) · Division(500~, +570 식·571 유형·572 분할재두께) · Part(600~611 Left/Right/Bottom/Top/Front/Back) · Parameters(800)/Parameter(801). 파트를 오른쪽 패널이 아니라 트리 안에 노드로 둔다 — 이게 종전 편집기가 안 읽히던 이유다. - 어휘도 imos 것을 쓴다 — 분할 유형
Division+TypeE(우리 dir V=DIV_Z 높이분할 · H=DIV_X 폭분할 · I=DIV_W 독립) · 공간 유형Zone+ZoneTypeE(NORMAL/FRONT/CARCASE/BACKPANEL/COLUMN) · 파트 유형PartDefinition+PartTypeE. - 🔴 3D 에서 부재를 집으면 트리가 따라온다 — 그러려면 부재가 자기 자리를 알아야 해서
schema.js emit(out, part, {zone, side})이 표식을 찍는다(계산에는 안 쓰인다). ⚠ 분할재(divider)는 부모가 아니라 그 뒤 자식 존에 찍는다 — emit 이 자식 walk 뒤에 불려서 부모 경로를 쓰면 어긋난다. - ⚠ 레이캐스트는 앞쪽 부재가 이긴다 — 측판을 겨눠도 도어가 앞이면 도어가 잡힌다. E2E 기대값은 「겨눈 부재」가 아니라 그 지점에서 실제로 맞는 앞쪽 부재로 잡아야 한다(안 그러면 멀쩡한 코드가 FAIL 로 뜬다).
- 🔴 선택 강조는 색을 갈아치우지 말고 발광(emissive)만 얹는다. 통째로 주황을 칠하면 공간을 골랐을 때 그 밑이 한 덩어리가 돼 형태가 사라진다(첫 판 캡처에서 드러남).
- 편집기 3D 는 도어를 기본으로 끈다 — 앞판이 덮으면 선반·칸 구성이 안 보인다(미리보기와 같은 이유). 「투시」 토글은 반투명.
- ⚠ 오버레이 헤더는 우상단 ~44px 을 비운다 — qahelper 토글이 그 자리를 덮어 「닫기」가 가려진다(devplan 전역 함정과 같은 것).
- 편집기 3D 는 자체 three 컨텍스트를 한 번만 만들어 재사용한다(열 때마다 만들면 컨텍스트가 샌다). 마우스 규약은 본 화면과 같다.
🆕 신규 (2026-08-18 오전 — 아티클 카탈로그 편집기 · 배리어블 · 묶음 백업)
- 🔴 용어 = 「배리어블」(variable). 페페 지시 2026-08-18 — 앞으로 문서·UI·코드 주석에서 변수를 배리어블로 지칭한다(상단바 버튼도
ƒ 배리어블). 「현장 변수」처럼 이미 굳은 화면 문구는 그대로 두고, 새로 쓰는 곳부터 적용한다. - 🔴 카탈로그 정본은 PFS DB 다 —
data/catalog.json은convert.mjs생성물이라 제작 아티클을 담을 수 없다(다음 재변환 때 조용히 사라진다). 테이블 3개 =pncad_article(최신본만·초안is_draft) ·pncad_var(배리어블 마스터) ·pncad_backup(묶음 판). 전부 멱등ensure_table이라 SSM DDL 스크립트가 없다(pncad_scene선례). 앱은/api/pncad/catalog를 먼저 받고 실패하면 파일로 떨어진다(단독 8799 = 읽기전용). - 🔴 배리어블 이름 PK 는 대소문자를 가려야 한다(
utf8mb4_bin). imos 실데이터에TC_R_Door_001과TC_R_DOOR_001이 둘 다 있는데 MySQL 기본 콜레이션(utf8mb4_general_ci)은 둘을 같은 키로 봐서 시딩 트랜잭션이 Duplicate entry 로 통째로 죽고/api/pncad/catalog가 전량 502 였다. 한글·영문 식별자를 PK 로 쓰는 새 테이블은 전부 이 검사를 먼저 한다. - 🔴 시딩·부트스트랩 코드에서 예외가 새면 uWSGI 워커가 죽어 nginx 가 502 를 준다(Python 500 이 아니다). 로그가
upstream prematurely closed connection뿐이라 원인이 안 보인다 → 부트스트랩은try/except로 감싸고 실패를 응답 필드(seed_error)로 실어 눈에 보이게 남긴다. 서버 로그 실물 =/home/ubuntu/pfs-log/pfs.log(EC2,python scripts/ssm_run.py로 읽는다). - 🔴
setSegCount()는 세그먼트 목록(1:$기둥목폭 MM:1) 전용이다. 분할식이 반복 템플릿(3*{1}·6{1})이면 lindiv 를 안 건드리고 자식만 늘려서, 전개(LINDIV 이 정본)가 원래 개수로 되돌린다 = 칸 수 편집이 조용히 안 먹는다. 반복 템플릿은normVar(String(n), lindiv)로 개수만 갈아끼운다(editor.js setZoneCount). 잎 존을 처음 나눌 때는 자식과 분할식을 같이 세운다(자식만 만들면 나눌 근거가 없다). - 면(부재정의)은 CP 이름 문자열 하나로 전부 파생된다 —
parseCP(name)이 role·line·rv·thk·variant 를 뽑는다. 그래서 편집 UI 는 6칸이 아니라 이름 1칸 +$변수스위치 + 인셋 셋이면 된다. - 미리보기 렌더러는 카드 썸네일과 같은 것을 쓴다(
previewURL(item, opt)). 편집기용 three 렌더러를 따로 만들면 GPU 컨텍스트가 둘이 된다. - 백업은 카탈로그 전체 한 묶음 단위(페페 확정 — 아티클별 이력·수시저장 이력은 안 쌓는다). 자동 판은 최근 50개, 이름 붙인 수동 판과
seed판은 안 지운다. 배리어블을 연달아 고칠 때 550KB 판이 수십 개로 불지 않게 5분 안의 자동 판은 갱신한다. 복원은 직전 상태를 자동 판으로 먼저 뜬 뒤 통째 교체한다. - imos 기준선(
origin='imos') 아티클은 삭제 금지(서버가 거부). 재추출 판과 비교할 기준이 무너진다 → 고치려면 복제해서 쓴다. - 🔴 마우스 규약(페페 지시) = AutoCAD 와 같다 — 가운데 휠 드래그 = 이동 · Shift+가운데 = 커서 중심 회전 · 좌클릭은 카메라를 안 건드린다(선택·장 이동 전용) · 관성 금지(
enableDamping=false). OrbitControls 는 target 만 축으로 돌기 때문에 커서 중심 회전은 컨트롤을 끄고 카메라를 직접 궤도 이동시킨다. 🔑 손 뗄 때 target 은 커서점이 아니라 「시선축 위로 투영한 점」으로 되돌린다 — 커서점을 그대로 넣으면camera.lookAt(target)이 걸려 화면이 튄다. - 자석 붙임 거리 = 90mm(45 → 2배, 페페 지시).
- ⚠
rayAt(e)는 Raycaster 를 돌려준다 —.ray는THREE.Ray라intersectObjects가 없다(intersectPlane만 있다). 객체 교차는rayAt(e).intersectObjects(...). - ⚠
place()는 배치 즉시 W/D/H 모달을 연다 → 마우스·픽킹 E2E 는 모달을 먼저 닫아야 한다(#iClose). 안 닫으면 오버레이가 캔버스를 덮어 모든 마우스 시험이 조용히 0 으로 나온다.
🆕 신규 (2026-08-18 새벽 — 코너장 근본수정 · imos 전량 대조)
- 🔴 대조는 오더 1건이 아니라 imos 2026 전량으로 한다 —
verify2.mjs+reference/imos-2026/instances.json(인스턴스 2,726 · 아티클 165 · 부재 28,709, 생성 =tools/pull_imos_2026.py). 오더변수(IDBVARVARCAT 0)와 인스턴스변수(VARCAT 1)를 안 물리면 대조가 무의미하다 — 구verify.mjs는 변수를 안 넣어 찬넬높이·기둥목폭이 카탈로그 기본값으로 굳어 있었다. 전량 기준 33.5% → 50.4%. - 🔴 앞면 부재는 내경이 아니라 「앞판 평면」 기준이다. 도어·덧방은 풀오버레이라 측판·천지판을 덮고 장 외곽까지 나온다.
schema.js walk()가 몸통 박스(box)와 앞판 평면(fb)을 나란히 내려보낸다 — 면·천지판 두께는fb를 안 깎고, 분할만 양쪽에 같이 먹인다. 실측 = 코너 덧방 폭(장W − 도어) − 213/13 편차 0, 높이(장H − 찬넬높이) − 2. - 🔴 어떤 분할이 「외곽 기준」인지는 imos
anglzone.SIZREF*가 알려준다 —SIZREFOUT1/EDG1/MID/EDG2/OUT2중 하나라도'O'(Outer)면 그 분할은 장 외곽을 다시 기준 삼는다(convert.mjs가outer:true로 옮긴다). 포인 도어 분할이 전부 이것이고, 일반 중간기둥(1:$기둥목폭 MM:1)은 전부'I'다. 이걸 모르면 코너장에서$기둥목폭02 mm:1(68) 안에 도어 400mm 가 들어가는 구조를 영영 못 푼다. ⚠ 재설정은 나누는 축에만 건다 — 다른 축까지 되돌리면 찬넬로 잘린 높이가 살아나 덧방이 찬넬만큼 길어진다. - 🔴 코너 기둥목은 존 폭이 아니라
$기둥목폭02(68) × 2본이다. 실측 = 2026 코너 인스턴스 2,971건 전량이 2본·폭 68(6,318/6,730행).TC_L은 370·600 존인데 부재는 68이라 존 폭일 수 없다. 종전post → box.w규칙이 맞아 보였던 건 우연이고, 두 번째 기둥은 0폭 존에 갇혀 부재가 통째로 사라졌다(페페 지적 「한쪽만 있고 한쪽은 안 보인다」의 정체). - 🔴 CP 파라미터 값이
$변수수식이면 숫자로 못 깎인다고 버리지 마라 — 원문을 보존해 전개 때 평가한다(schema.js pv(),mkcp2.py num()).주방가구_AS_Corner.UEBERST_V=-3-($기둥목폭02)= −71.null로 버리는 바람에 그 CP 부재가 통째로 71mm 어긋나 있었다. - 🔴 몸통 두께 스위치는 그 CP에 실측 두께 이력(
alt)이 있을 때만 먹인다. 계열이 '일반'이라고 무조건 18T 로 바꾸면뒤판각목_15T가 18T 로 부풀어 2,235매가 통째로 어긋난다(대조 원인표 1위였다). 도어(18T MDF)도 스위치 대상이 아니다. - 🔴 뒤판 높이·천지판 깊이는
RGRDE부호가 정한다 — 부호 > 0 = 홈 물림(그루브) → 그 판 두께를 뺀 채 두고, ≤ 0 = 평면 리빌 → 판 두께를 되돌려 더한다. 천/지판 깊이도 같은 부호로 갈린다(≥0 = 장 깊이, <0 = 뒤판 앞까지). 이름(_하부홈)으로 가르면 벽장·상하홈 변형에서 틀어진다. - ⚠ 밴드(TRAVERSE)는 두께 + 1mm 를 먹는다(아래 존이 그만큼 낮다). 천판(CABIN) 아래는 안 그렇다. 앞판 평면에는 안 먹인다.
- ⚠ 선형부재(CSIDE PRODCODE 12 좌대류·13 걸레받이/문틀재)는 「각목류」가 아니다 — 구성원칙이 반대(긴변이 F/B)다.
post판정에서 빼고, 칸막이로 쓰일 땐 깊이가 아니라 그 존의 폭이 부재 폭이다(18T_문틀재_VA0% → 75%). - ⚠ 기둥목 같은 CSIDE 구조재의 F면은 앞판 평면이 아니라 그 존 박스 기준이고 갭이 0이다. 오버레이인 도어·덧방만
fb+ 갭을 쓴다. - 🔑 변수 입력은 「모양 복원」으로 푼다(페페 지시 2026-08-18) — 원래 값의 모양을 기억했다가 사용자가 맨숫자를 넣으면 그 모양으로 되돌린다(
6{1}+3 →3{1}·100mm+450 →450mm). 콜론·중괄호·$가 섞이면 손대지 않고 그대로 저장한다. 정본 =lindiv.js의varKind/normVar/varHint(node lindiv.js자체검증). 이게 없으면 도어 사이즈 칸에450을 친 순간"450"= 비율 450:1 로 풀려 도어가 장 전체 폭이 된다. - 🔑 imos DB 는 미니PC에서 바로 붙는다 —
powershell -File C:/dev/nesting/dbquery.ps1 -SqlFile <utf8.sql> -OutFile <json>. ⚠ SQL 안에 한글 리터럴 금지(에러 없이 0행).anglzone은 77컬럼이고 우리가 쓰던 덤프는 13컬럼 발췌였다 — 필요하면 원본 스키마를 다시 본다(SIZREF*·LINDIVZ·DIVANGLE이 거기 있었다). - ⏳ 미해결(다음 세션 1순위): ①좌대·멍장·멍박스 =
#좌대_SCR같은 DESCRIPTOR 조건분할 미구현(DESCRIPTORVALUES에1/1:1/1:1:1/1:1:1:1+CONDITIONID로 들어 있다) ②텐덤 서랍 산출식(브랜드별 gap/margin/depth 실측표는 확보) ③커스텀 높이 오더의 선반 매수 — imos 가IDBVAR밖에서 정한다.
🆕 신규 (2026-08-17 밤 — 계산 버그 3종 근본원인)
- 🔴 imos 변수값은 단위째로 보관한다.
imos-vars.json의WERT가"100mm"인데vars-default.json이 숫자100으로 깎아 넣으면, LINDIV$BC_R_Door_001:1이 고정 100mm 가 아니라 비율 100:1 로 풀린다(도어 100 → 960).lindiv.js는 문자열"100mm"를 solo 변수 재파싱으로 이미 옳게 처리하므로 데이터만 옳으면 된다. 화면에서 변수를 고칠 때도 단위를 되붙여야 한다(putVar). 실측 = 단위 깎인 변수 25개. - 🔴 수량이 바뀌면 자식을 LINDIV 에 맞춘다 — 던지지 마라.
$W_선반수{1}·$문틀재Q01{1}같은 반복수 변수를 바꾸면 세그먼트 수가 카탈로그 children 과 달라진다. 예외를 던지면 그 장이 갱신을 통째로 잃어 「최초 수량에서만 정상」이 된다. SPEC-SCHEMA §5(LINDIV 이 정본)대로 부족하면 마지막 자식 복제, 남으면 자른다. - 🔴 칸 수를 바꿀 때 LINDIV 을 통째로 다시 쓰지 마라.
'1:1:1'로 덮으면 찬넬·밴드 같은 고정칸(mm)이 비율칸이 된다 →setSegCount()(lindiv.js) 로 대상 칸만 늘리고 줄이고 고정칸 원문은 건드리지 않는다. - 🔴 각목류(CSIDE)는 장 깊이가 아니라 그 존의 폭이 부재 폭이다. 기둥목 = imos 실부재
670×68(68 =$기둥목폭02로 갈린 존 폭). PRODCODE 01(측판)만 깊이를 쓴다. 안 그러면 기둥목이 측판처럼 길게 나온다. - ⚠ 존은 음수가 될 수 없다. 고정 세그먼트 합이 부모를 넘으면(실데이터에 있다) 앞에서부터 채우고 나머지 칸은 0. 부재도
l·w·t > 0이 아니면 안 만든다 — 안 막으면-47짜리 부재가 규격표·네스팅까지 나간다. - 🔑 존 편집 UI 는 「선택된 잎」과 「그 부모」를 같이 봐야 한다. 픽킹 대상은 잎 존뿐인데 칸 수·서랍 단수는 자식을 가진 부모의 값이라, 부모를 안 물리면 그 UI 가 영영 안 뜬다(2026-08-17 E2E 로 발견 — 코드는 처음부터 있었는데 도달 불가였다).
- ⚠ 치수를 치수선으로 그리면 라벨의 W×D×H 는 빼라 — 숫자가 두 벌로 겹쳐 읽을 수가 없다.
🆕 신규 (2026-08-17 밤 — 네스팅 편입 · 배포 확인)
- 🔴 PFS 배포 대기를 「HTTP 200 + 본문에 특정 문자열」로 판정하지 마라 — 비로그인은 200 + 로그인 HTML 이다. 26.8.17 네스팅 배포에서 폴링 루프 10회가 전부 「대기」로 나왔지만 그 시점에 이미 배포 완료였다. 판정은 로그인(
POST /api/auth/login={user_id, password}) 후 본문 대조 — 로컬 파일과 길이·해시 비교가 확실하다(서버는 LF, 로컬 체크아웃은 CRLF 라 바이트 수는 다르다 → 텍스트로 디코드해 비교). - ⚠
gh run list로 pfs Actions 를 못 본다 — PAT 가 프라이빗 리포 Actions 권한이 없어404. 배포 확인은 GH 가 아니라 서버 직접 조회로 한다. - 🔴 PFS 화면을 새로 붙이면 「라우트」와 「사이드메뉴 행」이 별개 작업이다 — 라우트만 배포하면 URL 직접입력으로만 열린다.
menu_catalogINSERT 는 SSM 스크립트를 직접 돌려야 반영된다(코드 배포에 안 딸려간다). 스크립트만 커밋하고 끝내면 조용히 누락된다. - 🔑 네스팅 부재 합산은 저장 시점
nest_rows를 서버가 더한다 — 네스팅 페이지가 씬 전개 엔진(schema.js·cp.json·three.js)을 다시 들이지 않아도 된다. 대신 PNCAD 에서 저장을 안 한 작업본은 부재가 0 이라skipped로 빠진다.
🆕 신규 (2026-08-17 밤 — 작업본 서버 저장)
- 🔴 작업본(씬) 저장소 = PFS DB
pncad_scene1테이블(페페 택1 2026-08-17). 저장 단위 = 현장 + 작업명 여러 벌(한 현장에 타입별로 나란히). API =/api/pncad/scene(목록·새저장) ·/api/pncad/scene/<no>(단건·덮어쓰기·소프트삭제). 목록 SELECT 에서data를 반드시 뺀다 — 씬 JSON 통짜라 목록에 실으면 응답이 수 MB 된다. - 🔑 PFS 새 테이블은 SSM DDL 스크립트 없이 만든다 —
company_vacation이 쓰는 멱등ensure_table()패턴(dao 안CREATE TABLE IF NOT EXISTS+ 전역 latch)을 그대로 따르면 앱서버가 첫 요청에 만든다. 운영 배포 시 DB 작업이 코드 배포 하나로 끝난다. - 🔴
MySQLDatabase는 쿼리 1건마다 새 커넥션을 연다 — insert 후SELECT LAST_INSERT_ID()를 따로 부르면 다른 세션이라 0 이 나온다. 신규 PK 가 필요하면 반드시MySQLUtil.execute_id()(같은 커서의lastrowid). - ⚠
MySQLUtil.fetchall(sql, params)는 params 가 있으면%이스케이프를 탄다 — SQL 안DATE_FORMAT(..., '%Y-%m-%d')는%%Y-%%m-%%d로 써야 한다. params 가 빈 리스트면cursor.execute(sql)라%하나로 둔다(같은 파일 안에서 규칙이 갈린다). - 🔴
loadScene같은 "불러온 뒤 clean 표시"는 재렌더 뒤에 찍어라 —rebuild()안에서save()가 불려 dirty 로 되돌린다. E2E ⑤b 로 실제로 잡혔다(테스트 1차 FAIL). - 저장 상태 표시는 색으로만 구분(저장됨 초록 · 변경됨 주황 · 미저장 회색). 페페 UI 취향 = 굵기로 강조하지 않는다.
- localStorage 는 크래시 복구용 자동저장으로만 남긴다(
poincad2.scene= 씬 ·pncad.sto= 현재 작업본 번호·이름·dirty). 진짜 저장은 서버. JSON 파일 저장·열기는 백업·외부 전달용으로 유지(페페 지시) — 파일에서 연 씬은STO.no=null로 끊어 다음 「저장」이 새 작업본을 만들게 한다. - PFS RestResponse
fail()은 반환값이 없다 —RestResponse().fail(e).result로 체이닝하면NoneType이다.response.fail(e)후response.result를 따로 낸다.
🆕 신규 (2026-08-17 밤 — PFS 편입 · 운영배포 완료)
- 🔴 PNCAD 운영 실행 경로 = PFS
생산 > PNCAD(첫 항목, 새 탭) →https://pfs.poin.co.kr/production/pncad/. devplan 8799 는 개발본이다. URL 끝 슬래시가 규칙 —app.html이./schema.js·data/*.json을 상대경로로 부르므로/production/pncad(슬래시 없음)면/production/schema.js로 새어 404 난다. 라우트도/production/pncad/로 걸어 Flask 가 자동 리다이렉트하게 둔다. - 🔴 PNCAD 자산은
pfs/web/pncad/에 둔다 —web/static/밑은 금지. Flask 정적 폴더는 로그인 없이 열려서 현장목록 1,988건·카탈로그·CP 마스터가 그대로 샌다. 서빙 =send_from_directory+@login_required(자산 라우트도 같이 건다). 검증 = 비로그인curl로 페이지·schema.js·data/fields.json셋 다 로그인 페이지가 나오고 데이터 0줄인지 본다. ⚠ PFSlogin_required는 302 가 아니라 200 으로 로그인 HTML 을 반환한다 — 상태코드만 보고 "인증이 안 걸렸다"고 오진하지 마라(이번에 한 번 오진했다). 본문을 봐야 한다. - 🔴 원본은
C:\dev\pncad, PFS 는 실행 사본이다. 동기화 =PYTHONUTF8=1 python tools/sync_to_pfs.py(어긋남 검사 =--check). S7 대조(verify.mjs)·변환(convert.mjs)이schema.js를 import 해서 pncad 리포에서 계속 도니까 리포를 합치지 않았다. PNCAD 를 고치면 sync → pfs 커밋까지가 한 작업이다. - PFS 안에서는 현장목록을
/api/field/라이브로 쓴다(같은 오리진 + 세션 쿠키 그대로). 단독 실행(8799)이면 401/404 라 스냅샷data/fields.json으로 자동 폴백. 이걸로 "스냅샷 갱신 잊어버림" 문제가 사라졌다 —tools/fetch_fields.py는 이제 단독 실행용 백업일 뿐이다. - app.html 은 Jinja 를 태우지 않는다 —
render_template이 아니라 파일 그대로 내보낸다(JS 템플릿 문법 보호). menu_catalog새 메뉴에 권한행을 만들 필요 없다. 자사(company_type='1')는 명시 row 가 없으면 신규 메뉴 자동 허용이고, 업체 계정은 기본 차단이다. OMC 처럼 특정 업체에 열어줄 때만company_permission행을 넣는다.- 생산 그룹 순서:
production(450) >production_pncad445 >production_sticker(451). 첫 항목으로 넣을 땐 기존 행을 재번호하지 말고 더 작은 값을 쓴다. - 사이드바 새 탭은
base.html한 줄 조건({% if child.url_path == '/production/pncad/' %} target="_blank"{% elif … %}).menu_catalog에 target 컬럼이 없어서 그렇다.startswith를 쓰면url_path가 None 인 행에서 터지니 정확일치로 건다. - EC2 에서 운영 DB 를 만질 땐
python scripts/ssm_run.py <스크립트>를 쓴다(base64 → /tmp → python3).scripts/_*는 gitignore 라 git 으로 안 올라간다 — 그래서 SSM 러너가 표준 경로다. - deploy-live 선별 포워드 전에
git diff origin/deploy-live origin/master -- <파일들>로 실제 딸려갈 내용을 본다. 커밋 목록(log deploy-live..master)은 과거 선별 포워드분이 영원히 "미머지"로 남아 부풀려 보인다 — 판정 근거는 diff다(이번엔 딸려간 것 0 확인).
🆕 신규 (2026-08-17 밤 — 리포 이전 · PFS 편입 착수)
- 🔴 리포 =
C:\dev\pncad(구C:\dev\poincad2, 2026-08-17 이전). URL도/pncad/app.html로 바뀌었다. devplanapp.pyMOUNTS를 고치면 서버 재시작이 필요하다(pythonw 상주라 terminate 후 재기동). 이전 검증 =find . -type f | sort스냅샷 393개 전후 대조 일치. - 폴더 이전 시
mv: Device or resource busy는 코디 자신의 셸이 범인이다 — Bash 도구 세션 CWD가 그 폴더면 잠긴다.psutil로pr.cwd()스캔해 확인하고, 셸을cd /c/dev로 빼고 다시 시도하면 풀린다(별 조치 필요 없음). - 🔴 현장 선택 모달 = PFS 견적서 팝업(
web/templates/popup/field_popup.html) 양식이 정본이다(페페 지시 2026-08-17): 상차일 from~to 범위 + 검색 → 현장명·유형·상차일 3컬럼 표(상차일 내림차순) → 행 클릭 = 선택표시 · 「선택」 = 확정 · 「취소」. 앞으로 현장명 선택창은 전부 이 컨셉으로 만든다. - ⚠ GitHub 보존은 막혀 있다 — 로컬 커밋은 됐지만(
164ce8a·7c8240b) 새 리포 생성이 안 된다: SSH 인증(git@github.com)은 통하는데 wincred에 저장된 PAT이 만료(401 Bad credentials)라 API로 리포를 못 만든다. 경로 = ①페페가 GitHub에서delix0731/pncad빈 리포 생성 후git remote add origin git@github.com:delix0731/pncad.git && git push -u origin master②또는 PAT 재발급(로그인 자격증명 작업 = 텔레그램 승인 항목). - PFS 사이드바는 DB 주도다(
sidebar_menuscontext_processor +menu_catalog테이블) — 코드만 배포해도 메뉴가 안 생긴다. 메뉴 추가 =menu_catalog행 + 권한 행 insert가 필요하고, RDS는 사무실/미니PC에서 직접 안 열린다 → 리포 관례대로scripts/_*_prod.py를 만들어 EC2에서 앱 컨텍스트로 실행해야 한다(permission_dao.company_field_menu_key계열이 선례).
🆕 신규 (2026-08-17 저녁 — 앱 UI 2차 · PFS 현장 연동)
- 🔴 PFS 현장목록은 스냅샷으로 쓴다 —
python tools/fetch_fields.py→data/fields.json(1,988건). 라이브 호출은 3중으로 막힌다: ①/api/field/가login_required(401) ②브라우저는 CORS ③RDSpoin-dbinstance...:3306은 사무실/미니PC에서 타임아웃(SG 미개방). EC2 SSM으로 들어가도 앱이MYSQL_*를 프로세스 env에 안 갖고 있어 자격증명 경로가 없다. 결론 = 코디가 PFS에 로그인(C:\dev\.secrets\pfs_login.env)해 GET으로 떠서 정적 JSON으로 둔다. - PFS
RestResponse는 성공 payload를message키에 담는다(data가 아니다).{"code":"0000","message":[...행...]}— 파서를data로만 짜면 "형식 모르겠다"로 죽는다. - 현장 선택 시
field_no를 씬에 같이 박는다(scene.fieldNo) — imosPROADMIN.ARTICLENO= PFSfield.field_no가 유일한 다리(SPEC-BOM §4). 이름만 저장하면 나중에 오더를 못 붙인다. - 🔴 벽이 기준면이다 — 가구 뒤판이 벽(three z=0)에 붙고 앞으로 나온다. 인스턴스마다 깊이가 달라서 three z 오프셋 = 그 인스턴스의 D. 앞면 정렬(z=0에 앞판)로 두면 깊이 다른 장들이 벽에서 들쭉날쭉해진다.
- 드래그 이동은 기본 가로(x)만이다. 원근 평면에서 화면 가로로 밀면 평면상 y(높이)도 딸려 올라가 장이 공중에 뜬다 → 기본은 x만 반영하고 Shift일 때만 z까지 옮긴다. 자석 = 이웃 장의 좌/우 모서리·상/하면 45mm 안쪽이면 정확히 붙임.
- ⚠ E2E에서 3D 픽킹/드래그를 합성 이벤트로 테스트할 땐 카메라가 돌아간다 — 드래그가 안 잡히면 OrbitControls가 직전 합성 이벤트로 카메라를 회전시켜 좌표가 어긋난 것이다(앱 버그로 오진했다). 매 시행마다
rebuild()→fit()→ 좌표 재계산 → 이벤트 순서를 지키고,PC2.hitAt(x,y)로 그 점이 정말 그 장을 맞는지 먼저 확인한다. - 카메라 fit 거리는 화각에서 역산한다 —
d = r / sin(min(fovV, fovH)/2). 고정 배율(maxSize × 2.6)로 잡으면 입면에서 벽(4200×2400)이 잘린다. 썸네일도 같은 공식.
🆕 신규 (2026-08-17 — S3~S6 앱 구축 + S7 대조)
- 🔴 앱 본체 =
C:\dev\poincad2\app.html(스키마 v2 ·schema.jsimport).mockup.html은 스키마 v1 시안이라 손대지 않고 보존한다. 실행 = http://100.108.234.45:8799/poincad2/app.html (devplanMOUNTS가 리포를 그대로 서빙 — 사본 만들지 말 것). 허브 카드site도 이걸로 교체했다. - 🔴 부재의
box(3D 위치)는size에서 만든다. S2까지box가 존 박스 복사본이라 선반 n매가 전부 바닥에 겹치고 서랍 5매가 한 점에 뭉쳤다. 렌더러를 고치지 말고schema.js에서 고친다(= 위치의 단일 출처). 헬퍼 =frontBox(도어·프론트) ·horizY(수평재는 뒤쪽 기준 정렬, 천/지판만 장 깊이 기준). - 🔴 뒤판 높이는 지판만 빼고 천판은 안 뺀다(실측): 벽장 H600 →
588 = (600−15)+5−2· 하부장 H750 →738 = 735+5−2· B2D H750 → 738. 내경(천·지 둘 다 뺀 값)으로 계산하면 상부장에서 15mm씩 짧아진다. - 🔴
rgrde(홈 물림)는 CABBACK 클래스에만 쓴다. 뒤판각목처럼 B면에 붙은 CSIDE/CABIN 부재는 수평재 규칙(내경 −1) 이다 — 벽장 W500 뒤판각목 469 = 470−1(실측 17매). 클래스를 안 보면 1mm씩 계속 틀린다. - 🔴 imos 오더변수는 CP 교체 스위치다. 존트리 CP가
ref:"$B_밴드_F"를 쥐고 있으면 스코프 변수값(CP 이름)으로 갈아끼워야 한다. 카탈로그 스냅샷 CP(밴드_0000_SCR_Front)를 그대로 쓰면 밴드가 폭 60이 아니라 풀깊이 539로 나온다. - 🔴 CP 이름에
_0000_이 있고 실부재 0건이면 부재를 안 뽑는다(imos 기본값 placeholder).밴드_0000_SCR_Front/Back이 카탈로그에서 30번 참조되는데 imos는 부재를 만든 적이 없다(두께·자재 공란). 조용히 버리지 말고out.skipped에 남긴다. - 몸통 두께 스위치는 계열별로 따로 먹는다 —
bodyThk.주방/bodyThk.일반. CP 이름에 박힌 두께(_15T)도 스위치를 받되 15/18(PB 계열)만 바꾼다. 3T 뒤판·4.5T·19T 도어마감까지 바꾸면 안 된다. imos도 오더변수바디자재_주방가구/_일반가구로 계열별로 건다. - imos는 서랍 측판·전후판을
15T 서랍재(PRODCODE 08)로 부르고, 서랍 바닥판은뒤판(06, 3T HDF)으로 부른다. CP 마스터 키서랍바닥판의role이뒤판인 게 오류로 보이지만 실부재 517건 기준이라 그게 맞다. node verify.mjs는 치수만 비교한다(이름·CP 무관). 원인 추적은ALL=1 node verify.mjs— 부재별우리 ↔ imos이름을 붙여 원인표를 찍는다. 짝짓기는 최근접 치수라 라벨이 엇갈릴 수 있다(천판↔지판으로 붙는다) → 원인 판정은 반드시gold-parts.json원본을 같이 본다.- 박스형 부자재(멍장·좌대)는 구성원칙이 반대다 — imos는 천/지판 풀폭(W1000 → 998) + 측판이 사이에 끼임(H700 → 669). 우리는 "측판 풀높이"로 고정돼 있어 이 군은 전부 틀린다. CP
UEBERST로는 판별이 안 된다(같은 −0.5) → 존별 구성원칙 옵션이 필요하다. - three.js 좌표 변환 = schema(
x폭 ·y깊이 앞→뒤 ·z높이) → three(y상):mesh.position = (x + w/2, z + h/2, −(y + d/2)). 앞면이 +z라 도어가 카메라 쪽에 선다. - 썸네일 카메라는 바운딩 스피어에서 역산한다 —
d = r / sin(fov/2) × 1.08. 최대 변 기준으로 잡으면 키큰장·상부장이 잘린다(첫 시도에서 잘렸다). - 존 픽킹은 앞면을 맞춘다 — 존 중심을 향해 레이를 쏘면 앞에 겹친 다른 존(서랍 사이 30mm 칸 등)이 먼저 맞는다. E2E에서 존을 고를 땐
mesh.position.z + depth/2 − 2(앞면)로 좌표를 잡아야 의도한 존이 선택된다.
🆕 신규 (2026-08-17 — S2 변환 + 몸통규격표 RDL 해독)
- ✅ 찬넬(CH) 실물 치수 확정(페페) = 깊이 37 × 높이 70 의 빈 공간을 만들고 그 공간을
19mm 도어마감 동일 자재로 메운다. 즉 찬넬은 구매 프로파일이 아니라 부재를 뽑는다 — 자재가 도어 계열(PRODCODE=21)이라 몸통규격표에서 빠져 imos 덤프에 부재 이력이 없었던 것. 변수$CH깊이=37 ·$CH높이=70과 정확히 일치. 현장별 전역변수 수정이 잦다. - ✅ 기둥목 폭(페페) = 일반 더블도어 장 45 기본(현장 사양에 따라 56·60 변동 = 변수
$기둥목폭) / 코너장 68 × 수직 2개(경첩 고정 기둥, 힘 받음 =$기둥목폭02). 가구 KB의 45와 imos 실측 68이 충돌해 보였으나 둘 다 맞다 — 장 종류가 다르다. -
가구 KB는 imos CP 필드 수준 값을 갖고 있지 않다(KB는 업계 교과서·카톡마이닝 실무값 위주). 치수 산출은 imos CP 실측이 1차 소스, KB는 교차검증·상충 발견용으로 쓴다(가구전문가 에이전트 판정 2026-08-17). KB-07의 일반값(뒤판 6~9mm PB/MDF, 홈파기 9mm)은 포인 실제(3T HDF, 물림 4.5×2)와 축 자체가 달라 그대로 쓰면 안 된다.
-
🔴 부재 치수를 추측하지 마라 — imos CP 테이블이 전부 갖고 있다(페페 지시 2026-08-17: "치수 추측에서 시간을 많이 뺏기고 있다").
CABIN.UEBERST_R/L/V/H(수평재 4방향 돌출) ·CABBACK.RGRDEL/R/T/B(뒤판 4방향 여유) +DISTLR(뒤판이 장 뒤에서 들어온 거리) ·CSIDE.DISTFRONT/BACK·TRAVERSE(밴드). 실측 검증 = 지판 569=570−1(UEB −0.5씩) · 이동선반 567=570−3($AS_LR여백=−1.5) · 뒤판 폭 579=570+4.5×2 · 뒤판 높이 738=735+5−2 · 밴드 폭 60(변수밴드폭). 이 이식만으로 실오더 대조 일치율이 16.5%→35.0%로 뛰었다. - CP 파라미터 값도
$변수다 —$AS_LR여백(−1.5)$찬넬깊이(−($CH깊이+1))$기둥목폭02(68)$밴드폭(60). 그래서 변수표는 전역 마스터 486행 전량을 쓴다. 아티클 존트리가 참조하는 71개만 뽑으면 CP 쪽이 빈다(첫 시도에서 그랬다). - CP 마스터는 CP 테이블 전량(1,672종) 기준으로 만든다. 2026 실부재(IDBGPL)에서 뽑은 322종만으론 부족하다 — 존트리가 2026 미사용 CP(
주방가구_AS_Rv160)도 참조한다. 자재·두께·PRODCODE는 실부재에서, 구성원칙 파라미터는 CP 테이블에서 합친다. THK가 빈CABIN행은 껍데기다. 밴드(주방가구_밴드_15T_F)가 CABIN에도 있지만 THK·UEBERST가 전부 비어 있고 진짜 주인은TRAVERSE다. 같은 이름이 양쪽에 있으면 TRAVERSE 우선.- 🔴 부재↔아티클 조인 =
IDBGRPS.HIGHARTID→IDBINFO.ID(몸통규격표 RDL 정본).PARTPOSSTR문자열 앞자리 파싱은 임시방편이다. - 🔴 imos↔PFS 실연동 다리 =
PROADMIN.ARTICLENO= PFSfield.field_no(몸통규격표 RDL의i_orderInfo가OPENQUERY(pfs, …)로 실사용 중). 1차 조사가 지목한PROADMIN.pfs_field_no는 2026년 사용 0건이 맞고, 실제로 도는 건ARTICLENO다. PRODCODE코드표(2026 실오더 40건 실측) =01측판02천판03지판04고정선반05이동선반06뒤판07뒤판각목08서랍재 /11속대류12좌대류13걸레받이·문틀재14경판·몸통재도어 /21도어33.1EP(제외). 메인 규격표 =CUT_FLAG=1 AND PRODCODE≤10 AND POSSTR≠999, 11~14는 별도 섹션. 상세 =poincad2/SPEC-BOM.md.- 규격표 치수 칸은
FWIDTH*FLENG(폭 먼저)이고,IDBGPL.ID_TEXT='PART_V'인 부재만 뒤집어FLENG*FWIDTH. 소수는FLOOR버림(반올림 아님). PART_V = 세로 부재(측판·뒤판각목 등 48종). BINDATA의 RDL blob은 base64로 뽑는다 —CAST(BINCONTENT AS VARCHAR(MAX))로 받으면 한글이 깨진다(cp949 변환).SELECT (SELECT BINCONTENT FROM … FOR XML PATH(''), BINARY BASE64)후<BINCONTENT>태그 안쪽만 디코드.- imos는 재단(CLENG)이 완성(FLENG)보다 크다(실측 420→428.8). "재단 = 완성 − 엣지"가 아니다 → 나중에
cut()을 켤 때 방향을 반대로 잡지 마라. - 고아 존은 "빈 존"일 때만 자른다. 검증 결과 잘라낸 76개 중 70개(92%)가 완전히 빈 존이었지만 6건은 내용(면·서랍)이 있었다 → 내용 있는 자식이 남으면 자르지 말고 lindiv을
_lindiv_raw로 보존한 뒤 균등분할 폴백(convert.mjs 실장, 7건 발생).
🆕 신규 (2026-08-17 — S1 스키마 v2 확정)
- 🔴 imos
anglzone에는 고아 존 행이 남는다 — LINDIV이 정본이고 children은 그 결과다. 실측: 자식 있는 존 3,185개 중 세그먼트 수와 자식 수가 정확히 일치 1,430 / 불일치 198(12.2%), 그리고 불일치는 항상 자식이 더 많다(diff ≥ 0,top/bot/divider개수와 무상관).1:1:1→1:1로 고쳤을 때 세 번째 존 행이 안 지워진 흔적이다. → 존 자식 수를 imos 행 수로 믿지 마라. S2 변환기는 세그먼트 수만큼만 가져오고 나머지는 버리고 로그에 남긴다.schema.js:validate()가 불일치를 에러로 잡는다.LINDIV1이 빈 문자열인데 자식이 있는 존 184건 = 자식 수만큼 균등분할로 읽는다. - CP는 마스터 목록이 아니라 (파트 × 자재 × Rv) 3축 조합이다(페페 확인 2026-08-17: "이동선반·고정선반은 계산식이 단순한데 imos에서 세부지정이 오래 걸려 필요한 수량만큼 일일이 만들었다"). 실측 뒷받침 = 덤프 CP 756종 중 Rv 붙은 270종이 Rv를 숫자 파라미터로 빼면 124종으로 접힌다(
일반가구_AS_Rv#24 ·주방가구_AS_Rv#21 ·일반가구_칸막이_Rv#17 ·일반가구_FS_Rv#16). → CP를 그대로 베끼지 말고 3축으로 풀되_imos원문은 항상 보존(S7 대조·역변환).CP_*로 시작하는 imos 순수 CP만raw:true로 통째 보존. - 도어 판정은 CP 원문명으로 한다 — role만 보면 놓친다.
밑장_SDO_noHandle은 3축 분해하면role="밑장"이라/도어|SDO|DDO|FDO/에 안 걸린다(찬넬 밴드 도어를 통째로 놓쳤던 실패). 판정식 =_imos ?? role. 도어 두께는 몸통 스위치와 무관하게 18T MDF 고정(imos 자재명 안 믿음, 기존 규칙과 동일). DIVDIR축 해석 = V 높이축(선반) · H 폭축(칸막이) · I 레이어 · A 서브아티클. 근거 =B_SD루트1:($찬넬높이)mm(찬넬 = 높이 방향)가DIVDIR='V'+(V,이동선반)418 ·(V,고정선반)252 ·(H,칸막이)231 ·(H,측판)62. ⚠(H,이동선반)124 ·(H,고정선반)75 = 199건은 이 해석과 어긋난다 → S2에서 실부재 치수로 확정하기 전엔 단정 금지.DIVDIR='A'(25행) = 서브아티클 삽입이다.DIVIDER컬럼에 존 분할부재가 아니라 아티클명이 들어간다(BC_1DR_1SD,$DM_SUBARTICLE,Plant_05). 대부분 imos 데모라 1차 구현 대상 아님 —dir:"A"자리만 두고 넘어간다.LINDIV2는 5행뿐이라 모델링 안 한다(원문만 보존).- 부재 치수는 자재 두께로만 계산한다 — 엣지는 치수에 안 닿는다(페페 2026-08-17). 부재는
size한 세트만 갖고, 재단치수는 필드가 아니라cut(part)함수로 만든다(현재 항등). 엣지는 변 4개 레코드로 따로 붙여 밴딩 길이 산출·도면 표기에만 쓴다. 보정을 켤 일이 생기면cut()한 군데만 고친다.
🆕 신규 (2026-08-17 — 카탈로그 이식 실측)
- 🔴
DIVDIR='I'= 겹침 레이어 복제 (2차 조사 "의미 미확정" 해소). 존 공간을 N개 독립 레이어로 복제하고 각 레이어가 따로 재분할된다. 순차 분할이 아니다. 실증:신발장_DD루트I 3*{1}→0.0 뒤판각목/0.1 선반/0.2 도어= 깊이 방향으로 겹친 3레이어.W_SD→기둥목/몸통보조목/선반. LINDIV가2*{1}(313)·3*{1}(136) 두 값뿐인 것도 이것으로 설명됨(레이어 2개 또는 3개). ⚠ 현mockup.html존 모델은 V/H 순차분할만 지원 → I를 쓰는 아티클 93종·1,472배치(53%)를 못 읽는다. 존 모델에 레이어 개념 추가가 이식 선결조건이다. - 면 개수를 셀 땐
anglelem.FUNCT='V'(가상면 931행)를 반드시 뺀다. 가상면은 경계만 정의하고 부재를 안 만든다 → 안 빼면 측판·도어 개수가 부풀려져 골격 판정이 틀어진다(코디 1차 시그니처가 그랬음). - 코드값 의미는 라이브 DB가 아니라 2차 조사본이 정본(라이브 수치는 100% 일치 확인).
DIVTYPEA=이동선반·F=고정선반·P=칸막이·S=측판·C=뒤판·B=지판·T=천판·V=없음(잎존 포함) →element-manager.md§DIVTYPE 9종.PARTTYPES=측판(CSIDE)·H=수평재(CABIN)·B=뒤판(CABBACK)·D=도어(DOOR),ELEMID0=front/1=left/2=back/3=right.anglprim.CONSTRUCTIONTYPE은 1,040행 전부 0 = 미사용. -
라이브 덤프는
C:\dev\poincad2\reference\catalog-2026-08-17\에 보존(zones 10,604 / elems 5,371 / prims 1,040 / catalog 233 + 시그니처·타입분해 스크립트). 같은 쿼리 재실행 금지 — 파일에서 읽는다. -
도어 자재 = MDF가 정답. imos
IDBGPL.MATNAME에18T MDF/하부도어자재가 섞여 있는 건 페페가 imos 기본값을 안 고치고 쓴 흔적일 뿐이다(2026-08-17 페페 확인). poincad2는 도어=MDF로 판정하고 imos 자재명을 그대로 믿지 않는다. - 도어 갭(상하좌우 빼기)은
DOOR테이블 68컬럼에 이미 파라미터로 있다 —DISTUP/DISTDN(상/하)DISTC(양문 중앙)DISTJ(조인트)FRONTDIST/FRONTDISTR(좌/우)NOD(도어 매수)DOORTYPE(1=오버레이·2=인셋)LR_SYM/LRSAMESIZE. 포인 도어 CP 232행. 값은 음수=빼기(포인 표준: 상하좌우 −3, 전면 좌우 −2, 플랩상부도어_FDO만 중앙 +16·상하 0). - 도어 갭의 기준면은 아티클이 아니라 "도어가 덮는 존"이다. 실부재 역산에서
하부도어_SDO_서랍마이다가 아티클H 대비 +394/+414로 튀는 건 그 도어가 장 전체가 아니라 서랍칸 존을 덮기 때문. 갭 계산을 아티클 W/H 기준으로 짜면 서랍·분할 도어에서 전부 틀어진다. -
장 종류별 도어 높이 규칙이 실제로 다르다(실측 2026) — 하부장
도어L = 장H − 34(지배값) / 상부장장H + 17(도어가 더 길다) / 키큰장−37과+3양분(462 vs 430건) / 플랩−37. 폭은 단문장W − 4, 양문(장W − 8) / 2로 일관. 페페 지적대로 상하부장·키큰장·배치별로 변수가 갈린다 — 단일 상수로 못 박는다. -
도어/몸통 경계 =
IDBGPL.PRODCODE.21= 도어라인(2026 부재 3,008 중 3,004가 도어 CP, 자재18T MDF·하부도어자재),01~20= 몸통·부자재. 두께로 MDF/PB를 판정하면 안 된다 — 도어는 18T라PB18로 오분류된다.C:\dev\nesting몸통재 탭이PRODCODE>=N'01' AND PRODCODE<=N'20'로 이미 도어를 걸러내고 있다(그래서 두께 CASE가 안전한 것). poincad2 산출도 같은 경계를 코드로 들고 간다. - 아티클 배치 인스턴스 =
IDBINFO.TYPE=2, 아티클명은CPID,WIDTH/DEPTH/HEIGHT가 실배치 치수.GROUPNAME은Article Designer Group같은 고정 라벨이라 아티클명이 아니다.IDBINFO.ORDERID는 int가 아니라 오더NAME(nvarchar30) —PROADMIN.ID로 조인하면 형변환 에러. dbquery.ps1SQL 입력에 한글 리터럴 금지(재확인).IN (N'붙박이장_DD_001', …)은 에러 없이 0행을 조용히 반환한다. 한글은 출력에만 쓰고 필터는 ASCII 키 또는 클라이언트측에서 건다.KT_FOLDER폴더경로는 재귀 CTE로 뽑는다(PARENT_ID=0앵커 →DIR_ID조인). 리프(TYPE=46)의NAME이 곧 아티클명이라articles와 바로 붙는다.
🆕 신규 (2026-07-27 — imos 실사조사에서 확정)
- imos 운영 DB(
poin)는 읽기 전용으로 다룬다. 조사·추출은 SELECT만. 쓰기가 필요하면 스냅샷 사본을 떠서 오프라인으로 한다(운영 부하·잠금 회피). GetActiveObject('AutoCAD.Application')금지. 이 PC에선 페페가 실작업 중인 AutoCAD 2016에 붙는다. iX CAD를 잡으려는 의도였다면 100% 오작동이다. iX CAD는 COM Application을 아예 노출하지 않으므로 CAD 자동화 시도 자체를 하지 않는다 — imos 연동은 SQL 읽기가 유일한 실용 경로.- iX CAD를 띄워 조사할 땐 ①새 인스턴스 ②초기화 완료(MainWindowTitle 생성) 확인 후 ③
SetWindowPos(-32000,-32000)로 파킹 ④PrintWindow(hwnd, dc, 2)캡처 순서를 지킨다. 초기화 전에ShowWindow(SW_HIDE)를 걸면 기동이 멈춘다(실측). 페페의 기존 인스턴스(PID)는 절대 건드리지 않는다. - imos 기능 조사는 스크린샷보다
imostemplate.cuix+imos.msg파싱이 우선. CUIX가 리본 전체(탭·패널·버튼·명령·설명)를 담고 있어 더 완전하다. WPF 리본은 UI Automation에 TabItem을 노출하지 않고 PostMessage 합성클릭도 무시하므로 탭 전환 캡처에 시간 쓰지 말 것. - .NET 어셈블리 조사는
ReflectionOnlyLoadFrom+ReflectionOnlyAssemblyResolve핸들러를 반드시 같이 건다. 핸들러 없이GetTypes()를 부르면 의존성 해석 실패로 타입 0개가 조용히 반환돼 "공개 API 없음"으로 오판한다(실측: 핸들러 추가 후imos.Data.dll0 → 1,216 public 타입). - 규격표 집계키 =
(부재명, CP, 자재, 완성치수 3축). imosIDBGPL.CHECKSUM1/2는 포인 환경에서 비어 있으므로 의존하지 않는다. - 치수는 완성/재단을 처음부터 분리해서 들고 간다. 한 필드로 뭉치면 엣지 두께 보정을 매번 재계산해야 하고 재단 오류가 난다. 엣지도 부재 속성이 아니라 변(edge) 4개 레코드로 둔다.
- 원격 도달성은 "포트 열림"이 아니라 기능검증(실제 쿼리·실제 파일읽기)까지 해야 판정된다 (2026-07-27 미니PC 실측: 사무실PC 445는 TCP 열림인데 SMB
Test-Path는 인증실패로 False,net view는 Tailscale 경유에서 오류 1702). 전역 규칙·머신 인벤토리·비침습 철칙 =rules/remote.md. - devplan 페이지 빌드는 이 사무실PC에서
build_hub.py를 직접 못 돌린다 —BASE/DEV가C:\dev하드코딩(미니PC 기준)인데 여기선 dev가 UNC(\\10.73.241.2\dev)다. 리포 파일을 고치지 말고 상수만 치환해 exec하는 래퍼로 실행한다. 치환 시re.sub대체문자열에 백슬래시가 있으므로 반드시 lambda repl을 쓴다(안 그러면bad escape \d). 검증된 래퍼 =scratchpad/build_hub_unc.py(치환 건수 1/1 아니면 abort).
🆕 신규 (2026-07-27 — 2차 심층조사에서 확정)
- imos DB 조회에
sqlcmd를 쓰지 마라 — 한글이 전부 깨진다(cp949 실측). 표준 = PowerShellSystem.Data.SqlClient직결(Server=(local)\IMOSSQL2012;Database=poin;Integrated Security=True), 출력은 UTF-8 TSV 파일로. 검증된 헬퍼 =scratchpad/imosq.ps1(SELECT 전용 정규식 가드 내장). 한글 리터럴은N'...'. - 사무실PC에서 Bash의
python은 깨진 venv(pfs/.venv)를 가리킨다. 반드시 절대경로C:\Users\poinpc\AppData\Local\Python\pythoncore-3.14-64\python.exe+PYTHONUTF8=1. - imos 테이블/컬럼의 정체를 "이름만 보고" 판정하지 마라 — 2차 조사에서만 오독 6건이 나왔다. 판정 근거는 항상 ①UNIQUE 인덱스 정의 ②
imos.Data.dll의 정본 enum(EnumShare.FolderItemType등) ③값이 어느 마스터 테이블에 조인되는지 셋 중 하나 이상이어야 한다. 실패 사례 =KMS(하드웨어세트 아니라 PD) ·Rv##(리비전 아니라 Reveal) ·poin_handle(손잡이 아니라 엔티티핸들) ·anglelem(존의 4변 아님) ·FAVORITES(즐겨찾기 아니라 MRU 캐시) ·TRAVERSE(iX PLAN 부속 아니라 포인 핵심 밴드 CP). - "파일이 디스크에 없다 = 기능 미도입"으로 단정하지 마라. imos는 RDL 리포트를 DB
BINDATA테이블에 varbinary로 저장한다. 1차 조사가 이걸 몰라 "리포트 미도입"으로 오판했고, 그 안에pfs링크드 서버 조인이 있었다(= imos↔PFS "단절" 판정도 무효). 자산 부재를 주장하려면 DB blob 테이블까지 훑어라. - 0행 테이블은 "포인 미사용"과 "기능 미도입"을 구분해 판정한다. 근거 =
IMOS.INI스위치(포인이 260키 중 실제로 바꾼 건 9개, 그 OFF들이 대부분의 0행 원인) + 설치 자산 유무 + 타 연도 데이터. - "2026년" 모집단을 폴더 기준으로 잡지 마라 — 사람이 오더를 옮기므로 재현 불가능하다. poincad2 회귀테스트 정본 =
PROADMIN.DATECREATE연도 기준 오더 223 / 부재 27,481 / 가공 117,185. (폴더 기준 153/17,369와 다르다. 도메인 3곳이 서로 다른 수를 쓰던 것을 이걸로 종결.) - 여러 에이전트가 같은 데이터를 조사하면 결론 충돌을 반드시 명시적으로 판정하라. 2차 조사에서 A파트 7건 · 감사 5건이 나왔고, enum 첫 글자 매칭보다 "값이 어느 테이블에 조인되는지"가 항상 강한 방법이었다. 충돌을 감추면 틀린 쪽이 설계에 들어간다.
- poincad2 스키마 철칙 3개(2차 조사에서 imos 실패를 보고 확정): ①면 주소를 존 경로에 이어붙이지 않는다(imos
anglelem "0.3"은 존인지 변인지 구분 불가 — 1차 조사 오독의 직접 원인) ②식별자에 사람이 읽는 이름을 섞지 않는다(imos는 오더명 문자열로 조인) ③단가에 유효일·통화를 넣고, 단가 0이면 조용히 더하지 말고incomplete로 표시한다(imos는 없어서 2009년 € 값이 2026년 한국 오더에 그대로 쓰였다).
🆕 신규 (2026-07-27 저녁 — iX CAD 실제 UI 구동 테스트에서 확정)
- iX CAD는 UI 자동화가 가능하다 — 단 "새 인스턴스 + Session 1 + SendInput" 조합일 때만. COM·LISP은 여전히 막혀 있지만 실사용자 입력 시뮬레이션(SendInput)은 통한다(실측: 아티클 삽입→복수선택→뷰 생성→자동치수까지 전 구간 구동). 조사 도구 =
scratchpad/ui.ps1(입력·창제어) ·shot.ps1/shotgrid.ps1(캡처·좌표그리드) ·dlg.ps1(모달 자동처리). - UI 구동 전 반드시
imos.exe·acad.exe실행 여부를 확인한다. 페페 작업 프로세스가 살아 있으면 구동하지 않는다(입력이 실작업 도면으로 샌다). 없을 때만 새 인스턴스를 띄운다. - 입력 가드는 hwnd 일치가 아니라 "같은 프로세스"로 잡는다. hwnd 완전일치로 하면 AutoCAD 자동완성 팝업·모달 대화상자에서 오탐으로 멈춘다(실측). 프로세스 단위여도 타 앱 유출은 막힌다.
- PowerShell에서
SendInput구조체를 조립하지 마라.$i.u.mi.dx = n은 중첩 값형식의 복사본에 써서 조용히 무시된다(실측: 커서가 전혀 안 움직임). 조립·전송을Add-TypeC# 메서드 안에서 한다. - PowerShell 5.1 함정 3종:
[ushort]없음 →[uint16]· 별칭이 함수보다 우선하므로 한두 글자 함수명 금지(r=Invoke-History,h=Get-History,gc=Get-Content에 가려져 엉뚱한 에러) ·-Pid파라미터명은 자동변수$PID와 충돌 →-ProcId. - 모달 대화상자는 픽셀 추측으로 누르지 말고
EnumChildWindows로 컨트롤 텍스트를 읽어 버튼 rect 중심을 클릭한다. 메시지 원문도 같이 수집돼 조사 근거가 된다. iX CAD 오류창은 연속 재발하므로Check1(다시 표시 안 함) 체크박스를 먼저 누르고 OK를 눌러야 끊긴다. - 뷰/치수 조사 시 오더는 저장하지 않는다. DB 기록은 오더 저장 시점에만 일어난다(실측: 미저장 테스트 후
PROADMIN신규 0건,SECTIONREFERENCE/MULTIARTICLECHECKSUM여전히 0행). 저장 안 하면 운영 DB 무흔적이다. -
🔴
SECTIONREFERENCE0행을 "POIN이 뷰를 안 만든다"의 근거로 쓰지 마라. imos 뷰는 2세대 공존이다 — 구(섹션 BREF,imosr24.arx, DB 기록 O, POIN 11오더) / 신(뷰셋 Viewset,ImosVisPart24.dbx, DWG 전용·DB 테이블 자체가 없음, POIN 85오더). POIN 주 경로는 신 계열이라 DB에 안 남는 게 정상이다. -
LINDIV파서는 공용 컴포넌트 1개로 만든다. 존분할·부재분할·커넥터배치·DESCRIPTOR·CSIDE·nut_erb·TRAVERSE·mppcpnts가 전부 같은 문법이다. 문법서 =survey2-2026-07-27/drangl-contour-objectdesigner.md.$(계산 시 치환)와#(지연 치환)는 모델에서 구분하고 원문식·평가결과를 둘 다 저장한다.