;;; ---------------------------------------------------------------------------
;;; SpyGlass.lsp
;;; code compiled by YZ August 2026
;;; ---------------------------------------------------------------------------
;;; LIVE OBJECT INSPECTOR
;;;
;;; Run SPYGLASS and a green ring follows your cursor. Sweep it over the
;;; drawing and every object it touches reports itself in a floating label:
;;; type, layer, colour, linetype, lineweight, and whatever else is relevant
;;; -- radius for a circle, length for a line, area for a closed polyline,
;;; the full attribute list for a block.
;;;
;;; No clicking, no dialog, no Properties palette taking up half the screen.
;;; You move the mouse and the drawing explains itself.
;;;
;;; The second mode turns the same ring into a layer isolator. Click any
;;; object and every layer except its own is switched off, so you can see one
;;; system at a time in a crowded drawing. Shift-click puts them all back.
;;;
;;; ---------------------------------------------------------------------------
;;; HOW IT WORKS
;;;
;;; The whole command lives inside a grread loop, which reports mouse
;;; movement and keystrokes continuously without ending the command.
;;;
;;; Two temporary entities are created up front on a scratch layer:
;;;   * a CIRCLE, which is the visible cursor ring;
;;;   * an MTEXT, which is the floating information label.
;;;
;;; On every mouse move both are repositioned by editing their entity data
;;; directly with entmod and entupd. That is far faster than erasing and
;;; recreating them, which is what makes the display keep up with the cursor.
;;;
;;; Detecting what is under the ring is done in two stages:
;;;   1. A crossing-window selection over the ring's bounding square finds
;;;      the handful of candidate objects cheaply.
;;;   2. Each candidate is tested with IntersectWith against the ring itself,
;;;      so only objects the ring genuinely crosses are reported.
;;;
;;; Stage 1 alone would report anything in the square corners; stage 2 alone
;;; would mean testing every object in the drawing on every mouse move.
;;;
;;; ---------------------------------------------------------------------------
;;; PROPERTY REPORTING
;;;
;;; Rather than a long list of type tests, the inspector walks a table of
;;; property names and asks each object whether it has that property. A
;;; circle answers to RADIUS and AREA but not to TEXTSTRING; a line answers
;;; to LENGTH but not to RADIUS. Nothing needs to know in advance what kind
;;; of object it is looking at, and an object type nobody anticipated still
;;; reports everything it can.
;;;
;;; A few properties get special treatment because their raw values are
;;; meaningless to read: colour and lineweight are enumerations that need
;;; naming, angles need formatting to the drawing's angular units, and
;;; justification codes need looking up in a table.
;;;
;;; Blocks are handled separately: they report their effective name, how many
;;; insertions exist in the drawing, whether they are dynamic or an xref, and
;;; the current value of every attribute.
;;;
;;; ---------------------------------------------------------------------------
;;; CONTROLS
;;;
;;;   move mouse   inspect whatever the ring touches
;;;   TAB          switch between INSPECT and LAYER ISOLATE
;;;   + or =       make the ring bigger
;;;   -            make the ring smaller
;;;   click        in isolate mode, isolate the layer of the object touched
;;;   Shift+click  in isolate mode, switch every layer back on
;;;   Enter/Space  finish
;;;   Esc          finish
;;;
;;; Shift detection needs Express Tools; without it, TAB back to inspect mode
;;; and re-run, or use LAYON.
;;;
;;; ---------------------------------------------------------------------------
;;; CAVEATS
;;;
;;; An object that lies entirely INSIDE the ring is not reported, because it
;;; does not intersect the ring. Shrink the ring with the minus key and it
;;; will be picked up.
;;;
;;; The ring size, the isolate mode, and the layers that were on when the
;;; command started are all restored on exit -- including after an error or
;;; a cancel.
;;;
;;; ---------------------------------------------------------------------------
;;;   SPYGLASS - hover over objects to inspect them, or isolate their layers
;;; ---------------------------------------------------------------------------

(vl-load-com)

;;; ---------------------------------------------------------------------------
;;; Persistent preferences. Global so that the ring size and mode you left
;;; the command in are the ones you get next time.
;;;
;;;   *SpyGlass:Mode*  0 = inspect, 1 = layer isolate
;;;   *SpyGlass:Size*  divisor applied to the view height to size the ring;
;;;                    a LARGER number gives a SMALLER ring
;;; ---------------------------------------------------------------------------

