feat: implement OLE2Frame embedded image parsing and exact coordinate rendering
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Model Space / Paper Space (Layout) discovery for CadParseResult.
|
||||
*
|
||||
* DWG entities are owned by a block header handle. System blocks
|
||||
* *Model_Space / *Paper_Space (and *Paper_Space0…) are the two spaces
|
||||
* AutoCAD switches between. Viewer2D must render only one at a time —
|
||||
* drawing both stacks survey-model geometry on top of layout sheet
|
||||
* geometry and produces the "all text piled in the center" glitch.
|
||||
*/
|
||||
|
||||
export type CadSpaceKind = 'model' | 'paper' | 'other';
|
||||
|
||||
export type CadSpace = {
|
||||
/** Stable id for UI (hex handle string, same as ownerHandle.value.toString(16)). */
|
||||
id: string;
|
||||
/** Display label, e.g. "Model" / "Layout" / raw block name. */
|
||||
label: string;
|
||||
/** Raw block name from tables.blocks. */
|
||||
name: string;
|
||||
/** Block handle as number when available. */
|
||||
handle: number | null;
|
||||
/** Hex string matching entity ownerHandle.value.toString(16). */
|
||||
handleHex: string;
|
||||
kind: CadSpaceKind;
|
||||
entityCount: number;
|
||||
};
|
||||
|
||||
function handleToHex(handle: unknown): string | null {
|
||||
if (handle == null) return null;
|
||||
if (typeof handle === 'object' && handle !== null && 'value' in handle) {
|
||||
const v = (handle as { value?: unknown }).value;
|
||||
if (typeof v === 'number' && Number.isFinite(v)) return v.toString(16);
|
||||
if (typeof v === 'string' && v.length) {
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n.toString(16) : v.toLowerCase();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (typeof handle === 'number' && Number.isFinite(handle)) return handle.toString(16);
|
||||
if (typeof handle === 'string' && handle.length) {
|
||||
const n = Number(handle);
|
||||
return Number.isFinite(n) ? n.toString(16) : handle.toLowerCase();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function ownerHex(entity: { ownerHandle?: unknown }): string | null {
|
||||
return handleToHex(entity.ownerHandle);
|
||||
}
|
||||
|
||||
function classifySpaceName(raw: string): CadSpaceKind | null {
|
||||
const name = raw.toLowerCase().replace(/\*/g, '').trim();
|
||||
if (!name) return null;
|
||||
if (name === 'model_space' || name === 'ms' || name.startsWith('model')) return 'model';
|
||||
if (name === 'paper_space' || name === 'ps' || name.startsWith('paper')) return 'paper';
|
||||
return null;
|
||||
}
|
||||
|
||||
function spaceLabel(kind: CadSpaceKind, name: string, paperIndex: number): string {
|
||||
if (kind === 'model') return 'Model';
|
||||
if (kind === 'paper') {
|
||||
// First paper space → "Layout"; further ones keep a disambiguator.
|
||||
if (paperIndex <= 1) return 'Layout';
|
||||
return `Layout (${name.replace(/^\*/, '')})`;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* List model/paper space blocks that own at least zero entities.
|
||||
* Always includes Model/Paper headers found in tables.blocks.
|
||||
*/
|
||||
export function listCadSpaces(result: {
|
||||
entities?: unknown[];
|
||||
tables?: { blocks?: Array<{ name?: string; handle?: unknown }> };
|
||||
} | null | undefined): CadSpace[] {
|
||||
const blocks = result?.tables?.blocks ?? [];
|
||||
const entities = (result?.entities ?? []) as Array<{ ownerHandle?: unknown }>;
|
||||
|
||||
const counts = new Map<string, number>();
|
||||
for (const e of entities) {
|
||||
const hex = ownerHex(e);
|
||||
if (!hex) continue;
|
||||
counts.set(hex, (counts.get(hex) || 0) + 1);
|
||||
}
|
||||
|
||||
const spaces: CadSpace[] = [];
|
||||
let paperIndex = 0;
|
||||
for (const b of blocks) {
|
||||
const kind = classifySpaceName(b.name ?? '');
|
||||
if (!kind) continue;
|
||||
const hex = handleToHex(b.handle);
|
||||
if (!hex) continue;
|
||||
if (kind === 'paper') paperIndex += 1;
|
||||
const handleNum =
|
||||
typeof b.handle === 'number'
|
||||
? b.handle
|
||||
: typeof b.handle === 'object' && b.handle && 'value' in b.handle
|
||||
? Number((b.handle as { value: unknown }).value)
|
||||
: null;
|
||||
spaces.push({
|
||||
id: hex,
|
||||
label: spaceLabel(kind, b.name ?? '', paperIndex),
|
||||
name: b.name ?? '',
|
||||
handle: Number.isFinite(handleNum as number) ? (handleNum as number) : null,
|
||||
handleHex: hex,
|
||||
kind,
|
||||
entityCount: counts.get(hex) || 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Stable order: Model first, then papers in table order.
|
||||
spaces.sort((a, b) => {
|
||||
if (a.kind !== b.kind) {
|
||||
if (a.kind === 'model') return -1;
|
||||
if (b.kind === 'model') return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return spaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default tab when a file is opened:
|
||||
* - Paper/Layout with entities if any (civil sheet drawings often leave Layout active)
|
||||
* - else Model
|
||||
* - else first listed space
|
||||
*/
|
||||
export function pickDefaultSpace(spaces: CadSpace[]): CadSpace | null {
|
||||
if (!spaces.length) return null;
|
||||
const paperWithContent = spaces.find((s) => s.kind === 'paper' && s.entityCount > 0);
|
||||
if (paperWithContent) return paperWithContent;
|
||||
const model = spaces.find((s) => s.kind === 'model');
|
||||
if (model) return model;
|
||||
return spaces[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Owners allowed when filtering to one space: the space handle itself plus
|
||||
* handles of entities that live under it (e.g. INSERT → ATTRIB children).
|
||||
*/
|
||||
export function buildAllowedOwnerHexes(
|
||||
entities: Array<{ handle?: unknown; ownerHandle?: unknown }>,
|
||||
activeSpaceHex: string,
|
||||
rounds = 3,
|
||||
): Set<string> {
|
||||
const allowed = new Set<string>([activeSpaceHex]);
|
||||
for (let pass = 0; pass < rounds; pass++) {
|
||||
let grew = false;
|
||||
for (const ent of entities) {
|
||||
const oh = ownerHex(ent);
|
||||
if (!oh || !allowed.has(oh)) continue;
|
||||
const h = handleToHex(ent.handle);
|
||||
if (h && !allowed.has(h)) {
|
||||
allowed.add(h);
|
||||
grew = true;
|
||||
}
|
||||
}
|
||||
if (!grew) break;
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
|
||||
export function isSystemSpaceBlockName(name: string): boolean {
|
||||
return classifySpaceName(name) != null;
|
||||
}
|
||||
Reference in New Issue
Block a user