;;; ---------------------------------------------------------------------------
;;; WallSection.lsp
;;; code compiled by YZ August 2026
;;; ---------------------------------------------------------------------------
;;; EXTERIOR WALL SECTION - FOOTING TO EAVE
;;;
;;; PURPOSE
;;;   Draws a complete exterior wall section in one command: strip footing,
;;;   foundation wall, basement slab, sill plate, floor framing, stud wall with
;;;   plates and finishes, and the eave with rafter, fascia and soffit - with
;;;   concrete hatching and batt insulation.
;;;
;;;   It is the drawing every set of house plans needs and nobody enjoys
;;;   redrawing.
;;;
;;; WHY THIS IS A REBUILD RATHER THAN A CONVERSION
;;;   The routine this replaces did the same job from eighty hard-coded points.
;;;   Every dimension that was not one of its nine prompts was a magic number
;;;   buried in the geometry - the gypsum at 0.5, sheathing at 0.625, plates at
;;;   2, subfloor at 0.75 - and the roof was fixed at 30 degrees by three
;;;   expressions containing a literal 0.866.
;;;
;;;   That meant it drew exactly one house. Change the roof pitch, use 2x4 walls,
;;;   or work in a country that does not build at 30 degrees, and it was no use.
;;;
;;;   Everything is a parameter here, and the section is BUILT rather than
;;;   plotted: each part is a function that knows where the part below it
;;;   finished. Change the joist depth and the wall, the eave and the insulation
;;;   all move up with it.
;;;
;;; HOW IT IS SET OUT
;;;   You pick ONE point: the inside face of the finished wall, at floor level.
;;;   That is the point a builder dimensions from and the one an architect draws
;;;   the room to.
;;;
;;;   From there the section builds outward and downward:
;;;
;;;     x = 0                 inside face of the plasterboard
;;;     x = -gypsum           face of the studs
;;;     x = -gypsum-stud      back of the studs
;;;     x = -gypsum-stud-sheathing    outside face, and the foundation below it
;;;
;;;     y = 0                 top of the subfloor
;;;     downward              joists, sill plate, foundation, footing
;;;     upward                plates, studs, top plates, eave
;;;
;;;   The section is drawn with the OUTSIDE to the left. That is the convention
;;;   for a wall section read alongside a plan with north up.
;;;
;;; THE EAVE
;;;   The rafter bears on the outside top corner of the wall, slopes up at the
;;;   pitch you give as rise and run, and is cut PLUMB at the tail - which is
;;;   what a rafter tail actually is, and why the fascia stands vertical.
;;;
;;;   A rafter of perpendicular depth D cut plumb measures D over cos(pitch)
;;;   on that vertical cut. That one relation places the whole tail.
;;;
;;;   WALLSECTION  - draw an exterior wall section from footing to eave
;;; ---------------------------------------------------------------------------

;;; ---------------------------------------------------------------------------
;;; SESSION MEMORY
;;;
;;; Defaults are nominal North American timber framing in inches: 2x6 walls
;;; (5.5 actual), 2x12 floor joists (11.25 actual), 2x6 rafters, half-inch
;;; plasterboard and sheathing, and a 6-in-12 roof.
;;; ---------------------------------------------------------------------------

(if (null *WallSect:Prefs*)
    (setq *WallSect:Prefs*
        (list (cons "STUD"    5.50)   ; stud depth, 2x6 dressed
              (cons "WALLHT" 96.00)   ; subfloor to top of the top plates
              (cons "GYP"     0.50)   ; plasterboard
              (cons "SHEATH"  0.50)   ; exterior sheathing
              (cons "PLATE"   1.50)   ; plate thickness, one 2x
              (cons "SUB"     0.75)   ; subfloor
              (cons "JOIST"  11.25)   ; floor joist depth, 2x12 dressed
              (cons "FDNW"    8.00)   ; foundation wall thickness
              (cons "FDNH"   96.00)   ; foundation wall height
              (cons "FTGW"   16.00)   ; footing width
              (cons "FTGH"   10.00)   ; footing depth
              (cons "SLAB"    4.00)   ; basement slab thickness
              (cons "RISE"    6.00)   ; roof pitch, rise
              (cons "RUN"    12.00)   ; roof pitch, run
              (cons "RAFTER"  5.50)   ; rafter depth, 2x6 dressed
              (cons "OVER"   24.00)   ; eave overhang, horizontal
              (cons "SHOWN"  30.00)   ; how far into the building to draw
              (cons "HSCALE"  1.00)   ; hatch scale
              (cons "INSUL"  "Yes")   ; draw batt insulation
              (cons "HATCH"  "Yes")   ; hatch the concrete
        )
    )
)

