/** * Turns the closed outline wires of one filled SVG element into face(s). * * SVG fill is region-based, not "largest wire = outer, rest = holes": an element's * path can contain several disjoint filled regions, holes, and even solids nested * inside holes (depth > 1). Which regions are solid is decided by a fill-rule * (`nonzero` / `evenodd`), and SVG does NOT constrain the winding direction of the * subpaths - so we cannot trust the source orientation. * * This builder therefore classifies the wires by containment (nesting depth) and * builds one face per solid region, adding that region's immediate non-solid * children as holes with explicitly forced opposite winding (outer CCW, holes CW), * which is what OCCT needs to cut a hole. It runs entirely on the planar (XY, z * constant) wires that come back from the path builder, before any 3D placement. */ import { BitbybitOcctModule, TopoDS_Shape, TopoDS_Wire } from "../../bitbybit-dev-occt/bitbybit-dev-occt"; import { OccHelper } from "../occ-helper"; /** Concrete rule resolved from the public strategy (auto is resolved per element earlier). */ export type SvgFaceRule = "nonzero" | "evenOdd" | "perSubpath"; export interface FacedElement { shape: TopoDS_Shape; isFace: boolean; } export declare class SvgFaceBuilder { private readonly occ; private readonly och; constructor(occ: BitbybitOcctModule, och: OccHelper); /** * Build face(s) from one element's closed wires. Returns a single face, a compound of faces, or * (on failure) a compound of the original wires. Never deletes the input `wires`. */ build(wires: TopoDS_Wire[], rule: SvgFaceRule, warnings: string[]): FacedElement; /** Each closed wire becomes its own independent face (no hole detection). */ private buildPerSubpath; /** * Containment-based reconstruction shared by nonzero and even-odd: classify each wire's nesting * depth, decide which regions are solid, and emit a face per solid region with its immediate * non-solid children as holes (forced to CW so OCCT subtracts them). * * Containment needs two tests, not one. A point-in-face test alone cannot order two wires drawn * around the same centre, because each contains the other's centroid, and the nesting depth then * comes out wrong for both. Requiring the enclosing wire to have the strictly larger absolute area * as well is what breaks that tie, and concentric outlines are ordinary in real drawings - a ring, * a washer, any letter with a counter. * @param ccw every wire, normalised to counter-clockwise * @param centroids one centroid per wire * @param areas one signed area per wire * @param sign the original winding of each wire * @param classFaces a face per wire, used only for the point-in-face test * @param rule the fill rule that decides which depths are solid * @returns one face per solid region, holes already subtracted */ private buildNested; private sample; /** Shoelace signed area in the XY plane; sign encodes the wire's winding. */ private signedAreaXY; /** Polygon centroid in XY (strictly interior for simple loops); falls back to the mean. */ private centroidXY; private isInside; }