Object Snap (OSNAP)
This page explains how OSNAP works in cad-viewer with data from realdwg-web.
Background
In AutoCAD/ObjectARX, each entity can override getOsnapPoints(...) to return candidate snap points for a specific snap mode.
In realdwg-web, we use a similar pattern with subGetOsnapPoints(...) on each entity class.cad-viewer consumes those returned points to perform snapping in the UI.
Reference:
- AutoCAD ObjectARX
getOsnapPoints: https://help.autodesk.com/view/OARX/2024/ENU/?guid=OARX-RefGuide-__OVERLOADED_getOsnapPoints_AcDbEntity
Core API in realdwg-web
Base definition:
AcDbEntity.subGetOsnapPoints(osnapMode, pickPoint, lastPoint, snapPoints, gsMark?)
Parameters:
osnapMode: one mode each callpickPoint: current cursor world pointlastPoint: previous committed point in command flowsnapPoints: output array to append candidate pointsgsMark(optional): sub-entity id, currently important forINSERT(AcDbBlockReference) sub-entity snapping
Supported OSNAP modes
Defined in AcDbOsnapMode:
EndPoint = 1MidPoint = 2Center = 3Node = 4Quadrant = 5Insertion = 7Perpendicular = 8Tangent = 9Nearest = 10Centroid = 11
Important:
- These values are ordinal ids, not bit flags.
- Use
acdbOsnapModesToMask(...)andacdbMaskToOsnapModes(...)for mask conversion.
Current cad-viewer UI exposure
Status bar (MlOsnapButton.vue) currently exposes:
- EndPoint
- MidPoint
- Center
- Node
- Quadrant
- Insertion
Not exposed in UI yet (commented out):
- Perpendicular
- Tangent
- Nearest
- Centroid
Default enabled mask in settings (AcApSettingManager):
- EndPoint + MidPoint + Center
Runtime Flow in cad-simple-viewer
AcEdFloatingInput.getPosition()callsgetOsnapPoint().getOsnapPoint()callsgetOsnapPoints().getOsnapPoints()runsview.pick(point, hitRadius).- For each picked item:
- resolve model-space entity by
item.id - if
item.childrenexists, call entity osnap withgsMark = child.id - otherwise call entity osnap without
gsMark
- For each active mode in current mask, call
entity.subGetOsnapPoints(...). - Choose nearest returned point to cursor.
- Apply world-distance threshold converted from screen
hitRadius(default 20 px). - If accepted, snap cursor and show marker.
Entity Implementation Snapshot (from realdwg-web code)
AcDbLine: EndPoint, MidPoint, Nearest, Perpendicular (Tangentbranch exists but no point)AcDbArc: EndPoint, MidPoint, Nearest, Tangent (Perpendicularbranch exists but no point)AcDbCircle: Center, Centroid, Quadrant, Nearest, TangentAcDbEllipse: EndPoint (open arc only), MidPoint (open arc only), Quadrant (closed ellipse only)AcDbPolyline: EndPointAcDb2dPolyline: EndPointAcDb3dPolyline: EndPointAcDbPoint: NodeAcDbText: InsertionAcDbMText: InsertionAcDbBlockReference(INSERT): Insertion + delegated sub-entity snapping viagsMark- Additional entities with limited support:
AcDbRay: EndPoint (base point)AcDbFace: EndPoint (vertices)AcDbTrace: EndPoint (vertices)AcDbSpline: EndPoint
INSERT (AcDbBlockReference) OSNAP Logic
subGetOsnapPoints has two paths:
Insertionmode:
- directly pushes block reference insertion point (
this._position)
- Other modes:
- only works when
gsMarkis provided - delegates to
subEntityGetOsnapPoints(...)
Delegation flow (subEntityGetOsnapPoints):
- Guard: if
gsMark === this.objectId, return (avoid recursion) - Resolve entity by
blockTable.getEntityById(gsMark) - Call that sub-entity’s
subGetOsnapPoints(...) - Transform returned points by this block reference
blockTransform - Push transformed points to output
Why INSERT snapping can fail (current behavior)
- No
gsMark:
- For non-
Insertionmodes,AcDbBlockReferencereturns nothing ifgsMarkis missing.
gsMarkmodel is simplified:
- Current
gsMarkis sub-entity object id from spatial child hits, not full AutoCAD GS marker semantics.
- Layer-grouped rendering for INSERT:
- Rendering may split one INSERT into multiple layer-based render groups with same parent object id.
- Picking relies on hierarchical child boxes to recover sub-entity ids.
- Complex/nested/edge transforms:
- Delegation applies
blockTransformof the current INSERT. - Edge cases (for example nested block scenarios or non-default normal/extrusion cases) can still produce incorrect or missing snap points.
This matches the known roadmap note in README.md:
- Endpoint snapping for INSERT is not fully reliable yet.
Known Limitations / TODO
- OSNAP coverage is entity-dependent; many mode/entity combinations are intentionally not implemented yet.
- INSERT non-insertion snapping depends on correct child hit ->
gsMarkpropagation. - Current marker mechanism is object-id based and not full AutoCAD GS marker behavior.
- More regression tests are needed for INSERT, especially nested blocks and transformed blocks.
Recommended Contribution Direction
- Improve robustness of child-hit to
gsMarkmapping for INSERT. - Add nested block regression tests for EndPoint/MidPoint/Center/Nearest/Tangent cases.
- Expand per-entity mode coverage incrementally.
- Align marker model closer to AutoCAD GS marker semantics when feasible.