;;; ---------------------------------------------------------------------------
;;; PointBridge.lsp
;;; code compiled by YZ August 2026
;;; ---------------------------------------------------------------------------
;;; MOVE POINT DATA BETWEEN THE DRAWING AND A FILE, IN ANY DIRECTION
;;;
;;; PointBridge reads coordinates from one place and writes them to another.
;;; Both ends can be any of five things:
;;;
;;;   BLOCK          the insertion points of block references
;;;   FILE           a text or CSV file of coordinates
;;;   POINT          AutoCAD point objects
;;;   LW POLYLINE    the vertices of lightweight polylines
;;;   3D POLYLINE    the vertices of 3D polylines
;;;
;;; So one command covers all of these jobs, and every other combination of
;;; the five:
;;;
;;;   * import a surveyor's CSV as blocks on a chosen layer, at a chosen
;;;     scale and rotation;
;;;   * export every manhole block's position to a file for the setting-out
;;;     engineer;
;;;   * turn a scatter of point objects into a polyline through them;
;;;   * pull the vertices of a boundary polyline out to a spreadsheet;
;;;   * convert a 3D polyline to a lightweight one, or the reverse.
;;;
;;; ---------------------------------------------------------------------------
;;; ATTRIBUTES TRAVEL WITH THE POINTS
;;;
;;; When both ends are a block or a file, attribute values are carried
;;; across. Export attributed blocks and each row of the file gets the
;;; coordinates followed by that block's attribute values; import the same
;;; file and the values are written back into the inserted blocks, in order.
;;;
;;; That is what turns a coordinate list into a real survey exchange: point
;;; number, description and level ride along with the position.
;;;
;;; ---------------------------------------------------------------------------
;;; COLUMN ORDER
;;;
;;; File columns are not assumed to be X, Y, Z. The Format button sets which
;;; column holds which coordinate, so a file that arrives as
;;; Easting, Northing, Level or as Y, X, Z is read correctly without editing
;;; it first. The same setting controls the order columns are written in.
;;;
;;; ---------------------------------------------------------------------------
;;; DELIMITER
;;;
;;; Point, comma, semicolon, tab and space are offered, plus a free-text box
;;; for anything else. A .CSV file forces the comma and locks the control, so
;;; a file with a .csv extension is always genuinely comma-separated.
;;;
;;; ---------------------------------------------------------------------------
;;; SORTING
;;;
;;; Points can be sorted by X, Y or Z, ascending or descending, before being
;;; written. Attribute values are reordered with them, so the pairing is
;;; never broken. Sorting by Y is how a scattered point file becomes an
;;; orderly chainage list.
;;;
;;; ---------------------------------------------------------------------------
;;; NOTES
;;;
;;; The input and output types are always different -- whichever is chosen at
;;; one end is removed from the list at the other, because converting
;;; something to itself is not a useful operation.
;;;
;;; Writing to an existing file REPLACES it. The file browser asks before
;;; overwriting.
;;;
;;; All settings are remembered for the rest of the AutoCAD session.
;;; POINTRESET puts them back to their defaults.
;;;
;;; ---------------------------------------------------------------------------
;;;   POINTBRIDGE - convert point data between drawing objects and files
;;;   POINTRESET  - clear the remembered settings
;;; ---------------------------------------------------------------------------

(vl-load-com)

;;; ---------------------------------------------------------------------------
;;; *PointBridge:Settings*
;;;
;;; Every dialog setting, held as one list so it can be saved and restored in
;;; a single step. Global so the dialog reopens as it was left.
;;;
;;; The elements, in order:
;;;    1  input file path
;;;    2  input block name(s), comma separated
;;;    3  output file path
;;;    4  output block name or drawing path
;;;    5  input type
;;;    6  output type
;;;    7  delimiter    (list index, "other" toggle, other text)
;;;    8  sorting      (on/off, by which axis, ascending/descending)
;;;    9  attributes   ("1" to include)
;;;   10  column order (three list indices, 0=X 1=Y 2=Z)
;;;   11  object options (layer, block rotation, block scale)
;;; ---------------------------------------------------------------------------

(or *PointBridge:Settings*
    (setq *PointBridge:Settings*
        (list
            nil                                     ;; input file
            (getvar "INSNAME")                      ;; input block
            nil                                     ;; output file
            (getvar "INSNAME")                      ;; output block
            "File"                                  ;; input type
            "Block"                                 ;; output type
           '("0" "0" "")                            ;; delimiter
           '("0" "0" "0")                           ;; sorting
            "1"                                     ;; attributes
           '("0" "1" "2")                           ;; column order
            (list (getvar "CLAYER") "0.0" "1.0")    ;; object options
        )
    )
)

;;; ---------------------------------------------------------------------------
;;; POINTRESET
;;;
;;; Discards the remembered settings, so the next run starts from the
;;; defaults. Useful when a stored block name or file path no longer exists.
;;; ---------------------------------------------------------------------------

(defun c:PointReset nil
    (setq *PointBridge:Settings* nil)
    (princ "\nPointBridge settings cleared.")
    (princ)
)

;;; ---------------------------------------------------------------------------
;;; POINTBRIDGE
;;; ---------------------------------------------------------------------------

