;;; ---------------------------------------------------------------------
;;;  FACE2STL-bgol.lsp
;;;  Command:  F2STL
;;;
;;;  PURPOSE
;;;  Exports selected 3DFACE entities from the current drawing into an
;;;  ASCII STL (stereolithography) file, the standard format used for
;;;  3D printing, rapid prototyping and CAM/CAE data exchange. Useful
;;;  for turning CAD-built TIN/DTM terrain or site surfaces into a
;;;  physically printable or web-viewable 3D model.
;;;
;;;  USAGE
;;;    1. Load this file (APPLOAD or drag-drop onto the drawing window).
;;;    2. Type   F2STL   at the command line and press Enter.
;;;    3. Select one or more 3DFACE entities that make up the surface.
;;;    4. Choose a destination file name/folder in the save dialog.
;;;    5. An ASCII .stl file is written containing one facet per
;;;       selected 3DFACE (triangles split automatically when a face
;;;       has 4 distinct corners).
;;;
;;;  NOTES
;;;    - Only 3DFACE entities are processed; other entity types in the
;;;      selection set are skipped.
;;;    - Facet normals are computed from the cross product of two edge
;;;      vectors of each triangle and normalized to unit length.
;;;    - This is an original, independently written implementation.
;;;      It is INSPIRED by the general idea of 3DFACE-to-STL export
;;;      utilities common in CAD/survey/terrain workflows, but the
;;;      code, command name and file name here are new and are NOT
;;;      copied from, nor identical to, any specific third-party
;;;      product.
;;;
;;;  ---------------------------------------------------------------------
;;;  Developed and shared for the CAD community by:  BGol Community
;;;  Community website:                              https://bgol.in/
;;;  License:  Open-source & free to use, modify, and share for all.
;;;            Provided "as is", without warranty of any kind.
;;;  ---------------------------------------------------------------------

(vl-load-com)

(defun BGOL:F2STL-EnsureLayer (lname / )
  (if (not (tblsearch "LAYER" lname))
    (entmake (list '(0 . "LAYER")
                   '(100 . "AcDbSymbolTableRecord")
                   '(100 . "AcDbLayerTableRecord")
                   (cons 2 lname)
                   '(70 . 0)
                   '(62 . 5)          ; blue
                   '(6 . "Continuous")))
  )
)

;; Vector helpers -------------------------------------------------------
(defun BGOL:F2STL-VSub (a b) (list (- (car a) (car b)) (- (cadr a) (cadr b)) (- (caddr a) (caddr b))))
(defun BGOL:F2STL-VCross (a b)
  (list (- (* (cadr a) (caddr b)) (* (caddr a) (cadr b)))
        (- (* (caddr a) (car b)) (* (car a) (caddr b)))
        (- (* (car a) (cadr b)) (* (cadr a) (car b))))
)
(defun BGOL:F2STL-VLen (a) (sqrt (+ (* (car a) (car a)) (* (cadr a) (cadr a)) (* (caddr a) (caddr a)))))
(defun BGOL:F2STL-VNorm (a / l)
  (setq l (BGOL:F2STL-VLen a))
  (if (> l 1e-9)
    (list (/ (car a) l) (/ (cadr a) l) (/ (caddr a) l))
    (list 0.0 0.0 1.0)
  )
)

;; Write one triangular facet to the open file handle -------------------
(defun BGOL:F2STL-WriteFacet (fh p1 p2 p3 / e1 e2 nrm)
  (setq e1 (BGOL:F2STL-VSub p2 p1))
  (setq e2 (BGOL:F2STL-VSub p3 p1))
  (setq nrm (BGOL:F2STL-VNorm (BGOL:F2STL-VCross e1 e2)))
  (write-line (strcat "  facet normal " (rtos (car nrm) 2 6) " " (rtos (cadr nrm) 2 6) " " (rtos (caddr nrm) 2 6)) fh)
  (write-line "    outer loop" fh)
  (write-line (strcat "      vertex " (rtos (car p1) 2 6) " " (rtos (cadr p1) 2 6) " " (rtos (caddr p1) 2 6)) fh)
  (write-line (strcat "      vertex " (rtos (car p2) 2 6) " " (rtos (cadr p2) 2 6) " " (rtos (caddr p2) 2 6)) fh)
  (write-line (strcat "      vertex " (rtos (car p3) 2 6) " " (rtos (cadr p3) 2 6) " " (rtos (caddr p3) 2 6)) fh)
  (write-line "    endloop" fh)
  (write-line "  endfacet" fh)
)

;; Write one 3DFACE (4 pts, split into 2 tris if p3<>p4) -----------------
(defun BGOL:F2STL-WriteFace (fh p1 p2 p3 p4)
  (BGOL:F2STL-WriteFacet fh p1 p2 p3)
  (if (not (equal p3 p4 1e-6))
    (BGOL:F2STL-WriteFacet fh p1 p3 p4)
  )
)

(defun C:F2STL ( / fname fh ss n i ent edata p1 p2 p3 p4 count)
  (BGOL:F2STL-EnsureLayer "BGOL-F2STL")
  (setq ss (ssget '((0 . "3DFACE"))))
  (if ss
    (progn
      (setq fname (getfiled "Save STL File" "" "stl" 1))
      (if fname
        (progn
          (setq fh (open fname "w"))
          (if fh
            (progn
              (write-line "solid BGOL_F2STL" fh)
              (setq n (sslength ss))
              (setq count 0)
              (setq i 0)
              (while (< i n)
                (setq ent (ssname ss i))
                (setq edata (entget ent))
                (setq p1 (cdr (assoc 10 edata)))
                (setq p2 (cdr (assoc 11 edata)))
                (setq p3 (cdr (assoc 12 edata)))
                (setq p4 (cdr (assoc 13 edata)))
                (if (and p1 p2 p3 p4)
                  (progn
                    (BGOL:F2STL-WriteFace fh p1 p2 p3 p4)
                    (setq count (1+ count))
                  )
                )
                (setq i (1+ i))
              )
              (write-line "endsolid BGOL_F2STL" fh)
              (close fh)
              (princ (strcat "\n" (itoa count) " 3DFACE entities exported to " fname))
            )
            (princ "\nCould not open file for writing.")
          )
        )
        (princ "\nNo output file selected.")
      )
    )
    (princ "\nNo 3DFACE entities selected.")
  )
  (princ)
)

(princ "\nFACE2STL-bgol.lsp loaded. Type F2STL to export selected 3DFACEs to an STL file.")
(princ "\n  -- Free & open-source, courtesy of BGol Community (https://bgol.in/) --")
(princ)
