;;--------------------=={Improved Roadway with Cycle-Based Preview}==--------------------;;
;;                                   09/24/25 - Improved                                        ;;
;;                    Preview-Adjust-Preview Cycle for Reliable Visual Feedback                 ;;

(defun c:RCS nil (c:RoadwayCyclePreview))

(defun c:RoadwayCyclePreview ( / *error* _StartUndo _EndUndo CreateVisibleOffsetPair ClearAllPreviews
                               ShowCurrentPreview doc sel obj preview_entities pavement_half 
                               gutter_width curb_width landscape_width sidewalk_width 
                               dcl_id dcl_file dialog_result continue_cycle)

  ;; Global variable to track preview entities for easy management
  (setq *ROADWAY_PREVIEW_ENTITIES* '())

  ;; Enhanced error handler that ensures clean preview cleanup
  (defun *error* ( msg )
    (ClearAllPreviews)  ; Remove any preview objects before exiting
    (if dcl_id (unload_dialog dcl_id))
    (if (and dcl_file (findfile dcl_file)) (vl-file-delete dcl_file))
    (and doc (_EndUndo doc))
    (or (wcmatch (strcase msg) "*BREAK,*CANCEL*,*EXIT*")
        (princ (strcat "\n** Error: " msg " **")))
    (princ)
  )

  (defun _StartUndo ( doc ) (vla-StartUndoMark doc))
  (defun _EndUndo ( doc ) (if (= 8 (logand 8 (getvar 'UNDOCTL))) (vla-EndUndoMark doc)))
  
  ;; Create layer if it doesn't exist and set properties to ByLayer
  (defun CreateLayerIfNeeded ( layer_name / layer_obj )
    (if (not (tblsearch "LAYER" layer_name))
      (progn
        (command "_LAYER" "_MAKE" layer_name "")
        (princ (strcat "\nCreated layer: " layer_name))
        ;; Set layer properties to ByLayer standards
        (setq layer_obj (vla-item (vla-get-layers doc) layer_name))
        (vla-put-color layer_obj 256)        ; ByLayer color
        (vla-put-lineweight layer_obj -1)    ; ByLayer lineweight
        (vla-put-linetype layer_obj "ByLayer") ; ByLayer linetype
      )
    )
  )
  
  ;; Create offset pair with reliable visual properties (based on successful test approach)
  (defun CreateVisibleOffsetPair ( source_object offset_distance color_index description target_layer / 
                                   offset_results positive_offset negative_offset is_preview )
    
    ;; This function uses the same approach that worked perfectly in our test
    ;; We create both positive and negative offsets, then apply strong visual properties
    
    (setq offset_results '())
    ;; Determine if this is a preview (no target layer) or final (has target layer)
    (setq is_preview (not target_layer))
    
    ;; Create positive offset (right side when looking along centerline direction)
    (if (not (vl-catch-all-error-p
              (setq positive_offset 
                (vl-catch-all-apply 'vlax-invoke 
                  (list source_object 'Offset offset_distance)))))
      (progn
        ;; Apply properties based on whether this is preview or final
        (foreach obj positive_offset
          (vla-put-color obj color_index)     ; Use specified color for identification
          (if is_preview
            (progn
              ;; Preview: Use thick lines and continuous linetype for visibility
              (vla-put-lineweight obj 50)
              (vla-put-linetype obj "CONTINUOUS")
            )
            (progn
              ;; Final: Set everything to ByLayer
              (vla-put-lineweight obj -1)      ; ByLayer lineweight
              (vla-put-linetype obj "ByLayer") ; ByLayer linetype
              (vla-put-layer obj target_layer) ; Assign to target layer
            )
          )
          (vla-update obj)                    ; Force immediate display update
        )
        (setq offset_results (append offset_results positive_offset))
      )
    )
    
    ;; Create negative offset (left side when looking along centerline direction)
    (if (not (vl-catch-all-error-p
              (setq negative_offset 
                (vl-catch-all-apply 'vlax-invoke 
                  (list source_object 'Offset (- offset_distance))))))
      (progn
        ;; Apply the same properties to negative offset
        (foreach obj negative_offset
          (vla-put-color obj color_index)     ; Same color as positive offset
          (if is_preview
            (progn
              ;; Preview: Use thick lines and continuous linetype for visibility
              (vla-put-lineweight obj 50)
              (vla-put-linetype obj "CONTINUOUS")
            )
            (progn
              ;; Final: Set everything to ByLayer
              (vla-put-lineweight obj -1)      ; ByLayer lineweight
              (vla-put-linetype obj "ByLayer") ; ByLayer linetype
              (vla-put-layer obj target_layer) ; Assign to target layer
            )
          )
          (vla-update obj)                    ; Force immediate display update
        )
        (setq offset_results (append offset_results negative_offset))
      )
    )
    
    ;; Provide user feedback about what was created
    (if offset_results
      (princ (strcat "\n  " description " created successfully (±" (rtos offset_distance 2 1) " ft)"))
      (princ (strcat "\n  WARNING: Failed to create " description))
    )
    
    offset_results
  )

  ;; Remove all existing preview objects and clear tracking list
  (defun ClearAllPreviews ( / )
    (if *ROADWAY_PREVIEW_ENTITIES*
      (progn
        (princ "\nRemoving previous preview objects...")
        (foreach ent *ROADWAY_PREVIEW_ENTITIES*
          (if (and ent (not (vlax-object-released-p ent)))
            (vl-catch-all-apply 'vla-delete (list ent))
          )
        )
        (setq *ROADWAY_PREVIEW_ENTITIES* '())
      )
    )
  )
  
  ;; Create complete roadway preview using current parameter values
  (defun ShowCurrentPreview ( source_obj pave_half gut_w curb_w land_w side_w / 
                              cumulative_dist new_objects )
    
    ;; Clear any existing preview first to avoid confusion
    (ClearAllPreviews)
    
    (princ "\n=== Creating Roadway Preview ===")
    (princ "\nUsing alternating colors for easy identification:")
    
    ;; Create each roadway element at its cumulative distance from centerline
    ;; Using alternating colors (red and yellow) to make each element easily distinguishable
    ;; This approach worked perfectly in our diagnostic test
    
    ;; Edge of Pavement (Red) - where asphalt meets gutter
    (if (> pave_half 0)
      (progn
        (setq cumulative_dist pave_half)
        (setq new_objects (CreateVisibleOffsetPair source_obj cumulative_dist 1 "Edge of Pavement (RED)" nil))
        (setq *ROADWAY_PREVIEW_ENTITIES* (append *ROADWAY_PREVIEW_ENTITIES* new_objects))
      )
    )
    
    ;; Back of Gutter (Yellow) - where gutter meets curb
    (if (> gut_w 0)
      (progn
        (setq cumulative_dist (+ pave_half gut_w))
        (setq new_objects (CreateVisibleOffsetPair source_obj cumulative_dist 2 "Back of Gutter (YELLOW)" nil))
        (setq *ROADWAY_PREVIEW_ENTITIES* (append *ROADWAY_PREVIEW_ENTITIES* new_objects))
      )
    )
    
    ;; Face of Curb (Red) - street-side edge of curb
    (if (> curb_w 0)
      (progn
        (setq cumulative_dist (+ pave_half gut_w curb_w))
        (setq new_objects (CreateVisibleOffsetPair source_obj cumulative_dist 1 "Face of Curb (RED)" nil))
        (setq *ROADWAY_PREVIEW_ENTITIES* (append *ROADWAY_PREVIEW_ENTITIES* new_objects))
      )
    )
    
    ;; Edge of Landscape (Yellow) - back of curb/edge of landscape strip
    (if (> land_w 0)
      (progn
        (setq cumulative_dist (+ pave_half gut_w curb_w land_w))
        (setq new_objects (CreateVisibleOffsetPair source_obj cumulative_dist 2 "Edge of Landscape (YELLOW)" nil))
        (setq *ROADWAY_PREVIEW_ENTITIES* (append *ROADWAY_PREVIEW_ENTITIES* new_objects))
      )
    )
    
    ;; Edge of ROW/Sidewalk (Red) - right-of-way line or outer edge of sidewalk
    (if (> side_w 0)
      (progn
        (setq cumulative_dist (+ pave_half gut_w curb_w land_w side_w))
        (setq new_objects (CreateVisibleOffsetPair source_obj cumulative_dist 1 "Edge of ROW/Sidewalk (RED)" nil))
        (setq *ROADWAY_PREVIEW_ENTITIES* (append *ROADWAY_PREVIEW_ENTITIES* new_objects))
      )
    )
    
    ;; Force complete screen regeneration to ensure all preview objects are visible
    ;; This step was crucial in making our diagnostic test work reliably
    (command "_.REGEN")
    
    ;; Provide summary of what was created
    (princ "\n=== Preview Complete ===")
    (princ (strcat "\nTotal preview objects: " (itoa (length *ROADWAY_PREVIEW_ENTITIES*))))
    (princ (strcat "\nTotal ROW width: " (rtos (* 2 cumulative_dist) 2 1) " ft"))
    (princ "\nLook for THICK RED and YELLOW lines parallel to your centerline")
  )

  ;; Initialize default values using typical roadway design standards
  (if (not *RCS:PAVEMENT_HALF*) (setq *RCS:PAVEMENT_HALF* "12"))      ; 12 ft from centerline to edge of pavement
  (if (not *RCS:GUTTER*) (setq *RCS:GUTTER* "2"))                     ; 2 ft gutter width (typical urban)
  (if (not *RCS:CURB*) (setq *RCS:CURB* "0.5"))                       ; 6 inch curb width (standard)
  (if (not *RCS:LANDSCAPE*) (setq *RCS:LANDSCAPE* "8"))                ; 8 ft landscape strip (good for utilities)
  (if (not *RCS:SIDEWALK*) (setq *RCS:SIDEWALK* "5"))                  ; 5 ft sidewalk width (ADA compliant minimum)

  ;; Create streamlined adjustment dialog that works with our cycle-based approach
  (defun create_adjustment_dialog ()
    (setq dcl_file (vl-filename-mktemp nil nil ".dcl"))
    (setq file (open dcl_file "w"))
    
    (write-line "adjust_dialog : dialog {" file)
    (write-line "  label = \"Adjust Roadway Parameters - Preview Mode\";" file)
    (write-line "  initial_focus = \"pavement\";" file)
    (write-line "  : column {" file)
    
    ;; Clear instructions for the cycle-based workflow
    (write-line "    : text {" file) 
    (write-line "      label = \"Modify values below, then click OK to see updated preview\";" file)
    (write-line "      alignment = centered;" file)
    (write-line "    }" file)
    (write-line "    spacer;" file)
    
    ;; Parameter inputs with clear roadway engineering terminology
    (write-line "    : boxed_column {" file)
    (write-line "      label = \"Roadway Cross-Section Parameters (feet)\";" file)
    
    (write-line "      : row {" file)
    (write-line "        : text { label = \"Edge of Pavement (from centerline):\"; width = 25; }" file)
    (write-line "        : edit_box { key = \"pavement\"; edit_width = 8; }" file)
    (write-line "        : text { label = \"ft\"; width = 3; }" file)
    (write-line "      }" file)
    
    (write-line "      : row {" file)
    (write-line "        : text { label = \"Gutter Width:\"; width = 25; }" file)
    (write-line "        : edit_box { key = \"gutter\"; edit_width = 8; }" file)
    (write-line "        : text { label = \"ft\"; width = 3; }" file)
    (write-line "      }" file)
    
    (write-line "      : row {" file)
    (write-line "        : text { label = \"Curb Width:\"; width = 25; }" file)
    (write-line "        : edit_box { key = \"curb\"; edit_width = 8; }" file)
    (write-line "        : text { label = \"ft\"; width = 3; }" file)
    (write-line "      }" file)
    
    (write-line "      : row {" file)
    (write-line "        : text { label = \"Landscape Strip Width:\"; width = 25; }" file)
    (write-line "        : edit_box { key = \"landscape\"; edit_width = 8; }" file)
    (write-line "        : text { label = \"ft\"; width = 3; }" file)
    (write-line "      }" file)
    
    (write-line "      : row {" file)
    (write-line "        : text { label = \"Sidewalk/ROW Width:\"; width = 25; }" file)
    (write-line "        : edit_box { key = \"sidewalk\"; edit_width = 8; }" file)
    (write-line "        : text { label = \"ft\"; width = 3; }" file)
    (write-line "      }" file)
    (write-line "    }" file)
    
    ;; Action buttons optimized for the cycle-based workflow
    (write-line "    spacer;" file)
    (write-line "    : row {" file)
    (write-line "      alignment = centered;" file)
    (write-line "      : button { key = \"accept\"; label = \"Update Preview\"; is_default = true; width = 15; }" file)
    (write-line "      : button { key = \"final\"; label = \"Create Final\"; width = 12; }" file)
    (write-line "      : button { key = \"cancel\"; label = \"Cancel\"; is_cancel = true; width = 8; }" file)
    (write-line "    }" file)
    
    (write-line "  }" file)
    (write-line "}" file)
    (close file)
    dcl_file
  )

  ;; Input validation function that handles edge cases gracefully
  (defun ValidateInput ( value_string element_name / num_value )
    (setq num_value (atof value_string))
    (cond
      ((or (null value_string) (= value_string ""))
       0.0)  ; Empty fields default to 0 (element won't be created)
      ((< num_value 0)
       (princ (strcat "\nNote: " element_name " cannot be negative, using 0"))
       0.0)
      (t num_value)
    )
  )

  (setq doc (vla-get-ActiveDocument (vlax-get-acad-object)))

  ;; Main program flow using the proven cycle-based approach
  (princ "\n=== Roadway Cross-Section Designer with Visual Preview ===")
  (princ "\nThis tool uses a Preview-Adjust-Preview cycle for reliable visual feedback")
  (princ "\nYou'll see the preview update after each adjustment")
  
  ;; Step 1: Get centerline selection (same as successful test)
  (initget "Exit")
  (setq sel (entsel "\nSelect centerline object for roadway cross-section [Exit] <Exit>: "))
  
  (cond
    ;; Handle cancellation or exit
    ((or (null sel) (eq sel "Exit"))
     (princ "\nRoadway design canceled.")
     (princ))
    
    ;; Process valid object selection
    ((vl-consp sel)
     ;; Validate object type using same criteria as successful test
     (if (and (wcmatch (cdr (assoc 0 (entget (car sel)))) "ARC,CIRCLE,ELLIPSE,SPLINE,LWPOLYLINE,XLINE,LINE")
              (setq obj (vlax-ename->vla-object (car sel))))
       (progn
         (princ "\nCenterline validated successfully!")
         
         ;; Step 2: Create initial preview using default values (like our test)
         (princ "\nCreating initial preview with default values...")
         (setq pavement_half (atof *RCS:PAVEMENT_HALF*))
         (setq gutter_width (atof *RCS:GUTTER*))
         (setq curb_width (atof *RCS:CURB*))
         (setq landscape_width (atof *RCS:LANDSCAPE*))
         (setq sidewalk_width (atof *RCS:SIDEWALK*))
         
         ;; Show initial preview - this worked perfectly in our test
         (ShowCurrentPreview obj pavement_half gutter_width curb_width landscape_width sidewalk_width)
         
         ;; Step 3: Enter the Preview-Adjust-Preview cycle
         (setq continue_cycle T)
         (while continue_cycle
           (princ "\n\nPreview is now displayed. Ready for adjustments...")
           
           ;; Open dialog for parameter adjustments
           (if (setq dcl_id (load_dialog (create_adjustment_dialog)))
             (progn
               (new_dialog "adjust_dialog" dcl_id)
               
               ;; Set current values in dialog
               (set_tile "pavement" (rtos pavement_half 2 2))
               (set_tile "gutter" (rtos gutter_width 2 2))
               (set_tile "curb" (rtos curb_width 2 2))
               (set_tile "landscape" (rtos landscape_width 2 2))
               (set_tile "sidewalk" (rtos sidewalk_width 2 2))
               
               ;; Define dialog actions
               (action_tile "accept"
                 ;; Update preview with new values
                 "(setq pavement_half (ValidateInput (get_tile \"pavement\") \"Pavement\"))
                  (setq gutter_width (ValidateInput (get_tile \"gutter\") \"Gutter\"))
                  (setq curb_width (ValidateInput (get_tile \"curb\") \"Curb\"))
                  (setq landscape_width (ValidateInput (get_tile \"landscape\") \"Landscape\"))
                  (setq sidewalk_width (ValidateInput (get_tile \"sidewalk\") \"Sidewalk\"))
                  (setq dialog_result \"UPDATE\")
                  (done_dialog)"
               )
               
               (action_tile "final"
                 ;; Create final objects and exit cycle
                 "(setq pavement_half (ValidateInput (get_tile \"pavement\") \"Pavement\"))
                  (setq gutter_width (ValidateInput (get_tile \"gutter\") \"Gutter\"))
                  (setq curb_width (ValidateInput (get_tile \"curb\") \"Curb\"))
                  (setq landscape_width (ValidateInput (get_tile \"landscape\") \"Landscape\"))
                  (setq sidewalk_width (ValidateInput (get_tile \"sidewalk\") \"Sidewalk\"))
                  (setq dialog_result \"FINAL\")
                  (done_dialog)"
               )
               
               (action_tile "cancel" 
                 "(setq dialog_result \"CANCEL\") 
                  (done_dialog)")
               
               ;; Display dialog and process results
               (start_dialog)
               (unload_dialog dcl_id)
               
               ;; Handle the three possible dialog outcomes
               (cond
                 ((= dialog_result "UPDATE")
                  ;; User wants to see updated preview - continue cycle
                  (princ "\nUpdating preview with new parameters...")
                  (ShowCurrentPreview obj pavement_half gutter_width curb_width landscape_width sidewalk_width)
                  ;; Continue cycle for more adjustments
                  )
                 
                 ((= dialog_result "FINAL")
                  ;; User is satisfied - create final objects and exit cycle
                  (princ "\nCreating final roadway elements with layer assignments...")
                  (ClearAllPreviews)  ; Remove preview objects first
                  
                  ;; Create required layers if they don't exist
                  (CreateLayerIfNeeded "C-ROAD-CNTR-N")
                  (CreateLayerIfNeeded "C-ROAD")
                  (CreateLayerIfNeeded "C-ROAD-CURB")
                  (CreateLayerIfNeeded "C-ROAD-WALK")
                  
                  ;; Move source centerline to centerline layer
                  (princ "\nMoving centerline to layer C-ROAD-CNTR-N...")
                  (vla-put-layer obj "C-ROAD-CNTR-N")
                  
                  ;; Create final objects using ByLayer properties
                  (_StartUndo doc)
                  (setq cumulative_dist 0.0)
                  
                  ;; Edge of Pavement - goes on C-ROAD layer
                  (if (> pavement_half 0)
                    (progn
                      (setq cumulative_dist pavement_half)
                      (CreateVisibleOffsetPair obj cumulative_dist 256 "Final Edge of Pavement" "C-ROAD")
                    )
                  )
                  
                  ;; Back of Gutter - goes on curb layer
                  (if (> gutter_width 0)
                    (progn
                      (setq cumulative_dist (+ pavement_half gutter_width))
                      (CreateVisibleOffsetPair obj cumulative_dist 256 "Final Back of Gutter" "C-ROAD-CURB")
                    )
                  )
                  
                  ;; Face of Curb - goes on curb layer
                  (if (> curb_width 0)
                    (progn
                      (setq cumulative_dist (+ pavement_half gutter_width curb_width))
                      (CreateVisibleOffsetPair obj cumulative_dist 256 "Final Face of Curb" "C-ROAD-CURB")
                    )
                  )
                  
                  ;; Edge of Landscape - goes on walk layer
                  (if (> landscape_width 0)
                    (progn
                      (setq cumulative_dist (+ pavement_half gutter_width curb_width landscape_width))
                      (CreateVisibleOffsetPair obj cumulative_dist 256 "Final Edge of Landscape" "C-ROAD-WALK")
                    )
                  )
                  
                  ;; Edge of ROW/Sidewalk - goes on walk layer
                  (if (> sidewalk_width 0)
                    (progn
                      (setq cumulative_dist (+ pavement_half gutter_width curb_width landscape_width sidewalk_width))
                      (CreateVisibleOffsetPair obj cumulative_dist 256 "Final Edge of ROW/Sidewalk" "C-ROAD-WALK")
                    )
                  )
                  
                  (_EndUndo doc)
                  (command "_.REGEN")
                  
                  (princ "\n=== Roadway Cross-Section Complete ===")
                  (princ (strcat "\nTotal ROW width: " (rtos (* 2 cumulative_dist) 2 1) " ft"))
                  (princ "\nFinal elements created with layer assignments:")
                  (princ "\n  Centerline: C-ROAD-CNTR-N")
                  (princ "\n  Edge of Pavement: C-ROAD")
                  (princ "\n  Gutter & Curb: C-ROAD-CURB")
                  (princ "\n  Landscape & Sidewalk: C-ROAD-WALK")
                  (princ "\nAll layers set to ByLayer for color, linetype, and lineweight")
                  
                  ;; Store values for next use
                  (setq *RCS:PAVEMENT_HALF* (rtos pavement_half 2 2))
                  (setq *RCS:GUTTER* (rtos gutter_width 2 2))
                  (setq *RCS:CURB* (rtos curb_width 2 2))
                  (setq *RCS:LANDSCAPE* (rtos landscape_width 2 2))
                  (setq *RCS:SIDEWALK* (rtos sidewalk_width 2 2))
                  
                  (setq continue_cycle nil)  ; Exit the cycle
                  )
                 
                 (t
                  ;; User canceled - clean up and exit
                  (princ "\nRoadway design canceled.")
                  (ClearAllPreviews)
                  (setq continue_cycle nil)
                  )
               )
             )
             (progn
               (princ "\n** Error loading dialog - exiting **")
               (setq continue_cycle nil)
             )
           )
         )
       )
       (princ "\n** Selected object must be a LINE, LWPOLYLINE, ARC, CIRCLE, ELLIPSE, SPLINE, or XLINE **")
     ))
  )
  
  ;; Final cleanup
  (if dcl_id (unload_dialog dcl_id))
  (if (and dcl_file (findfile dcl_file)) (vl-file-delete dcl_file))
  (princ)
)   

(vl-load-com) (princ)
(princ "\n:: Roadway Cross-Section with Cycle-Based Preview | C3D 2025 ::")
(princ "\n:: Type \"RCS\" or \"RoadwayCyclePreview\" to start ::")
(princ "\n:: Uses Preview-Adjust-Preview cycle for reliable visual feedback ::")
(princ)

;;------------------------------------------------------------;;
;;                         End of File                        ;;
;;------------------------------------------------------------;;