(defun c:PointBridge

    ( /
        ;; ---- nested helper functions ----
        *error* PointBridge:Restore PointBridge:FillList PointBridge:Split
        PointBridge:Join PointBridge:Unique PointBridge:To3d PointBridge:To2d
        PointBridge:TileModes PointBridge:DelimModes PointBridge:Delimiter
        PointBridge:CountText PointBridge:SortOrder PointBridge:AskFormat
        PointBridge:ShowFormat PointBridge:AskObject PointBridge:Validate

        ;; ---- the settings, unpacked ----
        inFile inBlock outFile outBlock inType outType delim sortSet
        attSet format objOpt

        ;; ---- working variables ----
        attribs attsub blk blkrot blkscl dch dcl des doc ent flag idx inList
        io lst modes newOrder obj objlay order outList pos res sel spc
        tmp undo vals vars
    )

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

    ;; Unpack the stored settings into working variables.
    (mapcar 'set
       '(inFile inBlock outFile outBlock inType outType delim sortSet
         attSet format objOpt)
        *PointBridge:Settings*
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:Restore
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:Restore ( )
        (if (= 'file (type des)) (close des))
        (if (and (= 'int (type dch)) (< 0 dch)) (unload_dialog dch))
        (if (and (= 'str (type dcl)) (findfile dcl)) (vl-file-delete dcl))
        (if undo (vla-endundomark doc))
        (while (= 8 (logand 8 (getvar 'undoctl)))
            (vla-endundomark doc)
        )
        (mapcar 'setvar vars vals)
        (princ)
    )

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

    ;;; -----------------------------------------------------------------------
    ;;; Small utilities.
    ;;; -----------------------------------------------------------------------

    ;; Loads a popup or list tile.
    (defun PointBridge:FillList ( key lst )
        (start_list key)
        (foreach x lst (add_list x))
        (end_list)
        (princ)
    )

    ;; Splits a delimited string into its pieces.
    (defun PointBridge:Split ( str del / lst pos )
        (while (setq pos (vl-string-search del str))
            (setq lst (cons (substr str 1 pos) lst)
                  str (substr str (+ pos 1 (strlen del)))
            )
        )
        (reverse (cons str lst))
    )

    ;; Joins a list of strings with a separator between each pair.
    (defun PointBridge:Join ( lst del / out )
        (setq out (car lst))
        (foreach x (cdr lst) (setq out (strcat out del x)))
        out
    )

    ;; Removes duplicates, keeping the first occurrence of each.
    (defun PointBridge:Unique ( lst / item out )
        (while (setq item (car lst))
            (setq lst (vl-remove item lst)
                  out (cons item out)
            )
        )
        (reverse out)
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:To3d / PointBridge:To2d
    ;;;
    ;;; ActiveX returns a polyline's vertices as one flat list of numbers, not
    ;;; as a list of points. These regroup it.
    ;;;
    ;;; A 3D polyline gives three numbers per vertex. A lightweight polyline
    ;;; gives two, because it is planar -- its single elevation is a separate
    ;;; property, and is supplied here as the Z of every point.
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:To3d ( lst )
        (if lst
            (cons (list (car lst) (cadr lst) (caddr lst))
                  (PointBridge:To3d (cdddr lst))
            )
        )
    )

    (defun PointBridge:To2d ( lst elev )
        (if lst
            (cons (list (car lst) (cadr lst) elev)
                  (PointBridge:To2d (cddr lst) elev)
            )
        )
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:TileModes
    ;;;
    ;;; Enables or greys every control according to the chosen input and
    ;;; output types.
    ;;;
    ;;; A file needs a filename box and a Browse button; a block needs the
    ;;; name box and a pick-from-screen button; points and polylines need
    ;;; neither, only the on-screen picker. Attributes only make sense when
    ;;; both ends are a block or a file, since nothing else carries them.
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:TileModes ( )
        (mapcar 'mode_tile
           '("input_file" "input_browse" "input_pick" "in_sel_txt"
             "output_file" "output_browse" "output_pick" "obj_opt")
            (append
                ;;  Input side: file box, browse, pick, count text.
                (cond
                    (   (= "File"  inType) '(0 0 1 1))
                    (   (= "Block" inType) '(0 1 0 0))
                    (   (vl-position inType '("Point" "LW Polyline" "3D Polyline"))
                        '(1 1 0 0)
                    )
                    (   t '(1 1 1 1))
                )
                ;;  Output side: file box, browse, pick, object options.
                (cond
                    (   (= "Block" outType) '(0 0 0 0))
                    (   (= "File"  outType) '(0 0 1 1))
                    (   t '(1 1 1 0))
                )
            )
        )
        ;; The count text is meaningless for a file input.
        (if (not (vl-position inType '("Block" "Point" "LW Polyline" "3D Polyline")))
            (set_tile "in_sel_txt" "")
        )
        (if (and (vl-position inType  '("Block" "File"))
                 (vl-position outType '("Block" "File"))
            )
            (mode_tile "attrib" 0)
            (mode_tile "attrib" 1)
        )
        ;; The sort field and order only apply when sorting is switched on.
        (mapcar 'mode_tile '("sort_ord" "sort_by")
                (if (= "1" (car sortSet)) '(0 0) '(1 1))
        )
        (princ)
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:DelimModes
    ;;;
    ;;; Sets the delimiter controls to suit the file being read or written.
    ;;;
    ;;; A .CSV file is comma separated by definition, so the delimiter is
    ;;; forced to a comma and every delimiter control locked -- which stops
    ;;; anyone producing a "CSV" that Excel cannot open. When neither end is a
    ;;; file the controls are locked because no delimiter is involved at all.
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:DelimModes ( )
        (cond
            (   (or (and (= "File" inType) inFile (/= "" inFile)
                         (= ".CSV" (strcase (vl-filename-extension inFile))))
                    (and (= "File" outType) outFile (/= "" outFile)
                         (= ".CSV" (strcase (vl-filename-extension outFile))))
                )
                (set_tile "del" "1")            ;; the comma entry
                (setq delim (list (car delim) (set_tile "del_other_tog" "0") (caddr delim)))
                (mapcar 'mode_tile '("del_other_tog" "del_other" "del") '(1 1 1))
            )
            (   (vl-position "File" (list inType outType))
                (mapcar 'set_tile  '("del" "del_other_tog" "del_other") delim)
                (mapcar 'mode_tile '("del_other_tog" "del_other" "del")
                        (list 0 (- 1 (atoi (cadr delim))) (atoi (cadr delim)))
                )
            )
            (   t
                (mapcar 'set_tile  '("del" "del_other_tog" "del_other") delim)
                (mapcar 'mode_tile '("del_other_tog" "del_other" "del") '(1 1 1))
            )
        )
        (princ)
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:Delimiter
    ;;;
    ;;; Returns the delimiter as a list of character codes, from either the
    ;;; drop-down list or the free-text box.
    ;;;
    ;;; Character codes rather than a string, because the tab and space
    ;;; entries cannot be typed into a list and are easier to hold as their
    ;;; codes: 46 point, 44 comma, 59 semicolon, 9 tab, 32 space.
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:Delimiter ( set )
        (if (zerop (atoi (cadr set)))
            (nth (atoi (car set)) '((46) (44) (59) (9) (32)))
            (vl-string->list (caddr set))
        )
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:CountText
    ;;;
    ;;; Updates the "n Selected" / "n Found" label beside the input controls.
    ;;;
    ;;; If the user has already picked a selection its size is shown; if not,
    ;;; the whole drawing is scanned so they can see how many objects the run
    ;;; would take before starting it.
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:CountText ( / found )
        (cond
            (   (and sel (set_tile "in_sel_txt"
                                   (strcat (itoa (sslength sel)) " Selected"))))
            (   (and (vl-position inType '("Block" "Point" "LW Polyline" "3D Polyline"))
                     (setq found
                         (ssget "_X"
                             (append
                                 (list (cons 0
                                     (cond
                                         ((= "Block"       inType) "INSERT")
                                         ((= "Point"       inType) "POINT")
                                         ((= "LW Polyline" inType) "LWPOLYLINE")
                                         ((= "3D Polyline" inType) "POLYLINE")
                                     )
                                 ))
                                 (if (and inBlock (/= "" inBlock) (= "Block" inType))
                                     (list (cons 2 inBlock))
                                 )
                             )
                         )
                     )
                )
                (set_tile "in_sel_txt" (strcat (itoa (sslength found)) " Found"))
            )
            (   (set_tile "in_sel_txt" "0 Found"))
        )
        (set_tile "block_info"
            (if (= "Block" inType) "Separate multiple block names with a comma" "")
        )
        (princ)
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:SortOrder
    ;;;
    ;;; Returns the ORDER the points should be taken in, as a list of indices
    ;;; into the original list.
    ;;;
    ;;; Indices rather than sorted points, because the attribute list has to
    ;;; be reordered exactly the same way. Sorting the two lists separately
    ;;; would break the pairing; applying one index order to both cannot.
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:SortOrder ( lst )
        (vl-sort-i lst
           '(lambda ( a b )
                (apply (if (= "0" (caddr sortSet)) '< '>)
                    (cond
                        (   (= "0" (cadr sortSet)) (list (car   a) (car   b)))
                        (   (= "1" (cadr sortSet)) (list (cadr  a) (cadr  b)))
                        (   t                      (list (caddr a) (caddr b)))
                    )
                )
            )
        )
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:AskFormat
    ;;;
    ;;; The column order sub-dialog. Returns the new order, or the old one if
    ;;; cancelled.
    ;;;
    ;;; All three columns must be different -- two columns both claiming to be
    ;;; X would silently discard a coordinate -- so duplicates are rejected
    ;;; before the dialog closes.
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:AskFormat ( handle / PointBridge:Dups picked )

        ;; Returns the values that appear more than once.
        (defun PointBridge:Dups ( lst / item out )
            (while (setq item (car lst))
                (if (vl-position item (setq lst (cdr lst)))
                    (setq out (cons item out)
                          lst (vl-remove item lst)
                    )
                )
            )
            out
        )

        (if (not (new_dialog "format" handle))
            (princ "\nUnable to open the format dialog.")
            (progn
                (foreach key '("col1" "col2" "col3")
                    (PointBridge:FillList key
                       '("X - Coordinate" "Y - Coordinate" "Z - Coordinate"))
                )
                (mapcar 'set_tile '("col1" "col2" "col3") format)

                (action_tile "accept"
                    (vl-prin1-to-string
                       '(if (PointBridge:Dups
                                (setq picked (mapcar 'get_tile '("col1" "col2" "col3"))))
                            (alert "Each column must hold a different coordinate.")
                            (done_dialog 1)
                        )
                    )
                )
                (action_tile "cancel" "(setq picked format) (done_dialog 0)")
                (start_dialog)
            )
        )
        (cond (picked) (format))
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:ShowFormat
    ;;;
    ;;; Displays the current column order on the main dialog, e.g. "X, Y, Z".
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:ShowFormat ( )
        (set_tile "format"
            (strcat "Current point format:     "
                (apply 'strcat
                    (mapcar 'strcat
                        (mapcar '(lambda ( x ) (nth (atoi x) '("X" "Y" "Z"))) format)
                       '(", " ", " "")
                    )
                )
            )
        )
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:AskObject
    ;;;
    ;;; The object options sub-dialog: the layer new objects are created on,
    ;;; and the rotation and scale of inserted blocks.
    ;;;
    ;;; A zero block scale would create invisible blocks, so it is rejected.
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:AskObject ( handle / layers picked )

        (if (not (new_dialog "objoptions" handle))
            (princ "\nUnable to open the object options dialog.")
            (progn
                (vlax-for lay (vla-get-layers doc)
                    (setq layers (cons (vla-get-name lay) layers))
                )
                (setq layers (acad_strlsort layers))

                (PointBridge:FillList "objlay" layers)
                ;; A stored layer that no longer exists falls back to the
                ;; first in the list rather than leaving the tile blank.
                (set_tile "objlay"
                    (itoa (cond ((vl-position (car objOpt) layers)) (0)))
                )

                ;; Rotation and scale apply to blocks only.
                (mapcar 'mode_tile '("blkrot" "blkscl")
                        (if (= "Block" outType) '(0 0) '(1 1)))
                (mapcar 'set_tile  '("blkrot" "blkscl") (cdr objOpt))

                (action_tile "accept"
                    (vl-prin1-to-string
                       '(if (or (null (distof (get_tile "blkscl")))
                                (equal 0.0 (distof (get_tile "blkscl")) 1e-8)
                            )
                            (alert "The block scale must be a non-zero number.")
                            (progn
                                (setq picked
                                    (list (nth (atoi (get_tile "objlay")) layers)
                                          (get_tile "blkrot")
                                          (get_tile "blkscl")
                                    )
                                )
                                (done_dialog 1)
                            )
                        )
                    )
                )
                (action_tile "cancel" "(setq picked objOpt) (done_dialog 0)")
                (start_dialog)
            )
        )
        (cond (picked) (objOpt))
    )

    ;;; -----------------------------------------------------------------------
    ;;; PointBridge:Validate
    ;;;
    ;;; Called when OK is pressed. Checks that both ends are usable and, if
    ;;; so, closes the dialog and returns (input output).
    ;;;
    ;;; Every failure sets the dialog's error line rather than closing, so the
    ;;; user can correct it without losing anything else they have set up.
    ;;;
    ;;; The "input" it returns is either a selection set or a file path,
    ;;; depending on the input type; likewise the output.
    ;;; -----------------------------------------------------------------------

    (defun PointBridge:Validate ( / ext inOk names outOk delOk in out )

        ;; ---- input side ----
        (cond
            (   (= "Block" inType)
                (setq names (get_tile "input_file"))
                (if (or (setq in sel)
                        (setq in (ssget "_X"
                                     (append (list '(0 . "INSERT"))
                                             (if (and names (/= "" names))
                                                 (list (cons 2 names))))))
                    )
                    (setq inOk t)
                    (set_tile "error" "No matching blocks found in the drawing.")
                )
            )
            (   (= "File" inType)
                (cond
                    (   (= "" (setq in (get_tile "input_file")))
                        (set_tile "error" "No input file entered.")
                    )
                    (   (not (vl-position (strcase (vl-filename-extension in)) '(".TXT" ".CSV")))
                        (set_tile "error" "The input file must be a .txt or .csv file.")
                    )
                    (   (not (setq in (findfile in)))
                        (set_tile "error" "Input file not found.")
                    )
                    (   (setq inOk t))
                )
            )
            (   t
                ;; Points and polylines: use the picked selection, or scan
                ;; the whole drawing.
                (if (or (setq in sel)
                        (setq in (ssget "_X"
                                     (list (cons 0
                                         (cond
                                             ((= "Point"       inType) "POINT")
                                             ((= "LW Polyline" inType) "LWPOLYLINE")
                                             ((= "3D Polyline" inType) "POLYLINE")
                                         )
                                     )))
                        )
                    )
                    (setq inOk t)
                    (set_tile "error" (strcat "No " inType " objects found in the drawing."))
                )
            )
        )

        ;; ---- output side ----
        (cond
            (   (= "Block" outType)
                (cond
                    (   (= "" (setq out (get_tile "output_file")))
                        (set_tile "error" "No block entered.")
                    )
                    (   (and (setq ext (vl-filename-extension out))
                             (/= ".DWG" (strcase ext))
                        )
                        (set_tile "error" "An external block must be a .dwg file.")
                    )
                    ;; A block can be one already defined in the drawing, or
                    ;; an external drawing file to be inserted.
                    (   (not (or (tblsearch "BLOCK" out)
                                 (findfile out)
                                 (findfile (strcat out ".dwg"))
                            )
                        )
                        (set_tile "error" "Block not found.")
                    )
                    (   t
                        (if (not (tblsearch "BLOCK" out))
                            (setq out
                                (if (vl-filename-extension out)
                                    (findfile out)
                                    (findfile (strcat out ".dwg"))
                                )
                            )
                        )
                        (setq outOk t)
                    )
                )
            )
            (   (= "File" outType)
                (cond
                    (   (= "" (setq out (get_tile "output_file")))
                        (set_tile "error" "No output file entered.")
                    )
                    (   (not (vl-position (strcase (vl-filename-extension out)) '(".TXT" ".CSV")))
                        (set_tile "error" "The output file must be a .txt or .csv file.")
                    )
                    (   (setq outOk t))
                )
            )
            (   t (setq outOk t))
        )

        ;; ---- delimiter ----
        (setq delim (list (get_tile "del") (get_tile "del_other_tog") (get_tile "del_other")))
        (if (and (= "1" (cadr delim)) (= "" (caddr delim)))
            (set_tile "error" "No delimiter entered.")
            (setq delOk t)
        )

        (if (and inOk outOk delOk) (done_dialog 1))
        (list in out)
    )

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

    (setvar 'cmdecho 0)

    (setq doc (vla-get-activedocument (vlax-get-acad-object))
          ;; New objects go wherever the user is actually working: model
          ;; space, the current layout, or a floating viewport's model space.
          spc (if (zerop (vla-get-activespace doc))
                  (if (= (vla-get-mspace doc) :vlax-true)
                      (vla-get-modelspace doc)
                      (vla-get-paperspace doc)
                  )
                  (vla-get-modelspace doc)
              )
          modes '("Block" "File" "Point" "LW Polyline" "3D Polyline")
    )

    ;; ---- build the dialog ---------------------------------------------------
    ;; Written to a uniquely named temporary file, loaded, and deleted on
    ;; exit, so two AutoCAD sessions cannot collide and nothing is left behind.

    (cond
        (   (not
                (and
                    (setq dcl (vl-filename-mktemp nil nil ".dcl"))
                    (setq des (open dcl "w"))
                    (progn
                        (foreach line
                           '(
                                "// Shared tile shapes, so the columns line up."
                                "edit30  : edit_box     { edit_width = 30; alignment = centered; }"
                                "edit3   : edit_box     { edit_width = 3;  alignment = centered; }"
                                "edit5   : edit_box     { edit_width = 5;  alignment = centered; }"
                                "butt10  : button       { width = 10; fixed_width = true; alignment = centered; }"
                                "pop     : popup_list   { width = 20; fixed_width = true; alignment = centered; }"
                                "pop2    : popup_list   { width = 35; fixed_width = true; alignment = centered; }"
                                "boxcol1 : boxed_column { height = 5.2; fixed_height = true; children_alignment = centered; }"
                                "boxcol2 : boxed_column { height = 5.5; fixed_height = true; children_alignment = centered; }"
                                "boxcol3 : boxed_column { height = 2.0; fixed_height = true; children_alignment = centered;"
                                "                         fixed_width = true; }"
                                "bar     : image        { width = 50; height = 0.3; color = -15; alignment = centered;"
                                "                         fixed_width = true; fixed_height = true; }"
                                ""
                                "ptman_dcl : dialog { label = \"Point Bridge\";"
                                "  spacer;"
                                "  : row {"
                                "    : boxcol1 { label = \"Input Type\"; children_alignment = centered;"
                                "      : pop { key = \"input_type\"; }"
                                "      spacer;"
                                "    }"
                                "    : boxcol1 { label = \"Input\"; children_alignment = left;"
                                "      : row {"
                                "        : edit30 { label = \"&File: \";    key = \"input_file\"; mnemonic = \"F\"; }"
                                "        : butt10 { label = \"B&rowse...\"; key = \"input_browse\"; mnemonic = \"r\"; }"
                                "        : button { width = 3; fixed_width = true; label = \">>\"; key = \"input_pick\"; }"
                                "      }"
                                "      : row {"
                                "        : text { key = \"block_info\";"
                                "                 label = \"Separate multiple block names with a comma\"; alignment = left; }"
                                "        : text { key = \"in_sel_txt\"; label = \"Selected\"; alignment = right; }"
                                "      }"
                                "      : spacer { height = 0.1; fixed_height = true; }"
                                "    }"
                                "  }"
                                "  : row {"
                                "    : boxcol1 { label = \"Output Type\"; children_alignment = centered;"
                                "      : pop { key = \"output_type\"; }"
                                "      spacer;"
                                "    }"
                                "    : boxcol1 { label = \"Output\"; children_alignment = left;"
                                "      : row {"
                                "        : edit30 { label = \"File: \";     key = \"output_file\"; }"
                                "        : butt10 { label = \"Browse...\";  key = \"output_browse\"; }"
                                "        : button { width = 3; fixed_width = true; label = \">>\"; key = \"output_pick\"; }"
                                "      }"
                                "      spacer;"
                                "      : row {"
                                "        : toggle { label = \" Write/enter block attributes\"; key = \"attrib\"; alignment = left; }"
                                "        : butt10 { label = \"Object Options\"; key = \"obj_opt\"; }"
                                "      }"
                                "      spacer;"
                                "    }"
                                "  }"
                                "  : row {"
                                "    : boxcol2 { label = \"Data Delimiter\"; fixed_width = true;"
                                "      : pop { key = \"del\"; }"
                                "      : row {"
                                "        spacer;"
                                "        : column {"
                                "          : spacer { height = 0.1; fixed_height = true; }"
                                "          : toggle { label = \" Other:\"; key = \"del_other_tog\"; alignment = right; }"
                                "        }"
                                "        : edit3 { key = \"del_other\"; }"
                                "        spacer;"
                                "      }"
                                "      spacer;"
                                "    }"
                                "    : boxcol2 { label = \"Point Options\"; children_alignment = centered;"
                                "      : row {"
                                "        : column {"
                                "          : row {"
                                "            : spacer { width = 0.1; fixed_width = true; }"
                                "            : text   { label = \" Sort\"; }"
                                "            : toggle { key = \"sort\"; }"
                                "            : spacer { width = 0.1; fixed_width = true; }"
                                "          }"
                                "        }"
                                "        : column {"
                                "          : spacer { width = 0.1; fixed_width = true; }"
                                "          : text   { label = \" By:\"; alignment = right; }"
                                "          : spacer { width = 0.1; fixed_width = true; }"
                                "        }"
                                "        : column {"
                                "          : row {"
                                "            : pop { key = \"sort_by\";  }"
                                "            : pop { key = \"sort_ord\"; }"
                                "          }"
                                "          : spacer { width = 0.1; fixed_width = true; }"
                                "        }"
                                "      }"
                                "      : bar { key = \"bar\"; }"
                                "      : row {"
                                "        : spacer { width = 1.6; fixed_width = true; }"
                                "        : column {"
                                "          : spacer { width = 0.1; fixed_width = true; }"
                                "          : text   { key = \"format\"; alignment = left;"
                                "                     label = \"Current point format:     X, Y, Z\"; }"
                                "          : spacer { width = 0.1; fixed_width = true; }"
                                "        }"
                                "        : butt10 { label = \"Change...\"; key = \"format_change\"; }"
                                "        : spacer { width = 1.6; fixed_width = true; }"
                                "      }"
                                "    }"
                                "  }"
                                "  : errtile { }"
                                "  : row { children_alignment = centered;"
                                "    spacer;"
                                "    ok_cancel;"
                                "    spacer;"
                                "  }"
                                "}"
                                ""
                                "format : dialog { label = \"Point Format\";"
                                "  spacer;"
                                "  : row { children_alignment = centered; alignment = centered;"
                                "    : boxcol3 { label = \"Column 1\"; : pop { key = \"col1\"; } spacer; }"
                                "    : boxcol3 { label = \"Column 2\"; : pop { key = \"col2\"; } spacer; }"
                                "    : boxcol3 { label = \"Column 3\"; : pop { key = \"col3\"; } spacer; }"
                                "  }"
                                "  spacer; ok_cancel;"
                                "}"
                                ""
                                "objoptions : dialog { label = \"Object Options\";"
                                "  spacer;"
                                "  : row {"
                                "    spacer;"
                                "    : column {"
                                "      : spacer { width = 0.1; fixed_width = true; }"
                                "      : text   { label = \"Layer: \"; }"
                                "      : spacer { width = 0.1; fixed_width = true; }"
                                "    }"
                                "    : pop2 { key = \"objlay\"; }"
                                "    spacer;"
                                "  }"
                                "  spacer;"
                                "  : bar { key = \"bar2\"; }"
                                "  spacer;"
                                "  : row {"
                                "    spacer;"
                                "    : edit5 { key = \"blkscl\"; label = \"Block Scale:\"; }"
                                "    spacer;"
                                "    : edit5 { key = \"blkrot\"; label = \"Block Rotation:\"; }"
                                "    spacer;"
                                "  }"
                                "  spacer_1; ok_cancel;"
                                "}"
                            )
                            (write-line line des)
                        )
                        (setq des (close des))
                        (< 0 (setq dch (load_dialog dcl)))
                    )
                )
            )
            (princ "\nUnable to create the dialog.")
        )

        (   t
            ;; ---- the dialog loop ------------------------------------------
            ;; The dialog reopens after the two "pick from screen" buttons,
            ;; which have to close it in order to let the user reach the
            ;; drawing. Any other result ends the loop.

            (while (not (vl-position flag '(0 1)))

                (if (not (new_dialog "ptman_dcl" dch))
                    (progn
                        (princ "\nUnable to display the dialog.")
                        (setq flag 0)
                    )
                    (progn
                        ;; The two type lists exclude each other's current
                        ;; choice, so input and output can never be the same.
                        (setq outList (vl-remove inType  modes)
                              inList  (vl-remove outType modes)
                        )
                        (mapcar 'PointBridge:FillList
                               '("input_type" "output_type" "del" "sort_by" "sort_ord")
                                (list inList outList
                                     '("Point [ . ]" "Comma [ , ]" "Semi-Colon [ ; ]"
                                       "Tab [       ]" "Space [   ]")
                                     '("X - Coordinate" "Y - Coordinate" "Z - Coordinate")
                                     '("Ascending" "Descending")
                                )
                        )
                        (mapcar 'set_tile '("input_type" "output_type")
                                (mapcar 'itoa
                                        (mapcar '(lambda ( v l ) (cond ((vl-position v l)) (0)))
                                                (list inType outType)
                                                (list inList outList)
                                        )
                                )
                        )
                        (mapcar 'set_tile '("attrib" "sort" "sort_by" "sort_ord")
                                (cons attSet sortSet))

                        ;; ---- fill the input and output name boxes ----------
                        ;; Each stored value is checked against the current
                        ;; drawing before being shown, so a block or file that
                        ;; no longer exists does not appear valid.

                        (and inBlock (= "Block" inType)
                             (vl-every '(lambda ( x ) (tblsearch "BLOCK" x))
                                       (PointBridge:Split inBlock ","))
                             (set_tile "input_file" inBlock)
                        )
                        (and outBlock (= "Block" outType)
                             (or (tblsearch "BLOCK" outBlock)
                                 (setq outBlock
                                     (findfile
                                         (if (vl-filename-extension outBlock)
                                             outBlock
                                             (strcat outBlock ".dwg")
                                         )
                                     )
                                 )
                             )
                             (set_tile "output_file" outBlock)
                        )
                        (and inFile (= "File" inType)
                             (setq inFile (findfile inFile))
                             (set_tile "input_file" inFile)
                        )
                        (and outFile (= "File" outType)
                             (setq outFile (findfile outFile))
                             (set_tile "output_file" outFile)
                        )

                        (PointBridge:DelimModes)
                        (PointBridge:CountText)
                        (PointBridge:TileModes)
                        (PointBridge:ShowFormat)

                        ;;; -------------------------------------------------
                        ;;; Callbacks.
                        ;;;
                        ;;; Written as quoted lists and converted with
                        ;;; vl-prin1-to-string, which produces correct quoting
                        ;;; every time where hand-escaped strings do not.
                        ;;; -------------------------------------------------

                        (action_tile "input_browse"
                            (vl-prin1-to-string
                               '(progn
                                    (if (setq tmp (getfiled "Select input file"
                                                            (cond (inFile) ("")) "txt;csv" 16))
                                        (set_tile "input_file" (setq inFile tmp))
                                    )
                                    (PointBridge:DelimModes)
                                )
                            )
                        )

                        (action_tile "output_browse"
                            (vl-prin1-to-string
                               '(progn
                                    (cond
                                        (   (= "Block" outType)
                                            (if (setq tmp (getfiled "Select block"
                                                                    (cond (outBlock) ("")) "dwg" 16))
                                                (set_tile "output_file"
                                                          (setq outBlock (findfile tmp)))
                                            )
                                        )
                                        (   (= "File" outType)
                                            ;; Flag 9 is 1 + 8: warn before
                                            ;; overwriting, and do not force
                                            ;; an extension on.
                                            (if (setq tmp (getfiled "Select file"
                                                                    (cond (outFile) ("")) "txt;csv" 9))
                                                (set_tile "output_file"
                                                          (setq outFile (cond ((findfile tmp)) (tmp))))
                                            )
                                        )
                                    )
                                    (PointBridge:DelimModes)
                                )
                            )
                        )

                        (action_tile "input_type"
                            (vl-prin1-to-string
                               '(progn
                                    (setq inType (nth (atoi $value) inList)
                                          sel    nil
                                    )
                                    (PointBridge:CountText)
                                    (PointBridge:TileModes)
                                    ;; The other list must be rebuilt so it
                                    ;; still excludes this new choice.
                                    (PointBridge:FillList "output_type"
                                        (setq outList (vl-remove inType modes)))
                                    (set_tile "output_type"
                                        (itoa (cond ((vl-position outType outList)) (0))))
                                    (cond
                                        (   (= "Block" inType)
                                            (and inBlock
                                                 (vl-every '(lambda ( x ) (tblsearch "BLOCK" x))
                                                           (PointBridge:Split inBlock ","))
                                                 (set_tile "input_file" inBlock)
                                            )
                                        )
                                        (   (= "File" inType)
                                            (and inFile
                                                 (setq inFile (findfile inFile))
                                                 (set_tile "input_file" inFile)
                                            )
                                        )
                                    )
                                    (PointBridge:DelimModes)
                                )
                            )
                        )

                        (action_tile "output_type"
                            (vl-prin1-to-string
                               '(progn
                                    (setq outType (nth (atoi $value) outList))
                                    (PointBridge:TileModes)
                                    (PointBridge:FillList "input_type"
                                        (setq inList (vl-remove outType modes)))
                                    (set_tile "input_type"
                                        (itoa (cond ((vl-position inType inList)) (0))))
                                    (cond
                                        (   (= "Block" outType)
                                            (and outBlock
                                                 (or (tblsearch "BLOCK" outBlock)
                                                     (setq outBlock
                                                         (findfile
                                                             (if (vl-filename-extension outBlock)
                                                                 outBlock
                                                                 (strcat outBlock ".dwg"))))
                                                 )
                                                 (set_tile "output_file" outBlock)
                                            )
                                        )
                                        (   (= "File" outType)
                                            (and outFile
                                                 (setq outFile (findfile outFile))
                                                 (set_tile "output_file" outFile)
                                            )
                                        )
                                    )
                                    (PointBridge:DelimModes)
                                )
                            )
                        )

                        (action_tile "del"       "(setq delim (cons $value (cdr delim)))")
                        (action_tile "del_other" "(setq delim (list (car delim) (cadr delim) $value))")
                        (action_tile "del_other_tog"
                            (vl-prin1-to-string
                               '(progn
                                    (setq delim (list (car delim) $value (caddr delim)))
                                    (mode_tile "del_other" (- 1 (atoi $value)))
                                    (mode_tile "del"       (atoi $value))
                                )
                            )
                        )

                        ;; The one name box serves both a file path and a
                        ;; block name, so which variable it writes to depends
                        ;; on the current type.
                        (action_tile "output_file"
                            (vl-prin1-to-string
                               '(progn
                                    (set (if (= outType "File") 'outFile 'outBlock) $value)
                                    (PointBridge:DelimModes)
                                )
                            )
                        )
                        (action_tile "input_file"
                            (vl-prin1-to-string
                               '(progn
                                    (set (if (= inType "File") 'inFile 'inBlock) $value)
                                    (PointBridge:CountText)
                                    (PointBridge:DelimModes)
                                )
                            )
                        )

                        (action_tile "attrib"   "(setq attSet $value)")
                        (action_tile "sort"
                            "(setq sortSet (cons $value (cdr sortSet))) (PointBridge:TileModes)")
                        (action_tile "sort_by"
                            "(setq sortSet (list (car sortSet) $value (caddr sortSet)))")
                        (action_tile "sort_ord"
                            "(setq sortSet (list (car sortSet) (cadr sortSet) $value))")

                        ;; The pick buttons close the dialog with their own
                        ;; codes, so the loop knows to reopen it afterwards.
                        (action_tile "output_pick" "(done_dialog 2)")
                        (action_tile "input_pick"  "(done_dialog 3)")

                        (action_tile "format_change"
                            "(setq format (PointBridge:AskFormat dch)) (PointBridge:ShowFormat)")
                        (action_tile "obj_opt"
                            "(setq objOpt (PointBridge:AskObject dch))")

                        (action_tile "accept" "(setq io (PointBridge:Validate))")
                        (action_tile "cancel" "(done_dialog 0)")

                        (setq flag (start_dialog))

                        (cond
                            ;;  Pick the output block from the drawing.
                            (   (= flag 2)
                                (while
                                    (progn
                                        (setq ent (car (entsel "\nSelect block: ")))
                                        (cond
                                            (   (= 'ename (type ent))
                                                (if (= "INSERT" (cdr (assoc 0 (entget ent))))
                                                    (not (setq outBlock
                                                              (cdr (assoc 2 (entget ent)))))
                                                    (princ "\nThat object must be a block.")
                                                )
                                            )
                                        )
                                    )
                                )
                            )
                            ;;  Pick the input objects from the drawing.
                            (   (= flag 3)
                                (setq sel
                                    (ssget
                                        (append
                                            (list (cons 0
                                                (cond
                                                    ((= "Block"       inType) "INSERT")
                                                    ((= "Point"       inType) "POINT")
                                                    ((= "LW Polyline" inType) "LWPOLYLINE")
                                                    ((= "3D Polyline" inType) "POLYLINE")
                                                )
                                            ))
                                            (if (and inBlock (/= "" inBlock) (= "Block" inType))
                                                (list (cons 2 inBlock))
                                            )
                                        )
                                    )
                                )
                                ;; Picking blocks also updates the name box to
                                ;; list exactly the block names that were
                                ;; picked, which is far quicker than typing
                                ;; them.
                                (if (and sel (= "Block" inType))
                                    (set_tile "input_file"
                                        (setq inBlock
                                            (PointBridge:Join
                                                (PointBridge:Unique
                                                    (mapcar '(lambda ( x )
                                                                 (cdr (assoc 2 (entget x))))
                                                            (vl-remove-if 'listp
                                                                (mapcar 'cadr (ssnamex sel)))
                                                    )
                                                )
                                                ","
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    )
                )
            )
            (setq dch (unload_dialog dch))
            (vl-file-delete dcl)
            (setq dcl nil)

            ;;; ===============================================================
            ;;;                    D O   T H E   W O R K
            ;;; ===============================================================

            (if (/= 1 flag)
                (princ "\nCancelled.")
                (progn
                    (setq undo   (not (vla-startundomark doc))
                          order  (mapcar 'atoi format)
                          objlay (car objOpt)
                          blkrot (angtof (cadr  objOpt))
                          blkscl (distof (caddr objOpt))
                          pos    0
                    )

                    ;; ---- READ ------------------------------------------------
                    (cond

                        ;;  ---- from blocks ----
                        (   (= "Block" inType)
                            (setq idx -1)
                            (while (setq ent (ssname (car io) (setq idx (1+ idx))))
                                (setq lst (cons (cdr (assoc 10 (entget ent))) lst))
                                (if (= "1" attSet)
                                    (if (= :vlax-true
                                           (vla-get-hasattributes
                                               (setq obj (vlax-ename->vla-object ent))))
                                        (progn
                                            ;; Both ordinary and constant
                                            ;; attributes are read, so a block
                                            ;; whose values live in the
                                            ;; definition exports too. The
                                            ;; constant call is caught because
                                            ;; older releases lack it.
                                            (foreach att
                                                (append
                                                    (vlax-invoke obj 'getattributes)
                                                    (   (lambda ( r )
                                                            (if (vl-catch-all-error-p r) nil r)
                                                        )
                                                        (vl-catch-all-apply 'vlax-invoke
                                                            (list obj 'getconstantattributes))
                                                    )
                                                )
                                                (setq attsub (cons (vla-get-textstring att) attsub))
                                            )
                                            (setq attribs (cons (reverse attsub) attribs)
                                                  attsub  nil
                                            )
                                        )
                                        ;; A block with no attributes still
                                        ;; needs a placeholder, or the
                                        ;; attribute list falls out of step
                                        ;; with the point list.
                                        (setq attribs (cons nil attribs))
                                    )
                                )
                            )
                            (setq lst     (reverse lst)
                                  attribs (reverse attribs)
                            )
                        )

                        ;;  ---- from a file ----
                        (   (= "File" inType)
                            (setq inFile (car io)
                                  des    (open (car io) "r")
                                  tmp    (vl-list->string (PointBridge:Delimiter delim))
                            )
                            (while (setq res (read-line des))
                                (setq lst (cons (PointBridge:Split res tmp) lst))
                            )
                            (setq des (close des))

                            ;; Each row becomes a point, reordered to X, Y, Z
                            ;; according to the column format. A row whose
                            ;; first two fields are not numbers is dropped --
                            ;; which is what silently skips a header line.
                            ;; Anything after the third field is taken as
                            ;; attribute values.
                            (setq lst
                                (mapcar
                                   '(lambda ( row / xyz )
                                        (if (not (vl-position nil
                                                     (setq xyz
                                                         (list (distof (car row))
                                                               (distof (cadr row))
                                                               (cond ((null (caddr row)) 0.0)
                                                                     ((distof (caddr row)))
                                                                     (0.0)
                                                               )
                                                         )
                                                     )
                                                 )
                                            )
                                            (progn
                                                (setq attribs (cons (cdddr row) attribs))
                                                (mapcar '(lambda ( i ) (nth i xyz)) order)
                                            )
                                        )
                                    )
                                    (reverse lst)
                                )
                            )
                            (setq attribs (reverse attribs))
                        )

                        ;;  ---- from point objects ----
                        (   (= "Point" inType)
                            (setq idx -1)
                            (while (setq ent (ssname (car io) (setq idx (1+ idx))))
                                (setq lst (cons (cdr (assoc 10 (entget ent))) lst))
                            )
                            (setq lst (reverse lst))
                        )

                        ;;  ---- from lightweight polylines ----
                        (   (= "LW Polyline" inType)
                            (setq idx -1)
                            (while (setq ent (ssname (car io) (setq idx (1+ idx))))
                                (setq obj (vlax-ename->vla-object ent)
                                      lst (append lst
                                              (PointBridge:To2d
                                                  (vlax-get obj 'coordinates)
                                                  (vla-get-elevation obj)
                                              )
                                          )
                                )
                            )
                        )

                        ;;  ---- from 3D polylines ----
                        (   (= "3D Polyline" inType)
                            (setq idx -1)
                            (while (setq ent (ssname (car io) (setq idx (1+ idx))))
                                (setq lst (append lst
                                              (PointBridge:To3d
                                                  (vlax-get (vlax-ename->vla-object ent)
                                                            'coordinates)
                                              )
                                          )
                                )
                            )
                        )
                    )

                    (setq lst (vl-remove nil lst))

                    ;; ---- SORT ------------------------------------------------
                    ;; The same index order is applied to both lists, so a
                    ;; point and its attributes can never be separated.
                    (if (= "1" (car sortSet))
                        (progn
                            (setq newOrder (PointBridge:SortOrder lst))
                            (setq lst (mapcar '(lambda ( i ) (nth i lst)) newOrder))
                            (if attribs
                                (setq attribs (mapcar '(lambda ( i ) (nth i attribs)) newOrder))
                            )
                        )
                    )

                    ;; ---- WRITE -----------------------------------------------
                    (cond

                        ;;  ---- to blocks ----
                        (   (= "Block" outType)
                            (setq blk (cadr io))
                            (foreach pt lst
                                ;; Insertion is caught per point: one bad
                                ;; coordinate, or a block that cannot be
                                ;; loaded, must not abandon the rest.
                                (setq res
                                    (vl-catch-all-apply 'vla-insertblock
                                        (list spc (vlax-3d-point pt) blk
                                              blkscl blkscl blkscl blkrot)
                                    )
                                )
                                (if (vl-catch-all-error-p res)
                                    (setq pos (1+ pos))
                                    (progn
                                        (vla-put-layer res objlay)
                                        ;; Attribute values are written in
                                        ;; order, stopping when either list
                                        ;; runs out -- so a block with fewer
                                        ;; attributes than the file has columns
                                        ;; simply takes as many as it has.
                                        (if (and (= "1" attSet)
                                                 (setq tmp (car attribs))
                                                 (= :vlax-true (vla-get-hasattributes res))
                                                 (setq obj (vlax-invoke res 'getattributes))
                                            )
                                            (while (and (car tmp) (car obj))
                                                (vla-put-textstring (car obj) (car tmp))
                                                (setq tmp (cdr tmp)
                                                      obj (cdr obj)
                                                )
                                            )
                                        )
                                    )
                                )
                                (setq attribs (cdr attribs))
                            )
                            (princ (strcat "\n" (itoa (- (length lst) pos)) " block"
                                           (if (= 1 (- (length lst) pos)) "" "s") " inserted."))
                            (if (< 0 pos)
                                (princ (strcat "\n" (itoa pos) " could not be inserted."))
                            )
                        )

                        ;;  ---- to a file ----
                        ;;  Opened for writing, not appending: the file
                        ;;  browser has already asked before overwriting, so
                        ;;  appending would silently produce a file with the
                        ;;  old contents still in it.
                        (   (= "File" outType)
                            (setq outFile (cadr io)
                                  des     (open (cadr io) "w")
                                  tmp     (vl-list->string (PointBridge:Delimiter delim))
                                  idx     (length lst)
                            )
                            (foreach pt lst
                                (setq res (mapcar 'rtos
                                              (mapcar '(lambda ( i ) (nth i pt)) order)))
                                (if (and (= "1" attSet) (car attribs))
                                    (setq res (append res (car attribs)))
                                )
                                (write-line (PointBridge:Join res tmp) des)
                                (setq attribs (cdr attribs))
                            )
                            (setq des (close des))
                            (princ (strcat "\n" (itoa idx) " point"
                                           (if (= 1 idx) "" "s") " written to " outFile))
                        )

                        ;;  ---- to point objects ----
                        (   (= "Point" outType)
                            (foreach pt lst
                                (vla-put-layer (vla-addpoint spc (vlax-3d-point pt)) objlay)
                            )
                            (princ (strcat "\n" (itoa (length lst)) " point"
                                           (if (= 1 (length lst)) "" "s") " created."))
                        )

                        ;;  ---- to a lightweight polyline ----
                        ;;  One polyline through every point. Its vertices are
                        ;;  2D, so the Z of each point is dropped; the
                        ;;  polyline sits at elevation zero.
                        (   (= "LW Polyline" outType)
                            (setq tmp (apply 'append
                                          (mapcar '(lambda ( pt ) (list (car pt) (cadr pt))) lst)))
                            (if (< 3 (length tmp))
                                (progn
                                    (vla-put-layer
                                        (vla-addlightweightpolyline spc
                                            (vlax-make-variant
                                                (vlax-safearray-fill
                                                    (vlax-make-safearray vlax-vbdouble
                                                        (cons 0 (1- (length tmp))))
                                                    tmp
                                                )
                                            )
                                        )
                                        objlay
                                    )
                                    (princ (strcat "\nPolyline created through "
                                                   (itoa (length lst)) " points."))
                                )
                                (princ "\nAt least two points are needed for a polyline.")
                            )
                        )

                        ;;  ---- to a 3D polyline ----
                        (   (= "3D Polyline" outType)
                            (setq tmp (apply 'append lst))
                            (if (< 5 (length tmp))
                                (progn
                                    (vla-put-layer
                                        (vla-add3dpoly spc
                                            (vlax-make-variant
                                                (vlax-safearray-fill
                                                    (vlax-make-safearray vlax-vbdouble
                                                        (cons 0 (1- (length tmp))))
                                                    tmp
                                                )
                                            )
                                        )
                                        objlay
                                    )
                                    (princ (strcat "\n3D polyline created through "
                                                   (itoa (length lst)) " points."))
                                )
                                (princ "\nAt least two points are needed for a polyline.")
                            )
                        )
                    )

                    (vla-endundomark doc)
                    (setq undo nil)
                )
            )
        )
    )

    ;; Remember everything for next time.
    (setq *PointBridge:Settings*
        (list inFile inBlock outFile outBlock inType outType delim sortSet
              attSet format objOpt)
    )

    (PointBridge:Restore)
    (princ)
)

(princ "\nPointBridge loaded. POINTBRIDGE to convert point data, POINTRESET to clear settings.")
(princ)

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