DXF-Import: Platzierungsoption (relativ zu 0 ODER in Ansichtsmitte)
This commit is contained in:
+50
-1
@@ -226,6 +226,43 @@ function promptCount(): number | null {
|
||||
return Number.isFinite(n) && n >= 1 ? n : null;
|
||||
}
|
||||
|
||||
/** Umriss (bbox) aller Drawing2D-Geometrien in Modell-Metern (null = leer). */
|
||||
function drawingsBBox(
|
||||
drawings: Drawing2D[],
|
||||
): { minX: number; minY: number; maxX: number; maxY: number } | null {
|
||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||
const acc = (x: number, y: number) => {
|
||||
minX = Math.min(minX, x); minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x); maxY = Math.max(maxY, y);
|
||||
};
|
||||
for (const d of drawings) {
|
||||
const g = d.geom;
|
||||
if (g.shape === "line") { acc(g.a.x, g.a.y); acc(g.b.x, g.b.y); }
|
||||
else if (g.shape === "polyline") for (const p of g.pts) acc(p.x, p.y);
|
||||
else if (g.shape === "rect") { acc(g.min.x, g.min.y); acc(g.max.x, g.max.y); }
|
||||
else if (g.shape === "circle" || g.shape === "arc") {
|
||||
acc(g.center.x - g.r, g.center.y - g.r);
|
||||
acc(g.center.x + g.r, g.center.y + g.r);
|
||||
} else if (g.shape === "text") acc(g.at.x, g.at.y);
|
||||
}
|
||||
return Number.isFinite(minX) ? { minX, minY, maxX, maxY } : null;
|
||||
}
|
||||
|
||||
/** Ein Drawing2D um (dx,dy) in Modell-Metern verschieben (alle Formen). */
|
||||
function translateDrawing(d: Drawing2D, dx: number, dy: number): Drawing2D {
|
||||
const mv = (p: Vec2): Vec2 => ({ x: p.x + dx, y: p.y + dy });
|
||||
const g = d.geom;
|
||||
switch (g.shape) {
|
||||
case "line": return { ...d, geom: { ...g, a: mv(g.a), b: mv(g.b) } };
|
||||
case "polyline": return { ...d, geom: { ...g, pts: g.pts.map(mv) } };
|
||||
case "rect": return { ...d, geom: { ...g, min: mv(g.min), max: mv(g.max) } };
|
||||
case "circle": return { ...d, geom: { ...g, center: mv(g.center) } };
|
||||
case "arc": return { ...d, geom: { ...g, center: mv(g.center) } };
|
||||
case "text": return { ...d, geom: { ...g, at: mv(g.at) } };
|
||||
default: return d;
|
||||
}
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
// Sprachwechsel abonnieren: re-rendert den gesamten Baum, wenn die Sprache
|
||||
// umgeschaltet wird (alle Kinder lesen denselben modulweiten Zustand über t).
|
||||
@@ -2136,12 +2173,24 @@ export default function App() {
|
||||
decision.target.kind === "new"
|
||||
? addDrawingLevel(decision.target.name)
|
||||
: decision.target.levelId;
|
||||
const drawings = buildDrawings(
|
||||
let drawings = buildDrawings(
|
||||
decision,
|
||||
levelId,
|
||||
project,
|
||||
activeCategoryCode,
|
||||
);
|
||||
// Platzierung „viewCenter": den Gesamt-Umriss so verschieben, dass seine
|
||||
// Mitte auf die Mitte des aktuellen Ausschnitts fällt (sonst bleibt alles an
|
||||
// den DXF-Koordinaten relativ zum Nullpunkt).
|
||||
if (decision.placement === "viewCenter" && drawings.length) {
|
||||
const center = planViewRef.current?.viewCenterModel();
|
||||
const bb = drawingsBBox(drawings);
|
||||
if (center && bb) {
|
||||
const dx = center.x - (bb.minX + bb.maxX) / 2;
|
||||
const dy = center.y - (bb.minY + bb.maxY) / 2;
|
||||
drawings = drawings.map((d) => translateDrawing(d, dx, dy));
|
||||
}
|
||||
}
|
||||
if (drawings.length) importDrawings(drawings);
|
||||
if (decision.importMeshes && decision.parsed.meshes.length) {
|
||||
addContextObjects(decision.parsed.meshes);
|
||||
|
||||
@@ -791,6 +791,9 @@ export const de = {
|
||||
"import.layers.heading": "Ebenen-Handling (DXF-Layer)",
|
||||
"import.layers.asCategories": "DXF-Layer als Kategorien übernehmen",
|
||||
"import.layers.toActive": "Alles auf die aktive Kategorie ({code})",
|
||||
"import.placement.heading": "Platzierung",
|
||||
"import.placement.origin": "An DXF-Koordinaten (relativ zum Nullpunkt)",
|
||||
"import.placement.viewCenter": "In die Mitte der aktuellen Ansicht",
|
||||
"import.mesh.heading": "Meshes",
|
||||
"import.mesh.asContext": "Meshes als Kontext-Geometrie (3D) importieren",
|
||||
"import.mesh.note": "Meshes sind keine 2D-Zeichnung — sie landen in der Kontext-Schicht.",
|
||||
|
||||
@@ -780,6 +780,9 @@ export const en: Record<TranslationKey, string> = {
|
||||
"import.layers.heading": "Layer handling (DXF layers)",
|
||||
"import.layers.asCategories": "Keep DXF layers as categories",
|
||||
"import.layers.toActive": "Everything onto the active category ({code})",
|
||||
"import.placement.heading": "Placement",
|
||||
"import.placement.origin": "At DXF coordinates (relative to origin)",
|
||||
"import.placement.viewCenter": "Centered in the current view",
|
||||
"import.mesh.heading": "Meshes",
|
||||
"import.mesh.asContext": "Import meshes as context geometry (3D)",
|
||||
"import.mesh.note": "Meshes are not 2D drawings — they go into the context layer.",
|
||||
|
||||
@@ -131,6 +131,8 @@ export interface PlanViewHandle {
|
||||
applyScale: (n: number) => void;
|
||||
/** Aktuelle Pixel/Meter-Skala (für Snap-Toleranz der Command-Engine). */
|
||||
pxPerMeter: () => number;
|
||||
/** Mitte des aktuellen Ausschnitts in Modell-Metern (z. B. für Import-Platzierung). */
|
||||
viewCenterModel: () => Vec2;
|
||||
}
|
||||
|
||||
/** Ergebnis eines Links-Klicks (Auswahl) an einer Modellposition. */
|
||||
@@ -631,6 +633,11 @@ export const PlanView = forwardRef<PlanViewHandle, PlanViewProps>(
|
||||
if (n > 0) setPaperScale(n);
|
||||
},
|
||||
pxPerMeter: () => currentPxPerMeter(),
|
||||
viewCenterModel: () => {
|
||||
// Mitte der frischen viewBox (aus Ref) → Modell (inverse toScreen).
|
||||
const v = viewRef.current;
|
||||
return { x: (v.x + v.w / 2) / PX_PER_M, y: -(v.y + v.h / 2) / PX_PER_M };
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
|
||||
+36
-1
@@ -36,6 +36,12 @@ export interface ImportDecision {
|
||||
categoryMode: CategoryMode;
|
||||
/** Ob die Meshes als Kontext-Geometrie übernommen werden. */
|
||||
importMeshes: boolean;
|
||||
/**
|
||||
* Platzierung der importierten Geometrie: `origin` = 1:1 an den DXF-Koordinaten
|
||||
* (relativ zum Nullpunkt); `viewCenter` = der Gesamt-Umriss wird in die Mitte
|
||||
* des aktuellen Ausschnitts geschoben.
|
||||
*/
|
||||
placement: "origin" | "viewCenter";
|
||||
/** Die geparsten Konturen-Sätze (für die spätere 2D-Konvertierung). */
|
||||
parsed: DxfImportResult;
|
||||
}
|
||||
@@ -127,6 +133,7 @@ export function ImportDialog({
|
||||
layers.length > 0 ? "byLayer" : "active",
|
||||
);
|
||||
const [importMeshes, setImportMeshes] = useState<boolean>(true);
|
||||
const [placement, setPlacement] = useState<"origin" | "viewCenter">("origin");
|
||||
|
||||
const canImport =
|
||||
!isDwg && !loading && (hasDrawings || (hasMeshes && importMeshes));
|
||||
@@ -137,7 +144,7 @@ export function ImportDialog({
|
||||
targetKind === "existing"
|
||||
? ({ kind: "existing", levelId: existingLevelId } as const)
|
||||
: ({ kind: "new", name: newName } as const);
|
||||
onImport({ target, categoryMode, importMeshes, parsed });
|
||||
onImport({ target, categoryMode, importMeshes, placement, parsed });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -295,6 +302,34 @@ export function ImportDialog({
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Platzierung: an den DXF-Koordinaten (relativ zu 0) ODER in die
|
||||
Mitte des aktuellen Ausschnitts geschoben. */}
|
||||
{hasDrawings && (
|
||||
<section className="imp-section">
|
||||
<div className="imp-section-title">
|
||||
{t("import.placement.heading")}
|
||||
</div>
|
||||
<label className="imp-radio">
|
||||
<input
|
||||
type="radio"
|
||||
name="imp-place"
|
||||
checked={placement === "origin"}
|
||||
onChange={() => setPlacement("origin")}
|
||||
/>
|
||||
<span>{t("import.placement.origin")}</span>
|
||||
</label>
|
||||
<label className="imp-radio">
|
||||
<input
|
||||
type="radio"
|
||||
name="imp-place"
|
||||
checked={placement === "viewCenter"}
|
||||
onChange={() => setPlacement("viewCenter")}
|
||||
/>
|
||||
<span>{t("import.placement.viewCenter")}</span>
|
||||
</label>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Mesh-Behandlung. */}
|
||||
{hasMeshes && (
|
||||
<section className="imp-section">
|
||||
|
||||
Reference in New Issue
Block a user