(or *SpyGlass:Mode* (setq *SpyGlass:Mode* 0))
(or *SpyGlass:Size* (setq *SpyGlass:Size* 25.0))

;;; ---------------------------------------------------------------------------
;;; Scratch layer used by the cursor ring and the label. It is deleted when
;;; the command finishes, so it never appears in the layer list afterwards.
;;; ---------------------------------------------------------------------------

(setq *SpyGlass:Layer* "YZ_SPYGLASS")

;;; ---------------------------------------------------------------------------
;;; SpyGlass:Doc
;;;
;;; Returns the active document, caching itself after the first call.
;;; ---------------------------------------------------------------------------

(defun SpyGlass:Doc nil
    (eval (list 'defun 'SpyGlass:Doc 'nil (vla-get-activedocument (vlax-get-acad-object))))
    (SpyGlass:Doc)
)

;;; ---------------------------------------------------------------------------
;;; The properties the inspector will try to report, in display order.
;;;
;;; Objects are asked whether each one applies; the ones that do not are
;;; silently skipped. Adding a property here is all that is needed to make it
;;; appear for every object type that supports it.
;;; ---------------------------------------------------------------------------

(setq *SpyGlass:Props*
   '(
        layer  color  linetype  lineweight
        alignment  arclength  area  attachmentpoint  center  circumference
        closed  customscale  degree  diameter  displaylocked  elevation
        height  length  measurement  obliqueangle  radius  rotation
        scalefactor  stylename  textoverride  textstring  totalangle  width
    )
)

;;; ---------------------------------------------------------------------------
;;; Justification lookup tables.
;;;
;;; The Alignment and AttachmentPoint properties return integer enumerations.
;;; Reporting "7" helps nobody; these turn them into the names shown in the
;;; Properties palette.
;;; ---------------------------------------------------------------------------

(setq *SpyGlass:Align*
    (list
        (cons acalignmentleft         "Left"         )
        (cons acalignmentcenter       "Center"       )
        (cons acalignmentright        "Right"        )
        (cons acalignmentaligned      "Aligned"      )
        (cons acalignmentmiddle       "Middle"       )
        (cons acalignmentfit          "Fit"          )
        (cons acalignmenttopleft      "Top-Left"     )
        (cons acalignmenttopcenter    "Top-Center"   )
        (cons acalignmenttopright     "Top-Right"    )
        (cons acalignmentmiddleleft   "Middle-Left"  )
        (cons acalignmentmiddlecenter "Middle-Center")
        (cons acalignmentmiddleright  "Middle-Right" )
        (cons acalignmentbottomleft   "Bottom-Left"  )
        (cons acalignmentbottomcenter "Bottom-Center")
        (cons acalignmentbottomright  "Bottom-Right" )
    )
)

(setq *SpyGlass:Attach*
    (list
        (cons acattachmentpointtopleft      "Top-Left"     )
        (cons acattachmentpointtopcenter    "Top-Center"   )
        (cons acattachmentpointtopright     "Top-Right"    )
        (cons acattachmentpointmiddleleft   "Middle-Left"  )
        (cons acattachmentpointmiddlecenter "Middle-Center")
        (cons acattachmentpointmiddleright  "Middle-Right" )
        (cons acattachmentpointbottomleft   "Bottom-Left"  )
        (cons acattachmentpointbottomcenter "Bottom-Center")
        (cons acattachmentpointbottomright  "Bottom-Right" )
    )
)

;;; ---------------------------------------------------------------------------
;;; SpyGlass:Dxf
;;;
;;; Returns the value of a DXF group code from an entity data list.
;;; ---------------------------------------------------------------------------

(defun SpyGlass:Dxf ( code lst )
    (cdr (assoc code lst))
)

;;; ---------------------------------------------------------------------------
;;; SpyGlass:Colour
;;;
;;; Turns a DXF 62 colour index into a readable name.
;;;
;;; Absent group 62 means the object inherits its layer's colour, and only
;;; the first seven indices have standard names; the rest are reported as
;;; plain numbers.
;;; ---------------------------------------------------------------------------

(defun SpyGlass:Colour ( enx / c )
    (if (setq c (cdr (assoc 62 enx)))
        (cond
            (   (cdr (assoc c
                   '(
                        (0 . "ByBlock") (1 . "Red")     (2 . "Yellow")
                        (3 . "Green")   (4 . "Cyan")    (5 . "Blue")
                        (6 . "Magenta") (7 . "White")
                    )
                ))
            )
            (   (itoa c))
        )
        "ByLayer"
    )
)

