Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
This commit is contained in:
@@ -236,6 +236,32 @@ export function drawRouteEditScene(context: CanvasRenderingContext2D, scene: Rou
|
||||
style.getPropertyValue("--color-text-secondary") || "#9ca3af",
|
||||
1.6,
|
||||
);
|
||||
// 곡선 시작·끝점 — 잡아서 직선 각도와 R 을 함께 바꾸는 손잡이(2026-09-07 사용자 지시).
|
||||
// **속을 비우고 테두리를 굵게** 그린다 — 선·노드와 색이 같으면 눈에도 안 띄고 집기도 어렵다.
|
||||
// 예상노선 뒤 · 노선 앞(414-2) — 노선이 네모 위를 지나감 · 고른 곡선은 `drawPickedCurve` 가 위에.
|
||||
context.save();
|
||||
context.lineWidth = 2;
|
||||
scene.curveInfo.forEach((curve) => {
|
||||
// **늘 보인다**(2026-09-07 사용자 지시) — 직선이 곡선에 닿는 자리는 손잡이이기 이전에
|
||||
// **읽을 정보**다. 한때 고른 곡선만 내보였더니 「표기가 다 사라졌다」는 지적을 받았다.
|
||||
// 노드를 못 집던 문제는 집기 우선순위(노드가 먼저)로 따로 풀었으므로 다 내놓아도 된다.
|
||||
if (scene.curveOn[curve.node_first] === false) return; // 곡선을 지운 자리에는 접선점도 없다.
|
||||
// 고른 곡선은 속을 채워 도드라지게 — 지금 끌 수 있는 것이 무엇인지 보이게.
|
||||
const isPicked = curve.node_first === scene.picked;
|
||||
[curve.start, curve.end].forEach((point) => {
|
||||
const [x, y] = toScreen([point[0], point[1]]);
|
||||
context.beginPath();
|
||||
const size = isPicked ? CURVE_HANDLE_PX + 1 : CURVE_HANDLE_PX;
|
||||
context.rect(x - size, y - size, size * 2, size * 2);
|
||||
context.fillStyle = isPicked
|
||||
? style.getPropertyValue("--map-route") || "#f97316"
|
||||
: style.getPropertyValue("--map-halo") || "rgba(255,255,255,0.95)";
|
||||
context.fill();
|
||||
context.strokeStyle = style.getPropertyValue("--map-route") || "#f97316";
|
||||
context.stroke();
|
||||
});
|
||||
});
|
||||
context.restore();
|
||||
// 선은 **폴리라인**(원호 포함)을 그리고, 잡는 동그라미는 **노드**에만 찍는다.
|
||||
// 노드를 옮기는 동안에는 폴리라인이 없으므로 노드를 곧바로 이어 미리 보인다.
|
||||
strokePolyline(
|
||||
@@ -294,29 +320,6 @@ export function drawRouteEditScene(context: CanvasRenderingContext2D, scene: Rou
|
||||
}
|
||||
});
|
||||
|
||||
// 곡선 시작·끝점 — 잡아서 직선 각도와 R 을 함께 바꾸는 손잡이(2026-09-07 사용자 지시).
|
||||
// **속을 비우고 테두리를 굵게** 그린다 — 선·노드와 색이 같으면 눈에도 안 띄고 집기도 어렵다.
|
||||
context.lineWidth = 2;
|
||||
scene.curveInfo.forEach((curve) => {
|
||||
// **늘 보인다**(2026-09-07 사용자 지시) — 직선이 곡선에 닿는 자리는 손잡이이기 이전에
|
||||
// **읽을 정보**다. 한때 고른 곡선만 내보였더니 「표기가 다 사라졌다」는 지적을 받았다.
|
||||
// 노드를 못 집던 문제는 집기 우선순위(노드가 먼저)로 따로 풀었으므로 다 내놓아도 된다.
|
||||
if (scene.curveOn[curve.node_first] === false) return; // 곡선을 지운 자리에는 접선점도 없다.
|
||||
// 고른 곡선은 속을 채워 도드라지게 — 지금 끌 수 있는 것이 무엇인지 보이게.
|
||||
const isPicked = curve.node_first === scene.picked;
|
||||
[curve.start, curve.end].forEach((point) => {
|
||||
const [x, y] = toScreen([point[0], point[1]]);
|
||||
context.beginPath();
|
||||
const size = isPicked ? CURVE_HANDLE_PX + 1 : CURVE_HANDLE_PX;
|
||||
context.rect(x - size, y - size, size * 2, size * 2);
|
||||
context.fillStyle = isPicked
|
||||
? style.getPropertyValue("--map-route") || "#f97316"
|
||||
: style.getPropertyValue("--map-halo") || "rgba(255,255,255,0.95)";
|
||||
context.fill();
|
||||
context.strokeStyle = style.getPropertyValue("--map-route") || "#f97316";
|
||||
context.stroke();
|
||||
});
|
||||
});
|
||||
context.restore();
|
||||
drawPickedCurve(context, scene, style);
|
||||
drawPickHover(context, scene, line, style, NODE_R);
|
||||
|
||||
@@ -441,7 +441,7 @@ export function attachDesignLinePick(input: {
|
||||
stationId?: string;
|
||||
/** 있으면 중심점 고름(끝 조각 · 키 `center`) — 그 측점 누가거리(356-3). */
|
||||
center?: number;
|
||||
/** 있으면 쪽마다 교차점 손잡이(설계선 ∩ 원지반 · 키 `toe:left|right` · 늘 보임 · 370-2). */
|
||||
/** 있으면 쪽마다 교차점 손잡이(설계선 ∩ 원지반 · 키 `toe:left|right` · `bare` 쪽만 · 370-2 · 414-1). */
|
||||
toes?: ReturnType<typeof toePoints>;
|
||||
/** 있으면 쪽마다 노견 끝 = 원지반(절·성토 없음 · 379-1) — 손잡이 값 앞 알림. */
|
||||
bare?: ReturnType<typeof edgeOnGround>;
|
||||
@@ -476,7 +476,10 @@ export function attachDesignLinePick(input: {
|
||||
: edges.right.elevation_m -
|
||||
((edges.left.elevation_m - edges.right.elevation_m) * edges.right.offset_m) /
|
||||
(edges.left.offset_m - edges.right.offset_m);
|
||||
const toeSides = (["left", "right"] as const).filter((at) => input.toes?.[at]);
|
||||
// 손잡이 = 그 쪽이 절 ↔ 성 바뀜 자리(노견 끝 = 원지반 · `bare`)일 때만(414-1)
|
||||
const toeSides = (["left", "right"] as const).filter(
|
||||
(at) => input.toes?.[at] && input.bare?.[at],
|
||||
);
|
||||
const rockLines = input.rock ? kids.filter((el) => el.classList.contains(ROCK_LINE_CLASS)) : [];
|
||||
const keys = [
|
||||
...pieceKeys(design, pieces),
|
||||
@@ -502,7 +505,7 @@ export function attachDesignLinePick(input: {
|
||||
// 중심점(356-3) = 끝 조각 · 판정 원은 고르기 차례가 보라 선 위로(`stackPickTargets`)
|
||||
const center = centerZ === null ? null : centerHit(x0, toDisplayY(centerZ));
|
||||
if (center) hits.push(center);
|
||||
// 교차점 손잡이(370-2) — 쪽마다 원 · 늘 보임 · 고르면 그 쪽 사면 ◀▶(중심점 원과 같은 고르기 차례)
|
||||
// 교차점 손잡이(370-2) — 바뀜 자리 쪽 원 · 평소 투명 · 고르면 그 쪽 사면 ◀▶(중심점 원과 같은 고르기 차례)
|
||||
for (const at of toeSides) {
|
||||
const toe = input.toes?.[at] as { offset_m: number; elevation_m: number };
|
||||
const hit = centerHit(x(toe.offset_m), toDisplayY(toe.elevation_m));
|
||||
|
||||
@@ -102,16 +102,15 @@
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
/* 교차점 손잡이(370-2) — 사면 ∩ 원지반 작은 원 · 늘 보임 · 고르면 채움 · 올리면 같은 모양(379-1). */
|
||||
/* 교차점 손잡이(370-2 · 414-1) — 절 ↔ 성 바뀜 자리 작은 원 · 평소 투명 · 올리거나 고르면 채움. */
|
||||
.b06-chart__toe-hit {
|
||||
fill: var(--color-surface);
|
||||
stroke: var(--color-royal-amethyst);
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
||||
.b06-chart__toe-hit:hover,
|
||||
.b06-chart__toe-hit.is-active {
|
||||
fill: var(--color-royal-amethyst);
|
||||
stroke: var(--color-royal-amethyst);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ out.stop = {
|
||||
};
|
||||
}
|
||||
|
||||
// ② 교차점 손잡이 — 쪽마다 원 · 늘 보임 · 키 toe:left|right · 고르면 ◀▶ 가 멈춤 자리로 보냄
|
||||
// ② 교차점 손잡이 — 바뀜 자리(노견 끝 = 원지반) 쪽만 원 · 키 toe:left|right · 고르면 ◀▶ 로 절 ↔ 성(414-1)
|
||||
const x = (o) => 300 - o * 20;
|
||||
const y = (e) => 400 - (e - 100) * 20;
|
||||
const attach = (sec, changes) => {
|
||||
@@ -260,21 +260,28 @@ const attach = (sec, changes) => {
|
||||
});
|
||||
return { layer, handle, picks };
|
||||
};
|
||||
const toesOf = (layer) => layer.children.filter((k) => k.classList.contains("b06-chart__toe-hit"));
|
||||
const near = section(crossing[0]); // 보통 단면(우 = 절토 · 바뀜 자리 아님)
|
||||
const nearToes = toesOf(attach(near, []).layer);
|
||||
const changes = [];
|
||||
const near = section(crossing[0]);
|
||||
const { layer, handle, picks } = attach(near, changes);
|
||||
const toes = layer.children.filter((k) => k.classList.contains("b06-chart__toe-hit"));
|
||||
const keys = pick.designLineMoves(near, () => undefined).toes;
|
||||
const bareSec = section(out.stop.value); // 바뀜 자리(우 노견 끝 = 원지반)
|
||||
const { layer, handle, picks } = attach(bareSec, changes);
|
||||
const toes = toesOf(layer);
|
||||
const moves = pick.designLineMoves(bareSec, () => undefined);
|
||||
const keys = moves.toes;
|
||||
const rightToeHit = toes.find((t) => Number(t.getAttribute("cx")) > x(0));
|
||||
out.toe = {
|
||||
nearCount: nearToes.length,
|
||||
nearSides: ["left", "right"].filter((s) => pick.designLineMoves(near, () => undefined).toes[s]),
|
||||
count: toes.length,
|
||||
bareSides: ["left", "right"].filter((s) => keys[s] && moves.bare[s]),
|
||||
centerClass: toes.every((t) => t.classList.contains("b06-chart__center-hit")),
|
||||
sides: ["left", "right"].filter((s) => keys[s]),
|
||||
rightOnGround:
|
||||
keys.right && Math.abs(keys.right.elevation_m - groundAt(keys.right.offset_m)) < 1e-9,
|
||||
rightCx: toes.length === 2 ? Number(toes[1].getAttribute("cx")) : null,
|
||||
rightCx: rightToeHit ? Number(rightToeHit.getAttribute("cx")) : null,
|
||||
rightX: keys.right ? x(keys.right.offset_m) : null,
|
||||
};
|
||||
toes[1].click();
|
||||
rightToeHit.click();
|
||||
handle.setActive(picks[0]);
|
||||
// 아래 띠 공용 칸(370-3) — 제목 · 버튼 줄(끝 = 값 글)
|
||||
const cellOf = (h) => ({
|
||||
@@ -287,9 +294,12 @@ const cellOf = (h) => ({
|
||||
});
|
||||
const cell = cellOf(handle);
|
||||
cell.button(x(-1) > x(0) ? "right" : "left").click(); // 화면 → 바깥(우 = −offset)
|
||||
cell.button(x(-1) > x(0) ? "left" : "right").click(); // 화면 → 안쪽
|
||||
out.toePick = {
|
||||
picked: picks,
|
||||
active: toes[1].classList.contains("is-active"),
|
||||
active: rightToeHit.classList.contains("is-active"),
|
||||
modeBefore: bareSec.design.section_mode,
|
||||
modes: changes.map((c) => section(c.shoulder_extend.valueM).design.section_mode),
|
||||
value: cell.value,
|
||||
title: cell.title,
|
||||
panelOpen: !handle.cell.classList.contains("is-empty"),
|
||||
@@ -321,8 +331,10 @@ out.inherit.label = cellOf(after.handle).title;
|
||||
// ⑤ 370-5 — 손잡이를 고른 뒤 넘어간 사면이 원지반과 못 만나면(손잡이 없음) 같은 쪽 첫 사면 선이 이어받음
|
||||
pick.setPickedLine("S1", null);
|
||||
{
|
||||
const again = attach(near, []);
|
||||
again.layer.children.filter((k) => k.classList.contains("b06-chart__toe-hit"))[1].click();
|
||||
const again = attach(bareSec, []);
|
||||
toesOf(again.layer)
|
||||
.find((t) => Number(t.getAttribute("cx")) > x(0))
|
||||
.click();
|
||||
pick.setPickedLine("S1", again.picks[0]);
|
||||
let extend = crossing[1];
|
||||
let open = null;
|
||||
|
||||
@@ -61,7 +61,7 @@ def test_반올림이_교차_앞이어도_다음_걸음은_넘어감(out) -> Non
|
||||
|
||||
|
||||
def test_손잡이_없는_넘어감도_고름_유지(out) -> None:
|
||||
"""370-5 — 넘어간 사면이 원지반과 못 만나 손잡이가 사라져도 같은 쪽 사면 선 · 암 칸으로 안 넘어감."""
|
||||
"""370-5 — 바뀜 자리 손잡이를 고른 뒤 사면이 원지반과 못 만나도 같은 쪽 사면 선 · 암 칸으로 안 넘어감."""
|
||||
got = out["noToe"]
|
||||
assert got["toeGone"] is True
|
||||
assert got["index"] is not None
|
||||
@@ -70,23 +70,28 @@ def test_손잡이_없는_넘어감도_고름_유지(out) -> None:
|
||||
assert got["buttons"] == ["left", "reset", "right"] # ◀↺▶ 남음
|
||||
|
||||
|
||||
def test_교차점_손잡이_쪽마다_늘_보임(out) -> None:
|
||||
def test_교차점_손잡이는_바뀜_자리만(out) -> None:
|
||||
"""414-1 — 원 = 그 쪽 노견 끝 = 원지반(±5 mm · `bare`)일 때만 · 보통 단면은 원 0."""
|
||||
toe = out["toe"]
|
||||
assert toe["count"] == len(toe["sides"]) == 2
|
||||
assert toe["nearSides"] == ["left", "right"] # 사면 ∩ 원지반 자리는 있어도
|
||||
assert toe["nearCount"] == 0 # 보통 단면(절 · 성 정해짐) = 원 0
|
||||
assert toe["count"] == len(toe["bareSides"]) == 1 and toe["bareSides"] == ["right"]
|
||||
assert toe["centerClass"] is True # 중심점 원과 같은 고르기 차례
|
||||
assert toe["rightOnGround"] is True
|
||||
assert abs(toe["rightCx"] - toe["rightX"]) < 1e-9
|
||||
|
||||
|
||||
def test_손잡이를_고르면_그_쪽_사면_좌우_이동(out) -> None:
|
||||
def test_손잡이를_고르면_좌우로_절성토_넘김(out) -> None:
|
||||
"""414-1 — 바뀜 자리 원을 고르면 ▶(바깥) = 성토 · ◀(안쪽) = 절토."""
|
||||
got = out["toePick"]
|
||||
assert got["active"] is True and got["panelOpen"] is True
|
||||
assert got["title"] == "사면 이동 (우)" # 아래 띠 공용 칸(370-3)
|
||||
assert got["value"].startswith("노선에서 ")
|
||||
assert len(got["sent"]) == 1 and got["sent"][0]["side"] == "right"
|
||||
assert got["value"].startswith("절·성토 없음 · 노선에서 ")
|
||||
assert [c["side"] for c in got["sent"]] == ["right", "right"]
|
||||
low, high = out["stop"]["crossing"]
|
||||
assert abs(got["sent"][0]["valueM"] - out["stop"]["value"]) < 1e-9 # ◀▶ 도 멈춤 자리
|
||||
assert low < got["sent"][0]["valueM"] < high
|
||||
outward, inward = (c["valueM"] for c in got["sent"])
|
||||
assert outward >= high and inward <= low # 멈춤 자리에서 다시 안 섬
|
||||
assert got["modes"] == ["left_cut", "both_cut"] # 우 = 성토 → 절토
|
||||
|
||||
|
||||
def test_절에서_성으로_넘어가도_고름_유지(out) -> None:
|
||||
|
||||
@@ -23,12 +23,23 @@ def test_노드는_라벨_뒤_맨_위_회전_안():
|
||||
scene = render[render.index("export function drawRouteEditScene(") :]
|
||||
scene = scene[: scene.index("context.restore(); // 회전 끝")]
|
||||
labels = scene.index("drawContourLabels(context, scene, band, style);")
|
||||
for later in ("scene.planned.forEach(", "scene.curveInfo.forEach(", "drawPickHover("):
|
||||
for later in ("scene.planned.forEach(", "drawPickedCurve(", "drawPickHover("):
|
||||
assert labels < scene.index(later), later
|
||||
for earlier in ("drawStationMarks(", "scene.overStations?.(", "drawMeasureMarks("):
|
||||
assert scene.index(earlier) < labels, earlier
|
||||
|
||||
|
||||
def test_곡선_네모는_예상노선_뒤_노선_앞():
|
||||
"""414-2 — 모든 곡선 BC · EC 네모 = 예상노선 뒤 · 노선 앞(노선이 네모 위를 지나감)."""
|
||||
render = _read("B05_Profile_UI_RouteEdit_Render.ts")
|
||||
scene = render[render.index("export function drawRouteEditScene(") :]
|
||||
expected = scene.index("scene.expected,")
|
||||
squares = scene.index("scene.curveInfo.forEach(")
|
||||
route = scene.index("strokePolyline(", squares)
|
||||
assert expected < squares < route
|
||||
assert scene[route:].split(",")[2].strip() == "line"
|
||||
|
||||
|
||||
def test_집기_맴돔_한_함수():
|
||||
pointer = _read("B05_Profile_UI_RouteEdit_Pointer.ts")
|
||||
assert "pickAtScreen(" in pointer
|
||||
|
||||
Reference in New Issue
Block a user