(defun WallSect:Get ( key ) (cdr (assoc key *WallSect:Prefs*)))

(defun WallSect:Put ( key val )
    (setq *WallSect:Prefs*
        (cons (cons key val)
              (vl-remove-if '(lambda (p) (= (car p) key)) *WallSect:Prefs*)))
    val
)

(defun WallSect:Ask ( key prompt / v )
    (initget 6)
    (setq v (getdist (strcat "\n  " prompt " <" (rtos (WallSect:Get key) 2 3) ">: ")))
    (if v (WallSect:Put key v) (WallSect:Get key))
)

(defun WallSect:AskYN ( key prompt / v )
    (initget "Yes No")
    (setq v (getkword (strcat "\n  " prompt " [Yes/No] <" (WallSect:Get key) ">: ")))
    (if v (WallSect:Put key v) (WallSect:Get key))
)

;;; ---------------------------------------------------------------------------
;;; DRAWING PRIMITIVES
;;;
;;; Everything is placed relative to the single picked origin, so the section can
;;; be drawn anywhere and as many times as wanted. The original always drew at
;;; whatever point was picked but computed from absolute offsets, which is the
;;; same thing done less clearly.
;;; ---------------------------------------------------------------------------

(defun WallSect:Layer ( name )
    (if (tblsearch "layer" name)
        (command "_.LAYER" "_ON" name "_THAW" name "_UNLOCK" name "")
        (command "_.LAYER" "_NEW" name ""))
    (setvar "CLAYER" name)
)

;;; A point in section coordinates: x across (negative is outside), y up.
(defun WallSect:P ( org x y ) (list (+ (car org) x) (+ (cadr org) y) 0.0))

;;; Closed polyline through a list of section points. Returns the entity.
(defun WallSect:Box ( org pts / )
    (command "_.PLINE" (WallSect:P org (caar pts) (cadar pts)) "_W" 0 0)
    (foreach p (cdr pts) (command (WallSect:P org (car p) (cadr p))))
    (command "_C")
    (entlast)
)

(defun WallSect:Line ( org pts / )
    (command "_.LINE")
    (foreach p pts (command (WallSect:P org (car p) (cadr p))))
    (command "")
    (princ)
)

;;; Hatch one closed boundary. Wrapped because a pattern that is not on the
;;; support path should not bring the whole section down.
(defun WallSect:Hatch ( ent pattern scale / ss )
    (setq ss (ssadd))
    (ssadd ent ss)
    (vl-catch-all-apply
        '(lambda ( )
             (command "_.-HATCH" "_P" pattern scale 0 "_S" ss "" "")))
    (princ)
)

;;; ---------------------------------------------------------------------------
;;; BATT INSULATION
;;;
;;; The looped symbol, drawn as one polyline rather than as arrays of arcs.
;;;
;;; A batt is a chain of semicircles whose ends alternate between the two faces
;;; of the cavity. Each semicircle is a bulge of 1 - the tangent of a quarter of
;;; 180 degrees - and the sign alternates so successive loops face opposite ways,
;;; which is what gives the symbol its coil appearance.
;;;
;;; The number of loops is worked out from the cavity, not fixed. The routine
;;; this replaces drew two arcs and then ARRAYed them a hard-coded 11, 6 and 5
;;; times in three different places, so any change of wall height left the
;;; insulation stopping short or running past the plate.
;;;
;;; Written to run in ANY direction, because the same symbol is wanted vertically
;;; in the wall cavity and horizontally between the floor joists.
;;;
;;;   base   start point, in section coordinates, on the first face
;;;   ang    direction the cavity runs
;;;   len    how far to run
;;;   width  cavity width, measured 90 degrees anticlockwise from ang
;;; ---------------------------------------------------------------------------

(defun WallSect:Batt ( org base ang len width / perp step n i p0 p pts bulges )

    (setq perp (+ ang (* pi 0.5))
          ;; Loops roughly as long as the cavity is wide read correctly at any
          ;; scale, and stay proportional in a 2x4 wall or a 2x12 floor.
          step (abs width)
          n    (fix (/ len step))
          p0   (WallSect:P org (car base) (cadr base)))

    (if (and (> step 0.0) (> n 1))
        (progn
            ;; Points alternate between the two faces, advancing one step along
            ;; the cavity each time; the arc between each pair is a semicircle.
            (setq i 0 pts nil bulges nil)
            (while (<= i n)
                (setq p (polar (polar p0 ang (* i step))
                               perp
                               (if (zerop (rem i 2)) 0.0 width))
                      pts (cons p pts)
                      ;; Alternating sign is what makes the loops face opposite
                      ;; ways instead of spiralling one direction.
                      bulges (cons (if (zerop (rem i 2)) 1.0 -1.0) bulges)
                      i (1+ i)))
            (setq pts (reverse pts) bulges (reverse bulges))

            (entmake
                (append
                    (list '(0 . "LWPOLYLINE") '(100 . "AcDbEntity")
                          (cons 8 (getvar "CLAYER"))
                          '(100 . "AcDbPolyline")
                          (cons 90 (length pts)) '(70 . 0))
                    (apply 'append
                        (mapcar
                            '(lambda (p b)
                                 (list (cons 10 (list (car p) (cadr p)))
                                       (cons 42 b)))
                            pts bulges))))))
    (princ)
)

;;; ---------------------------------------------------------------------------
;;; BREAK LINE
;;;
;;; The zigzag that says "this carries on". Drawn at the inboard edge of the
;;; floor and the ceiling, because a wall section is a cut through a house, not
;;; a drawing of a house one foot wide.
;;; ---------------------------------------------------------------------------

(defun WallSect:Break ( org x y1 y2 / mid amp )
    (setq mid (/ (+ y1 y2) 2.0)
          amp (min 3.0 (/ (abs (- y2 y1)) 4.0)))
    (WallSect:Line org
        (list (list x y1)
              (list x (- mid amp))
              (list (+ x amp) (- mid (/ amp 2.0)))
              (list (- x amp) (+ mid (/ amp 2.0)))
              (list x (+ mid amp))
              (list x y2)))
)

;;; ---------------------------------------------------------------------------
;;; MAIN COMMAND
;;; ---------------------------------------------------------------------------

(defun c:WALLSECTION ( / *error* vars vals org opt
                         stud wallHt gyp sheath plate sub joist
                         fdnW fdnH ftgW ftgH slab
                         rise run rafter over shown hscale insul hatch
                         xGyp xStud xOut xSh xFdnI xFtgO xFtgI
                         yFloor yJoistT yJoistB ySill yFdnT yFdnB yFtgT yFtgB
                         ySlabT yPlateT yTopB yWall
                         pitch tanP secP xTail yTailB yTailT ent )

    (setq vars '("CMDECHO" "OSMODE" "BLIPMODE" "CLAYER" "PLINEWID")
          vals (mapcar 'getvar vars))

    (defun WallSect:Restore ( )
        (mapcar 'setvar vars vals)
        (while (= 8 (logand 8 (getvar 'undoctl))) (command "_.UNDO" "_End"))
        (vl-catch-all-apply '(lambda ( ) (*pop-error-mode*)) '())
        (princ)
    )

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

    (setvar "CMDECHO" 0)
    ;; AutoCAD 2015 and later refuse (command) inside an *error* handler
    ;; unless the routine says up front that it will use one. Restore does,
    ;; to close this undo group.
    (vl-catch-all-apply '(lambda ( ) (*push-error-using-command*)) '())
    (command "_.UNDO" "_Begin")
    (setvar "BLIPMODE" 0)

    ;; --- dimensions ---------------------------------------------------------
    ;; Grouped by trade, so a change to the framing does not mean answering
    ;; twenty prompts about the foundation first.
    (initget "All Framing Foundation Roof Draw")
    (setq opt (getkword "\nChange which dimensions [All/Framing/Foundation/Roof/Draw] <Draw>: "))
    (if (null opt) (setq opt "Draw"))

    (if (member opt '("All" "Framing"))
        (progn
            (princ "\nFRAMING")
            (WallSect:Ask "STUD"   "Stud depth")
            (WallSect:Ask "WALLHT" "Wall height, subfloor to top plate")
            (WallSect:Ask "PLATE"  "Plate thickness")
            (WallSect:Ask "JOIST"  "Floor joist depth")
            (WallSect:Ask "SUB"    "Subfloor thickness")
            (WallSect:Ask "GYP"    "Plasterboard thickness")
            (WallSect:Ask "SHEATH" "Sheathing thickness")))

    (if (member opt '("All" "Foundation"))
        (progn
            (princ "\nFOUNDATION")
            (WallSect:Ask "FDNW" "Foundation wall thickness")
            (WallSect:Ask "FDNH" "Foundation wall height")
            (WallSect:Ask "FTGW" "Footing width")
            (WallSect:Ask "FTGH" "Footing depth")
            (WallSect:Ask "SLAB" "Basement slab thickness")))

    (if (member opt '("All" "Roof"))
        (progn
            (princ "\nROOF")
            (WallSect:Ask "RISE"   "Roof pitch, rise")
            (WallSect:Ask "RUN"    "Roof pitch, run")
            (WallSect:Ask "RAFTER" "Rafter depth")
            (WallSect:Ask "OVER"   "Eave overhang")))

    (if (= opt "All")
        (progn
            (WallSect:Ask   "SHOWN"  "How far into the building to draw")
            (WallSect:AskYN "INSUL"  "Draw batt insulation")
            (WallSect:AskYN "HATCH"  "Hatch the concrete")))

    ;; --- pull everything into locals ---------------------------------------
    (setq stud   (WallSect:Get "STUD")    wallHt (WallSect:Get "WALLHT")
          gyp    (WallSect:Get "GYP")     sheath (WallSect:Get "SHEATH")
          plate  (WallSect:Get "PLATE")   sub    (WallSect:Get "SUB")
          joist  (WallSect:Get "JOIST")   fdnW   (WallSect:Get "FDNW")
          fdnH   (WallSect:Get "FDNH")    ftgW   (WallSect:Get "FTGW")
          ftgH   (WallSect:Get "FTGH")    slab   (WallSect:Get "SLAB")
          rise   (WallSect:Get "RISE")    run    (WallSect:Get "RUN")
          rafter (WallSect:Get "RAFTER")  over   (WallSect:Get "OVER")
          shown  (WallSect:Get "SHOWN")   hscale (WallSect:Get "HSCALE")
          insul  (WallSect:Get "INSUL")   hatch  (WallSect:Get "HATCH"))

    (cond
        ((<= ftgW fdnW)
         (princ "\n** The footing must be wider than the foundation wall. **"))

        ((>= (* 2.0 plate) wallHt)
         (princ "\n** The wall is not tall enough for its own plates. **"))

        ((>= slab ftgH)
         (princ "\n** The slab is as thick as the footing - check those two. **"))

        ((null (setq org (getpoint "\nInside face of the wall, at floor level: ")))
         (princ "\nCancelled."))

        (t
            (setvar "OSMODE" 0)

            ;; --- horizontal setting out, outward is negative ---------------
            (setq xGyp   0.0
                  xStud  (- gyp)
                  xOut   (- (+ gyp stud))
                  xSh    (- (+ gyp stud sheath))
                  xFdnI  (+ xSh fdnW)
                  xFtgO  (- xSh (/ (- ftgW fdnW) 2.0))
                  xFtgI  (+ xFtgO ftgW))

            ;; --- vertical setting out --------------------------------------
            (setq yFloor  0.0
                  yJoistT (- sub)
                  yJoistB (- yJoistT joist)
                  ySill   yJoistB
                  yFdnT   (- ySill plate)
                  yFdnB   (- yFdnT fdnH)
                  yFtgT   yFdnB
                  yFtgB   (- yFtgT ftgH)
                  ySlabT  (+ yFtgT slab)
                  yPlateT plate
                  yWall   wallHt
                  yTopB   (- wallHt (* 2.0 plate)))

            ;; ================= CONCRETE =====================================
            (WallSect:Layer "WALLSECT-CONC")

            (setq ent (WallSect:Box org
                          (list (list xFtgO yFtgB) (list xFtgI yFtgB)
                                (list xFtgI yFtgT) (list xFtgO yFtgT))))
            (if (= hatch "Yes") (WallSect:Hatch ent "AR-CONC" hscale))

            (setq ent (WallSect:Box org
                          (list (list xSh yFdnB) (list xFdnI yFdnB)
                                (list xFdnI yFdnT) (list xSh yFdnT))))
            (if (= hatch "Yes") (WallSect:Hatch ent "AR-CONC" hscale))

            ;; Basement slab, bearing on the footing and running inboard.
            (setq ent (WallSect:Box org
                          (list (list xFdnI yFtgT) (list shown yFtgT)
                                (list shown ySlabT) (list xFdnI ySlabT))))
            (if (= hatch "Yes") (WallSect:Hatch ent "AR-CONC" hscale))

            ;; ================= FRAMING ======================================
            (WallSect:Layer "WALLSECT-WOOD")

            ;; Sill plate, on top of the foundation and set in from its face.
            (WallSect:Box org (list (list xSh yFdnT) (list xFdnI yFdnT)
                                    (list xFdnI ySill) (list xSh ySill)))

            ;; Rim joist at the wall, and the floor joists running inboard.
            (WallSect:Box org (list (list xSh yJoistB) (list (+ xSh plate) yJoistB)
                                    (list (+ xSh plate) yJoistT) (list xSh yJoistT)))
            (WallSect:Line org (list (list (+ xSh plate) yJoistT) (list shown yJoistT)))
            (WallSect:Line org (list (list (+ xSh plate) yJoistB) (list shown yJoistB)))

            ;; Subfloor.
            (WallSect:Box org (list (list xSh yJoistT) (list shown yJoistT)
                                    (list shown yFloor) (list xSh yFloor)))

            ;; Bottom plate, and the double top plate.
            (WallSect:Box org (list (list xStud yFloor) (list xOut yFloor)
                                    (list xOut yPlateT) (list xStud yPlateT)))
            (WallSect:Box org (list (list xStud yTopB) (list xOut yTopB)
                                    (list xOut yWall) (list xStud yWall)))
            ;; The line between the two top plates - it is two members, and a
            ;; single rectangle would read as one.
            (WallSect:Line org (list (list xStud (+ yTopB plate))
                                     (list xOut  (+ yTopB plate))))

            ;; ================= FINISHES =====================================
            (WallSect:Layer "WALLSECT-FINISH")

            ;; Plasterboard, floor to ceiling on the inside face.
            (WallSect:Box org (list (list xGyp yFloor) (list xStud yFloor)
                                    (list xStud yWall) (list xGyp yWall)))

            ;; Sheathing, running from the foundation up past the top plate to
            ;; meet the underside of the rafter.
            (WallSect:Box org (list (list xOut yJoistB) (list xSh yJoistB)
                                    (list xSh yWall) (list xOut yWall)))

            ;; Ceiling line, inboard from the top plate.
            (WallSect:Line org (list (list xStud yWall) (list shown yWall)))

            ;; ================= EAVE =========================================
            (WallSect:Layer "WALLSECT-WOOD")

            (setq pitch (atan rise run)
                  tanP  (/ (sin pitch) (cos pitch))
                  ;; A rafter of perpendicular depth D, cut plumb, measures
                  ;; D / cos(pitch) on that vertical cut.
                  secP  (/ rafter (cos pitch))
                  xTail (- xSh over)
                  ;; The underside falls away from the bearing as it runs out.
                  yTailB (- yWall (* over tanP))
                  yTailT (+ yTailB secP))

            ;; Rafter: underside from the tail up past the wall to the shown
            ;; edge, plumb cut at the tail, top edge parallel.
            (WallSect:Box org
                (list (list xTail yTailB)
                      (list shown (+ yWall (* (- shown xSh) tanP)))
                      (list shown (+ yWall (* (- shown xSh) tanP) secP))
                      (list xTail yTailT)))

            ;; Fascia on the plumb cut.
            (WallSect:Layer "WALLSECT-FINISH")
            (WallSect:Box org
                (list (list (- xTail plate) yTailB) (list xTail yTailB)
                      (list xTail yTailT) (list (- xTail plate) yTailT)))

            ;; Soffit, level from the fascia back to the sheathing.
            (WallSect:Box org
                (list (list (- xTail plate) yTailB)
                      (list xSh yTailB)
                      (list xSh (- yTailB plate))
                      (list (- xTail plate) (- yTailB plate))))

            ;; ================= INSULATION ===================================
            (if (= insul "Yes")
                (progn
                    (WallSect:Layer "WALLSECT-INSUL")
                    ;; Wall cavity: runs UP between the plates. The cavity width
                    ;; is measured 90 degrees anticlockwise from "up", which is
                    ;; toward the outside - exactly the stud depth.
                    (WallSect:Batt org (list xStud yPlateT) (* pi 0.5)
                                   (- yTopB yPlateT) stud)
                    ;; Floor cavity: runs INBOARD between the joists, and its
                    ;; width measured anticlockwise from "inboard" is upward -
                    ;; the joist depth.
                    (WallSect:Batt org (list (+ xSh plate) yJoistB) 0.0
                                   (- shown (+ xSh plate)) joist)))

            ;; ================= BREAK LINES ==================================
            (WallSect:Layer "WALLSECT-WOOD")
            (WallSect:Break org shown yFtgT ySlabT)
            (WallSect:Break org shown yJoistB yFloor)
            (WallSect:Break org shown (+ yWall (* (- shown xSh) tanP))
                                      (+ yWall (* (- shown xSh) tanP) secP))

            (princ (strcat "\nWall section drawn - " (rtos stud 2 2) " studs, "
                           (rtos wallHt 2 2) " wall, "
                           (rtos rise 2 1) " in " (rtos run 2 1) " roof, "
                           (rtos over 2 2) " eave."))
        )
    )

    (WallSect:Restore)
    (princ)
)

(princ)
