Files
Aislo/M02_MasterTemplete/M02_Item_Paste.py
T
eomsangdonandClaude Opus 5.5 0f510eb53b feat(M02): 공용 나무 여러 개 고르기 · 복사 · 붙여넣기 · 여러 개 옮기기 · 지우기 + 층 사이 붙여넣기 서버 · 「+」 오른쪽 맞춤 (PLAN 30-2)
- 나무 부품 — Ctrl · Shift 클릭 고름(TreePanel_Pick) · 고른 줄 배경 · 층 단추 줄 「N개 고름」 · 오른쪽 클릭 메뉴가 고른 것 전부(복사 · 옮기기 · 지우기) · Ctrl+C · Ctrl+V · Delete · 복사는 읽기 층에서도 · 붙여넣기는 쓸 수 있는 층만 · 콜백 api.many · api.paste · clipKind(일위대가 쪽이 붙일 자리)
- 서버 — POST /layers/{층}/items/paste(여러 항목 · 폴더 · 새 SC/SP 키 · 본뜬 = 원본 · 같은 이름 「(2)」 · 세 파일 · 입력 연결 · 자재 조합 · 빈 폴더까지 · M02_Layer_Access 쓰기 층만) · 나무 고침 대상 여러 개(옮기기 · 지우기 한 번 저장 · 막히면 안 씀)
- 층 단추 줄 「N개 고름」 · 「+」 오른쪽 끝
- 시험 test_m02_tree_paste 새로 · 고르기 규칙 Node 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:05:01 +09:00

106 lines
4.4 KiB
Python

"""여러 항목 · 폴더를 층 사이 붙여넣기(PLAN 30-2) — 구조물 라이브러리 나무의 [복사] → [붙여넣기].
붙일 폴더(`dest`) 아래 새 항목 — 대상 층 새 키(SC · SP) · 열 `본뜬` = 원본(층 · 주인 · 키) ·
같은 이름은 「이름 (2)」 · 폴더는 안의 항목 · 빈 폴더까지(폴더 이름이 겹치면 「폴더 (2)」).
파일(도면 · 면적 · 재료 · 입력 연결 · 자재 조합)은 `Items.copy_item` 이 옮김. 권한은 부르는 쪽 몫.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
from M02_MasterTemplete import M02_MasterTemplete_Items as items_store
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_MasterTemplete_Tree as tree
def _under(path: list[str], folder: list[str]) -> bool:
return path[: len(folder)] == folder
def outer(targets: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""고른 것 가운데 다른 고른 폴더 안에 든 것은 뺌(폴더가 안의 것까지 다룸) —
옮기기 · 지우기 · 붙여넣기."""
folders = [list(t.get("경로") or []) for t in targets if not t.get("키")]
def inside(t: dict[str, Any]) -> bool:
path = list(t.get("경로") or [])
return any(_under(path, f) and (t.get("키") or path != f) for f in folders if f)
return [t for t in targets if not inside(t)]
def _table(folder: Path) -> dict[str, Any]:
with store.at(folder):
return tree.normalize_table(store.read("table", store.STRUCTURE_TABLE)["문서"])
def _items(doc: dict[str, Any]) -> list[dict[str, Any]]:
return [c for c in doc.get("열") or [] if tree.is_item(c)]
def _unique(path: list[str], taken: list[list[str]]) -> list[str]:
n, out = 2, path
while out in taken:
out, n = [*path[:-1], f"{path[-1]} ({n})"], n + 1
return out
def paste(
source: Path,
target: Path,
targets: list[dict[str, Any]],
dest: list[str],
origin: dict[str, Any],
) -> dict[str, Any]:
"""`targets` = `[{경로, 키?}]`(키 = 항목 · 없으면 폴더 경로) → `target` 층 `dest` 아래 ·
돌려줌 알림 `{op: "paste", 경로, 열: [새 열 id]}`. `origin` = `{layer, owner}`(본뜬)."""
src = _table(source)
with store.at(source):
keys = {str(r.get("열")): str(r.get("키")) for r in store.read_combos().get("줄", [])}
src_items = [(tree.path_of(c), keys.get(c["id"])) for c in _items(src)]
src_folders = tree.folders_of(src)
items_store.ensure(target)
dst = _table(target)
taken = [tree.path_of(c) for c in _items(dst)]
taken_folders = tree.folders_of(dst)
plan: list[tuple[str, list[str]]] = [] # (원본 키, 새 경로)
new_folders: list[list[str]] = []
def busy(path: list[str]) -> bool:
paths = [*taken, *(p for _, p in plan)]
return path in [*taken_folders, *new_folders] or any(_under(p, path) for p in paths)
for one in outer(targets):
if one.get("키"):
key = str(one["키"])
found = next((p for p, k in src_items if k == key), None)
if found is None:
raise store.StoreError(404, f"없는 항목 「{key}」")
plan.append((key, _unique([*dest, found[-1]], [*taken, *(p for _, p in plan)])))
continue
folder = list(one.get("경로") or [])
if not folder or (
folder not in src_folders and not any(_under(p, folder) for p, _ in src_items)
):
raise store.StoreError(404, f"없는 폴더 「{' '.join(folder)}」")
top, n = [*dest, folder[-1]], 2
while busy(top):
top, n = [*dest, f"{folder[-1]} ({n})"], n + 1
new_folders.append(top)
new_folders += [[*top, *f[len(folder) :]] for f in src_folders if _under(f, folder)]
plan += [(k, [*top, *p[len(folder) :]]) for p, k in src_items if _under(p, folder) and k]
made = [
items_store.copy_item(source, target, key, {**origin, "key": key}, path)["열"]
for key, path in plan
]
if new_folders: # 빈 폴더도 남게 — 집계표 `폴더`
with store.at(target):
got = store.read("table", store.STRUCTURE_TABLE)
doc = got["문서"]
doc["폴더"] = [*(doc.get("폴더") or []), *new_folders]
store.write("table", store.STRUCTURE_TABLE, got["판"], doc)
return {"op": "paste", "경로": dest, "열": made}