;;; ---------------------------------------------------------------------------
;;; SpyGlass:Weight
;;;
;;; Turns a DXF 370 lineweight into a readable value.
;;;
;;; Lineweights are stored as hundredths of a millimetre, with three negative
;;; values reserved for the inherited settings.
;;; ---------------------------------------------------------------------------

(defun SpyGlass:Weight ( enx / w )
    (if (setq w (cdr (assoc 370 enx)))
        (cond
            (   (cdr (assoc w '((-1 . "ByLayer") (-2 . "ByBlock") (-3 . "Default")))))
            (   (strcat (rtos (/ w 100.) 2 2) "mm"))
        )
        "ByLayer"
    )
)

;;; ---------------------------------------------------------------------------
;;; SpyGlass:Scale
;;;
;;; Formats a block's X, Y and Z scale factors as a readable triple.
;;; ---------------------------------------------------------------------------

(defun SpyGlass:Scale ( enx )
    (vl-princ-to-string
        (mapcar '(lambda ( code ) (rtos (SpyGlass:Dxf code enx) (getvar 'lunits) 2))
               '(41 42 43)
        )
    )
)

;;; ---------------------------------------------------------------------------
;;; SpyGlass:Name
;;;
;;; Returns the name a block shows in the user interface. A dynamic block
;;; with changed parameters is stored under an anonymous name such as "*U8";
;;; EffectiveName is the one the user recognises.
;;; ---------------------------------------------------------------------------

