尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证SketchUp 2021~2026 SKP 파일을 설치 없이 열고 태그·재질·거리를 확인하는 무료 3D 뷰어 (OpenCAD)
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗SketchUp(.skp) 파일을 설치 없이 브라우저에서 3D로 여는 뷰어 앱과, 그 안에서 쓰는 SketchUp 해석기입니다.
파일은 서버로 보내지 않고 브라우저 안(웹 워커)에서 읽고, 결과 장면(Scene3d)은 opencad-viewer3d로 그립니다.
SketchUp 형식은 공개 명세가 없습니다. 처음에는 인터넷에 공개된 표본 파일의 바이트만 보고 구조를 추론했고(SketchUp SDK·다른 SKP 읽기 프로그램의 코드는 보지 않음), 2026-10에 비공개 참조 해석기(SketchUp C API를 비교 기준으로 맞춘 SKP 해석기)의 형식 지식과 비공개 참조 뷰어의 화면 규칙(단위 표기·부드러운 음영·치수선 그리기·장면 적용)을 옮겨 더했습니다(코드는 이 저장소 꼴로 새로 씀). SketchUp 7·8·2013~2020의 옛 형식(MFC 객체 직렬화)도 같은 때 참조 해석기의 legacy 해석 지식을 옮겨 읽습니다. 무늬(텍스처) 투영·색 바꾼 무늬 규칙도 같은 기록에서 옮겼습니다. 조사 기록과 출처는 docs/formats/skp.md에 있습니다.
.skp 파일을 창 어디에나 끌어다 놓습니다. 휴대폰·태블릿(손가락 기기)은 파일 고르기 창에서 형식을 거르지 않습니다(iOS가 모르는 확장자를 흐리게 막지 않게 — 고른 뒤 해석기가 알립니다). "예제 열기"는 브라우저에서 만든 작은 집 모형(태그 넷·재질 11개·컴포넌트)을 엽니다.
한 번에 파일 하나를 열고, 새 파일을 열면 바꿉니다(새 파일을 못 열면 보던 모형은 그대로 둡니다).
opencad-skp)에 새것부터 8개까지 저장합니다(60 MB 초과·예제는 저장하지 않음, 서버로 보내지 않음).
휴대폰 브라우저가 뒤로 보낸 탭을 버렸다가 다시 읽어도 시작 화면의 최근 문서에서 바로 다시 엽니다. 줄마다 ✕로 지우고, "모두 지우기"로 목록을 비웁니다. 비공개 창처럼 저장소를 못 쓰면 카드가 나오지 않습니다.
파일은 고른 그대로(File) 저장하므로 화면이 파일 바이트를 따로 읽어 들지 않습니다(그렇게 저장하지 못하는 브라우저면 바이트로 저장).triangleEdges: false를 두어 숨은 선·와이어프레임에서 삼각형 대각선을 그리지 않고(와이어프레임도 SketchUp처럼 모서리만),
모형 모서리 묶음에는 hiddenEdges: true를 두어 스타일 "뒤 모서리"(보기 설정의 "뒤 모서리" 토글)가 면 뒤의 모서리를 흐린 점선으로 그립니다
(코어 0.8.0 setRenderOptions({ hiddenEdges: { shaded: true, color } }) — SketchUp처럼 숨은 선뿐 아니라 음영 방식에서도, 점선 색은 지금 모형 모서리가 띠는 색).src/app/linecontrast.ts).
같은 규칙으로 구한 색을 코어 0.8.0 선 선택에도 넘깁니다(edgeLooks): 카메라를 보는 컴포넌트 윤곽선(edgeColor — 예전에는 와이어프레임·X-ray에서 재질 색이라
어두운 테마에서 어두운 재질이면 묻혔음)·뒤 모서리 점선 색, 외곽선(모서리 색을 굵게 — 바탕과 3:1이 안 되면 읽히는 선 색).meta.annotationRuns — 문자·치수 라벨과 같은 규칙).
| 키 | 하는 일 |
|---|---|
| F | 전체 보기 |
| 1~7 | 앞 · 뒤 · 왼쪽 · 오른쪽 · 위 · 아래 · 남동 등각 |
| F7 | 바닥 격자 |
| T | 줄자 켜고 끄기 |
| 0~9 · Enter | 줄자 시작점을 찍은 뒤: 측정값 칸에 길이 넣기(그 길이에 안내점, 모서리 위에서 시작하면 나란한 안내선) |
| 0~9 · Enter | 각도기 기준 방향을 찍은 뒤: 측정값 칸에 각도 넣기(그 각도로 안내선) |
| Shift(누르고 있기) | 줄자 끝점·각도기 방향을 잡는 동안: 지금 방향(축·모서리와 나란히·수직, 시작점을 지나는 모서리 위면 그 모서리 방향) 잠금. 각도기 중심 전: 지금 평면 고정 |
| → · ← · ↑ | 줄자 끝점·각도기 방향을 잡는 동안: 빨강·녹색·파랑 축으로 잠금(다시 누르면 풀기). 각도기 중심 전: 그 축 평면에 고정 |
| ↓ | 줄자 끝점·각도기 방향을 잡는 동안: 모서리와 나란히·수직으로 잠금(다시 누르면 풀기) |
| Ctrl(Mac은 Option) 눌렀다 떼기 | 줄자·각도기: 안내 두기 켜고 끄기(줄자 둘째·각도기 셋째 누르기로 안내점·안내선) |
| Delete · Backspace | 눌러서 고른 안내점·안내선 지우기(여럿이면 함께, 줄자·각도기를 끈 동안에도) |
| Ctrl · Shift(Mac은 Option · Shift)+누르기·끌기 | 줄자·각도기를 끈 동안: 안내 더하기(Ctrl·Option)·넣고 빼기(Shift)·빼기(Shift+Ctrl·Shift+Option), 끌면 고르기 상자(오른쪽은 다 든 것, 왼쪽은 걸친 것까지) |
| Ctrl+A · ⌘A | 줄자·각도기를 끈 동안: 보이는 안내점·안내선 모두 고르기(숨긴 안내는 빼고 — 입력 칸에서는 글 모두 고르기) |
| Ctrl+T · ⌘⇧A | 줄자·각도기를 끈 동안: 고른 안내·형상 모두 풀기(브라우저가 Ctrl+T를 새 탭으로 먼저 쓰면 Esc) |
| Ctrl+Z · ⌘Z | 안내점·안내선을 두거나 지운 것 되돌리기(100단계, 줄자·각도기를 끈 동안에도 — 입력 칸에서는 글 되돌리기) |
| Ctrl+Y · Ctrl+Shift+Z · ⌘⇧Z | 되돌린 안내 다시 하기 |
| PageUp · PageDown | 이전 · 다음 장면 |
| Ctrl/⌘+O | 열기 |
| ? | 키보드 바로 가기 안내 |
| Esc | 여는 중이면 취소 → 진행 중 측정 점·끌어온 점·결과 지우기 → (한 번 더) 줄자·각도기 끄기 → 고른 것 지우기 |
캔버스에 초점이 없어도(입력 칸 제외) 위 키가 3D 보기로 갑니다. 패널 목록·메뉴·슬라이더에 초점이 있으면 화살표는 그쪽이 씁니다.
주소 인자: ?sample은 예제를 바로 열고, ?open=<같은 출처 주소>는 그 파일을 바로 엽니다(자동 확인용, 다른 출처 주소는 막습니다).
?shared=<열쇠>는 공유 받은 파일(서비스 워커가 최근 문서에 넣은 것)을 열고 주소에서 지웁니다.
자동 확인용으로 window.opencadApp에 상태(state() — 나무 노드 수·펼친 수·고르기 방식·단면·테마 고르기·패널 크기·카메라·장면·파일 단면·숨긴 태그·라벨 수·측정 도구 등, parsed, docName)를 둡니다.
테마 단추는 "시스템 설정 따르기 · 밝은 테마 · 어두운 테마"를 고르는 메뉴입니다(시스템을 고르면 기억을 지웁니다). 좁은 화면에서 시트를 열고 닫을 때, 확대·이동해 둔 보기는 위쪽에 보이던 것이 같은 자리·같은 크기로 남게 카메라를 옮깁니다(움직이지 않았으면 다시 전체 보기).
홈 화면에 추가: 앱 정보(public/manifest.webmanifest, 언어 페이지는 빌드 때 만든 그 언어 사본)와 아이콘(192·512, 마스크용 512, Apple 180)이 있어,
휴대폰의 "홈 화면에 추가"로 OpenCAD 아이콘과 주소 막대 없는 앱 창(standalone)으로 엽니다. 그 창에서는 제목 막대를 노치·상태 표시줄 아래로 내립니다.
제목 막대·상태 막대를 아래로 끌어도 당겨서 새로 고침되지 않고, 패널·시트 목록은 그대로 스크롤됩니다.
오프라인·새 버전·파일 받기(2026-10-21): 배포 빌드는 서비스 워커(dist/sw.js — 공용 사이트 도구가 빌드 끝에 tools/sw-template.js에
앱 파일 목록과 그 내용 해시(판)를 채워 만든다, 설정은 tools/site.mjs)를 둡니다. 설치할 때 앱 쪽(뿌리·언어 쪽)·스크립트(문서를 열 때 받는 3D 보기·측정 도구 조각 포함)·스타일·아이콘·앱 정보를 담아 오프라인으로도 열리고,
쪽(HTML)은 네트워크를 먼저 봐서(안 되면 캐시) 새 배포가 막히지 않습니다. 캐시를 먼저 쓰는 것은 미리 담은 목록과 빌드 결과(assets/)뿐이고,
그 밖(?open=으로 받는 같은 출처 모형 파일 등)과 앱 범위 밖 주소는 가로채지 않아 바뀐 파일이 옛것으로 열리거나 연 모형이 캐시에 쌓이지 않습니다. 새 판은 설치만 하고 기다리다가 "새 버전이 있습니다" 알림에서 새로 고침을 고르면 바뀝니다(처음 설치는 바로).
앱 정보에 Android 공유 받기(share_target: POST …/share-target — 워커가 파일을 이 브라우저의 최근 문서 IndexedDB에 넣고 ?shared=<id>로 엶,
60 MB보다 크면 최근 목록 밖 한 번 쓸 자리 — 열 때 꺼내 지우고, 열다 멈춘 탭이 남긴 사본은 10분이 지나면 다음에 앱을 열 때 지움)와 데스크톱 설치 앱의 파일 열기(file_handlers + launchQueue, .skp)를 둡니다. 앱 정보에 id는 여전히 두지 않습니다.
SketchUp 6·7·8·20132020에서 저장한 옛 파일도 같은 화면으로 엽니다(태그·재질·구성·장면·문자·치수·단면). SketchUp 35에서 저장한 아주 옛 파일은 형식이 달라 읽지 못하고, 열면 판(예: SketchUp 5 · 5.0.1)과 함께 안내를 보입니다.
styles/*/style.xml: 면 스타일·X-ray·모서리·배경·하늘·지면·앞뒷면 색·글/치수/보조선/단면 보이기),
문자·치수(선형·반지름·지름, 붙은 점 풀기)·보조선·보조점·단면 평면, 곡선·호, 속성 사전(모형·정의·인스턴스·면·모서리·태그), 인스턴스·정의 GUID, 정의 설명·동작, 잠김, 모형 이름·설명, 글꼴.meta.location·meta.shadow·장면 shadow),
그리기 축(01fc → 4650 원점·축 → meta.axes, 기본 축이면 없음), 장면 재생 넣기(includeInAnimation)·되풀이(meta.animation.loop).
2020 이하는 모형 꼬리 CShadowInfo(깃발 넷은 자리를 몰라 뺀다)·CSketchCS. 위경도는 표준 파일(7·8·15·20·26판 georeference)의 C API 값과 같고, 북쪽 각은 GeoReferenceNorthAngle과 같다.
참조 해석기의 SketchUp 2026 표준 파일 20개(C API 기대값)와 견주어 같은 값을 읽는 것을 확인했습니다(표준 파일은 저장소에 넣지 않음).backColor·backTexture·backUvs, 뒷면 무늬는 뒤 투영). 삼각형을 면 법선 쪽에서 반시계로 감고 거울상 배치도 바로잡아 SketchUp처럼 면 뒤쪽이 뒷면 색으로 보입니다.
재질 목록은 뒷면에만 칠한 재질도 "뒷면 n"으로 보입니다. 예전에는 한 면을 앞 → 물려받기 → 뒤 중 한 재질로 양면을 같게 칠했습니다.billboard(배치 원점, Z축, 앞 = 컴포넌트 −Y)를 달아
카메라를 따라 돌리고, 그 배치의 모서리는 같은 태그 그물의 윤곽선(Mesh3d.edges)으로 함께 돌립니다(모서리 태그를 숨기면 함께 숨음 — 그 태그의 면이 없는 모서리만 첫 그물을 따름). 나누는 단계보다 깊어도 그 노드는 따로 나눠 항목째 숨기고,
측정 추정은 돌아간 그물의 저장된 꼭짓점에 붙지 않고 면 위로만 붙습니다(부드러운·숨긴 모서리는 돌릴 수 없어 숨긴 형상 보기에서도 그리지 않고, 숨긴 면은 돌리지 않은 자리에 그림).textures 선택): material.xml의 무늬 크기(xScale·yScale 인치)와 면마다 저장된 무늬 위치(앞·뒤 따로, 행렬·방향 투영·원근 q)를 읽어
그물에 무늬 좌표를 둡니다. 저장된 위치가 없거나 인스턴스 재질을 물려받은 면은 SketchUp 기본 투영(정의 원점·면 평면 틀). 색을 바꾼 무늬(재질 type 2)는
앱 워커가 그림 화소의 색을 바꿔 넘기고, 투명 화소를 쓰는 PNG는 반투명으로 그립니다.
옛 판(2020 이하, 2026-10-21)도 재질의 CTexture(그림·무늬 크기 인치·평균색·색 바꾸기 꼴)와 면의 CFaceTextureCoords(쪽마다 f64 12개 중 앞 9개 = 2021+와 같은 열 우선 3×3,
깃발 비트 0일 때만)를 같은 투영(faceUvq)으로 입힙니다. 참조 해석기의 옛 판 표준 파일 301개의 C API 무늬 투영 124쪽과 같은 값임을 확인했습니다.
readSkpImages가 그림을 꺼내려고 읽은 모형은 바로 이어 부르는 skpToScene이 다시 읽지 않고 씁니다(워커 차례 — 옛 파일을 두 번 읽지 않음).src/mfc*.ts): 2021+와 같은 중간 모형으로 면·모서리·재질(색·불투명도·무늬 그림과 면 무늬 위치)·태그(숨김·색·선 종류·2020 폴더)·
정의·인스턴스(GUID·숨김·잠김)·곡선·문자·치수·보조선·단면·속성 사전·단위·카메라·장면(숨긴 태그·숨긴 개체·활성 단면·스타일·2020 미리보기)·스타일(CSkpStyle + 렌더링 설정)·글꼴·문자/치수 기본값을 읽습니다.
참조 해석기의 옛 판 표준 파일 34개(7·8·2013import { skpToScene } from 'opencad-skp';
const scene = skpToScene(bytes, { name: 'model.skp' });
// scene: Scene3d (opencad-viewer3d의 Viewer3d.setScene에 넘긴다), scene.warnings에 읽지 못한 것
선택:
outline: true — 그룹·컴포넌트를 하나씩 고르게 나눕니다. 기본은 꺼져 있어 예전처럼 (태그, 재질)마다 하나로 모읍니다.
meta.outline: 모형 바로 아래 항목 목록(이름·종류·정의·태그·면 수·안쪽까지의 항목 id·나무 노드). 윗단계 인스턴스가 1,000개를 넘으면 나누지 않고 meta.outlineSkipped에 수를 적습니다.meta.tree: 여러 단계 나무(SkpTree, 형식 배열). 넓이 우선으로 노드 5만 개까지(단계 단위로 자르고 TREE_MORE 표시).
그리기 호출이 터지지 않게, 그물·모서리 항목 수 어림이 3,000 안인 가장 깊은 단계(splitDepth)까지만 노드마다 따로 모으고,
더 깊은 노드는 그 단계 조상의 그물에 함께 넣습니다. 펼치기는 깊이 우선이라 노드 안쪽이 각 그물 안에서 이어진 구간이 되므로,
구간 목록(runItem·runStart·runNode, 노드마다 runFirst·runEnd)으로 고른 삼각형의 가장 안쪽 노드를 찾고 깊은 노드도 강조·맞춰 보기할 수 있습니다.extras: true — 부드러운·매끈한 모서리(skp-soft-*)와 숨긴 형상(숨긴 면·모서리·인스턴스, skp-hidden-*)을 따로 묶어 더 넣고 meta.extraItems에 id·수를 적습니다(범위·통계에는 넣지 않음).smooth: true — 매끈한 모서리(깃발 0x10)로 이어진 면의 꼭짓점 법선을 평균해 Mesh3d.normals로 냅니다(둥근 곡면 음영, 크게 접힌 곳은 평균하지 않음). 기본 꺼짐(예전과 같은 평면 음영).meta.faceRuns(그물마다 면 경계 — 면 k의 삼각형은 [runs[k], runs[k + 1]))·meta.segmentCurves(모서리 묶음마다 선분의 놓인 곡선 번호, -1 = 곡선 아님):
고른 삼각형·선분에서 SketchUp 면 하나·곡선 하나를 찾습니다(화면의 면 넓이·곡선 길이). 해석기가 면마다 삼각형을 이어 붙이므로 출력 형상은 그대로입니다.annotations(기본 켬) — 문자·치수·보조선·단면을 펼쳐 선분 묶음(skp-dims-*·skp-leaders-*·skp-guides-*·skp-sections-*, 보조점은 scene.points)과 meta.texts·meta.dimensions·meta.sectionPlanes로 냅니다(범위에는 넣지 않음). 주석이 없는 모형은 출력이 예전과 같습니다.onProgress(fraction, stage) — 진행률(단계 안에서도 몇천 항목·몇 MB마다)을 알립니다(워커에서 진행 막대용).skpToSceneAsync(bytes, opts) — skpToScene과 같은 장면을 내되, 압축된 model.dat가 streamMin(기본 STREAM_MIN_BYTES = 1 MiB) 이상인 2021+ 파일은
브라우저 내장 DecompressionStream('deflate-raw')로 조각조각 풀면서 읽습니다(풀린 model.dat 전체를 들지 않음 — 직접 쓴 풀기보다 몇 배 빠름).
내장 풀기가 없거나 실패하면 skpToScene처럼 직접 풀고, 뿌리·정의 목록의 꼬리표 길이가 깨진 꼴은 통째로 풀어 예전처럼 앞부분을 살립니다(같은 결과).
앱 워커는 아래 skpFileToScene으로 같은 길을 씁니다. onModelRead는 어떻게 읽었는지(stream·held — 재질·글꼴 절이 정의 절보다 뒤에 오거나 없어 정의 절을 통째로 든 것 ·
whole · shape · inflate)를 알립니다.inflated — 미리 푼 꾸러미 항목(Map<'model.dat', Uint8Array>). 길이가 ZIP 목록의 원래 크기와 같을 때만 쓰고 아니면 직접 풉니다.
해석기는 꺼내 쓴 항목을 이 Map에서 지웁니다(모형 요소를 다 읽으면 풀린 바이트가 놓이게).import { readSkpImages, skpToScene } from 'opencad-skp';
const images = readSkpImages(bytes);
const scene = skpToScene(bytes, { textures: images.materials }); // Viewer3d.setScene(scene)이 무늬를 풀어 입힌다
meta에는 판(version)·그릇(container)·단위(lengthUnit)·개수 통계와 함께, 태그별 면·모서리 수(tagStats), 재질별 색·면 수·그물 id·꾸러미 폴더·입힌 무늬 id(materialStats),
깨짐·한도 표시(damaged·limited)가 들어 있습니다. 2026-10에 더한 칸(units·camera·pages·activePage·style·tagFolders·modelName·texts·dimensions·sectionPlanes·
annotationItems·attributes·treeInfo 등)의 꼴은 src/meta.ts에 있습니다. 파일 스타일에 앞면 색이 있으면 재질 없는 면을 그 색으로 칠합니다.
꾸러미 안 그림은 readSkpImages(bytes, { maxCount, maxBytesEach, maxTotalBytes })로 꺼냅니다: 모형 미리보기(meta/model_thumbnail.png), 재질 폴더별 무늬 그림,
컴포넌트 미리보기(thumbnails/), 장면 미리보기(scene_thumbnails/). 꼴은 첫 바이트로 가리고 개수 600·한 장 8 MB·전체 64 MB 한도를 둡니다.
재질 폴더에 그림이 여럿이면(PBR 그림 등) material.xml이 가리키는 그림을 고릅니다. 색 바꾸기 규칙은 colorizeRgba(rgba, { type, color, average })로 씁니다.
2020 이하 파일은 객체 직렬화 안의 그림 기록(CDib: 모형 미리보기·재질 무늬·컴포넌트 미리보기·2020 장면 미리보기)을 꺼냅니다. 같은 바이트로 skpToScene을 먼저 불렀으면 그때 모은 자리를 써서 다시 풀지 않습니다.
| 경로 | 내용 |
|---|---|
index.html | 앱 진입(제목·설명·파비콘, 자바스크립트 없을 때 안내) |
src/app/main.ts, src/app/app.ts | 앱 입구와 화면(제목 막대·보기 도구 막대·3D 보기·상태 막대·시작 화면·키·끌어 놓기) |
src/app/core.ts | 3D 뷰어 코어 입구와 측정 도구, 큰 모형 놓기 조각을 문서를 열 때 한 번씩 나눠 받기(첫 화면 JS에서 그리기·고르기·측정을 뺌), 처음 켤 때 받기를 기다리는 도구 켜기, 받기 실패 안내 |
src/app/release.ts | 큰 모형을 올린 뒤 푼 무늬 그림 놓기(색을 바꾼 무늬는 그림 바이트를 장면에서 빼 Blob으로 쥠 — 법선·무늬 좌표는 놓지 않아 코어가 되찾을 때 다시 올림), 그래픽 문맥 잃음·되찾음 안내(코어가 무늬까지 다 올렸다고 알릴 때 거둠)와 라벨 가림 판정 다시 하기, 되찾으면 쥐어 둔 색 바꾼 무늬를 작은 손질 워커로 다시 만들어 코어에 돌려주기(문서를 열 때 받는 조각) |
src/app/panel.ts | 오른쪽 패널(태그·재질 / 구성 나무 / 정보) |
src/app/doc.ts | 열린 문서 상태·패널 목록·고르기·보이기·그림 주소(순수 함수) |
src/app/tree.ts | 구성 나무 도움: 고른 삼각형 → 가장 안쪽 노드, 펼친 단계, 깊은 노드 강조 선분·범위(순수 함수) |
src/app/layout.ts, src/app/view.ts | 패널 폭·시트 높이(기억), 시트를 여닫을 때 보기 지키기·맞춰 보기 카메라(순수 함수) |
src/app/pages.ts, src/app/history.ts | 장면 적용 계획·SketchUp 카메라 변환·처음 보기·파일 단면, 이전·다음 보기 기록(순수 함수) |
src/app/overlay.ts, src/app/annotations.ts | 3D 위 덧그림 층(DOM·SVG), 문자·치수 라벨(배치·겹침 피하기) |
src/app/snap.ts, src/app/measure.ts, src/app/vcb.ts | 줄자·각도기 추정(끝점·중간점·모서리·면·축 고정·모서리와 나란히·수직·방향 잠금·안내점·안내선·끌어온 축·축이 모서리·면과 만나는 점·잠근 선이 모서리·면·끌어온 축과 만나는 점)과 도구(안내 두기·모서리 두 번 누르기 안내선·안내 고르기·지우기·되돌리기·다시 하기, 도구를 끈 뒤에도 안내 그리기·안내 보이기 설정, 측정값 칸에 넣기와 못 넣은 까닭 글, 잠금·안내 두기 키 안내 글, 결과 글·툴팁이 띠·장면 탭·뷰 큐브·가장자리 비키기, 결과 글이 모형 글·치수 라벨 비키기, 휴대폰 잠글 방향 고르기 줄, 줄자·각도기를 끈 동안 고르기로 안내 고르기·여럿 고르기(수정 키·고르기 상자)·모두 고르기·모두 풀기(Ctrl+A·Ctrl+T)·지우기·숨기기와 고른 것 카드의 숨기기·지우기 단추(손가락 기기는 모두 고르기 단추·메뉴 항목도), 숨긴 안내 모두 보이기, 정밀 조준 십자선이 장면 탭·뷰 큐브·띠 비키기), 측정값 칸(길이·각도) |
src/app/guidestore.ts | 파일 내용 지문(SHA-256)을 열쇠로 이 브라우저에 안내점·안내선과 숨긴 안내 기억(파일 40개·40만 글자 한도, 오래된 것부터 버림 — 측정 도구 조각) |
src/app/measurehints.ts | 측정 도구를 받기 전에도 쓰는 키 규칙(도구 이름, 안내 두기 키 — 첫 화면에 남음) |
src/app/entity.ts, src/app/units.ts | 엔티티 정보 행·속성 사전, 모형 단위 글(십진·건축·분수·공학, 넓이·부피·각도) |
src/app/protocol.ts, src/app/client.ts |
npm install
npm run dev # http://localhost:4403/ (예제 바로 열기: http://localhost:4403/?sample)
npm run typecheck
npm test
npm run build # dist/
npm run preview
시험은 표본 파일 없이 tests/build.ts(본체는 src/app/skpwrite.ts)와 tests/mfcwrite.ts(2020 이하 꼴)로 만든 작은 바이트로 돌립니다. 표본 파일은 저장소에 넣지 않습니다.
확인용 표본·임시 파일은 .runtime/(git 무시)에만 둡니다.
SKP_BIG=1 npx vitest run tests/large.test.ts는 수십만 면 합성 모형으로 해석 시간을 재고 .runtime/tmp/에 파일을 씁니다(확인 뒤 지우세요).
3D 뷰어 코어는 0.35.0(main 병합 커밋 3a19fe6 — 0.34.0부터는 태그 없이 커밋 전체 SHA로 고정. 단면 보기·묶음 원점·무늬, 터치 끌기 문턱 10화소·터치 고르기 허용 14화소, 0.7.0의 여러 단면·잘린 면 묶음 채우기·숨은 선·단색·해 조명·그림자·안개·하늘 바탕·걷기·마주 보는 그물·뒷면 재질·렌즈 이동, 0.7.1의 삼각형 변 끄기 triangleEdges·선분 묶음 뒤 모서리 점선 hiddenEdges, 0.8.0의 음영 방식 뒤 모서리 점선·선 색 선택 edgeColor·외곽선 색·걷기 중력과 부딪힘, 0.9.0의 교차점 계산 segmentIntersection·캔버스 키보드 초점 테두리·큰 배열 나눠 올리기, 0.9.1의 누른 뒤 초점 테두리 감추기, 0.10.0의 작은 camera 모듈의 표준 뷰 키 표 VIEW_KEYS·잠근 선 위 점 lockedSnap, 0.12.0의 모서리 끝 방향 잠금 판정 edgeLineLock, 0.13.0의 측정 글 자리 고르기 placeMeasureLabel, 0.20.0의 그물 면만 고르기 pick(x, y, { meshesOnly: true }), 0.21.0의 잠근 선과 끌어온 축의 교차 lockTrackSnap, 0.23.0의 뷰 큐브 자리 viewCubeRect, 0.24.0의 먼저 놓인 글상자 비키기 placeLabelAmong, 0.25.0의 올린 뒤 놓기 setScene(…, { release })와 문맥 잃음·되찾음 알림 onContextLost·onContextRestore, 0.26.0의 작은 법선·무늬 좌표 배열과 줄이기 도우미 packNormals·packUvs, 0.28.0의 글상자 자리가 막히면 예전 차례 placeLabelAmong(…, { first, fallback: 'topRight' }), 0.29.0의 되찾은 뒤 다시 올리기 끝 알림 onContextRestoreDone·restorePending, 0.30.0의 처음 올릴 때 무늬 다시 그리기 모으기 — 0.27.0의 새 선택 setItemPickThrough·inference.trackDirs·setMeasureLabelSpot, 0.28.0의 trackDirs: { edge }, 0.29.0의 trackDirs: { meets }·inference.parallelEdge·setMeasureLabelSpot의 fallback, 0.30.0의 trackDirs: { meets: { guides } }·itemExtent·itemsExtent, 0.31.00.35.0의 측정 글 꼴 둘째 인자·0.14.0에는 이 앱이 따로 만든 것이 코어로 들어갔고, 그 가운데 코어 순수 함수로 바꿀 수 있는 것은 바꿨습니다(15차).
모서리 끝에서 그 모서리를 따라 가리킬 때의 방향 잠금 판정(직선이 시작점을 지나는지, 커서 점이 시작점 자리인지)은 0.12.0 에 모서리마다 맡깁니다.
허용치는 앱이 쓰던 상대 1e-6(모서리 길이·시작점 거리에 견줌)을 넘기고, 코어는 세계 축만 알아 축은 모형의 그리기 축으로 앱이 고릅니다.
시험이 바꾸기 전 판정과 합성 4,000가지(세계·옮긴 그리기 축, float32 좌표, 큰 좌표, 허용치 둘레의 비낀 시작점)를 견줘 모두 같고,
다른 것은 커서 점이 시작점에서 상대 1e-925차 안내 고르기·잠근 선과 끌어온 축·여럿 고르기 확인이 25차 빌드와 화소·값까지 모두 같음).
코어 README "앱이 할 일"대로 줄자·각도기 결과 글은 순수 ()으로 놓습니다. 덧그림 층에서 측정 도구보다 먼저 그리는
모형 글·치수 라벨의 배치(글자 수로 어림한 상자 — 흐리게 둔 것은 뺌)와 앞서 놓은 결과 글을 먼저 놓인 글상자로 넘기고, 22차 자리(띠·툴팁·가장자리를 비킨 자리,
없으면 결과 선 가운데)가 그것과 겹칠 때만 같은 차례로 다시 고릅니다. 그래서 위 확인은 넓은 화면에서 그대로 같았고, 375px 결과 글 비키기 10자리 가운데
22차 자리가 현관 지시선 글과 겹치던 2자리(띠 위·왼쪽 가장자리)만 그 글을 비킨 자리로 바뀌었습니다. 위에서 본 예제 집을 멀리 빼 집 폭 결과 글이 치수 글과 겹치는 자리에서
25차 빌드는 겹쳐 그리고 새 빌드는 비켜 그렸습니다(넓은 화면 밝게·375px 어둡게).
0.25.0(27차)은 놓기를 주지 않으면 0.24.0과 같아, 예제 모형(작아서 놓지 않음)으로 같은 헤드리스 측정 순서 17단계와 3D 보기 그림, 잠근 축 354값,
375px 결과 글 비키기 306값, 축 둘레 추정 3만 8천여 자리, 2326차 확인까지 화소·값이 모두 같았습니다.
코어 README "앱이 할 일"대로 큰 모형(나눠 올리는 기준 — 워커도 같은 함수를 씀)은 해석 워커가 장면을 넘기기 전에 법선을 Int8 정점 4값(),
무늬 좌표를 Int16과 ·()으로 줄입니다(). 무늬 좌표는 그물마다 줄인 값을 코어 셰이더처럼 다시 펴서 원래 값과 견주고,
가장 큰 차가 그 무늬를 올리는 크기(긴 변 2048 안)로 1/16화소를 넘으면 Float32로 둡니다. 실제 큰 표본 38개(무늬 좌표 배열 14,034개)에서 이 오차는 가운데 0.001·99번째 0.024·
가장 큰 2.05화소였고, 이 한도로 Float32에 남는 것은 26개(4.9 MB — 거의 모두 165 MB 표본에서 무늬를 수백 번 되풀이해 u·v 범위가 넓은 그물)입니다. 법선 방향 오차는 가장 큰 것이 0.38°였습니다.
놓은 큰 모형의 문맥을 되찾을 때는 처음에 줄였는지()와 그물에 남은 변환(코어 놓기는 지우지 않음)을 함께 보내, 워커가 다시 푼 같은 파일을 같은 함수로 다시 줄여
변환이 같을 때만 줍니다(다르면 주지 않아 위치·색으로 그림, Float32로 둔 그물은 Float32로). 같은 165 MB 표본에서 다시 줄인 그물 915개가 처음과 꼴·값까지 모두 같았습니다.
결과 글 자리는 0.26.0 로 바꾸지 않았습니다. (가운데가 피할 것과 겹칠 때만 코어 기본 차례로 옮김)과 (가운데를 당기지 않는 기본 자리)를
무작위 입력 4,000가지로 견주니 3,357가지만 같았고(는 캔버스 안으로 당겨 2,922가지), 다른 것은 옮길 때 측정 점 표식을 비키려고 오른쪽 위를 건너뛰는 것(70가지),
피할 영역 위·아래 자리의 차례와 가로 자리(기본 자리 가로가 앞에 더해짐 — 265가지), 먼저 놓인 글상자와 겹칠 때 다시 고르는 차례(308가지)였습니다.
바꾸면 2226차 확인까지 화소·값이 모두 같았습니다. 이 앱은 자체 줄자를 쓰고 판 너머 고르기를 쓰지 않아 새 선택은 넣지 않았습니다(코어 README "앱이 할 일"에서 SketchUp은 선택).
29차에 큰 모형의 문맥 복구를 바꿨습니다(). 놓기는 푼 무늬 그림만() 주고, 28차에 줄인 법선·무늬 좌표는 장면에 두어
문맥을 되찾을 때 코어가 그대로 다시 올리게 합니다(코어 README 0.25.0 — 놓지 않은 배열은 장면에서 다시 올림). 줄인 배열을 앱이 쥐었다가 로 주는 길도 있지만,
같은 메모리를 쥐면서 그물마다 기록을 다시 만들고 그동안 곡면이 각진 음영으로 보여 고르지 않았습니다. 워커가 색을 바꾼 무늬는 그림 바이트를 장면에서 빼 Blob으로 쥐고(),
되찾을 때 코어가 바라면 작은 손질 워커( — 해석기를 싣지 않음)가 처음 열 때와 같은 손질( — 그림 크기는 해석기처럼 그림 머리에서 읽음)로
다시 그림을 만들어 로 줍니다(견본 그림은 만들지 않음). 되찾을 때 파일을 읽지 않으므로 해석 워커의 요청과 다시 줄이기는 뺐고, 큰 모형은 주소로 받은 파일도 놓습니다
(위 27·28차의 "다시 풀어 받기"·"주소로 받은 파일은 놓지 않음"은 이것으로 바뀜 — 165 MB 표본의 메모리·시간은 아래 "큰 모형 메모리").
0.28.0·0.29.0(30차)도 새 선택을 쓰지 않으면 0.27.0과 같아(바뀐 기본 동작은 되찾는 동안 무늬마다 다시 그리지 않는 것 하나), 코어만 올린 빌드가 29차 빌드와
같은 헤드리스 측정 순서 17단계와 3D 보기 그림, 잠근 축 354값, 375px 결과 글 비키기 306값, 축 둘레 추정 38,120자리, 2331차 확인(문맥 복구·다른 언어 375px 측정 글 816값·메뉴 201값 포함)까지
모두 같았고, 큰 모형을 처음 열 때 무늬가 다 보이기까지가 반쯤으로 줄었습니다(아래 "큰 모형의 메모리" 32차).
0.31.029개)에서
견주니 모두 같은 자리였습니다( — 가운데 그대로 1,290가지, 옮김 2,710가지).
문맥 복구 안내는 0.29.0의 끝 알림( — 나눠 올리기, 앱이 다시 줄 것, 코어가 장면의 그림 바이트를 다시 풀어 올리기까지 모두 끝남)에 거둡니다.
29차까지는 되찾음 알림이나 앱이 색 바꾼 무늬를 다 준 때에 거둬, 놓아 올린 큰 모형은 코어가 남은 무늬를 다시 푸는 동안 안내 없이 무늬 면이 평균색이었습니다.
이제 놓아 올린 문서는 그동안 "무늬를 다시 준비하는 중입니다"(앞 제목 "색을 바꾼 무늬를…"을 넓힘), 그 밖의 문서는 잃음 안내를 끝 알림까지 둡니다(무늬가 없으면 되찾음 알림 바로 뒤에 옴).
앱이 다 준 뒤에는 으로 코어가 아직 푸는지 보고, 다시 손질하지 못한 무늬가 남으면 끝 알림이 오지 않으므로 평균색 안내는 그대로 로 정합니다.
0.29.0의 첫 점 나란함 기준 거르기()와 0.28.0의 끌어온 점 나란함 기준 거르기()는 코어 뷰어 측정()의 선택이라,
앱 추정( — 지나간 모서리를 앱이 기억)을 쓰는 이 앱에는 걸 곳이 없고, 같은 거르기를 앱이 기억하는 기준 모서리에 넣지도 않았습니다
(31차 — SketchUp은 원·호 조각도 모서리로 추론함, 위 "모서리와 나란히").
머무르기 시간은 코어 (0.5초)로 앱이 재고(), 정밀 조준만 손가락 떨림을 받으려고 머물기 시작한 자리 6화소 안을 같은 자리로 봅니다.
기본으로 켜 두되(SketchUp에는 끄는 설정이 없고, 0.5초 동안 2화소 안에 멈춘 커서만 세어 지나가는 커서는 끌어오지 않음) 의 ·로 끌 수 있고,
끄면 상태가 없어 같은 헤드리스 측정 순서의 값·추정·잠금·결과 글 자리가 끄기 전 빌드와 같습니다(3D 보기 그림은 같은 빌드끼리도 몇 화소가 1만큼 흔들리는 범위 안).
0.11.0의 그물 하나 바꾸기(·)도 쓸 곳이 없습니다(보이기는 ·, 새 문서는 장면 전체).
새 API를 쓰지 않으면 코어는 0.10.0과 화소·값까지 같다고 코어 판마다 화소 비교로 밝혀 두었고, 이 앱도 헤드리스로 같은 측정 순서(끝점에서 모서리를 따라 Shift 잠금,
축 추정, 각도기)를 0.10.0 빌드와 견줘 값·추정·잠금·결과 글 자리와 3D 보기 그림이 모두 같은 것을 확인했습니다.
숨은 선 방식의 뒤 모서리 점선이 보이는 모서리 끝에 조금 겹치던 것과, 새로 올릴 배열이 8 MB를 넘는 묶음을 4 MB씩 여러 프레임에 나눠 올리는 것은 코어가 바꾼 기본 동작입니다.
0.7.0에서 쓰던 길이 0 윤곽선을 달아 늘 숨기는 꼼수()는 0.7.1로 올리며 없앴습니다.
측정 도구의 누르기 문턱은 코어의 (마우스·펜 4, 터치 10화소)를, 추정의 가운데·둘레 고르기는 코어 마우스 허용 거리(6화소)를 그대로 씁니다(터치 추정 반경 22화소가 코어 터치 허용 거리 14화소를 덮습니다).setMeasureLabelAvoid의 points·snapshotPoints·setPointerClaim·setItemMatrices·setItemLooks·refineEnds·열린 교차 meets: { open, openFaces }는 쓰지 않음)으로 받습니다.
0.9.0부터 코어가 캔버스의 키보드 초점 테두리(:focus-visible일 때만 안쪽 2화소)를 그리므로, 앱은 그 색만 강조색으로 둡니다(--v3d-focus).
0.9.1부터는 키보드로 초점을 준 캔버스를 마우스·터치로 누르면 테두리가 사라지고, Tab으로 다시 오면 보입니다(앱의 전역 :focus-visible 규칙보다 코어 규칙이 구체적). 줄자·각도기는 코어 측정을 쓰지 않고 앱 추정()이 축을 정하므로 0.9.1의 ·는 쓰지 않습니다.
0.10.0의 추론 잠금·나란함(, )도 코어 측정 모드 안의 상태라 쓰지 않고,
같은 규칙(누르는 순간의 방향 추론 잠금, 점 추정은 잠근 선에 수직으로 내림, 두 점째에서 풀림, 지나간 모서리와 나란함)을 앱 추정에 넣었습니다.
앱 추정은 축을 화면 각 6°로 고르고 모형의 그리기 축을 따르며, 나란함도 같은 각으로 고릅니다(코어는 화면 붙잡기 거리와 세계 축).
잠근 선 위 점은 코어 순수 함수 ( — 측정 도구 조각과 함께 받는 작은 모듈)을 그대로 씁니다.
0.11.0
첫 화면 JS: 시작 화면(파일 열기·최근 문서·예제·언어 메뉴)은 코어와 측정 도구 없이 그립니다. 코어 입구(opencad-viewer3d — 뷰어·렌더러·셰이더·고르기)와
측정 도구(measure.ts·snap.ts와 코어 inference — 줄자·각도기·추정·안내 두기·고르기)는 파일을 열기 시작할 때 src/app/core.ts의 import()로 워커 해석과 함께 받습니다
(오프라인 앱은 서비스 워커가 이 조각들도 미리 담음). 뷰어는 측정 도구를 기다리지 않고, 측정 도구는 받는 대로 만듭니다.
그 전에 줄자·각도기를 누르면 FirstUse가 받은 뒤 마지막으로 누른 도구를 켜고(받기는 한 번, 다시 누르거나 Esc면 거둠), 받지 못하면 연결이 끊겼는지에 따라 쉬운 안내를 보입니다.
받지 못한 조각은 다음에 부를 때 다시 받습니다(onceImport).
첫 화면 모듈은 작은 코어 모듈(camera·pointer·viewcube·theme·section·types·math·bounds)만 값으로 가져오고,
표준 뷰 키 표(keys.ts)와 단면 수 한도(doc.ts)는 같은 값을 앱에 두어 시험이 코어 표와 견줍니다. 측정 도구를 받기 전에 쓰는 키 규칙(안내 두기 키)은 measurehints.ts에 두고,
도구를 켠 뒤에만 쓰는 잠금·안내 두기 키 안내 글과 잠근 방향 이름은 측정 도구 조각이 줍니다(lockHintText·lockName).
tests/app/core-split.test.ts가 src/app/main.ts에서 정적 가져오기로 닿는 모듈만 따라가 이 규칙을 지킵니다(인라인 type만 쓴 가져오기도 verbatimModuleSyntax에서는 모듈을 불러오므로 값으로 셉니다).
2026-11-01 기준(코어 0.35.0) 첫 화면 JS는 274 KB(gzip 95.2 KB — 측정 도구를 나누기 전에는 313 KB · 107 KB, 코어를 나누기 전에는 467 KB · 158 KB)이고,
문서를 열 때 받는 코어 조각은 231 KB(gzip 75 KB — v0.10.0은 187 KB · 61 KB), 측정 도구 조각은 77 KB(gzip 25 KB)와
코어 inference·snap·measurelabel·track·pick·section·bvh를 묶은 조각 58 KB(gzip 22 KB), 큰 모형 놓기·문맥 복구 조각은 4.1 KB(gzip 1.9 KB),
해석 워커 조각은 213 KB(gzip 81 KB), 문맥을 되찾을 때만 받는 무늬 손질 워커는 6.7 KB(gzip 3.1 KB), 큰 모형을 열 때만 받는 지문 워커는 3.5 KB(gzip 1.9 KB)입니다. v0.14.0으로 올리며 첫 화면에 남는 bounds 모듈이 조금 커져(gzip 15바이트),
잠금 안내 글을 측정 도구 조각으로 옮겨 첫 화면은 오히려 0.4 KB(gzip) 줄었습니다. v0.15.0은 첫 화면을 바꾸지 않고 코어 조각(gzip +0.7 KB)과 inference 조각(+0.6 KB)만 키웁니다.
v0.16.0도 첫 화면을 바꾸지 않고, 뷰어 측정 모드에 들어온 끌어온 축으로 코어 조각만 gzip 1.6 KB 커집니다.
줄자·각도기의 끌어온 축(17차)은 track 모듈을 측정 도구와 나눠 받는 조각으로 옮겨 코어 조각 −0.7 KB, 나눠 받는 조각 +0.7 KB, 측정 도구 조각 +1.0 KB(gzip)이고 첫 화면은 그대로입니다.
v0.17.0과 끌어온 축끼리·모서리와의 교차, 휴대폰 다시 재기(18차)도 첫 화면을 늘리지 않고(조각 이름의 해시를 같은 글로 바꾸고 gzip하면 전·후 같음 — 다시 재기 단추를 켤지는 측정 도구 조각이 정함),
코어 조각 +0.4 KB, 나눠 받는 조각 +0.8 KB(코어 track), 측정 도구 조각 +0.5 KB(gzip)입니다.
v0.18.0과 축이 모서리·면과 만나는 점(19차)도 첫 화면을 늘리지 않고(조각 이름의 해시를 같은 글로 바꾸고 gzip하면 전·후 같음),
코어 조각 +1.0 KB(뷰어 측정 모드에 들어온 같은 후보), 나눠 받는 조각 +0.5 KB(코어 track), 측정 도구 조각 +0.9 KB(gzip)입니다.
v0.19.0과 잠근 선이 모서리·면과 만나는 점(20차)도 첫 화면을 늘리지 않고(같은 방법으로 전·후 같음),
코어 조각 +0.4 KB(뷰어 측정 모드에 들어온 같은 후보), 나눠 받는 조각 +0.1 KB(코어 track), 측정 도구 조각 +0.3 KB(gzip)입니다.
v0.20.0과 면만 다시 맞추기(21차)도 첫 화면을 늘리지 않습니다(같은 방법으로 전·후 같음). 코어 이 코어 을 가져오게 되어 이 코어 조각에서
나눠 받는 조각으로 옮겨 가(둘 다 문서를 열 때 받음) 코어 조각 −1.1 KB, 나눠 받는 조각 +2.5 KB이고, 측정 도구 조각은 면 위 선 판정을 빼 −0.2 KB(gzip)입니다.
결과 글이 뷰 큐브·장면 탭·가장자리를 비키기와 한 손 잠금(잠글 방향 고르기 줄 — 22차)은 측정 도구 조각에 두어(·)
첫 화면은 쓰지 않게 된 "잠글 방향이 없습니다" 알림 글만큼 줄었고(gzip 93,886 → 93,834바이트) 측정 도구 조각은 +0.8 KB(gzip)입니다.
줄자·각도기를 끈 동안의 안내 고르기(23차)는 고르기·키·카드 글과 지우기 단추를 측정 도구 조각에 두고(···)
첫 화면에는 부르는 줄만 두어(누른 자리 기억, 고른 것 카드 보이기) gzip 93,834 → 94,044바이트(+210)이고, 측정 도구 조각은 +0.6 KB, 스타일은 +39바이트(gzip)입니다.
같은 차수에 넣었던 무늬 원본 놓기 조각은 리뷰 뒤 되돌렸고, 27차에 코어 0.25.0의 놓기로 다시 넣었습니다(아래 "큰 모형 메모리").
v0.23.0과 잠근 선이 끌어온 축과 만나는 점(24차)도 첫 화면을 늘리지 않습니다(같은 방법으로 전·후 같음 — gzip 94,044 → 94,045바이트는 조각 이름 해시 차이).
코어 이 0.23.0에서 ··을 가져오게 되어 그 셋이 코어 조각에서 나눠 받는 조각으로 옮겨 가(둘 다 문서를 열 때 받음)
코어 조각 −3.9 KB, 나눠 받는 조각 +9.2 KB(둘을 합쳐 +5.4 KB — 코어가 커진 만큼)이고, 측정 도구 조각은 +0.1 KB(gzip)입니다.
안내 여럿 고르기·고르기 상자·숨기기와 정밀 조준 십자선 비키기(25차)는 수정 키 규칙·상자 판정·숨긴 안내·카드의 숨기기 단추·메뉴 항목을 측정 도구 조각에 두고
(····), 첫 화면에는 누르기의 수정 키를 넘기는 것과 상자로 고르면 형상을 푸는 줄,
바로 가기 안내 한 줄만 더해 gzip 94,045 → 94,200바이트(+155)입니다. 측정 도구 조각은 +2.2 KB, 스타일은 +21바이트(gzip)입니다.
v0.24.0(26차)도 첫 화면을 늘리지 않습니다(같은 방법으로 전·후 같음). 코어 조각은 +0.4 KB, 나눠 받는 조각은 +1.0 KB(코어 ·)입니다.
결과 글의 모형 라벨 비키기와 안내 모두 고르기·모두 풀기(26차)는 자리 고르기·키 규칙을 측정 도구 조각에 두고(···),
첫 화면에는 모형 라벨 배치와 모두 풀기의 형상 풀기를 넘기는 두 줄과 바로 가기 안내 두 줄만 더해 gzip 94,117 → 94,249바이트(+132 — 조각 이름 해시를 같은 글로 바꾼 값)입니다.
측정 도구 조각은 +0.35 KB(gzip)이고 스타일은 그대로입니다.
v0.25.0(27차)도 첫 화면 코어 모듈을 바꾸지 않고 코어 조각만 +2.8 KB(gzip — 놓기·문맥 복구) 커집니다. 큰 모형 놓기와 문맥 잃음·되찾음 안내(27차)는
놓기 준비·안내 글·다시 읽기를 새 조각( — 파일을 열 때 코어·측정 도구와 함께 받음)에 두고, 첫 화면에는 조각을 받아 부르는 줄
(에 놓기 선택 넘기기, 새 파일을 여는 동안 다시 읽기 멈추기, 안내 보이기·거두기, 상태 값)만 더해 gzip 94,249 → 94,661바이트(+412 — 조각 이름 해시를 같은 글로 바꾼 값)입니다.
해석 워커 조각은 다시 줄 것 고르기로 +0.4 KB, 스타일은 알림을 장면 탭·뷰 큐브 아래로 내리는 규칙으로 +29바이트(gzip)입니다.
v0.26.0(28차)도 첫 화면 코어 모듈을 바꾸지 않고 코어 조각 +1.2 KB, 나눠 받는 조각 +0.1 KB(gzip)만 커집니다(코어만 올린 빌드에서 첫 화면은 전·후 같음).
큰 모형 법선·무늬 좌표 줄이기(28차)는 해석 워커 조각에 두고(코어 포함 +1.4 KB), 첫 화면에는 큰 모형 기준을 워커와 함께 쓰게 로 옮긴 것과
줄였는지 놓기 조각에 넘기는 줄만 더해 gzip 94,661 → 94,716바이트(+55 — 조각 이름 해시를 같은 글로 바꾼 값), 놓기 조각은 +39바이트(gzip)입니다.
v0.27.0(29차)도 첫 화면을 바꾸지 않고(코어만 올린 빌드에서 전·후 같음) 코어 조각 +0.7 KB, 나눠 받는 조각 +0.6 KB(gzip)만 커집니다.
큰 모형 문맥 복구를 쥔 것으로 하기(29차)는 첫 화면에서 파일·줄였는지 넘기기를 빼 gzip 94,716 → 94,662바이트(−54), 놓기 조각 −96바이트, 해석 워커 조각 −0.5 KB(다시 풀기를 뺌)이고,
다시 손질은 새 워커 조각( gzip 3.1 KB — 해석기 없이 무늬 손질만)에 둡니다. 손가락 기기의 안내 모두 고르기(29차)는 단추·메뉴 항목·그림을 측정 도구 조각에 두고
(+0.3 KB) 첫 화면에는 메뉴에 손가락 기기인지 넘기는 한 줄만 더해 gzip +1바이트, 스타일은 +14바이트(gzip)입니다.
v0.29.0(30차)도 첫 화면을 바꾸지 않고(코어만 올린 빌드에서 전·후 같음 — 조각 이름 해시를 같은 글로 바꾼 gzip 94,663바이트) 코어 조각 +1.9 KB, 나눠 받는 조각 +0.6 KB(gzip)만 커집니다.
결과 글 자리를 코어 한 번으로 고르기와 문맥 복구 안내를 끝 알림에 거두기(30차)도 첫 화면을 바꾸지 않고(같은 방법으로 전·후 같음) 측정 도구 조각 −30바이트, 놓기 조각 +34바이트(gzip)입니다.
다른 언어의 측정 툴팁·도구 막대 메뉴를 그 언어 글로 재어 놓기와 최근 측정 기록 줄 번역(31차)은 첫 화면에 지금 언어로 옮기는 함수()와 메뉴·복사에서 부르는 줄만 더해
gzip 94,663 → 94,719바이트(+56 — 조각 이름 해시를 같은 글로 바꾼 값), 측정 도구 조각 +36바이트, 스타일 +35바이트(gzip)이고, 사전을 고쳐 영어·독일어 사전 조각은 조금 줄었습니다.
v0.30.0(32차)도 첫 화면을 거의 바꾸지 않고(같은 방법으로 94,719 → 94,721바이트) 코어 조각 +0.8 KB, 결과 글 조각 +0.2 KB(gzip)만 커집니다.
휴대폰 도구 막대의 안내 모두 고르기(32차)는 단추·자리 셈을 측정 도구 조각에 두고(+0.5 KB), 첫 화면에는 단추를 붙이는 두 줄과 스타일(+20바이트)만,
메뉴를 연 채 언어를 바꾸면 다시 여는 것은 첫 화면에 두어 함께 94,721 → 94,864바이트(+143 — gzip)입니다.
해석 줄이기(33차 — 색 바꾸기 기억 칸·무늬 미리 풀기·투영 자리 쓰기·묶음 열쇠만 세기)는 첫 화면과 코어·측정 도구 조각을 바꾸지 않고(같은 방법으로 94,864바이트 그대로)
해석 워커 조각 +1.1 KB, 손질 워커 +0.4 KB(gzip)만 커집니다.
모서리·면 읽기 줄이기와 큰 모형의 해석 워커 끝내기(34차)는 첫 화면에 워커 손님의 지문 워커 부르기만 더해 94,864 → 95,072바이트(+208 — 같은 방법),
해석 워커 조각은 +0.6 KB(gzip)이고, 지문 워커는 새 조각(gzip 1.9 KB — ·만 묶임)입니다.
코어 0.35.0(35차 — 0.31.0~0.35.0)은 첫 화면을 바꾸지 않고(같은 방법으로 95,072 → 95,071바이트) 코어 조각 +2.8 KB, 나눠 받는 조각 +1.9 KB(gzip — 코어 의 열린 교차)만 커지고,
측정 도구·해석 워커·손질 워커·지문 워커 조각은 그대로입니다.
장면 만들기 줄이기(36차 — 아래 "큰 모형의 메모리")는 해석 워커 조각만 +1.4 KB(gzip +0.5 KB)이고, 첫 화면·코어·측정 도구·놓기·손질 워커·지문 워커 조각은
그대로입니다(조각 이름 해시를 같은 글로 바꾸고 gzip하면 전·후 같음).
파일을 통째로 들지 않고 열기(16차)는 첫 화면에서 지문 계산을 빼 gzip 12바이트 줄였고, 해석 워커 조각은 조각 읽기와 SHA-256으로 gzip 3.3 KB 늘었습니다.
안내 되돌리기·다시 하기·안내 보이기·파일별 안내 기억처럼 측정 도구에 딸린 기능은 단추·메뉴 항목·키 처리·저장까지 측정 도구 조각에 두고
(····), 첫 화면에는 부르는 줄만 둡니다(파일 내용 지문은 해석 워커가 구해 보냄).
휴대폰 검사 워크플로는 Lighthouse 전·후 가운데값을 (성능 90 이상, FCP 2.2초·LCP 2.5초·TBT 400 ms 아래, JS 108 KiB 아래 등)과
견주어() 넘으면 실패합니다. 코어를 다시 첫 화면에 넣으면 JS가 약 57 KiB 늘어 이 단계에 걸리고, 측정 도구는 약 15 KiB 늘립니다.
의존을 올릴 때는 (태그가 없는 판은 )로 받아야 잠금 파일의 커밋이 바뀝니다.
공용 사이트 도구: 다섯 언어 페이지·검색 정보·사이트맵·내용 수정일·언어별 앱 정보·서비스 워커 빌드, 공용 번역기, 언어 메뉴, 서비스 워커 등록·새 판 알림,
방문 통계, 휴대폰 성능 비교 명령은 다른 OpenCAD 앱과 함께 쓰는 opencad-site-kit
(github:GyuTae-Kim/opencad-site-kit#v0.1.4, 잠금 커밋 a1de400, 빌드 때만 써서 devDependencies)에 있습니다.
이 앱에만 있는 값은 tools/site.mjs(앱 정보 원본, 미리 담을 파일, 서비스 워커 판 이름 짓기)와 tools/sw-template.js(서비스 워커 본문 — 주석까지 sw.js에 그대로 들어갑니다)에 둡니다.
check:seo·check:i18n·test:seo·update:content-dates와 휴대폰 검사 워크플로의 성능 명령(serve-performance·performance-summary·check-performance-budget)은 도구의 opencad-site 명령입니다
(워크플로는 기준 빌드 뒤 이 커밋으로 돌아와 npm ci를 다시 해 명령을 찾습니다). 도구 판을 올릴 때는 빌드 결과를 견줍니다.
npx vite build && cp -R dist .runtime/dist-before # 올리기 전
npm install -D "opencad-site-kit@github:GyuTae-Kim/opencad-site-kit#<태그>" # package.json·잠금 파일을 함께
npx vite build && npx opencad-site compare-dist .runtime/dist-before dist # 바뀐 파일과 처음 다른 곳
도구의 판 설명에 적힌 것 밖의 차이가 없으면, 스크립트가 바뀐 경우 내용 수정일을 갱신하고(아래 "검색·공유 메타데이터와 콘텐츠 수정일") 빌드·시험을 돌립니다.
해석기를 고치기 전과 뒤에 가진 SketchUp 파일을 모두 열어 보고 결과를 견주는 도구입니다(scripts/sweep/). 표본은 저장소에 없으므로 각자 로컬 폴더를 줍니다.
npm run sweep -- --dirs "$HOME/Documents:$HOME/Downloads" --out .runtime/sweep/before # 폴더는 쌍점으로 구분(OPENCAD_SWEEP_DIRS도 됨)
npm run sweep -- --dirs "$HOME/Documents:$HOME/Downloads" --out .runtime/sweep/after
npm run sweep:compare -- .runtime/sweep/before .runtime/sweep/after --fail-on-change
npm run sweep -- --dirs "$HOME/Downloads" --min-mb 25 --mem --jobs 1 --out .runtime/sweep/mem # 큰 모형의 단계별 메모리
.skp를 고르고(node_modules, .git, .claude 폴더는 건너뜀) 내용이 같은 파일은 sha256으로 한 번만 엽니다. 해석은 앱 워커와 같은 skpFileToScene·같은 선택으로 합니다. 파일은 Node 파일 손잡이로 필요한 자리만 읽고(2020 이하 등은 통째로), 압축된 model.dat가 1 MiB 넘는 2021+ 파일은 내장 풀기로 풀면서 읽습니다.
--stream-min 0을 주면 압축된 모든 2021+ 파일이 그 길을 탑니다(풀면서 읽기를 고친 뒤 모든 파일로 견줄 때). 요약에 model.dat를 어떻게 읽었는지(modelRead)와
파일을 어떻게 읽었는지(fileRead — 필요한 자리만·통째로)별 파일 수를 적습니다.--jobs, 기본은 CPU 절반이고 많아야 4)가 나눠 합니다. 파일 하나가 --timeout(기본 300초)을 넘거나 프로세스가 죽으면 그 파일을 예외로 적고 이어 갑니다.records.jsonl(열쇠는 내용 해시)과 요약 summary.json을 씁니다. 요약에는 판마다 정상, 경고, 빈 장면, 읽지 않는 판, SketchUp 파일 아님, 예외로 나눈 개수와 경고 종류별 파일 수, 면과 정의와 인스턴스 합, 해석 시간 백분위, 큰 파일의 시간과 메모리가 들어갑니다. 파일 경로는 records.jsonl에만 있으니 결과는 .runtime/(git이 무시함)에 두고 저장소에 넣지 않습니다.digest — 모든 그물·선분·점 배열의 바이트, 무늬, 경고, meta)를 적습니다. 해석기 자료 구조를 바꿀 때 좌표 하나가 바뀐 것까지 찾으려고 넣었습니다.sweep:compare는 같은 해시끼리 견주어 분류가 바뀐 파일, 지표나 경고나 장면 출력 해시가 바뀐 파일(--top 개수만큼), 새 예외를 찍습니다(장면 출력 해시는 두 결과에 다 있을 때만 봅니다). --fail-on-change를 주면 바뀐 것이 있을 때 1로 끝납니다.--mem은 파일마다 새 프로세스를 띄워 단계(그림 꺼내기, 꾸러미, 해석 — 풀면서 읽으면 풀기도 여기, 장면 만들기)마다 가장 컸던 heap, external, rss, 같은 때의 heap + external 합과 그 파일을 여는 동안의 최대 rss를 잽니다. 기계의 메모리가 빠듯하면 rss가 같은 코드에서도 수백 MB씩 흔들리므로 heap + external로 견줍니다. 단계마다 gc를 부르므로 이때 잰 시간은 다른 결과와 견주지 않습니다.main에 올리면 GitHub Actions(.github/workflows/deploy.yml)가 타입 확인·시험·빌드를 거쳐 dist/를 gh-pages 가지에 올립니다(GitHub Pages).
빌드는 상대 경로(base: './')라 https://gyutae-kim.github.io/opencad-skp/ 같은 하위 경로에서 돕니다.
끌어오기 요청에서는 .github/workflows/ci.yml이 같은 확인을 합니다.
CRelationship(정의 관계) 읽기trackDirGuides·trackDirSnap)를 주었지만, SketchUp 공개 도움말 "Introducing Drawing Basics and Concepts"는
머문 점에서 끌어오는 추론(From Point)을 축 색 점선으로만 적고 나란함·수직은 그리는 방향을 모서리에 맞추는 것으로만 적어, 끌어온 점을 지나 모서리와 나란한·수직인 선에 붙는다는 근거가 없습니다
(28차에 찾아 봄 — SketchUp에서 실제로 그렇게 붙는지 확인한 뒤 넣을 일). 휴대폰은 정밀 조준으로만 끌어오며, 잠근 동안에는 머물러도 끌어오지 않아 잠근 선과 만날 점은 잠그기 전에 끌어 둬야 합니다.
유리 같은 반투명 재질 너머의 면을 고르거나 재는 것(코어 0.23.0 throughTranslucent)은 넣지 않았습니다. SketchUp 공개 도움말 "Face Styles"는 모든 면을 비치게 하는
X-ray 면 스타일에서 면 뒤에 숨은 점·모서리를 보고 고르고 붙을 수 있다고만 하고, "Editing Materials"는 불투명도를 보이는 정도로만 설명해 반투명 재질의 면을 지나 고른다는 근거가 없습니다.
축·잠근 선이 면을 뚫는 점은 커서가 그물 면 위일 때만 보고(커서가 모서리 6화소 안이면 모서리 쪽 추정), 곡면은 그 곡면을 나눈 삼각형 위 점이며 이웃 삼각형으로 다시 구하는 것은 3번까지입니다.
축·잠근 선과 만나는 모서리는 커서 둘레에서 고른 모서리·안내선만 보고, "항상 카메라 보기" 컴포넌트의 모서리(그물 윤곽선으로 돌려 그림 — 모서리 위 추정도 없음)는 보지 않습니다.
곡면을 이루는 부드러운 모서리는 "부드러운 모서리 보기"를 켜면 보통 모서리처럼 만나고, 끈 동안에는 만나지 않습니다.
끈 동안이나 숨긴 모서리에도 SketchUp이 모서리 위로 추론하는지는 공개 도움말이 밝히지 않아(부드럽게 해 묶은 면에도 그 안 형상의 추론이 보인다고만 함) 그대로 둡니다.
나란함·수직의 기준은 마지막으로 지나간 모서리 하나이고, 모서리에 수직은 그 수직 평면 안의 몇 방향(내린 수직·축을 비친 방향·커서 밑 면 안의 수직)만 봅니다.
원·호 조각도 나란함·수직의 기준으로 두는 것(위 "모서리와 나란히")은 도움말의 호 조각 끝점·중간점 추론과 사용자 포럼 글을 근거로 정했습니다.
도움말이 나란함·수직을 곡선 조각에 직접 적지는 않아, SketchUp에서 실제로 그렇게 붙는지 확인해 볼 일입니다.
휴대폰 한 손 잠금은 재기 전에 잠글 방향 고르기 줄에서 고르는 것이라, 정밀 조준으로 손가락을 대고 있는 동안 지금 맞춘 방향을 잠그거나 풀려면 여전히 다른 손가락이 필요하고,
고르기 줄에 "모서리에 수직"은 없습니다(수직은 방향 하나가 아니라 평면이라 — SketchUp for iPad의 Lock Perpendicular에 맞는 규칙을 더 찾아야 함).
정밀 조준 십자선은 장면 탭·뷰 큐브·측정 띠 밑으로 가지 않으므로, 그 밑에 보이지 않는 형상은 화면을 옮긴 뒤 겨눕니다2dcad-viewer의 src/formats/skp/와 docs/formats/skp.md에서 분리했습니다(커밋 d7c584a 기준, 처음 들어온 것은 PR #7).
앱 화면은 그 커밋의 2D 뷰어 안 3D 탭 화면(src/app/view3d/의 태그·재질 패널, 워커, 프로토콜)을 바탕으로, 혼자 도는 앱에 맞게 새로 짰습니다.
MIT. 의존성 earcut은 ISC입니다(THIRD_PARTY_NOTICES.md). SketchUp은 Trimble Inc.의 상표이며, 이 프로젝트는 Trimble과 관계가 없습니다.
site/metadata.json을 공통 원본으로 삼아 Vite 플러그인이 제목·설명·canonical·Open Graph·Twitter Card·JSON-LD를 초기 HTML에 넣습니다. 같은 원본으로 앱의 공개 주소를 담은 sitemap.xml도 생성합니다. 공유 이미지는 각 앱의 public/og-opencad.jpg이며 앱 파일과 함께 배포합니다. robots.txt는 같은 호스트 루트(https://gyutae-kim.github.io/robots.txt)의 설정이 적용됩니다.
site/content-history.json에는 실제 콘텐츠 수정 시각과 지문을 저장합니다. 초기 HTML 본문·메타데이터·공유 이미지와 빌드된 앱/워커 JavaScript·CSS를 비교하므로 앱 기능이 바뀌면 날짜 갱신이 필요합니다. 수정일 자체와 소스 맵은 비교에서 제외합니다.
npm run update:content-dates -- --at YYYY-MM-DDTHH:mm:ssZ를 실행합니다.npm run build로 다시 빌드합니다. 변경되지 않은 콘텐츠는 기존 날짜를 유지하며, 날짜가 오래되었거나 잘못된 경우 SEO 검사가 실패해 배포를 막습니다.npm run test:seo와 기존 테스트를 실행하고 메타데이터·수정일 기록을 함께 커밋합니다.일반 빌드나 배포 시각으로 날짜를 자동 갱신하지 않습니다. sitemap.xml의 lastmod와 앱 구조화 데이터의 dateModified는 동일한 기록을 사용합니다. 배포 뒤에는 앱·사이트맵·공유 이미지의 실제 공개 응답을 확인하고, 포트폴리오 통합 사이트맵의 연결 날짜도 새 앱 사이트맵과 맞춥니다. 사이트맵 게시와 검색엔진의 실제 수집·색인 완료는 별도로 확인합니다.
한국어 기본 주소와 영어(/en/), 일본어(/ja/), 중국어 간체(/zh-CN/), 독일어(/de/) 페이지를 제공합니다. 각 언어의 소개 본문과 검색·공유 메타데이터를 빌드할 때 생성하며, hreflang과 사이트맵이 같은 페이지의 언어들을 연결합니다. site/locales/*.json에 한국어 원문과 번역을 저장하고, 브라우저는 선택한 언어의 사전만 내려받습니다. 방문 중 외부 번역 서비스는 호출하지 않습니다.
우측 상단 지구본 아이콘에서 언어를 선택합니다. 방향키로 이동하고 Escape나 바깥 클릭으로 닫을 수 있습니다. 언어 선택은 URL에 반영하며, 열린 문서나 편집 상태를 다시 만들지 않고 화면 문구·접근성 설명·오류·도움말을 갱신합니다. 명령 식별자·입력값·파일명·모델 이름·원본 도면의 문자는 번역하지 않습니다. 새 데이터 이름을 표시하는 요소에는 translate="no"를 지정하고, UI 문구의 {0} 같은 변수 자리를 변경하지 마세요. 여러 줄 메시지와 새 한국어 문구도 번역 사전에 추가해야 합니다. ·로 이은 문장은 짧은 변수 문구(예: 면 {0})가 뒤 조각까지 그대로 잡으면 조각마다 따로 옮깁니다(남는 한국어가 적은 쪽을 고름 — 잡은 이름은 여전히 그대로).
화면 번역층은 앱이 글을 넣은 뒤에 바꾸므로, 글 크기를 재어 자리를 정하는 요소(측정 툴팁, 도구 막대 메뉴)는 같은 번역기로 지금 언어 글을 만들어 넣고(src/app/i18n.ts의 uiText)
그 요소에 data-i18n-skip을 둡니다. 2026-10-31(31차) 전에는 번역 전 한국어 크기로 놓여, 다른 언어의 긴 툴팁이 휴대폰 폭에서 3D 보기 밖으로 잘리고 메뉴가 화면 끝에 붙어 좁아졌습니다.
번역층이 건너뛰는 요소는 언어를 바꿀 때 앱이 다시 그립니다(측정 툴팁은 다음 그리기에, 열린 도구 막대 메뉴는 그 단추로 다시 열어 새 언어 글·너비로 — reopenMenu).
메뉴를 연 채 키보드로 언어 고르기에 옮겨 언어를 바꾸면 32차 전에는 메뉴를 닫을 때까지 앞 언어였습니다. 다시 열어도 굴린 자리와 언어 고르기 단추의 초점은 그대로입니다.
npm run test:i18n은 언어 전환과 문서 데이터 보존, 같은 소리 다른 뜻의 CAD 낱말(면·모서리·판·분수·소수)을 다른 뜻으로 옮긴 번역과 원문에 없는 줄바꿈이 사전에 다시 들지 않는지를, npm run check:i18n은 다섯 언어의 정적 소개·canonical·언어 링크·사전 변수·에셋 경로를 검사합니다. 번역이나 앱을 수정하면 기존 콘텐츠 수정일 갱신 절차를 따른 뒤 다시 빌드합니다. 자동 번역 초안의 주요 메뉴와 제품명은 교정했으며, 긴 기술 설명에는 추가 원어민 감수가 필요합니다.
meta.revealItems, 범위·통계 밖)
그 장면을 고르면 보이고, 그 노드를 숨기는 장면이면 다시 숨습니다(숨긴 형상 보기의 반투명 그림은 그동안 숨김).setNavigation('walk'))는 끌기 = 제자리에서 둘러보기, 휠·W/S·↑/↓ = 앞뒤, A/D = 옆, ←/→ = 돌기,
Q/E·PageUp/PageDown = 위아래, Shift = 빠르게, 두 번 누르기 = 그 자리에 눈높이 5′6″(1.676 m)로 서기, Esc = 끝내기입니다.
SketchUp 걷기처럼 중력(바닥·계단을 따라감 — 그동안 위아래 키는 듣지 않음)과 부딪힘(벽 앞에서 멈추고 벽을 따라 미끄러짐)을 처음부터 켜고(코어 0.8.0 gravity·collide),
걷는 동안 카메라 띠의 "중력"·"부딪힘"으로 켜고 끕니다(발밑에 바닥이 없으면 그 높이 그대로). 걷는 동안 누르기는 고르지 않고
(서기용 두 번 누르기가 그룹에 들어가지 않게), 패널 목록·메뉴·슬라이더에 초점이 있으면 화살표·PageUp·PageDown은 그쪽이 씁니다. 휴대폰은 왼쪽 아래 조이스틱으로 걷고
(코어 setWalkInput), 한 손가락으로 둘러보고 두 손가락으로 벌려 앞으로·끌어 옆/위아래로 갑니다. 원근으로 바꾸고 측정을 켜면 걷기를 끝냅니다.
2점 투시(SketchUp 카메라 › 2점 투시)는 눈 자리·방향을 두고 수평으로 본 뒤 렌즈를 위아래로 옮겨(코어 shiftY) 세로선을 곧게 하며, 돌려 앙각이 생기면 끝납니다.
파일·장면의 2점 투시 카메라도 이렇게 열고, 위 벡터가 기운 카메라는 굴림(roll)으로 엽니다(90° 넘게 기운 깨진 값은 버림). 시야각을 바꾸면 렌즈 이동도 함께 맞춥니다.segmentIntersection, 허용치는 그 자리 반 화소).
호·원의 중심(초록 원과 십자 — 곡선을 한 번 훑고 가운데로 가면 붙음, 최근 넷까지 기억)과, 시작점에서 모서리·면에 내린 수직 발(자홍 ㄴ — "모서리에 수직"·"면에 수직",
맞은편 벽까지의 거리)에도 붙습니다. 최근 결과 10개는 패널 아래 띠의 "최근 측정"(휴대폰은 측정 띠의 목록 단추)에 남고, 줄마다·모두 복사합니다
(다른 언어 화면에서는 기록 줄과 복사한 글의 "거리·각도"도 그 언어로 — 값·단위·dx·dy·dz는 그대로).
inference.parallelEdge는 원호를 참 곡선으로 가진 NC·STEP용입니다).capGroup, 칸마다 하나 — 범위 상자가 겹치거나 안에 든 입체는 그 칸의 다른 묶음, 4개까지)을 줍니다(코어는 묶음 안을 홀짝으로 채워 겹친 곳이 비므로). 단면에서 그 입체의 잘린 면을 채우고(SketchUp 2018+ 스타일 "단면 채우기"를 따르며 없으면 2018 이상만 채움),
"채우기" 단추(휴대폰은 단면 메뉴)로 켜고 끕니다. 열린 면·얇은 판·반투명 재질이 든 입체는 테두리만 그립니다.
같은 칸 안에서 서로 겹친 입체는 겹친 곳이 비어 보일 수 있습니다(홀짝 채우기). 엔티티 정보는 SketchUp 입체(방향이 맞고 떠 있는 모서리·안쪽 그룹 없음)를
"입체 그룹·입체 컴포넌트"로 부르고 부피를 보입니다.
파일 단면(처음 열 때 활성 단면, 장면이 기억하는 단면, 태그·재질 탭의 단면 평면 목록)은 여러 개를 함께 씁니다(코어 setSections, 한 번에 6개):
모형 단면은 모형 전체를, 그룹·컴포넌트 안 단면은 그 그룹 안쪽만 자르고(해석기가 단면이 든 노드를 따로 나눠 항목을 안다), 같은 그룹의 단면은 하나만 켭니다.
그 그룹만 자를 수 없는 단면(그룹이 너무 많아 나누지 않음)·한도를 넘은 단면은 목록 아래에 수를 알립니다. 글·치수 라벨과 측정 추정도 모든 단면을 따릅니다.meta.damagedmeta.unsupported)을 돌려줍니다(조사 결과: docs/formats/skp.md "SketchUp 3model.dat를 찾고, model.dat가 잘렸거나 압축이 깨졌으면 앞부분만 풀어
꼬리표 길이가 맞는 데까지 읽습니다(meta.damaged). 잘린 DEFLATE 흐름이 끝없이 도는 문제를 막았습니다.
2020 이하 파일에서 모서리의 정점·곡선, 고리의 모서리를 가리키는 칸 번호가 깨졌으면(바이트 하나가 바뀐 파일 — 참조 꼬리표는 클래스와 관계없이 2·6바이트라 뒤 줄은 그대로)
그 모서리와 그 모서리를 쓰는 면만 빼고 이어 읽고 버린 참조 수를 경고합니다(2026-10-27, 예전에는 첫 깨진 참조에서 형상 읽기 전체가 멈췄음)..skp 1,118개(같은 내용을 빼면 716개 — SketchUp 7·8·2015·2017·2020·2021·2024·2026)를 node에서 읽어
예외 0, 경고가 있는 파일 18개(17개는 다른 프로그램이 쓴 시험 파일·합성 파일)였고, 위 깨진 참조를 고친 한 파일 말고는 형상 결과가 같았습니다(표본은 저장소에 넣지 않음).src/entities.ts)로 두고, 미리 푼 model.dat와 파일 바이트는 다 쓰면 바로 놓고,
장면 배열은 쌓을 때 버린 버퍼를 곧바로 떼어 놓습니다(src/grow.ts). 165 MB 모형(면 64만 개, 모서리 106만 개)을 Node에서 열 때 가장 많이 쓰는 heap + external이
2.1 GB에서 750 MB쯤으로, 해석 시간이 5.7초에서 3.7초로 줄었습니다. 헤드리스 Chrome에서 같은 모형을 열면 화면과 해석 워커가 도는 렌더러 프로세스가 2.7~3.1 GB에서 1.9 GB까지 씁니다.
실제 파일 1,351개의 장면 출력(모든 배열 바이트와 meta)은 줄이기 전과 한 바이트도 다르지 않습니다.model.dat가 1 MiB를 넘는 2021+ 파일은 통째로 풀지 않고 브라우저 내장 풀기가 내는 조각을 받는 대로 읽습니다
(skpToSceneAsync, src/modelstream.ts). model.dat의 뿌리 절은 실제 파일 모두에서 설정·재질·태그·글꼴 → 정의(크기의 거의 전부) → 모형 요소 →
장면·스타일 차례라, 정의 절 밖의 작은 절은 따로 복사해 두고 정의는 하나씩 그 바이트만 받아 읽은 뒤 놓습니다. 붙은 치수 점이 가리키는 정점은
예전처럼 model.dat를 두 번째로 훑지 않고 첫 훑기에서 영구 번호로 적어 둡니다. 장면을 만들 때는 마지막 배치까지 놓은 정의의 삼각화 배열과
중간 모형 표를 바로 놓습니다. 165 MB 모형을 Node에서 열 때 가장 많이 쓰는 heap + external이 750 MB쯤에서 580 MB쯤으로,
헤드리스 Chrome의 해석 워커(V8 heap + ArrayBuffer)가 810~880 MB에서 615 MB쯤으로 줄었습니다. 실제 파일 1,375개의 장면 출력은 줄이기 전과 같습니다
(압축된 model.dat가 있는 2021+ 파일 888개를 모두 풀면서 읽게 해도 같음).skpFileToScene, src/range.ts).
2021+ 파일은 머리(4 KiB) → 끝 레코드(파일 끝 64 KiB) → 중앙 목록 → 필요한 항목(material.xml·style.xml·그림 — 작은 항목은 256 KiB 덩이 하나에서)만 읽고,
압축된 model.dat는 1 MiB씩(다음 덩이를 하나 미리) 읽어 내장 풀기에 넣습니다. 2020 이하, 끝 목록이 없거나 깨진 꾸러미, SketchUp 파일이 아닌 것은 예전처럼 파일 전체를 읽습니다.
예전에는 화면이 파일을 통째로 읽어 지문을 구하고(내장 SHA-256이 바이트를 한 벌 더 복사) 워커로 넘겨, 워커가 model.dat를 다 읽을 때까지 파일 바이트를 들었습니다.
지문은 이제 워커가 모형을 보낸 뒤 4 MiB씩 읽으며 구합니다(sourceSha256 — 순수 JS SHA-256 src/sha256.ts, Node에서 초당 180 MB쯤).
2026-10-31(34차)부터 큰 모형은 화면이 결과를 받자마자 해석 워커를 끝내고, 지문은 해석기를 싣지 않은 작은 지문 워커(src/worker/print.worker.ts)가 같은 방법으로 구합니다(아래 "남은 일").
같은 기계에서 165 MB 모형을 헤드리스 Chrome으로 두 번씩 열어(부하 평균 10?open=으로 1,441·1,437 MB → 1,224·1,171 MB, 해석 워커(V8 heap + ArrayBuffer) 최고치가 539·597 MB → 524·486 MB였습니다.
여는 시간(12meta.limited).
무늬: 무늬 재질 256개 · 무늬 좌표를 다는 정점 600만(넘으면 그 뒤 그물은 평균색), 그림은 readSkpImages 한도(개수 600·한 장 8 MB·전체 64 MB),
화면은 기기에 따라 한 장 긴 변 2048(메모리 4 GB 이하·손가락+좁은 화면 1024, 2 GB 이하 512) 화소·GPU 예산 256(96·48) MB.Mesh3d.origin·LineSet3d.origin)을 두어 positions를 그만큼 빼서 적습니다
(float32 떨림 방지, 코어 v0.4). 모든 형상이 100 m 안인 모형은 예전 출력과 같습니다.releaseBytes: true — 파일 바이트를 다 쓰면(2021+는 재질·스타일을 읽고 model.dat를 꺼낸 뒤 — 풀면서 읽으면 model.dat를 다 읽은 뒤, 2020 이하는 모형을 읽은 뒤)
그 버퍼를 떼어 놓습니다(넘긴 bytes는 길이 0이 됨).
파일 바이트를 더 쓰지 않는 쪽이 파일 크기만큼 덜 잡게 켭니다. 그림은 그 전에 readSkpImages로 꺼내야 합니다. 기본은 꺼짐.skpFileToScene(file, opts) — 파일(Blob·File, 또는 { size, read(start, end) } 꼴의 SkpSource — blobSource·bytesSource)을 통째로 들지 않고 해석합니다.
그림을 꺼내고(readSkpImages와 같음, 선택은 images) 그 무늬 그림으로 장면을 만들어 { scene, images, imageError }를 냅니다. 결과는 파일 전체 바이트로
readSkpImages → skpToSceneAsync(같은 선택, textures·releaseBytes)를 부른 것과 같습니다. 2021+ 꾸러미는 머리·끝 레코드·중앙 목록과 필요한 항목만 읽고
압축된 model.dat는 1 MiB씩 읽으며 풉니다. 2020 이하·끝 목록이 없거나 깨진 꾸러미·SketchUp 파일이 아닌 것은 파일 전체를 읽습니다(onFileRead가 parts·whole로 알림).
앱 워커와 훑기 도구가 이것을 씁니다. sourceSha256(source)은 같은 꼴의 파일을 덩이씩 읽어 파일 전체로 구한 것과 같은 SHA-256을 냅니다.readModelDat이 내는 중간 모형(SkpModel)의 면·모서리는 형식 배열 표(SkpFaces·SkpEdges)입니다. 고리는 두 단계 구간(면 → 고리 loopStart, 고리 → 꼭짓점 cornerStart)이고,
없는 번호는 NaN입니다. 면 하나를 객체로 보려면 faceAt·edgeAt, 객체 목록에서 표를 만들려면 facesFrom·edgesFrom을 씁니다.outline)가 있으면 meta.annotationRuns에 주석 선분 묶음마다 선분 구간별 놓인 노드(start·node, -1 = 모형에 바로)를 적습니다.meta.annotationMarks(묶음 id → 표시마다 끝점·바깥 방향·치수 평면 수직 geom,
꼴 kind 1 빗금·2 점·3 닫힌 화살·4 열린 화살·5 넘침, 붙은 선의 세계 길이 span, 놓인 노드 node)로 냅니다 — 화면 크기로 그릴 쪽(앱 src/app/dimmarks.ts)이 그립니다.meta.style.edgeColorMode('material'·'axis' — 모두 같게는 없음)를 적고,
모형 모서리 묶음(skp-edges-*·skp-reveal-edges-*)에 선분마다 정점 색(LineSet3d.colors)을 달며 그 방식을 meta.edgeColoring에 적습니다.
묶음 색(color)은 그대로 스타일 모서리 색이라, 모두 같게인 장면에서는 그 색 한 색으로 덮으면 됩니다(앱은 코어 setItemColor).textures: readSkpImages(bytes).materials — 무늬 재질 면에 무늬 좌표(Mesh3d.uvs, 원근 무늬가 섞인 그물은 (u·q, v·q, q) 3값)와 무늬 id(Mesh3d.texture)를 두고,
쓰인 그림을 scene.textures(Texture3d: 그림 바이트를 복사하지 않고 같은 배열, 화소 크기)에 넣습니다(코어 v0.5). meta.textures에는 무늬마다
재질·폴더·그림 경로·무늬 크기(인치)·알파 가능성·색 바꾸기(colorize: 방식·재질 색·원래 평균색)를 적습니다. maxTextures·maxUvVertices로 한도를 바꿉니다.
주지 않으면 예전처럼 평균색만 칠합니다(출력이 바뀌지 않음).| 해석 워커 메시지·이전 목록(형식 배열·ImageBitmap), 워커 손님(파일은 Blob 그대로 넘기고, 결과 뒤에 오는 파일 내용 지문을 약속으로 붙임 — 큰 모형은 결과를 받자마자 해석 워커를 끝내고 지문 워커에 지문을 물음) |
src/app/texlimits.ts | 기기별 무늬 그림 한도(한 장 긴 변·GPU 예산, 순수 함수) |
src/app/format.ts, src/app/keys.ts, src/app/launch.ts, src/app/theme.ts | 글 꼴, 키, 주소 인자, 테마(순수 함수) |
src/app/shortcuts.ts | 키보드 바로 가기 목록과 안내 상자(? 키) |
src/app/sample.ts, src/app/skpwrite.ts | 예제 모형 만들기와 SKP 바이트 쓰기 도구(시험과 함께 씀) |
src/app/dom.ts, src/app/styles.css | DOM 도우미·아이콘, 스타일(밝은·어두운 테마) |
src/worker/parse.worker.ts, src/worker/textures.ts, src/worker/pack.ts, src/worker/recolor.worker.ts, src/worker/print.ts, src/worker/print.worker.ts | 해석 워커(파일을 필요한 자리만 읽어 해석하고 결과 뒤에 지문을 보냄 — 큰 모형은 지문을 보내지 않고 끝남), 무늬 손질(색 바꾸기·알파 재기 — OffscreenCanvas, 문맥 복구 때 쥐어 둔 그림 바이트를 처음과 같은 손질로 다시 만들기), 큰 모형의 법선·무늬 좌표 줄이기(다시 편 오차를 무늬 화소로 재어 큰 것은 Float32로 둠), 문맥 복구용 작은 손질 워커(해석기를 싣지 않음), 파일 내용 지문과 큰 모형용 작은 지문 워커(해석기를 싣지 않음) |
src/index.ts | 해석기 공개 입구(skpToScene, skpFileToScene, readSkpImages, 지원 판 범위) |
src/header.ts | 파일 머리와 판 |
src/zip.ts, src/inflate.ts | ZIP 꾸러미(잘린 파일은 지역 머리 따라가기)와 deflate 풀기(잘린 흐름은 앞부분만) |
src/range.ts, src/sha256.ts | 파일을 통째로 들지 않고 필요한 자리만 읽기(Blob·파일 손잡이 — 끝 레코드·중앙 목록·항목 덩이, model.dat 1 MiB씩), 덩이씩 받는 SHA-256(파일 내용 지문) |
src/images.ts | 꾸러미 안 그림(무늬·미리보기) 꺼내기 |
src/tlv.ts, src/model.ts | model.dat 꼬리표-길이-값 해석(형상·중간 모형 자료형) |
src/modelstream.ts | 큰 model.dat를 내장 풀기 조각으로 받으며 정의 하나씩 읽기(skpToSceneAsync) |
src/entities.ts, src/grow.ts | 중간 모형의 면·모서리·면 무늬 투영 표(형식 배열)와 객체 ↔ 표 바꾸기, 길이를 모르고 쌓는 형식 배열(표·장면 배열 — 버린 버퍼를 곧바로 떼어 놓는다) |
src/mfcstream.ts, src/mfcstate.ts, src/mfcmodel.ts, src/mfcview.ts, src/mfcannot.ts | SketchUp 6~2020 해석: MFC 객체 꼬리표·문자열, 앞머리·형 있는 값, 모형 본문·형상·재질·태그·정의, 카메라·스타일·장면·모형 꼬리, 주석 |
src/vfftags.ts, src/vffcommon.ts, src/vffroot.ts, src/vffannot.ts | 꼬리표 표, 형 있는 값·속성 사전·설정, 뿌리 절(카메라·장면·태그·폴더·스타일·글꼴), 주석(문자·치수·보조선·단면·곡선) |
src/styles.ts, src/meta.ts | 렌더링 설정 + style.xml → 스타일, 화면과의 meta 계약 |
src/scene-meta.ts, src/scene-annotations.ts, src/scene-smooth.ts | 단위·카메라·장면 meta, 주석 펼치기(치수 배치·보조선 자르기·단면 기호), 꼭짓점 법선 |
src/materials.ts, src/scene.ts, src/solid.ts | 재질(색·무늬)과 Scene3d 만들기(면은 earcut 삼각화, 무늬 좌표, 태그·재질 통계, outline), 닫힌 입체·부피 가리기 |
src/projection.ts, src/colorize.ts | 면 무늬 투영(평면 틀·행렬·원근 q), 색 바꾼 무늬의 화소 색 바꾸기 |
tests/ | 해석기 시험(tests/build.ts는 시험용 바이트 만들기, tests/mfcwrite.ts는 2020 이하 꼴 바이트 만들기), tests/app/은 앱 순수 로직 시험 |
tools/site.mjs, tools/sw-template.js | 이 앱의 사이트 설정(앱 정보 원본·미리 담을 파일·서비스 워커 판 이름 짓기 — vite.config.ts와 시험이 같이 씀)과 오프라인 서비스 워커 본문. 언어 페이지·검색 정보·사이트맵·언어별 앱 정보·dist/sw.js는 공용 사이트 도구의 Vite 플러그인이 이 설정으로 만든다 |
site/i18n-core.mjs, site/i18n.mjs, site/locales/ | 공용 번역기에 이 앱의 사이트 정보(site/metadata.json)를 넘기고 사전을 넣는다 |
scripts/sweep/ | 로컬 표본 폴더의 실제 파일을 한꺼번에 해석하고 두 결과를 견주는 개발 도구(아래 "실제 파일 훑기") |
docs/formats/skp.md | 형식 조사 기록 |
snap.tsMeasureResult.axisAaxisBsetMeasureSnap({ inference: { lock, parallel } })setMeasureInferenceLocklockedSnapopencad-viewer3d/inferenceedgeLineLockplaceMeasureLabel(opencad-viewer3d/measurelabel)을 씁니다.
줄자·각도기는 코어 측정 모드를 쓰지 않으므로, 그 안의 상태인 setMeasureInferenceLock({ direction, edge, kind })·measureHoverLock·measurePoints·measureLabelRect·setMeasureLabelAvoid는
쓰지 않고 같은 규칙만 따릅니다(측정 글이 비킬 자리는 앱이 아는 측정 띠·측정값 칸·정밀 조준 십자선·손가락·툴팁).
0.14.0의 붙잡기 점 다듬기(setMeasureSnap({ refine }))는 쓰지 않습니다. SketchUp 모형은 호·원도 모형이 가진 모서리 선분 그대로라 화면에 그리는 꺾은선이 곧 형상이고,
끝점·중간점·모서리 위 점·시작점에서 내린 수직 발이 이미 정확한 값입니다(호·원의 중심은 해석기의 곡선 정보로 앱이 따로 구함).
0.15.0의 멀리서 내린 곡선 위 수직 발과 두 곡선의 교차점 다듬기도 refine을 줄 때만 붙으므로 이 앱에는 바뀌는 것이 없습니다(16차에 올림 —
같은 헤드리스 측정 순서 15단계의 값·추정·잠금·결과 글 자리·3D 보기 그림이 0.14.0 빌드와 모두 같음).
0.16.0의 붙잡은 선분 식별자(measurePoints[].segment)와 끌어온 축(setMeasureSnap({ inference: { track } }))도 코어 측정 모드 안의 것이라 올리기만으로는 바뀌는 것이 없습니다
(17차에 올림 — 같은 헤드리스 측정 순서 15단계의 값·추정·잠금·결과 글 자리·3D 보기 그림이 0.15.0 빌드와 모두 같음).
끌어온 축은 같은 규칙의 순수 함수(opencad-viewer3d/track의 MeasureTracker·trackCrossSnap·trackAxisSnap)로 줄자·각도기에 넣었습니다(17차).
0.17.0의 끌어온 축끼리의 교차(trackPairSnap)와 끌어온 축과 모서리의 교차(trackEdgeSnap)는 코어 README "앱이 할 일"대로 시작점 축 위 줄 맞춘 자리와
chooseTrackPoint로 묶어(화면에서 가까운 것, 뒤 차례는 1화소 넘게 가까워야 이김) 시작점 축 앞에 두고, 모서리는 커서 둘레에서 고른 선분에 그 깊이 한 화소의 세계 길이(wpp)를 붙여 줍니다(18차).
앱 추정(snap.ts)의 차례는 점 추정 → 끌어온 점 후보와 축이 모서리·면과 만나는 점 가운데 점인 것(줄 맞춘 자리·축끼리 교차·끌어온 축과 모서리·시작점 축과 모서리·
축이 면을 뚫는 점) → 모서리 위 → 축·나란함·수직 → 끌어온 축이고, 축은 모형의 그리기 축(axes)을 넘깁니다.
0.17.0으로 올리기만 한 빌드는 같은 헤드리스 측정 순서 15단계의 값·추정·잠금·결과 글 자리·3D 보기 그림이 0.16.0 빌드와 모두 같았습니다.
0.18.0의 시작점 축과 모서리의 교차(axisEdgeSnap)와 축이 면을 뚫는 점(axisFaceSnap)은 코어 README "앱이 할 일"대로 chooseTrackPoint 묶음 끝에
모서리 → 면 차례로 넣었습니다(19차). 모서리는 끌어온 축과 같은 커서 둘레 선분을 주고, 면은 커서 고르기가 그물 면(단면 채운 면이 아님)일 때 그 삼각형 꼭짓점으로 평면을 정해
under로, 그 자리의 그물 면만 고르기(0.20.0 viewer.pick(x, y, { meshesOnly: true }) — 숨긴 항목·단면에서 잘린 쪽을 거름)를 hitAt으로 줍니다
(20차까지는 선을 면보다 앞세우는 보통 고르기를 주고, 그 자리에서 선이 맞으면 그 선이 앞서 맞춘 면 평면 위(1.5화소 안)이고 광선이 그 삼각형 안일 때만 그 면으로 봤습니다).
축 직선은 시작점 X·Y·Z와 끌어온 점 X·Y·Z(앞 직선과 같은 직선은 뺌)를 하나씩 넘기고, 다시 맞춰 보는 고르기는 한 번 추정에 12번까지입니다.
MeasureTool의 axisMeets: false·setAxisMeets(false)로 끌 수 있고, 끄면 같은 헤드리스 측정 순서 15단계의 값·추정·잠금·결과 글 자리와
그림을 찍는 때의 측정 덧그림 DOM이 18차 빌드와 같습니다. 3D 보기 그림은 18차 빌드와 두 번 견주어 두 번 다 같은 4화소가 1만큼 달랐는데,
20차에 19차 빌드를 설정을 건드리지 않고 두 번 돌려 보니 둘째 번 각도기 그림이 그 4화소만 1만큼 달라 19차의 끈 그림과 화소까지 같았습니다.
끄기 때문이 아니라 같은 빌드를 다시 돌릴 때 생기는 그리기 흔들림입니다(그 자리에서 두 그림 가운데 하나가 나옴).
켠 채로도 그 15단계(시작점이 없는 단계 포함)는 값이 모두 같았습니다(그 순서에는 축이 모서리·면과 만나는 자리가 없음).
0.18.0으로 올리기만 한 빌드도 같은 15단계의 값과 3D 보기 그림까지 0.17.0 빌드와 모두 같았습니다.
0.19.0의 잠근 선과 선분의 교차(lockEdgeSnap)와 잠근 선이 면을 뚫는 점(lockFaceSnap)은 코어 README "앱이 할 일"대로 잠근 동안 커서 밑 점 추정(잠근 선에 내린 자리)과
잠근 선 위 커서에 가까운 점 사이에 넣었습니다(20차). 둘 사이는 chooseTrackPoint(모서리가 앞)이고, 선분·면 맞춤은 19차 시작점 축과 같은 것을 넘기며 잠근 방향을 dir로 줍니다.
면이 모형 그대로 평면이라 0.19.0의 곡면 다듬기(refineFace)는 쓸 곳이 없습니다. MeasureTool의 lockMeets: false·setLockMeets(false)로 끌 수 있고,
끄거나 켠 빌드 모두 같은 헤드리스 측정 순서의 값·추정·잠금·결과 글 자리·덧그림 DOM이 19차 빌드와 같았으며(켠 빌드는 그 순서의 잠근 자리에 만나는 점이 없음),
각도기 3D 보기 그림은 위의 두 그림 가운데 하나였습니다. 0.19.0으로 올리기만 한 빌드는 3D 보기 그림까지 19차 빌드와 같았습니다.
0.20.0으로 올리며(21차) 축·잠근 선이 면을 뚫는 점을 다시 맞춰 보는 고르기를 코어 README "앱이 할 일"대로 그물 면만 고르기로 바꾸고, 앱이 따로 하던 면 위 선 판정을 없앴습니다.
면 위에 그린 선은 예전과 같이 면을 가리지 않고, 면 앞에 떠 있는 선이 그 자리 6화소 안을 지나도 이제는 그 밑 면에 붙습니다(선은 그 자리 화소를 덮지 않음).
같은 헤드리스 측정 순서(줄자·각도기 17단계와 3D 보기 그림, 넓은 화면·375px의 잠근 축이 면·모서리와 만나는 자리)의 값·추정·잠금·결과 글 자리·덧그림 DOM이 20차 빌드와 모두 같았고,
예제 모형의 시작점 16곳에서 세 축을 따라(잠그지 않고·그 축에 잠그고) 3만 8천여 자리의 추정을 견줘도 모두 같았습니다.
그 가운데 다시 맞춰 보는 고르기는 두 판 다 260번이었고(20차 판정에서 선이 맞은 10번도 새 고르기로 같은 면), 그 고르기에 든 시간은 모두 합쳐 5 ms에서 3 ms였습니다.
0.20.0의 축과 꺾은선 토막의 열린 교차(openEdgeSnap)와 축이 아닌 잠근 선 다듬기는 refine을 줄 때만이고, SketchUp 곡선은 모형 선분 그대로라 쓰지 않습니다.
0.20.0으로 올리기만 한 빌드도 같은 측정 순서와 위의 추정이 20차 빌드와 모두 같았습니다.
0.21.0의 잠근 선과 끌어온 축의 교차(lockTrackSnap)는 코어 README "앱이 할 일"대로 잠근 동안의 chooseTrackPoint 묶음 맨 앞에 넣었습니다
(24차 — [lockTrackSnap(…, axes), lockEdgeSnap(…), lockFaceSnap(…)]). 끌어온 점은 머물러 기억한 점(최근 셋)을, 축은 모형의 그리기 축을 넘기고, 같은 lockMeets·setLockMeets로 끕니다.
면이 모형 그대로 평면이라 0.21.0의 면 위 점 다듬기(refineFace의 'onFace')와 다듬기 기억 비우기(clearMeasureRefine)는 쓸 곳이 없습니다.
0.22.0의 누르기 알림의 그물 면만 고르기(onPick(cb, { meshesOnly: true }))는 이 앱에 면 위 선을 누른 뒤 그 아래 면을 따로 고르는 곳이 없어 쓰지 않습니다
(면 고르기 방식에서 모서리를 누르면 그 모서리를 고름). 0.22.0의 면의 열린 교차·한 토막 활꼴의 두 교차와 0.23.0의 열린 교차 받는 한도는 refine·refineFace를 줄 때만이고,
0.23.0의 덧그림 글상자 비키기는 코어 setAnnotations·setMeasureLabelAvoid를 쓰는 앱에서만이라(이 앱의 결과 글·치수 글은 앱 덧그림) 바뀌는 것이 없습니다.
결과 글·툴팁이 비킬 뷰 큐브 자리는 코어 내부 요소(canvas.v3d-cube)를 찾던 것을 0.23.0 viewCubeRect로 바꿨고, 반투명 그물 너머 고르기(throughTranslucent)는 남은 일에 적은 까닭으로 쓰지 않습니다.
같은 헤드리스 측정 순서(줄자·각도기 17단계와 3D 보기 그림, 넓은 화면·375px의 잠근 축이 면·모서리와 만나는 자리 354값), 375px 결과 글 비키기,
예제 모형의 시작점 16곳 축 둘레 3만 8천여 자리의 추정(잠그지 않고·그 축에 잠그고 — 코어만 올린 빌드도)이 23차 빌드와 모두 같았습니다.
예제 집에서 문 위 끝점을 끌어온 채 벽 밑 끝점에서 파랑 축을 잠그면 문 높이(2100 mm)에서 끌어온 점의 빨강 축과 만나는 점에 붙는 것도 넓은 화면·375px에서 확인했습니다.
0.24.0의 그물마다 고르기 불투명(setItemPickOpaque)·반투명 너머 고르기만 바꾸기(setMeasurePickThrough)는 throughTranslucent를 쓰지 않아,
덧그림 글상자끼리 비키기는 코어 setAnnotations를 쓰지 않아, 면의 열린 교차 둘째 힌트는 refineFace를 쓰지 않아 올리기만으로 바뀌는 것이 없습니다
(26차 — 코어만 올린 빌드의 같은 헤드리스 측정 순서 17단계와 3D 보기 그림, 잠근 축 354값, 375px 결과 글 비키기 277값, 축 둘레 추정 3만 8천여 자리,
23placeLabelAmongopencad-viewer3d/measurelabelFile)에서 열면 setScene(scene, { incremental, release: { normals: true, uvs: true, textures: 'bitmap' } })로 주고,
그 전에 워커가 색을 바꾼 무늬는 무늬 객체에서 그림 바이트(data)를 빼 둡니다(src/app/release.ts prepareRelease — 남겨 두면 되찾을 때 코어가 원래 색으로 풂).
onContextLost에 안내를, onContextRestore(빈 need도 옴)에 안내 거두기와 라벨·치수 끝 표시의 가림 판정 다시 하기를 걸고, need가 있으면 같은 파일을 새 해석 워커로
처음 열 때와 같은 선택으로 다시 풀어 그 그물의 법선·무늬 좌표(restoreMeshArrays)와 색을 바꿔 다시 손질한 무늬(restoreTexture)만 받고 워커를 끝냅니다
(arrays 요청 — 정점 수가 다른 그물은 파일이 바뀐 것이라 주지 않음). 놓은 뒤 앱은 장면의 법선·무늬 좌표·무늬 원본을 읽지 않습니다(고르기·측정·안내·단면·라벨 가림·그림 저장·장면 바꾸기는
위치·색인만 쓰고, 문서를 다시 열면 새 장면 — 27차에 src/app을 다시 훑어 봄). 주소로 받은 파일·예제는 놓지 않습니다(다시 읽으려면 파일을 쥐어야 함).
같은 큰 모형을 열어 문맥을 잃었다 되찾은 뒤의 그림은 26차 빌드와 새 빌드 모두 처음과 화소까지 같았습니다(무늬 색 포함 — 넓은 화면 밝게·375px 어둡게, 아래 "큰 모형 메모리").
0.26.0(28차)은 새 선택(정수 법선·무늬 좌표 배열, 글상자 기본 자리 first, 누르기의 front)을 쓰지 않으면 0.25.0과 같아, 코어만 올린 빌드가 27차 빌드와
같은 헤드리스 측정 순서 17단계와 3D 보기 그림, 잠근 축 354값, 375px 결과 글 비키기 306값, 축 둘레 추정 3만 8천여 자리, 23isBigScenepackNormalsuvTransformbackUvTransformpackUvssrc/worker/pack.tsParseResult.packedfirstlabelSpotplaceLabelAmong(…, { first: { x, y } })'center'fallback으로 같은 자리를 코어 한 번으로 고르게 바꿈 — 아래).
끌어온 점의 나란함·수직(순수 trackDirGuides·trackDirSnap)도 넣지 않았습니다(아래 "남은 일").
0.27.0(29차)은 새 선택(판 너머 고르기에서 그물 일부 면만 지나가기 setItemPickThrough, 끌어온 점의 나란함·수직 inference.trackDirs, 뷰어 글상자 기본 자리 setMeasureLabelSpot)을
쓰지 않으면 0.26.0과 같아, 코어만 올린 빌드가 28차 빌드와 같은 헤드리스 측정 순서 17단계와 3D 보기 그림, 잠근 축 354값, 375px 결과 글 비키기 306값,
축 둘레 추정 3만 8천여 자리, 23src/app/release.tsrelease: { textures: 'bitmap' }restoreMeshArraysprepareReleasesrc/worker/recolor.worker.tsrecolorTexturesrestoreTexturearrayssnap.ts)을 쓰는 줄자·각도기에는 닿지 않습니다(snap.ts가 부르는 코어 track 함수는 바뀐 함수를 부르지 않음).
코어만 올린 빌드가 34차 빌드와 측정 순서·그림 해시 103값, 잠근 축·375px 정밀 조준 361값, 375px 어둡게 결과 글 109값, 축 둘레 추정 38,120자리가 같았고,
165 MB·82 MB 표본 처음 열기의 그림(넓은 화면·375px 어둡게)이 화소까지 같고 처음 열기 때·메모리(개인 사용량 최고치 — 아래 "큰 모형의 메모리" 35차)는 흔들림 안이었습니다.
0.31.0이 bounds의 itemExtent·itemsExtent를 작은 모듈 extent로 옮겨(bounds가 다시 내보냄) tests/app/core-split.test.ts의 작은 코어 모듈에 extent를 더했습니다.
결과 글 자리(labelSpot)는 0.28.0의 placeLabelAmong(…, { first: 가운데, fallback: 'topRight' }) 한 번으로 고릅니다(측정 점 표식은 avoid.soft).
29차까지의 앱 계산(가운데가 막혔는지 먼저 보고 placeMeasureLabel·placeLabelAmong을 골라 부른 뒤 옮긴 자리가 다시 막혔는지 따짐)을 시험에 두고,
측정 도구가 주는 꼴의 무작위 입력 4,000가지(정밀 조준 원·손가락, 툴팁, 3D 보기 위에 뜬 것, 가장자리 띠, 화면 선분과 측정 점 표식, 먼저 놓인 글상자 0tests/app/labelspot.test.tsonContextRestoreDonerestorePendingrestoreNeedinference.parallelEdgetrackDirs: { edge }setMeasureSnapsnap.tsTRACK_DWELL_MSsetTimeoutMeasureTooltrack: falsesetTrack(false)setMeshremoveMeshsetItemVisiblesetGroupVisibleaddEdgeStubsdragStartPxtracksnapsnapmountLockMenutoggleLockMenupickGuideselectKeyguidePickTextmountGuideDeletetrackbvhsectionpickguideSelectOpguidesInBoxhideSelectedGuidesunhideGuidesaimPointmeasurelabeltrackplacedRectsselectAllKeyselectAllGuidesselectNonerelease.tssetSceneattribsprotocol.tsrecolor.worker.tsuiTextrange.tssha256.tstrackmountUndoguideMenuItemsundoKeyopenFileguidestore.tstools/performance-budget.jsonopencad-site check-performance-budgetnpm install "opencad-viewer3d@github:GyuTae-Kim/opencad-viewer3d#<태그>"#<main 병합 커밋 전체 SHA>releaseModel) 두 번 돌려면 그 표를 끝까지 들고 있어야 해 오히려 늘어납니다.
2026-10-29(20차)에 실제로 해 보았습니다. 길이만 세는 첫 번(중간 표를 놓지 않음)과 센 길이로 채우는 둘째 번으로, 165 MB 파일의 장면 배열 빈자리
(최고치 때 잡은 225 MB 가운데 쓴 것 151 MB — 빈자리 74 MB)는 0이 되고 해석 결과도 같았지만, Node heap + external 최고치는 534ArrayBuffer transfer(0)).
앞 판과 번갈아 세 번씩 잰 장면 단계 최고치는 548.6·533.1·546.2 MB → 483.8·477.0·476.3 MB(load average 23freeTables — transfer(0), 해석 워커 조각 gzip +0.1 KB). 앞 판과 번갈아 여섯 번 잰 장면 단계 최고치는
486.4·475.1·590.3·478.2·481.1·477.2 MB → 476.2·472.6·442.8·433.5·439.4·470.3 MB(load average 12release: { normals, uvs, textures: 'bitmap' } — 그림 바이트는 남겨 되찾을 때 코어가 다시 풂),
워커가 색을 바꾼 무늬만 그림 바이트도 빼 둡니다. 같은 파일을 26차 빌드와 번갈아 두 번씩 열어 잰 ArrayBuffer는 272.6 → 201.3 MB(두 번 모두 — 법선 37.9·무늬 좌표 17.5·
색을 바꾼 무늬의 바이트 15.9 MB가 빠짐), 장면에 남은 푼 무늬 그림은 51장(화소 148 MB 어림) → 0장, JS heap은 7.9 → 8.6 MB였고(load average 10WEBGL_lose_context — 잃음 사건 한 틱 뒤 되찾기)
코어가 위치·색으로 먼저 그리고, 앱이 같은 파일을 새 해석 워커로 다시 풀어 그물 915개의 배열과 무늬 51장을 돌려준 뒤 워커를 끝냅니다. 다 돌아오기까지 19.5·25.2초
(넓은 화면, load average 15onContextRestoreDone)까지 안내를 둡니다(위 "개발").
같은 165 MB 파일을 29차 빌드와 번갈아 다섯 번씩 열어 문맥을 잃었다 되찾으니(헤드리스 넓은 화면, load average 2stats.buffers로 잽니다. 이 파일의 GPU 버퍼는 4,537개 119.9 MB(그물 위치 40.3·선분 위치 40.2·색인 15.7·법선 12.6·무늬 좌표 10.7·뒷면 무늬 좌표 0.4 MB)이고,
되찾은 뒤에도 같습니다. 28차에 줄인 법선(Float32였으면 37.9 MB)·무늬 좌표(17.5 MB)의 GPU 쪽 감소(55.4 → 23.7 MB, −31.7 MB)가 이 값으로 바로 보입니다(무늬 그림은 stats.textures 268 MB 어림이 따로).
2026-10-31(32차)에 코어 0.30.0이 처음 올릴 때도 무늬마다 다시 그리지 않게 바뀌어, 처음 열기를 31차 빌드와 번갈아 재었습니다(헤드리스 "열기" — 앱과 코어 코드는 그대로 두고 그린 화면·무늬 올리기의 때를 적음).
이 앱은 무늬를 두 꼴로 줍니다. 워커가 색을 바꾸거나 알파를 잰 무늬는 푼 그림(ImageBitmap)으로, 나머지(JPEG·알파 없는 PNG)는 그림 바이트로 주어 코어가 풉니다.
165 MB 파일은 115장 가운데 51장이 푼 그림, 64장(27.3 MB, 원본 7,500만 화소 — 긴 변 최대 5,500)이 그림 바이트라 코어 개선이 닿습니다.
넓은 화면 세 번씩(load average 2texImage2D)에 걸린 시간 합 12.0ps rss는 같은 빌드끼리도 표준 편차 9src/worker/print.worker.ts)가 구합니다. 33차 빌드와 번갈아 네 번씩(load average 13TlvReader.firstEach — 꼬리표마다 first를 부른 것과 같은 값·같은 곳에서 던짐),
모서리·면 객체 없이 표에 바로 적으며, 면 무늬 투영 행렬은 면마다 Float64Array(9)(72바이트라 heap 밖에 버퍼를 잡음)를 새로 잡지 않고 다시 쓰는 자리에 적습니다.
푼 model.dat 한 버퍼 읽기만 같은 프로세스에서 번갈아 여덟 번씩 재면(load average 6) 165 MB 표본 1,233 → 953 ms(−23%), 82 MB 표본 652 → 548 ms이고,
세 표본의 장면 출력 해시와 실제 파일 1,614개 훑기의 분류·지표·경고는 같습니다(Node 해석 단계 heap + external 최고치 407 → 388tests/smoothroots.test.ts).
같은 표본에서 형식 배열을 옮긴 바이트는 자라며 332 → 33 MB, 끝에 줄이며 370 → 158 MB(남은 것은 펼치기의 묶음 배열 — 배치를 다 돌기 전에는 크기를 모름),
JS heap 할당은 1.93 → 1.44 GB(earcut 0.61 → 0.40 GB, 면 삼각화 0.25 → 0.12 GB), 장면 단계 GC는 가운데값 33 → 26번입니다.
번갈아 열 번 잰 장면 단계 CPU 시간은 가운데값 1,677 → 1,588 ms(짝 차 가운데값 −101 ms·−6%, 열 번 가운데 일곱 번 줄어듦 — load average 15--mem과 같은 Node 단계 메모리(번갈아)로 본 장면 단계 heap + external 최고치는 165 MB 표본 448.6Math.hypot(정규화 — 인자를 힙 수로 담아 0.1 GB 남짓 할당)과
earcut은 바꿀 수 없어, 남은 큰 몫은 꼭짓점 법선·무늬 투영·펼치기의 묶음 배열입니다.
같은 때 무늬가 적은 모형에서 워커가 첫 그림 전에 2D 그리기를 기다리는 몫도 재었습니다(헤드리스 Chrome 156, 새 임시 프로필 — 워커 안에서 그림 풀기·2D 그리기·PNG 굳히기 때를 적음).
이 기계 GPU(ANGLE Metal)로는 워커의 첫 drawImage가 0restoreTexture뿐이라 앱만으로는 기하를 먼저 보낼 수 없습니다
(코어가 setScene 뒤에 손질한 무늬를 받는 길을 주면 할 수 있음 — 코어 요청).