discontinuedPackage jet_cad_floor_plan has been discontinued and replaced by jet_cad_2d_plan.

attachCandidates function

List<AttachedEnd> attachCandidates(
  1. DraftDocument doc,
  2. SpatialIndex index,
  3. Vector2 q, {
  4. required bool objectSnap,
  5. required double thickest,
  6. 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():

  1. every live wall that owns a child whose stored world box touches the square q ± dimAttach.linear;
  2. 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 square q ± (dimAttach.linear + thickest), thickest being T (thickestWall). The host is kept only when FilterEvaluator.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 carry EntityFlags.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;
}