(defun SpyGlass:Name ( obj )
    (if (vlax-property-available-p obj 'effectivename)
        (vla-get-effectivename obj)
        (vla-get-name obj)
    )
)

;;; ---------------------------------------------------------------------------
;;; SpyGlass:Str
;;;
;;; Converts any property value into something printable.
;;;
;;; ActiveX booleans come back as :vlax-true / :vlax-false, which would be
;;; unhelpful on screen, so they are turned into Yes and No first.
;;; ---------------------------------------------------------------------------

(defun SpyGlass:Str ( val / typ )
    (setq val
        (cond
            (   (= :vlax-true  val) "Yes")
            (   (= :vlax-false val) "No")
            (   val)
        )
    )
    (cond
        (   (= 'str  (setq typ (type val))) val)
        (   (= 'int  typ) (itoa val))
        (   (= 'real typ) (rtos val))
        (   (vl-princ-to-string val))
    )
)

;;; ---------------------------------------------------------------------------
;;; SpyGlass:Label
;;;
;;; Wraps a string in the MTEXT formatting codes that give the label its
;;; bold Arial face.
;;;
;;;   \f    font name
;;;   b1    bold on
;;;   i0    italic off
;;;   c0    character set
;;;   p34   pitch and family
;;; ---------------------------------------------------------------------------

(defun SpyGlass:Label ( str )
    (strcat "{\\fArial|b1|i0|c0|p34;" str "}")
)

;;; ---------------------------------------------------------------------------
;;; SpyGlass:Put
;;;
;;; Replaces one or more DXF groups on an entity and redraws it.
;;;
;;; Editing entity data in place like this, rather than erasing and
;;; recreating the entity, is what allows the ring and label to be redrawn on
;;; every mouse movement without visible flicker. All the changes are applied
;;; in a single entmod so the entity is touched once per frame.
;;;
;;;   ent   - entity name
;;;   pairs - list of dotted pairs, e.g. '((10 . pt) (40 . radius))
;;; ---------------------------------------------------------------------------

(defun SpyGlass:Put ( ent pairs / enx )
    (setq enx (entget ent))
    (foreach pair pairs
        (setq enx (subst pair (assoc (car pair) enx) enx))
    )
    (entmod enx)
    (entupd ent)
    ent
)

;;; ---------------------------------------------------------------------------
;;; SpyGlass:PurgeLayer
;;;
;;; Deletes the scratch layer. Both the lookup and the delete are caught: the
;;; layer will not exist if entity creation failed, and it cannot be deleted
;;; while anything still sits on it. Neither case is worth an error message
;;; during cleanup.
;;; ---------------------------------------------------------------------------

(defun SpyGlass:PurgeLayer ( / lay )
    (setq lay
        (vl-catch-all-apply 'vla-item
            (list (vla-get-layers (SpyGlass:Doc)) *SpyGlass:Layer*)
        )
    )
    (if (not (vl-catch-all-error-p lay))
        (vl-catch-all-apply 'vla-delete (list lay))
    )
    (princ)
)

;;; ---------------------------------------------------------------------------
;;; SPYGLASS
;;; ---------------------------------------------------------------------------

(defun c:SpyGlass

    ( / *error* SpyGlass:Describe SpyGlass:Inspect SpyGlass:Restore
        code data express found half layers modes msg msgs onlayers
        rad ring sel text vals vars view
    )

    (setq vars '("CMDECHO" "OSMODE")
          vals (mapcar 'getvar vars)
    )

    ;;; -----------------------------------------------------------------------
    ;;; SpyGlass:Restore
    ;;;
    ;;; Removes the temporary entities, deletes the scratch layer, and
    ;;; restores system variables.
    ;;;
    ;;;   layerstoo - non-nil to also switch every layer that was on at the
    ;;;               start back on
    ;;;
    ;;; Layers are deliberately NOT restored on a normal exit: isolating a
    ;;; layer and then leaving the command is the whole point of that mode,
    ;;; and undoing it on the way out would make the feature useless. They
    ;;; ARE restored when the command errors or is cancelled, so a half-run
    ;;; isolate can never strand the user with a mostly-blank drawing.
    ;;; -----------------------------------------------------------------------

    (defun SpyGlass:Restore ( layerstoo )
        (if ring (vl-catch-all-apply 'entdel (list ring)))
        (if text (vl-catch-all-apply 'entdel (list text)))
        (if layerstoo
            (foreach lay onlayers
                (vl-catch-all-apply 'vla-put-layeron (list lay :vlax-true))
            )
        )
        (SpyGlass:PurgeLayer)
        (mapcar 'setvar vars vals)
        (while (= 8 (logand 8 (getvar 'undoctl)))
            (command "_.UNDO" "_End")
            (vl-catch-all-apply '(lambda ( ) (*pop-error-mode*)) '())
        )
        (princ)
    )

    (defun *error* ( msg )
        (SpyGlass:Restore t)
        (if (and msg (not (wcmatch (strcase msg t) "*break*,*cancel*,*exit*")))
            (princ (strcat "\n** SPYGLASS error: " msg " **"))
        )
        (princ)
    )

    ;;; -----------------------------------------------------------------------
    ;;; SpyGlass:Describe
    ;;;
    ;;; Builds the report string for one object.
    ;;;
    ;;;   obj - VLA object
    ;;;   enx - its entity data list
    ;;;
    ;;; Blocks take a dedicated path because the things worth knowing about
    ;;; them -- effective name, how many exist, dynamic, xref, attribute
    ;;; values -- are not ordinary properties.
    ;;;
    ;;; Everything else is described by walking the property table.
    ;;; -----------------------------------------------------------------------

    (defun SpyGlass:Describe ( obj enx / atts blks cnt out val )

        (setq out (strcat "{\\C4;" (SpyGlass:Dxf 0 enx) "}"))

        (if (= "INSERT" (SpyGlass:Dxf 0 enx))
            (progn
                ;; How many insertions of this block exist anywhere in the
                ;; drawing -- immediately useful when deciding whether a
                ;; symbol is a one-off or part of a set.
                (if (setq blks (ssget "_X" (list '(0 . "INSERT")
                                                 (cons 2 (SpyGlass:Name obj)))))
                    (setq cnt (itoa (sslength blks)))
                )

                ;; DXF 66 = 1 means the block carries attributes.
                (if (= 1 (SpyGlass:Dxf 66 enx))
                    (progn
                        ;; Attributes are listed in a non-bold face so they
                        ;; read as a sub-list rather than competing with the
                        ;; headline properties.
                        (setq atts "\nATTRIBUTES:\n{\\fArial|b0|i0|c0|p34;")
                        (foreach att (vlax-invoke obj 'getattributes)
                            (setq atts (strcat atts (vla-get-tagstring att) ":  "
                                                    (vla-get-textstring att) "\n"))
                        )
                        (setq atts (strcat atts "}"))
                    )
                )

                (setq out
                    (strcat out
                        "\nNAME:  "       (SpyGlass:Name obj)
                        (if cnt (strcat "\nINSTANCES:  " cnt) "")
                        "\nLAYER:  "      (SpyGlass:Dxf 8 enx)
                        "\nCOLOR:  "      (SpyGlass:Colour enx)
                        "\nLINETYPE:  "   (vla-get-linetype obj)
                        "\nLINEWEIGHT:  " (SpyGlass:Weight enx)
                        "\nROTATION:  "   (angtos (SpyGlass:Dxf 50 enx))
                        "\nSCALE:  "      (SpyGlass:Scale enx)
                        "\nDYNAMIC:  "    (SpyGlass:Str (vla-get-isdynamicblock obj))
                        "\nXREF:  "
                        (SpyGlass:Str
                            (vlax-get-property
                                (vla-item (vla-get-blocks (SpyGlass:Doc)) (SpyGlass:Name obj))
                                'isxref
                            )
                        )
                        (if atts atts "")
                    )
                )
            )

            ;; ---- everything that is not a block ----------------------------
            (foreach prop *SpyGlass:Props*
                (if (and (vlax-property-available-p obj prop)
                         (/= "" (setq val (vlax-get obj prop)))
                    )
                    (setq out
                        (strcat out "\n" (strcase (vl-princ-to-string prop)) ":  "
                            (cond
                                ;; Colour and lineweight are read from the DXF
                                ;; data, where the inherited settings are
                                ;; distinguishable; the ActiveX properties
                                ;; flatten them to bare numbers.
                                (   (= prop 'color)      (SpyGlass:Colour enx))
                                (   (= prop 'lineweight) (SpyGlass:Weight enx))
                                ;; Booleans.
                                (   (vl-position prop '(displaylocked closed))
                                    (SpyGlass:Str val)
                                )
                                ;; Enumerations that need naming.
                                (   (= prop 'alignment)
                                    (cdr (assoc val *SpyGlass:Align*))
                                )
                                (   (= prop 'attachmentpoint)
                                    (cdr (assoc val *SpyGlass:Attach*))
                                )
                                ;; Angular values, formatted to the drawing's
                                ;; angular units. Measurement and TotalAngle
                                ;; are angles only on the object types listed;
                                ;; on anything else they are lengths.
                                (   (or (= prop 'rotation)
                                        (and (= prop 'measurement)
                                             (vl-position (vla-get-objectname obj)
                                                '("AcDb2LineAngularDimension"
                                                  "AcDb3PointAngularDimension"))
                                        )
                                        (and (= prop 'totalangle)
                                             (= "AcDbArc" (vla-get-objectname obj))
                                        )
                                    )
                                    (angtos val)
                                )
                                (   (SpyGlass:Str val))
                            )
                        )
                    )
                )
            )
        )
        out
    )

    ;;; -----------------------------------------------------------------------
    ;;; SpyGlass:Inspect
    ;;;
    ;;; Finds the first object in the candidate set that genuinely crosses
    ;;; the cursor ring, writes its description into the label, and returns
    ;;; the object.
    ;;;
    ;;; IntersectWith is wrapped in a catch: a few object types do not
    ;;; implement it, and hovering over one of those must not end the
    ;;; command.
    ;;;
    ;;;   sel - candidate selection set, or nil
    ;;;
    ;;; Returns the VLA object found, or nil.
    ;;; -----------------------------------------------------------------------

    (defun SpyGlass:Inspect ( sel / hit idx obj ringobj )
        (setq ringobj (vlax-ename->vla-object ring))
        (if (and sel (< 0 (sslength sel)))
            (progn
                (setq idx -1)
                (while (and (null hit) (setq obj (ssname sel (setq idx (1+ idx)))))
                    (setq obj (vlax-ename->vla-object obj))
                    (if (   (lambda ( r )
                                (and (not (vl-catch-all-error-p r)) r)
                            )
                            (vl-catch-all-apply 'vlax-invoke
                                (list obj 'intersectwith ringobj acextendnone)
                            )
                        )
                        (setq hit obj)
                    )
                )
                (if hit
                    (progn
                        ;; Turn the ring red while something is being
                        ;; reported, so the user can see at a glance that the
                        ;; label belongs to an object rather than being stale.
                        (vla-put-color ringobj acred)
                        (SpyGlass:Put text
                            (list
                                (cons 62 251)
                                (cons 1
                                    (SpyGlass:Label
                                        (SpyGlass:Describe hit
                                            (entget (vlax-vla-object->ename hit))
                                        )
                                    )
                                )
                            )
                        )
                    )
                    ;; Nothing under the ring: put it back to green so the
                    ;; colour never lies about what is being reported.
                    (vla-put-color ringobj acgreen)
                )
                hit
            )
        )
    )

    ;;; =======================================================================
    ;;;                       M A I N   R O U T I N E
    ;;; =======================================================================

    (setvar 'cmdecho 0)
    ;; Object snap is silenced: grread reports raw cursor positions and a
    ;; live snap marker chasing the ring is pure visual noise.
    (setvar 'osmode 0)
    ;; AutoCAD 2015 and later refuse (command) inside an *error* handler
    ;; unless the routine says up front that it will use one. Restore does,
    ;; to close this undo group. The declaring call is absent on older
    ;; releases, so it is wrapped rather than tested for.
    (vl-catch-all-apply '(lambda ( ) (*push-error-using-command*)) '())
    (command "_.UNDO" "_Begin")

    ;; ---- record which layers are on ----------------------------------------
    ;; The scratch layer is excluded from both lists, so an isolate can never
    ;; switch off the layer the ring itself is drawn on.

    (vlax-for lay (vla-get-layers (SpyGlass:Doc))
        (if (/= (strcase *SpyGlass:Layer*) (strcase (vla-get-name lay)))
            (progn
                (setq layers (cons lay layers))
                (if (= :vlax-true (vla-get-layeron lay))
                    (setq onlayers (cons lay onlayers))
                )
            )
        )
    )

    ;; ---- Express Tools shift detection --------------------------------------
    ;; acet-sys-shift-down reports whether SHIFT is held. Everything except
    ;; the shift-click shortcut works without it.

    (setq express
        (and (vl-position "acetutil.arx" (arx))
             (not (vl-catch-all-error-p
                      (vl-catch-all-apply 'acet-sys-shift-down '())
                  )
             )
        )
    )

    (setq modes '("INSPECT" "LAYER ISOLATE")
          msgs
         '("\n[TAB] mode  [+/-] ring size - move the cursor over objects to inspect them..."
           "\n[TAB] mode  [+/-] ring size - click an object to isolate its layer, Shift+click to show all..."
          )
    )

    ;; ---- build the cursor ring ---------------------------------------------
    ;; entmake creates the scratch layer automatically by referring to it.
    ;; The radius is a fraction of the current view height, so the ring keeps
    ;; a constant size on screen however far the user zooms.

    (setq view (getvar 'viewsize)
          rad  (/ view (float *SpyGlass:Size*))
          ring (entmakex
                   (list
                       (cons 0 "CIRCLE")
                       (cons 8 *SpyGlass:Layer*)
                       (cons 10 (getvar 'viewctr))
                       (cons 40 rad)
                       (cons 62 3)                    ;; green
                   )
               )
    )

    ;; ---- build the floating label -------------------------------------------
    ;; Group 90 = 3 and 63 = 256 give the label an opaque background matching
    ;; the drawing background, so text underneath it does not show through and
    ;; make it unreadable. Group 45 pads that background slightly.

    (setq text (entmakex
                   (list
                       (cons 0 "MTEXT")
                       (cons 100 "AcDbEntity")
                       (cons 100 "AcDbMText")
                       (cons 8 *SpyGlass:Layer*)
                       (cons 1 (SpyGlass:Label (nth *SpyGlass:Mode* modes)))
                       (cons 10 (getvar 'viewctr))
                       (cons 40 (/ view 60.0))        ;; text height
                       (cons 50 0.0)                  ;; rotation
                       (cons 62 71)
                       (cons 71 1)                    ;; top-left attachment
                       (cons 90 3)                    ;; background fill on
                       (cons 63 256)                  ;; use drawing background
                       (cons 45 1.2)                  ;; background box scale
                   )
               )
    )

    (if (not (and ring text))
        (progn
            (princ "\nUnable to create the cursor graphics.")
            (SpyGlass:Restore t)
        )
        (progn
            (princ (setq msg (nth *SpyGlass:Mode* msgs)))

            ;; ---- the main loop -------------------------------------------
            (while
                (progn
                    (setq sel  nil
                          data (grread t 15 1)
                          code (car  data)
                          data (cadr data)
                          view (getvar 'viewsize)
                    )
                    (cond

                        ;;  ---- mouse moved ----
                        (   (and (= 5 code) (listp data))

                            ;; Rescale the ring in case the user zoomed, and
                            ;; move it to the cursor.
                            (setq rad  (/ view (float *SpyGlass:Size*))
                                  half (sqrt (* 2. rad rad))   ;; half-diagonal of the
                                                               ;; ring's bounding square
                            )
                            (SpyGlass:Put ring (list (cons 10 data) (cons 40 rad)))

                            ;; The label sits just below and right of the ring,
                            ;; offset far enough not to overlap it.
                            (SpyGlass:Put text
                                (list
                                    (cons 10 (polar (polar data (/ pi -4.) rad)
                                                    0 (/ view 90.0)))
                                    (cons 40 (/ view 60.0))
                                )
                            )

                            ;; Candidates: a crossing window over the ring's
                            ;; bounding square. Cheap, and narrows the whole
                            ;; drawing to a handful of objects.
                            (if (setq sel (ssget "_C" (polar data (/ pi 4.) half)
                                                 (polar data (/ (* 5 pi) 4.) half)))
                                (progn
                                    ;; The ring and label are inside their own
                                    ;; window, so they are removed before the
                                    ;; intersection test.
                                    (ssdel ring sel)
                                    (ssdel text sel)
                                    (setq found (SpyGlass:Inspect sel))
                                )
                                (setq found nil)
                            )
                            t
                        )

                        ;;  ---- a key was pressed ----
                        (   (= 2 code)
                            (cond
                                ;;  + or = : bigger ring, so a smaller divisor.
                                (   (vl-position data '(43 61))
                                    (if (< 1.0 *SpyGlass:Size*)
                                        (progn
                                            (setq *SpyGlass:Size* (1- *SpyGlass:Size*)
                                                  rad (/ view (float *SpyGlass:Size*))
                                            )
                                            (SpyGlass:Put ring (list (cons 40 rad)))
                                        )
                                        (princ (strcat "\nMaximum ring size reached." msg))
                                    )
                                    t
                                )
                                ;;  - : smaller ring.
                                (   (= 45 data)
                                    (setq *SpyGlass:Size* (1+ *SpyGlass:Size*)
                                          rad (/ view (float *SpyGlass:Size*))
                                    )
                                    (SpyGlass:Put ring (list (cons 40 rad)))
                                    t
                                )
                                ;;  TAB : switch mode.
                                (   (= 9 data)
                                    (setq *SpyGlass:Mode* (rem (1+ *SpyGlass:Mode*) 2))
                                    (SpyGlass:Put text
                                        (list (cons 1 (SpyGlass:Label
                                                          (nth *SpyGlass:Mode* modes))))
                                    )
                                    (princ (setq msg (nth *SpyGlass:Mode* msgs)))
                                    t
                                )
                                ;;  Enter or Space : finish.
                                (   (vl-position data '(13 32)) nil)
                                (   t t)
                            )
                        )

                        ;;  ---- clicked, in isolate mode ----
                        (   (and (= 3 code) (listp data) (= 1 *SpyGlass:Mode*))
                            (if (and express (acet-sys-shift-down))
                                ;; Shift+click: show every layer that was on
                                ;; when the command started.
                                (foreach lay onlayers
                                    (vla-put-layeron lay :vlax-true)
                                )
                                ;; Plain click: switch off every layer except
                                ;; the one the touched object sits on.
                                (if (and found
                                         (/= (strcase *SpyGlass:Layer*)
                                             (strcase (vla-get-layer found)))
                                    )
                                    (foreach lay layers
                                        (if (/= (strcase (vla-get-layer found))
                                                (strcase (vla-get-name lay)))
                                            (vl-catch-all-apply 'vla-put-layeron
                                                (list lay :vlax-false)
                                            )
                                        )
                                    )
                                )
                            )
                            t
                        )

                        ;;  ---- clicked in inspect mode, or right-clicked ----
                        (   (= 25 code) nil)
                        (   (and (= 3 code) (listp data)) t)
                        (   t t)
                    )
                )
            )
        )
    )

    ;; Normal exit: temporary graphics go, but any layer isolation the user
    ;; performed stays in force.
    (SpyGlass:Restore nil)
    (princ)
)

(princ "\nSpyGlass loaded. Type SPYGLASS to inspect objects under the cursor.")
(princ)

;;; ---------------------------------------------------------------------------
;;; End of file
;;; ---------------------------------------------------------------------------
