attachCandidates function
- DraftDocument doc,
- SpatialIndex index,
- Vector2 q, {
- required bool objectSnap,
- required double thickest,
- Map<
Handle, WallPoints> ? points,
The attach candidates at world point q (spec 11 D10, R-16; decision
23; S-13): every (W, k, side) of a live wall W whose point
(wallEndPoint, among every other live wall: wallsInDocument) lies
within dimAttach.linear of q, Euclidean. Ascending by wall handle,
then k, then side (left, centre, right).
Only while object snap (F3) is on, and then by position
(decision 23): objectSnap false gives none, so every end is fixed;
otherwise any point attaches, whether an object snap or the grid put it
at q. The tool and the grips see only the resolved point, so they
behave the same.
The candidate walls come from two rect queries on index, with
QueryFilter.rendering():
- every live wall that owns a child whose stored world box touches the
square
q ± dimAttach.linear; - the host (
OpeningParams.host, when it is a live wall the renderer draws) of every live opening that owns a child whose stored box touches the squareq ± (dimAttach.linear + thickest),thickestbeingT(thickestWall). The host is kept only whenFilterEvaluator.acceptsNode(host, QueryFilter.rendering())holds — its group and every group above it visible, the visibility the first query sees through each of a wall's own children — and the host's object layer (objectLayer, spec 12b D6, R-7) is visible: the parametric system writes that layer on every child it generates, so it is the layer the first query sees through them. So a wall on a hidden layer, or in a hidden group, attaches through neither query, even when its opening sits on a visible layer (a layer move makes that state with commands). One check per host found, the group's answer cached per group within the call. Not covered, a residual limit only a file reaches (no command makes either): a visible wall whose own children carryEntityFlags.invisible, or sit on a hidden layer other than its object layer (a file written by hand), while its opening's children are drawn. The wall draws nothing there, yet its host test passes, so it still attaches through the opening; a group's children's layers and flags cannot be read in O(1) through the public API.
Why the hosts (S-13): an opening flush with a flat wall end (a T
butt or a free end; placeCut clamps a door placed near a T to exactly
there) leaves none of that end's points stored: 08's _pieces keeps no
piece shorter than wallJoin.linear, so the end's ring and centreline
pieces are gone, and the wall's own children do not reach the corner.
Why the box is grown by T: an opening's children do not reach every
corner of its cut either. A door's leaf and arc stand on its swing face,
so only that face's jamb corner is inside the door's own box; the
centreline point and the other face's corner lie up to a wall thickness
away.
The line test: a candidate wall is kept only if q lies within
dimAttach.linear of one of its three lines, the left face line, the
centreline or the right face line (_onALine). Every attach point
lies on one of these lines: a mitre, T-butt, node or lobe corner is the
meeting of two face lines (a node's taken through its hub, within
wallJoin.linear of the wall's own), a clamped or squared cap and the
free rectangle lie on the face lines, and the centre point on the
centreline. So the test loses none, and a point in a wall's band off
those lines builds no WorldWall. Only a wall that passes is laid out
(wallsInDocument, then its six points).
points, when given, memoises each wall's six points: a wall found
in it is not laid out again, and one laid out is stored in it. The
result is the same, bit for bit, since the points are the same
computation's. Its owner, the Dimension tool's hover memo or
DimensionGrips' per-drag memo (a grip's preview), clears it whenever
the document may have changed; a click, a commit and a grip drop pass
none, and gather afresh (the plan's Ruling 11-8).
Implementation
List<AttachedEnd> attachCandidates(
DraftDocument doc, SpatialIndex index, Vector2 q,
{required bool objectSnap,
required double thickest,
Map<Handle, WallPoints>? points}) {
if (!objectSnap) return const [];
final walls = <Handle>{};
final tight = dimAttach.linear;
index.forEachInRect(
Aabb2.raw(q.x - tight, q.y - tight, q.x + tight, q.y + tight),
const QueryFilter.rendering(), (slot) {
final owner = doc.entities.ownerAt(slot);
if (isLiveObject<WallParams>(doc, owner)) walls.add(owner);
});
final grown = dimAttach.linear + thickest;
FilterEvaluator? drawn;
index.forEachInRect(
Aabb2.raw(q.x - grown, q.y - grown, q.x + grown, q.y + grown),
const QueryFilter.rendering(), (slot) {
final owner = doc.entities.ownerAt(slot);
if (!isLiveObject<OpeningParams>(doc, owner)) return;
final host = doc.components.get<OpeningParams>(owner)!.host;
if (!isLiveObject<WallParams>(doc, host)) return;
// Only a host the renderer draws (D10: what is drawn attaches): the
// opening's children passed `rendering()`, its host's group must too,
// and its object layer must be visible (spec 12b D6, R-7).
drawn ??= FilterEvaluator(doc);
if (drawn!.acceptsNode(host, const QueryFilter.rendering()) &&
_layerVisible(doc, objectLayer(doc, host))) {
walls.add(host);
}
});
final out = <AttachedEnd>[];
for (final h in walls.toList()..sort((a, b) => a.value.compareTo(b.value))) {
if (!_onALine(doc, h, q)) continue;
debugLineTestPasses++;
final six = points == null
? _wallPointsOf(doc, h)
: (points[h] ??= _wallPointsOf(doc, h));
for (final (k, side, p) in six) {
if ((p - q).length <= dimAttach.linear) out.add(AttachedEnd(h, k, side));
}
}
return out;
}