;;; ------------------------------------------------------------
;;; AutoCATS ¸®½À ÀÚ·á½Ç
;; ¸í·É: PLCLEAN
;; ±â´É: 2D LWPOLYLINEÀÇ Áßº¹ ¹× ÀÏÁ÷¼± Á¤Á¡ Á¤¸®
;; ÃâÃ³: https://www.autocats.co.kr/lisp/polyline-clean
;; ¹öÀü: 1.0 (2026-07-27)
;; ¶óÀÌ¼±½º: ÀÚÀ¯ »ç¿ë¡¤¼öÁ¤, Àç¹èÆ÷ ½Ã ÃâÃ³(autocats.co.kr) Ç¥±â
;; ¾ÈÀü: *error* ÇÚµé·¯ + UNDO ±×·ì, È£ ¹× °¡º¯ Æø Æú¸®¼± º¸È£
;;; ------------------------------------------------------------

(vl-load-com)

(defun autocats-plclean-locked-p (ent / row)
  (setq row (tblsearch "LAYER" (cdr (assoc 8 (entget ent)))))
  (and row (= 4 (logand 4 (cdr (assoc 70 row))))))

(defun autocats-plclean-protected-geometry-p (ent / data protected)
  (setq data (entget ent) protected nil)
  (foreach pair data
    (if (or (and (= (car pair) 42) (> (abs (cdr pair)) 0.000000000001))
            (and (member (car pair) '(40 41)) (> (abs (cdr pair)) 0.000000000001)))
      (setq protected T)))
  protected)

(defun autocats-plclean-points (ent / result)
  (setq result nil)
  (foreach pair (entget ent)
    (if (= (car pair) 10)
      (setq result (cons (cdr pair) result))))
  (reverse result))

(defun autocats-plclean-remove-nth (index values / idx result)
  (setq idx 0 result nil)
  (foreach value values
    (if (/= idx index) (setq result (cons value result)))
    (setq idx (1+ idx)))
  (reverse result))

(defun autocats-plclean-duplicates (points closed tolerance / result point)
  (setq result nil)
  (foreach point points
    (if (or (null result) (> (distance point (car result)) tolerance))
      (setq result (cons point result))))
  (setq result (reverse result))
  (if (and closed (> (length result) 1)
           (<= (distance (car result) (last result)) tolerance))
    (setq result (reverse (cdr (reverse result)))))
  result)

(defun autocats-plclean-collinear-p (left middle right tolerance / cross dot)
  (setq cross
        (- (* (- (car middle) (car left)) (- (cadr right) (cadr left)))
           (* (- (cadr middle) (cadr left)) (- (car right) (car left))))
        dot
        (+ (* (- (car middle) (car left)) (- (car middle) (car right)))
           (* (- (cadr middle) (cadr left)) (- (cadr middle) (cadr right)))))
  (and (<= (abs cross) tolerance) (<= dot 0.0)))

(defun autocats-plclean-collinear (points closed tolerance / changed idx count prev next)
  (setq changed T)
  (while (and changed (> (length points) (if closed 3 2)))
    (setq changed nil idx 0 count (length points))
    (while (and (< idx count) (not changed))
      (if (and (or closed (and (> idx 0) (< idx (1- count))))
               (setq prev (nth (rem (+ idx count -1) count) points))
               (setq next (nth (rem (1+ idx) count) points))
               (autocats-plclean-collinear-p prev (nth idx points) next tolerance))
        (progn
          (setq points (autocats-plclean-remove-nth idx points)
                changed T)))
      (setq idx (1+ idx))))
  points)

(defun autocats-plclean-vertex-pairs (points / result)
  (setq result nil)
  (foreach point points
    (setq result (append result (list (cons 10 point)))))
  result)

(defun autocats-plclean-rebuild (data points / result inserted)
  (setq result nil inserted nil)
  (foreach pair data
    (cond
      ((member (car pair) '(10 40 41 42 91)))
      ((= (car pair) 90)
       (setq result (append result (list (cons 90 (length points))))))
      ((and (= (car pair) 210) (not inserted))
       (setq result
             (append result
               (autocats-plclean-vertex-pairs points)
               (list pair))
             inserted T))
      (T (setq result (append result (list pair))))))
  (if inserted
    result
    (append result (autocats-plclean-vertex-pairs points))))

(defun autocats-plclean-set-points (ent points / updated)
  (setq updated (entmod (autocats-plclean-rebuild (entget ent) points)))
  (if updated (progn (entupd ent) T) nil))

(defun autocats-plclean-apply (ent duplicate-tolerance remove-collinear
                               collinear-tolerance / closed original cleaned)
  (if (autocats-plclean-protected-geometry-p ent)
    nil
    (progn
      (setq closed (/= 0 (logand 1 (cdr (assoc 70 (entget ent)))))
            original (autocats-plclean-points ent)
            cleaned (autocats-plclean-duplicates original closed duplicate-tolerance))
      (if remove-collinear
        (setq cleaned
              (autocats-plclean-collinear cleaned closed collinear-tolerance)))
      (if (or (< (length cleaned) (if closed 3 2))
              (not (autocats-plclean-set-points ent cleaned)))
        nil
        (- (length original) (length cleaned))))))

(defun c:PLCLEAN (/ *error* doc undo-open duplicate-tolerance answer remove-collinear
                     collinear-tolerance ss idx ent result changed removed skipped)
  (defun *error* (msg)
    (if undo-open
      (progn (vla-EndUndoMark doc) (setq undo-open nil)))
    (if (and msg (not (wcmatch (strcase msg) "*CANCEL*,*QUIT*,*EXIT*,*Ãë¼Ò*")))
      (princ (strcat "\n[PLCLEAN] Áß´Ü: " msg)))
    (princ))

  (initget 6)
  (setq duplicate-tolerance (getdist "\n[PLCLEAN] Áßº¹ Á¤Á¡ Çã¿ë°Å¸® <0.01>: "))
  (if (null duplicate-tolerance) (setq duplicate-tolerance 0.01))
  (initget "Yes No")
  (setq answer (getkword "\n[PLCLEAN] ÀÏÁ÷¼± Á¤Á¡µµ Á¦°ÅÇÒ±î¿ä? [Yes/No] <Yes>: ")
        remove-collinear (/= answer "No"))
  (if remove-collinear
    (progn
      (initget 6)
      (setq collinear-tolerance (getreal "\n[PLCLEAN] ÀÏÁ÷¼± ÆÇÁ¤ ¿ÀÂ÷ <0.001>: "))
      (if (null collinear-tolerance) (setq collinear-tolerance 0.001))))
  (princ "\n[PLCLEAN] Á¤¸®ÇÒ 2D LWPOLYLINEÀ» ¼±ÅÃÇÏ¼¼¿ä.")
  (setq ss (ssget '((0 . "LWPOLYLINE"))))
  (if ss
    (progn
      (setq doc (vla-get-ActiveDocument (vlax-get-acad-object))
            idx 0 changed 0 removed 0 skipped 0)
      (vla-StartUndoMark doc)
      (setq undo-open T)
      (while (< idx (sslength ss))
        (setq ent (ssname ss idx))
        (if (or (autocats-plclean-locked-p ent)
                (null (setq result
                  (autocats-plclean-apply ent duplicate-tolerance
                    remove-collinear collinear-tolerance))))
          (setq skipped (1+ skipped))
          (progn
            (if (> result 0) (setq changed (1+ changed)))
            (setq removed (+ removed result))))
        (setq idx (1+ idx)))
      (vla-EndUndoMark doc)
      (setq undo-open nil)
      (princ (strcat "\n[PLCLEAN] ¿Ï·á: " (itoa changed)
                     "°³ Æú¸®¼± º¯°æ / " (itoa removed)
                     "°³ Á¤Á¡ Á¦°Å / " (itoa skipped) "°³ °Ç³Ê¶Ü")))
    (princ "\n[PLCLEAN] ¼±ÅÃµÈ Æú¸®¼±ÀÌ ¾ø½À´Ï´Ù."))
  (princ))

(princ "\n[AutoCATS] PLCLEAN ·Îµå ¿Ï·á - ¸í·ÉÇà¿¡ PLCLEAN ¸¦ ÀÔ·ÂÇÏ¼¼¿ä.")
(princ)
