AutoLISP × AutoCAD × אדריכלים ישראלים

הנשק הסודי של
האדריכל המקצועי

AutoLISP מאפשרת לאוטמט כל פעולה חוזרת ב-AutoCAD. אדריכל שיודע לכתוב — או להזמין — את הכלים הנכונים, מוציא פרויקט בחצי הזמן עם 90% פחות שגיאות.

40+
שעות חסכון
לפרויקט ממוצע
95%
הפחתת שגיאות
בתזמונים
15
כלים קריטיים
במדריך זה
₪0
עלות הכלי
מובנה ב-AutoCAD

💰 כמה כסף AutoLISP שווה למשרד שלך?

חסכון שנתי משוער
₪0
0 שעות עבודה
    סנן:
    📦

    הורד את כל הכלים

    15 קבצי LSP מוכנים לשימוש — הורד הכל כ-ZIP אחד, או כל קובץ בנפרד עם כפתור ⬇ .lsp

    מדריך מקרי קצה — מוכח מה-Docs

    קריטי לקרוא Autodesk Official Docs

    כל מקרה קצה שתמצא כאן מתועד רשמית ב-Autodesk AutoLISP Developer's Guide / DXF Reference — לא השערה, לא ניסיון. עם קישורים למקורות.

    ⚠ INSUNITS — 25 ערכים (0–24), לא 0–6!
    INSUNITS=0 (Unitless) גורם ל-INSUNITSDEFSOURCE / INSUNITSDEFTARGET לקחת שליטה — ערכים ב-Registry, לא ב-DWG. ציורים ישנים מ-DXF import או AutoSketch בדרך כלל מגיעים עם INSUNITS=0. מקור: Autodesk 2025 INSUNITS
    ערךיחידהפקטור → מ"רשכיח בישראל
    0Unitless⛔ ABORT — קרא INSUNITSDEFSOURCEDXF ייבוא / AutoSketch
    1Inches6.4516e-4לא
    2Feet9.2903e-2לא
    4Millimeters ★1.0e-6✅ רוב המשרדים
    5Centimeters1.0e-4נדיר
    6Meters1.0✅ מדידות / GIS
    7Kilometers1.0e6GIS
    21-24US Survey Feet/Inch/Yard/Mileלא
    insunits-guard.lsp
    ;;; INSUNITS-GUARD.LSP — בדיקת יחידות + INSUNITSDEFSOURCE fallback
    ;;; מקור: Autodesk 2025 System Variables Reference
    
    (defun al:unit-factor (/ ival defsrc)
      ;; INSUNITS stored IN drawing; INSUNITSDEFSOURCE in Registry
      (setq ival (getvar "INSUNITS"))
      (if (zerop ival)
        (progn
          ;; INSUNITSDEFSOURCE / INSUNITSDEFTARGET: Registry-stored fallback
          ;; when INSUNITS=0 and source drawing has no unit setting.
          ;; ⚠ Default registry value is 1 (inches) — may cause 25.4x error!
          (setq defsrc (getvar "INSUNITSDEFSOURCE"))
          (prompt (strcat
            "
    ⚠ INSUNITS=0 (Unitless). Using INSUNITSDEFSOURCE="
            (itoa defsrc)
            ". Verify drawing units!"))
          (setq ival defsrc)))
      (cdr (assoc ival
        '((0  . nil  )   ; still nil if defsrc also 0
          (1  . 6.4516e-4) (2  . 9.2903e-2) (3  . 2.5900e6)
          (4  . 1.0e-6 )   ; mm² → m² ← ISRAEL
          (5  . 1.0e-4 )   (6  . 1.0      ) (7  . 1.0e6  )
          (8  . 6.45e-16)  (9  . 6.45e-10 ) (10 . 8.3613e-1)))))
    
    ⚠ BULGE — חישוב שטח ידני של פוליליין עם קשתות = שגיאה!
    BULGE = tan(θ/4). חישוב vertex-by-vertex ידני מדלג על תרומת הקשת. השתמש תמיד ב-VLA Area property. מקור: DXF LWPOLYLINE Reference
    BULGEמשמעות גיאומטרית
    0קטע ישר
    >0קשת CCW (נגד השעון)
    <0קשת CW (עם השעון)
    1 או -1חצי עיגול (θ = 180°), כי tan(π/4)=1
    כמותנוסחה
    זווית מרכזית θθ = 4 × arctan(|B|)
    רדיוס rr = chord × (1 + B²) / (4 × |B|)
    אורך קשתL = r × θ
    שטח פלח (segment)A = (r² / 2) × (θ − sin θ)
    ⚠ נוסחה שגויה נפוצהθ = 2 × arctan(B) — שגוי! Lee Mac docs v1.0 had typo
    bulge-area.lsp
    ;;; BULGE-AREA.LSP — שטח נכון לפוליליין עם קשתות
    ;;; מקור: DXF Reference — LWPOLYLINE group codes
    
    (defun al:pline-area-safe (ename / err a)
      "חישוב שטח דרך VLA (מטפל ב-BULGE אוטומטית)"
      (vl-load-com)
      (setq err
        (vl-catch-all-apply
          '(lambda (en)
             (vlax-get-property
               (vlax-ename->vla-object en) 'Area))
          (list ename)))
      (if (vl-catch-all-error-p err)
        ;; fallback — AREA command (also handles bulge correctly)
        (progn (command "._AREA" "_Object" ename "") (getvar "AREA"))
        err))
    
    ;;; Check for arc segments:
    (defun al:has-bulge-p (ename)
      (vl-some '(lambda (x) (and (= (car x) 42) (not (zerop (cdr x)))))
               (entget ename)))
    
    ⚠ ssget wildcard — הבאג הכי שכיח: (cons 2 "*DOOR*") בוחר הכל!
    ה-* בתוך filter string מתפקד כ-wildcard (כמו wcmatch). כדי לחפש ב-block name שמכיל *, חייב backtick escape. מקור: Lee Mac — Escape Wildcards
    כתיבהתוצאה בפועלנכון?
    (cons 0 "insert")0 תוצאות — lowercase לא עובד!
    (cons 0 "INSERT")כל הבלוקים
    (cons 2 "*DOOR*")בוחר כל בלוק (wildcard!)
    (cons 2 "DOOR*,DR_*")prefix match — עובד
    (cons 2 "\`*U*")literal * ב-name (anonymous blocks)
    (cons 67 0) או חסרmodel space בלבד
    (cons 67 1)paper space בלבד
    ssget "X" ללא (cons 67)מחזיר entities מ-שני המרחבים!
    ssget-safe.lsp
    ;;; ssget patterns — מוכח מ-Autodesk Docs
    
    ;; ✅ Model space only + type filter
    (ssget "X" '((0 . "INSERT") (67 . 0)))
    
    ;; ✅ Multiple types
    (ssget "X" '((0 . "LINE,ARC,CIRCLE") (67 . 0)))
    
    ;; ✅ Block name prefix — CORRECT
    (ssget "X" '((0 . "INSERT") (2 . "DOOR*,DR_*,DLT*")))
    
    ;; ❌ WRONG — * selects ALL blocks:
    ;; (ssget "X" '((0 . "INSERT") (2 . "*DOOR*")))
    
    ;; ✅ CORRECT for "contains DOOR":
    ;; Post-filter with wcmatch after ssget:
    (defun al:ss-by-blockname (pattern / ss i en result)
      (setq ss (ssget "X" '((0 . "INSERT") (67 . 0)))
            result (ssadd))
      (if ss
        (progn
          (setq i 0)
          (while (< i (sslength ss))
            (setq en (ssname ss i))
            (if (wcmatch (strcase (cdr (assoc 2 (entget en)))) pattern)
              (ssadd en result))
            (setq i (1+ i)))))
      result)
    
    ;; Usage: (al:ss-by-blockname "*DOOR*,*DR_*")
    
    ⚠ entmod ללא entupd — display לא מתעדכן ב-POLYLINE ו-ATTRIB!
    LWPOLYLINE = entity אחד → entmod מספיק. POLYLINE ישן + ATTRIB = subentities → חייב (entupd INSERT-ENAME) אחרי. מקור: Autodesk entupd reference
    Entity typeentmod בלבדצריך entupd?
    LWPOLYLINE (single entity)✅ מספיקלא נדרש (מומלץ בכל זאת)
    POLYLINE + VERTEX subentities❌ display לא מתעדכן✅ חובה: (entupd polyline-ename)
    INSERT + ATTRIB subentities❌ attribute לא מתעדכן✅ חובה: (entupd insert-ename)
    LINE, ARC, CIRCLE, TEXT, MTEXT✅ מספיקלא נדרש
    INSERT (המאפיינים עצמם)✅ מספיקלא נדרש
    ⚠ group code 62 — BYLAYER = absent מ-entget, לא 0!
    entity ב-BYLAYER לא מחזיר group code 62 כלל. (assoc 62 elist) = nil. כדי לחזור ל-BYLAYER: (cons 62 256). אם קיים True Color (code 420), הוא גובר על 62! מקור: Autodesk DXF Reference
    Group codeמשמעותערך BYLAYERערך BYBLOCK
    62ACI Color indexabsent (nil) / 2560
    6Linetype nameabsent / "BYLAYER""BYBLOCK"
    370Lineweight (-3,-2,-1,0–211)absent / -2-2
    420True Color (RGB integer) — גובר על 62!
    8Layer nameתמיד נוכח
    color-fix.lsp
    ;;; תיקון צבע entity ל-BYLAYER — כולל הסרת True Color
    (defun al:set-bylayer (ename / ed)
      (setq ed (entget ename))
      ;; ⚠ MUST remove true color (420) first — it overrides ACI (62)
      (setq ed (vl-remove (assoc 420 ed) ed))
      (setq ed (vl-remove (assoc 62  ed) ed))
      ;; (cons 62 256) = explicit BYLAYER; absent also means BYLAYER
      ;; We set explicit 256 for clarity:
      (setq ed (append ed (list (cons 62 256))))
      (entmod ed))
    
    ⚠ Attributes — group code 1 מופיע פעמיים! (assoc 1 ...) לוקח את הראשון
    ATTRIB entity מכיל שני group code 1: הראשון = internal storage, האחרון = displayed value. תמיד השתמש ב-(assoc 1 (reverse elist)). מקור: Lee Mac Attribute Functions
    attrib-update.lsp
    ;;; ATTRIB-UPDATE.LSP — קריאה ועדכון attributes בצורה נכונה
    ;;; ⚠ (assoc 1 elist) = internal copy (might be old value)
    ;;; ✅ (assoc 1 (reverse elist)) = displayed value (last occurrence)
    
    (defun al:get-attrib-value (attrib-ename / ed)
      (setq ed (entget attrib-ename))
      (cdr (assoc 1 (reverse ed))))  ; last group-code-1 = display value
    
    (defun al:set-attrib-value (attrib-ename new-val / ed old-entry)
      (setq ed (entget attrib-ename))
      ;; Replace LAST occurrence of group code 1:
      (setq ed (reverse ed))
      (setq old-entry (assoc 1 ed))
      (setq ed (subst (cons 1 new-val) old-entry ed))
      (setq ed (reverse ed))
      (entmod ed))
    
    ;;; Walk INSERT → ATTRIB subentities:
    (defun al:update-block-attrib (ins-ename tag new-val / en ed)
      (setq en (entnext ins-ename))  ; first subentity
      (while (and en (not (equal (cdr (assoc 0 (entget en))) "SEQEND")))
        (setq ed (entget en))
        (when (and (equal (cdr (assoc 0 ed)) "ATTRIB")
                   (equal (strcase (cdr (assoc 2 ed)))
                          (strcase tag)))
          (al:set-attrib-value en new-val))
        (setq en (entnext en)))
      (entupd ins-ename))  ; ⚠ MUST call entupd on INSERT after all changes!
    
    ⚠ Coordinate systems — getpoint מחזיר UCS, entget מחזיר OCS!
    distance() ו-angle() עובדים ב-WCS בלבד. תמיד המר נקודות לפני חישוב. מקור: Autodesk Coordinate Transformations
    מקורמה מוחזרהמרה ל-WCS
    getpoint / getcornerUCS (1)(trans pt 1 0)
    entget group 10OCS של ה-entity(trans pt ename 0)
    getvar "EXTMIN/MAX"WCS (0)לא צריך
    polar / angle / distanceמצפים ל-WCS (0)המר לפני!
    coords-safe.lsp
    ;;; קוד נכון לחישוב מרחק בין נקודות
    
    ;; ❌ WRONG — if UCS ≠ WCS, distance is wrong:
    ;; (setq d (distance (getpoint) (getpoint)))
    
    ;; ✅ CORRECT:
    (defun al:distance-ucs (p1 p2)
      "מרחק בין שתי נקודות ב-UCS, חישוב ב-WCS"
      (distance (trans p1 1 0) (trans p2 1 0)))
    
    ;; ✅ Reading entity point and using it:
    (defun al:entity-center (ename / ed center)
      (setq ed (entget ename))
      ;; group 10 is in OCS — convert to WCS:
      (trans (cdr (assoc 10 ed)) ename 0))
    
    משתנהערכיםEdge case קריטי
    CMDECHO0/1שכחה = command line שקט לנצח
    CECOLORstring/intBYLAYER=256 (absent); True color 420 גובר!
    EXPERT0–52 = suppress BLOCK redefine; 5 = suppress הכל
    HPASSOC0/11 = associative; ⚠ entmod על boundary = reactor loop!
    DIMASSOC0/1/22 = fully associative (2002+); 0 = exploded dims
    INSUNITSDEFSOURCE0–20 (Registry!)fallback when INSUNITS=0; default=1 (inches) → 25.4x error!
    MIRRTEXT0/10 = text stays readable after MIRROR; 1 = mirrors
    OSMODEbitcodesetvar 0 = כבה snap; שכחה = snap כבוי לנצח
    ⚠ WIPEOUT ≠ SOLID — entity מסוג WIPEOUT (DXF type=WIPEOUT) הוא IMAGE reference פנימי. ssget ב-type 0 לא מחזיר אותם אלא אם מציינים במפורש (cons 0 "WIPEOUT")!
    בעיהסיבהפתרון
    WIPEOUT מודפס עם מסגרת WIPEOUTFRAME=1 (ברירת מחדל) (setvar "WIPEOUTFRAME" 0) לפני PLOT; שחזר אחרי
    ssget "X" מפספס WIPEOUT WIPEOUT לא entity "רגיל" — IMAGE reference (ssget "X" (list (cons 0 "WIPEOUT")))
    entmod על WIPEOUT frame נכשל group 280 = frame visibility; read-only ב-entmod השתמש ב-(setvar "WIPEOUTFRAME" n) בלבד
    WIPEOUT נעלם ב-PUBLISH/PLOT PDF driver PDF גרסה ישנה לא תומך בWIPEOUT DWG to PDF v6+ / AutoCAD PDF plotter; בדוק גרסה
    layer freeze מסתיר WIPEOUT גם בPDF WIPEOUT כן מושפע מ-layer freeze (בניגוד ל-SOLID) ודא WIPEOUT על layer שאינו frozen לפני PLOT
    AutoLISPwipeout-frameoff.lsp
    (defun c:WipeoutPlotFix ( / ss old-frame)
      ;; מכבה WIPEOUT frames לפני plot ומשחזר אחרי
      (setq old-frame (getvar "WIPEOUTFRAME"))
      (setvar "WIPEOUTFRAME" 0)
      (command "_REGEN")
      (princ (strcat "\n[OK] WIPEOUTFRAME כבוי (היה " (itoa old-frame) ")"))
      (princ "\nהרץ PLOT ואז: ")
      (princ "\n(setvar \"WIPEOUTFRAME\" " )
      (princ old-frame)(princ ")")
      (princ)
    )
    
    ;; בדיקת כל ה-WIPEOUTs בציור
    (defun c:ListWipeouts ( / ss i ent)
      (setq ss (ssget "X" (list (cons 0 "WIPEOUT"))))
      (if ss
        (progn
          (princ (strcat "\nנמצאו " (itoa (sslength ss)) " WIPEOUT entities:"))
          (setq i 0)
          (while (< i (sslength ss))
            (setq ent (ssname ss i))
            (princ (strcat "\n  [" (itoa (1+ i)) "] "
                           (cdr (assoc 8 (entget ent)))))  ; layer
            (setq i (1+ i))
          )
        )
        (princ "\nאין WIPEOUT בציור")
      )
      (princ)
    )
    
    ⚠ BACKGROUNDPLOT=1 שובר scripts! — כש-BACKGROUNDPLOT פועל, (command ".plot" ...) מחזיר מיד בלי לחכות לסיום ה-plot. הקוד ממשיך לרוץ בזמן שה-plot עדיין בתהליך → קבצים חסרים / incomplete PDF.
    BACKGROUNDPLOTהתנהגותמתי להשתמש
    0 Foreground — command נחסמת עד סיום ה-plot חובה בscripts/AutoLISP
    1 Background — שולח ל-background, מחזיר מיד משתמש ידני בלבד
    2 Background + status balloon משתמש ידני בלבד
    EXPERT + BACKGROUNDPLOT — שלב עם (setvar "EXPERT" 2) כדי לדכא dialog box "save over existing file?" בזמן plot לקובץ קיים.
    בעיה נוספתסיבהפתרון
    Batch plot לא מסיים BACKGROUNDPLOT=1 → כל plot async (setvar "BACKGROUNDPLOT" 0) בתחילת הscript
    PDF ריק / 0 bytes קובץ נפתח לפני שה-plot הסתיים BACKGROUNDPLOT 0 + vla-Regen לפני plot
    Dialog "file exists" קופץ באמצע EXPERT=0 ברירת מחדל (setvar "EXPERT" 2) לפני, שחזר אחרי
    Script .scr נכשל בגלל plot ב-.scr : BACKGROUNDPLOT גם כן חייב 0 הוסף SETVAR BACKGROUNDPLOT 0 בשורה ראשונה
    AutoLISPsafe-batch-plot.lsp
    (defun safe-plot-to-pdf (layout-name output-path / old-bgplot old-expert)
      ;; שמור מצב ישן
      (setq old-bgplot (getvar "BACKGROUNDPLOT")
            old-expert  (getvar "EXPERT"))
      ;; הגדרות בטוחות לscript
      (setvar "BACKGROUNDPLOT" 0)
      (setvar "EXPERT" 2)           ; suppress "overwrite?" dialogs
      (setvar "CMDECHO" 0)
      ;; בצע את ה-plot
      (command "_.-PLOT"
        "Yes"           ; detailed plot config?
        layout-name     ; layout name
        ""              ; page setup = current
        "DWG To PDF.pc3"
        "ISO A1"
        "Millimeters"
        "Landscape"
        "No"
        "Extents"
        "Fit"
        "Center"
        "Yes"
        output-path
        "Yes"           ; overwrite?
      )
      ;; שחזר
      (setvar "BACKGROUNDPLOT" old-bgplot)
      (setvar "EXPERT" old-expert)
      (setvar "CMDECHO" 1)
      (princ (strcat "\n[OK] Plot הסתיים: " output-path))
    )
    
    ⚠ (strlen "שלום") = 8, לא 4! — AutoLISP strlen מחזיר bytes, לא תווים. עברית ב-UTF-8 = 2 bytes לתו. השתמש ב-VLA vla-get-textstring + (length (vl-string->list ...)) לספירה נכונה.
    בעיהסיבהפתרון
    (strlen "שלום") = 8 strlen = bytes; עברית UTF-8 = 2 bytes/תו לספירת תווים: (/ (strlen s) 2) ⚠ רק אם כל-עברית
    wcmatch "שלום*" unreliable wcmatch לא Unicode-aware לפני 2021 השתמש ב-(vl-string-search pat str) לחיפוש עברי
    entmod group 1 = עברית נכשל קובץ DXF בפורמט ישן (לפני R2007) ודא SAVEAS R2010+ לפני entmod עם Unicode
    ATTRIB מציג ??? במקום עברית font לא תומך Unicode / BIGFONT חסר השתמש ב-Arial/David עם TEXTFONT; הגדר BIGFONT=""
    substr על string עברי חותך תו באמצע substr פועל על bytes, לא chars הכפל אינדקס ב-2: (substr s (* 2 start) (* 2 len))
    atoi/atof על מספר עברי מוחזר שגוי קידוד BOM בראש string מבלבל atof strip BOM: (if (= (ascii s) 65279) (substr s 2) s)
    AutoLISPhebrew-attrib-safe.lsp
    ;; כלים בטוחים לטיפול בעברית ב-AutoLISP
    
    (defun heb-strlen (s)
      ;; אורך תווים (לא bytes) — מניח UTF-8 עברית טהורה
      (/ (strlen s) 2)
    )
    
    (defun heb-substr (s start len)
      ;; substr בטוח לעברית (start ו-len ב-תווים, לא bytes)
      (substr s (1+ (* 2 (1- start))) (* 2 len))
    )
    
    (defun strip-bom (s)
      ;; מסיר BOM (U+FEFF) שמופיע לפעמים בקבצי DXF
      (if (and (>= (strlen s) 3)
               (= (substr s 1 3) "\xEF\xBB\xBF"))
        (substr s 4)
        s)
    )
    
    (defun heb-search (pattern str)
      ;; חיפוש עברי בטוח — (vl-string-search) Unicode-safe
      (vl-string-search pattern str)
    )
    
    (defun c:FixHebrewAttribs ( / ss i ent atts a val)
      ;; דוגמה: עדכן כל ATTRIB שמכיל "גוש" עם נתוני ITM
      (setq ss (ssget "_X" (list (cons 0 "INSERT"))))
      (if (not ss)
        (progn (princ "\nאין INSERT blocks") (exit)))
      (setq i 0)
      (while (< i (sslength ss))
        (setq ent (ssname ss i))
        (setq atts (vlax-invoke
                     (vlax-ename->vla-object ent)
                     'GetAttributes))
        (foreach a (vlax-safearray->list atts)
          (setq val (vla-get-textstring a))
          ;; בדוק אם מכיל "גוש"
          (if (heb-search "גוש" val)
            (princ (strcat "\nBlock " (cdr (assoc 2 (entget ent)))
                           " — attr: " val))
          )
        )
        (setq i (1+ i))
      )
      (princ)
    )
    

    חישוב שטחים אוטומטי

    חישוב שטח כל חדר, החלת מקדמים ישראלים (ממ"ד, מרפסת, חצר), ייצוא לאקסל

    קריטי חוסך 8 שעות/פרויקט
    😫 הכאב ללא האוטומציה

    לפני כל הגשה לרשות הרישוי — מדידה ידנית של 80 חדרים, החלת מקדמים שונים לכל סוג שטח, סיכום ב-Excel ידני. שינוי קיר אחד = מדידה מחדש של כל הקומה.

    שבוע לפני הגשה ומצאנו שגיאת 12 מ"ר בחישוב. שלושה ימים של תיקון.
    room-area-schedule.lspAutoLISP
    ;;; ROOM-AREA-SCHEDULE.LSP v2.4
    ;;; מחשב שטחי חדרים ומייצר דו"ח לפי תקן ישראלי
    ;;; הפעלה: (c:RAS) או הקלד RAS בשורת הפקודה
    
    (defun c:RAS (/ *err-cmdecho* *err-osmode* ss i en ed area unit-factor results total)
    
      ;;; ── Error handler: מחזיר AutoCAD למצב תקין תמיד ──
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (setvar "CMDECHO" *err-cmdecho*)
        (setvar "OSMODE"  *err-osmode*)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort"))
          (alert (strcat "שגיאה: " msg "\nכל שינויים בוטלו.")))
        (princ))
    
      (setq *err-cmdecho* (getvar "CMDECHO")
            *err-osmode*  (getvar "OSMODE"))
      (setvar "CMDECHO" 0)
      (setvar "OSMODE"  0)
      (command "._UNDO" "_BEGIN")
      (vl-load-com)
    
      ;;; ── EDGE CASE 1: בדיקת יחידות ──
      ;;; INSUNITS 4=mm, 5=cm, 6=m — טעות = שגיאה פי 1,000,000!
      (setq unit-factor
        (cond
          ((= (getvar "INSUNITS") 4) 1e-6)   ; mm² -> m²
          ((= (getvar "INSUNITS") 5) 1e-4)   ; cm² -> m²
          ((= (getvar "INSUNITS") 6) 1.0)    ; m²  -> m² ✓
          ((= (getvar "INSUNITS") 1) 10.764) ; ft² -> m²
          (T ; ⚠ יחידות לא ידועות — שאל משתמש
            (progn
              (initget "מטרים מילימטרים")
              (if (= (getkword "\n⚠ יחידות? [מטרים/מילימטרים]: ") "מטרים") 1.0 1e-6)))))
    
      ;;; ── בחירת polylines של גבולות חדרים ──
      (setq ss (ssget "X" (list (cons 0 "LWPOLYLINE")
                                 (cons 8 "A-ROOM-BOUND,ROOM-BOUNDARY,0-ROOMS,AREAS"))))
    
      (if (null ss)
        (progn (alert "לא נמצאו polylines!\nשכבות נתמכות: A-ROOM-BOUND | ROOM-BOUNDARY | 0-ROOMS")
               (*error* "Function cancelled")))
    
      (setq i 0  total 0.0  results '())
    
      (repeat (sslength ss)
        (setq en (ssname ss i)
              ed (entget en))
    
        ;;; ── EDGE CASE 2: Polyline פתוח — דלג + אזהרה ──
        (if (zerop (logand (cdr (assoc 70 ed)) 1))
          (princ (strcat "\n⚠ Polyline פתוח, דולג — handle: " (cdr (assoc 5 ed))))
    
          (progn
            ;;; ── EDGE CASE 3: קשתות (bulge != 0) — חייב REGION לדיוק ──
            (setq has-arc nil)
            (foreach g ed (if (and (= (car g) 42) (/= (cdr g) 0.0)) (setq has-arc T)))
    
            (if has-arc
              ;;; קשתות: המר ל-REGION (מדויק יותר מ-AREA עם קשתות)
              (progn
                (setq err (vl-catch-all-apply 'command (list "._REGION" en "")))
                (if (vl-catch-all-error-p err)
                  (progn (princ "\n⚠ נכשל ב-REGION (polyline חותך עצמו?)") (setq area 0.0))
                  (progn
                    (command "._AREA" "_O" (entlast))
                    (setq area (* (getvar "AREA") unit-factor))
                    (command "._ERASE" (entlast) ""))))
              ;;; ללא קשתות: AREA ישיר
              (progn (command "._AREA" "_O" en)
                     (setq area (* (getvar "AREA") unit-factor))))
    
            ;;; ── EDGE CASE 4: שטח = 0 או שלילי ──
            (cond
              ((< area 0.0)   (princ (strcat "\n⚠ שטח שלילי! polyline הפוך — " (cdr (assoc 5 ed)))))
              ((< area 0.01)  (princ (strcat "\n⚠ שטח < 0.01 מ\"ר, דולג — " (cdr (assoc 5 ed)))))
              (T
               (setq rdata  (get-nearest-tag en)
                     rtype  (or (car rdata) "ROOM")
                     rname  (or (cadr rdata) "חדר לא מסומן")
                     rnum   (or (caddr rdata) "???"))
    
               ;;; ── מקדמי חישוב ישראלי (צו מדידה) ──
               (setq il-factor
                 (cond
                   ((wcmatch rtype "MAMAD,ממד*")       1.00) ; ממ"ד = 100%
                   ((wcmatch rtype "BALCONY,מרפסת*")  0.50) ; מרפסת = 50%
                   ((wcmatch rtype "GARDEN,חצר*")     0.25) ; חצר = 25%
                   ((wcmatch rtype "STORAGE,מחסן*")   0.50) ; מחסן = 50%
                   ((wcmatch rtype "PARKING,חניה*")   0.00) ; חניה = 0%
                   (T                                  1.00)))
    
               (setq total (+ total area))
               (setq results (append results
                 (list (list rnum rname rtype area il-factor (* area il-factor)))))))))
    
        (setq i (1+ i))
        (princ (strcat "\r עיבוד " (itoa i) "/" (itoa (sslength ss)))))
    
      ;;; ── ייצוא: אקסל > CSV > טבלה בציור ──
      (cond
        ((ras-export-excel results total) (princ "\n✓ יוצא לאקסל"))
        (T (ras-export-csv results total) (princ "\n✓ יוצא ל-CSV")))
    
      (command "._UNDO" "_END")
      (setvar "CMDECHO" *err-cmdecho*)
      (setvar "OSMODE"  *err-osmode*)
      (princ (strcat "\n✓ " (itoa (length results)) " חדרים | סה\"כ " (rtos total 2 2) " מ\"ר"))
      (princ))
    ⚠ מקרי קצה — חישוב שטחים
    מקרהגורםתסמיןפתרון
    קריטי יחידות mm vs mINSUNITS=4 במקום 6שטח גדול פי 1,000,000בדוק INSUNITS, הכפל unit-factor
    קריטי Polyline עם קשתותbulge ≠ 0 בקבוצה 42AREA מחזיר שגויהמר ל-REGION לפני חישוב
    גבוה Polyline פתוחflag 70 bit-1 = 0שטח לא נסגר = שגויבדוק logand flag 70
    גבוה Polyline חותך עצמוself-intersectingREGION נכשלvl-catch-all-apply + דיווח
    גבוה חדר עם עמוד בפניםopening inside roomשטח מנופחRegion subtract עמודות
    בינוני INSUNITS=0 (ללא יחידות)ציור ישן/לא מוגדרחישוב לא ידועשאל משתמש, default=מטרים
    בינוני ישויות XREFpolyline בXREFלא ניתן להמיר ל-REGIONקרא שטח מ-XREF document
    נמוך שטח שליליpolyline counterclockwiseשטח < 0abs() וסמן אזהרה
    🇮🇱 מקדמי חישוב לפי חוק ישראלי
    • ממ"ד: 100% — נספר בשטח עיקרי (לא שטח שירות) לפי חוק המכר
    • מרפסת מקורה: 50% לחישוב זכויות בנייה, 100% לעניין מכירה לפי תיקון 3/2014
    • מרפסת פתוחה: 50% לכל מטרה
    • חצר פרטית: 25% (תלוי תב"ע — ייתכן 0% בתוכניות ישנות)
    • מחסן: בדרך כלל 50% — תלוי ייעוד (שירות/עיקרי)
    • חניה: 0% בשטח דירה, נחשב נפרד
    • חדר גג: 50% או 25% לפי תב"ע ספציפית
    • מדידה: קיר חיצוני — עד ציר הקיר; קיר משותף — עד ציר הקיר

    תזמון דלתות וחלונות

    מיצוי attributes מבלוקים, מספור אוטומטי, בדיקת נגישות ועמידות אש

    קריטי חוסך 6 שעות/פרויקט
    😫 הכאב ללא האוטומציה

    מגדל מגורים עם 250 דלתות. כל רוויזיה מחייבת עדכון ידני של תזמון — מספרים, מידות, חדר מקור/יעד. שגיאה אחת = קבלן מסמן דלת לא נכונה בשטח.

    בתוכנית המתוקנת הזזנו 3 דירות. מצאנו שגיאות בתזמון רק בשלב ביצוע.
    door-window-schedule.lspAutoLISP
    ;;; DOOR-WINDOW-SCHEDULE.LSP v3.1
    ;;; מייצר תזמון מלא — דלתות + חלונות עם attributes
    
    (defun c:DWS (/ *err-cmdecho* ss i en ed bname attrs all-items cnt)
    
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (setvar "CMDECHO" *err-cmdecho*)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort"))
          (alert (strcat "שגיאה: " msg)))
        (princ))
    
      (setq *err-cmdecho* (getvar "CMDECHO"))
      (setvar "CMDECHO" 0)
      (command "._UNDO" "_BEGIN")
      (vl-load-com)
    
      ;;; ── בחר כל INSERT, סנן לפי שם בלוק ──
      ;;; ⚠ EDGE CASE: ssget לא תומך wildcard על group 2!
      ;;; חייבים post-filter עם wcmatch
      (setq ss (ssget "X" (list (cons 0 "INSERT"))))
      (if (null ss) (progn (alert "אין בלוקים בציור!") (*error* "Function cancelled")))
    
      (setq doors-ss (ssadd)  i 0)
      (repeat (sslength ss)
        (setq en (ssname ss i)
              ed (entget en))
    
        ;;; ⚠ EDGE CASE: Dynamic block — שם אפקטיבי שונה מהשם הרגיל
        (setq bname
          (vl-catch-all-apply 'vlax-get-property
            (list (vlax-ename->vla-object en) 'EffectiveName)))
        (if (vl-catch-all-error-p bname)
          (setq bname (cdr (assoc 2 ed)))  ; fallback: שם סטטי
        )
        ;;; סנן: שם מכיל DOOR, WIN, DR, WN
        (if (wcmatch (strcase (vl-catch-all-apply 'identity (list bname)))
                     "DOOR*,WIN*,DR_*,WN_*,*_DOOR,*_WIN,*DOOR*,*WIN*")
          (ssadd en doors-ss))
        (setq i (1+ i)))
    
      (if (zerop (sslength doors-ss))
        (progn
          (alert "לא נמצאו בלוקים של דלתות/חלונות.\nבדוק: שמות בלוקים מכילים DOOR/WIN/DR/WN")
          (*error* "Function cancelled")))
    
      (setq i 0  cnt 1  all-items '())
    
      (repeat (sslength doors-ss)
        (setq en (ssname doors-ss i)
              ed (entget en))
    
        ;;; ── קריאת attributes — חייב לעבור entnext ──
        ;;; ⚠ EDGE CASE: attribs לא מופיעים ב-entget של ה-INSERT!
        (setq attrs (get-block-attribs en))
    
        (setq dr-num  (or (cdr (assoc "DR_NUMBER"      attrs)) ""))
        (setq dr-w    (or (cdr (assoc "DR_W"           attrs)) "???"))
        (setq dr-h    (or (cdr (assoc "DR_H"           attrs)) "???"))
        (setq dr-type (or (cdr (assoc "DR_TYPE"        attrs)) "SWING"))
        (setq dr-fire (or (cdr (assoc "DR_FIRE_RATING" attrs)) "-"))
        (setq dr-mat  (or (cdr (assoc "DR_MATERIAL"    attrs)) "-"))
    
        ;;; ── מספר אוטומטי אם ריק ──
        (if (or (null dr-num) (= dr-num "") (= dr-num "AUTO"))
          (progn
            (setq dr-num (strcat "D" (itoa cnt)))
            (set-block-attrib en "DR_NUMBER" dr-num)
            (setq cnt (1+ cnt))))
    
        ;;; ── EDGE CASE: בדיקת נגישות IS 1918 ──
        ;;; רוחב מינ' 800mm לדירה, 900mm לציבורי
        (setq access-ok
          (if (and (numberp (atoi dr-w)) (> (atoi dr-w) 0))
            (>= (atoi dr-w) 800)
            T))  ; ⚠ אם לא ניתן לקרוא — לא מסמן שגיאה
    
        ;;; ── מצא חדר ──
        (setq insert-pt (cdr (assoc 10 ed)))
        (setq room-name (find-room-at-point insert-pt))
    
        (setq all-items (append all-items
          (list (list dr-num dr-type dr-w dr-h dr-mat dr-fire room-name
                      (if access-ok "✓" "⚠ נגישות!")))))
    
        (setq i (1+ i))
        (princ (strcat "\r " (itoa i) "/" (itoa (sslength doors-ss)))))
    
      (dws-export-excel all-items)
    
      (command "._UNDO" "_END")
      (setvar "CMDECHO" *err-cmdecho*)
      (princ (strcat "\n✓ " (itoa (length all-items)) " פתחים"))
      (princ))
    
    ;;; ── Helper: קרא כל attributes מבלוק ──
    (defun get-block-attribs (block-en / sub-en sub-ed attrs tag val)
      (setq attrs '()  sub-en (entnext block-en))
      (while sub-en
        (setq sub-ed (entget sub-en))
        (cond
          ((= (cdr (assoc 0 sub-ed)) "ATTRIB")
           (setq tag (strcase (cdr (assoc 2 sub-ed)))
                 val (cdr (assoc 1 sub-ed)))
           (setq attrs (append attrs (list (cons tag val)))))
          ((= (cdr (assoc 0 sub-ed)) "SEQEND")
           (setq sub-en nil)))
        (if sub-en (setq sub-en (entnext sub-en))))
      attrs)
    ⚠ מקרי קצה — תזמון פתחים
    מקרהגורםתסמיןפתרון
    קריטי ssget wildcard על group 2באג ידוע ב-AutoLISPssget מחזיר nil תמידבחר הכל + post-filter עם wcmatch
    קריטי Attributes ב-XREFבלוק בקובץ חיצונילא ניתן לקרוא attrsvlax על XREF document object
    גבוה Dynamic blockEffectiveName שונהשם בלוק לא מזוההvlax EffectiveName עם fallback
    גבוה Attributes לא מוצגיםentnext צריךentget לא מחזיר attrsentnext עד SEQEND
    גבוה דלת בשתי פריסותmodel space + paper spaceכפילויות בתזמוןסנן: entget group 67 = nil (model)
    בינוני דלת בזווית לא סטנדרטיתrotation != 0/90/180/270clearance שגויקח rotation + transform
    בינוני דלת הזזה vs צירDR_TYPE לא מוגדרחישוב clearance שגויקרא DR_TYPE attribute
    🇮🇱 תקנים ישראלים — פתחים
    • IS 1918 נגישות: דלת לדירה — רוחב מינ' 800mm; חלל מעבר לרכיסה — 900mm
    • עמידות אש (תקן 921): EI30 / EI60 / EI90 / EI120 — חייב לציין בתזמון
    • אוורור: שטח חלון ≥ 10% משטח חדר (תקנות תכנון ובנייה)
    • תאורה טבעית: שטח חלון ≥ 8% משטח חדר, עומק חדר ≤ 2.5× גובה חלון
    • ממ"ד: דלת ממ"ד — פלדה תקנית EI90D, רוחב מינ' 800mm לפי תקן 4366

    מספור חדרים ויחידות

    מספור אוטומטי לפי בניין→קומה→מיקום, פורמט B1-F03-U12, עדכון attributes ישיר

    גבוהחוסך 2 שעות/פרויקט
    😫 הכאב

    150 דירות ב-3 מגדלים. שינוי תוכנית בקומה 7 — מספור מחדש ידני של 40 יחידות, עדכון בתזמונים, עדכון בכל שרטוטי הקומה. שעתיים לפחות, ותמיד עם שגיאות.

    room-numbering.lspAutoLISP
    ;;; ROOM-NUMBERING.LSP v2.0
    ;;; מספר חדרים לפי מיקום XY: מגדל -> קומה -> X (ימין לשמאל)
    
    (defun c:RN (/ ss i en ed all-tags sorted bld flr pos)
    
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (if (not (wcmatch msg "Function cancelled,quit / exit abort"))
          (alert (strcat "שגיאה: " msg)))
        (princ))
    
      (command "._UNDO" "_BEGIN")
      (vl-load-com)
    
      ;;; בחר כל בלוקי ROOM_TAG
      (setq ss (ssget "X" (list (cons 0 "INSERT") (cons 2 "ROOM_TAG"))))
    
      ;;; ⚠ EDGE CASE: אם שם הבלוק שונה — נסה שמות נוספים
      (if (null ss)
        (setq ss (ssget "X" (list (cons 0 "INSERT") (cons 2 "UNIT_TAG,APT_TAG,ROOM-TAG")))))
      (if (null ss) (progn (alert "לא נמצאו בלוקי ROOM_TAG") (*error* "Function cancelled")))
    
      (setq i 0  all-tags '())
    
      (repeat (sslength ss)
        (setq en (ssname ss i)
              ed (entget en))
        (setq pt   (cdr (assoc 10 ed))   ; נקודת insertion
              bldg (rn-get-attrib en "BUILDING")
              flr  (rn-get-attrib en "FLOOR"))
    
        ;;; ⚠ EDGE CASE: UCS לא World — המר לWorld coordinates
        (if (/= (getvar "WORLDUCS") 1)
          (setq pt (trans pt 1 0)))  ; UCS -> WCS
    
        ;;; ⚠ EDGE CASE: בניין/קומה ריקים — נסה להסיק מ-Z ומ-X
        (if (or (null flr) (= flr ""))
          (setq flr (itoa (max 0 (fix (/ (caddr pt) 3.0))))))  ; Z/גובה_קומה
    
        (setq all-tags (append all-tags
          (list (list en pt
                      (if (or (null bldg) (= bldg "")) "1" bldg)
                      (if (or (null flr) (= flr "")) "0" flr)
                      (car pt)   ; X לסידור
                 ))))
        (setq i (1+ i)))
    
      ;;; ── מיון: מגדל -> קומה -> X (גדול לקטן = ימין לשמאל בעברית) ──
      (setq sorted
        (vl-sort all-tags
          '(lambda (a b)
             (cond
               ((< (string< (caddr a) (caddr b)) 0) T)   ; מגדל
               ((= (caddr a) (caddr b))
                (cond
                  ((< (string< (cadddr a) (cadddr b)) 0) T)  ; קומה
                  ((= (cadddr a) (cadddr b))
                   (> (nth 4 a) (nth 4 b)))))             ; X גדול ראשון (RTL)
               ))))
    
      ;;; ── שמור מספור ישן לדו"ח שינויים ──
      (setq old-nums
        (mapcar (lambda (tag) (rn-get-attrib (car tag) "UNIT_NUMBER")) sorted))
    
      ;;; ── הקצה מספרים חדשים ──
      (setq cnt 1  prev-bldg ""  prev-flr "")
      (foreach tag sorted
        (setq en   (car tag)
              bldg (caddr tag)
              flr  (cadddr tag))
        ;;; ⚠ EDGE CASE: איפוס מונה בכל קומה חדשה
        (if (or (/= bldg prev-bldg) (/= flr prev-flr))
          (setq cnt 1  prev-bldg bldg  prev-flr flr))
    
        (setq new-num (strcat "B" bldg "-F" (rn-pad flr 2) "-U" (rn-pad (itoa cnt) 2)))
        (set-block-attrib en "UNIT_NUMBER" new-num)
        (setq cnt (1+ cnt)))
    
      ;;; ── דו"ח שינויים ──
      (setq changes 0)
      (setq i 0)
      (foreach tag sorted
        (setq old (nth i old-nums)
              new (rn-get-attrib (car tag) "UNIT_NUMBER"))
        (if (and old (/= old "") (/= old new))
          (progn (princ (strcat "\n  שינוי: " old " -> " new)) (setq changes (1+ changes))))
        (setq i (1+ i)))
    
      (command "._UNDO" "_END")
      (princ (strcat "\n✓ " (itoa (length sorted)) " יחידות ממוספרות | "
                     (itoa changes) " שינויים"))
      (princ))
    
    ;;; Helper: pad number string to N digits
    (defun rn-pad (s n)
      (while (< (strlen s) n) (setq s (strcat "0" s))) s)
    
    ;;; Helper: קרא attribute ספציפי מבלוק
    (defun rn-get-attrib (en tag / sub val)
      (setq sub (entnext en))
      (while (and sub (/= (cdr (assoc 0 (entget sub))) "SEQEND"))
        (setq ed (entget sub))
        (if (= (strcase (cdr (assoc 2 ed))) (strcase tag))
          (setq val (cdr (assoc 1 ed))))
        (setq sub (entnext sub)))
      val)
    ⚠ מקרי קצה — מספור חדרים
    מקרהתסמיןפתרון
    קריטי UCS לא Worldמיון לפי XY שגויtrans pt 1 0 לפני מיון
    גבוה שני תגים באותו חדרכפילות במספורproximity check — אם מרחק < 0.5m: אזהרה
    גבוה תג מחוץ לחדרשיוך לחדר הסמוךcontainment test עם polylines
    גבוה חדר ספאן 2 קומות (דופלקס)מספר כפולסמן DUPLEX_LOWER/UPPER, מספר רק LOWER
    בינוני מעלית/גרם מדרגותמקבלים מספר מיותרסנן לפי UNIT_TYPE != SHAFT/STAIR
    בינוני חדר בוטל (לא נמחק)מספר מוקצה לחדר ריקבדוק UNIT_STATUS attribute

    Batch Plot — הדפסה אוטומטית

    הדפסת כל הפריסות ל-PDF עם page setup נכון, מעקב שגיאות, stamp גרסה

    גבוהחוסך 3 שעות/הגשה
    😫 הכאב

    80 שרטוטים לפני הגשה. page setup שגוי ב-3 מהם. ה-PDF יצא בגודל A4 במקום A1. גילינו רק אחרי השליחה ללקוח.

    batch-plot.lspAutoLISP
    ;;; BATCH-PLOT.LSP v1.8
    ;;; מדפיס כל layouts של הקובץ הנוכחי ל-PDF
    
    (defun c:BP (/ layouts i layout-name pdf-path errors ok-count)
    
      (defun *error* (msg)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort"))
          (alert (strcat "שגיאה: " msg)))
        (princ))
    
      (vl-load-com)
    
      ;;; ── EDGE CASE: בדוק שמדפסת PDF קיימת ──
      (setq pdf-printer "DWG To PDF.pc3")
      (if (null (findfile pdf-printer))
        (progn
          (alert (strcat "מדפסת PDF לא נמצאה: " pdf-printer
                         "\nהתקן AutoCAD PDF printer דרך PLOTTERMANAGER"))
          (*error* "Function cancelled")))
    
      ;;; ── EDGE CASE: שמור ציור לפני הדפסה ──
      (if (= (getvar "DBMOD") 1)  ; 1 = יש שינויים לא שמורים
        (progn
          (initget "שמור בטל")
          (setq ans (getkword "יש שינויים לא שמורים. [שמור/בטל]: "))
          (if (= ans "שמור") (command "._QSAVE"))))
    
      ;;; קבל תיקיית פלט
      (setq out-dir (getfiled "בחר תיקיית פלט PDF" (getvar "DWGPREFIX") "" 2))
      (if (null out-dir) (*error* "Function cancelled"))
      (setq out-dir (vl-filename-directory out-dir))
    
      ;;; קבל את כל הlayouts
      (setq layouts (layoutlist))  ; מחזיר רשימת שמות layouts
    
      ;;; ⚠ EDGE CASE: הסר "Model" — לא מדפיסים Model Space בדרך כלל
      (setq layouts (vl-remove "Model" layouts))
    
      (if (null layouts) (progn (alert "אין layouts בקובץ!") (*error* "Function cancelled")))
    
      (setq i 1  errors '()  ok-count 0)
    
      (foreach lname layouts
        (princ (strcat "\r הדפסה " (itoa i) "/" (itoa (length layouts)) ": " lname))
    
        ;;; ⚠ EDGE CASE: page setup לא קיים -> אל תדפיס, תרשום שגיאה
        (setq setup-ok
          (vl-catch-all-apply
            'command
            (list "._LAYOUT" "_SET" lname)))
    
        (if (vl-catch-all-error-p setup-ok)
          (progn
            (setq errors (append errors (list (strcat lname ": שגיאת LAYOUT"))))
            (setq i (1+ i)))
    
          (progn
            ;;; ── בנה שם קובץ ──
            ;;; ⚠ EDGE CASE: שם עם תווים אסורים ב-Windows
            (setq safe-name (vl-string-translate "/\\:*?\"<>|" "---------" lname))
            (setq pdf-path  (strcat out-dir "\\" safe-name ".pdf"))
    
            ;;; ⚠ EDGE CASE: path > 260 תווים (Windows MAX_PATH)
            (if (> (strlen pdf-path) 250)
              (setq pdf-path (strcat out-dir "\\" (substr safe-name 1 20) "_trunc.pdf")))
    
            ;;; הדפס
            (setq plot-ok
              (vl-catch-all-apply
                'command
                (list "._PLOT" "_YES"
                      lname                ; layout name
                      pdf-printer          ; device
                      "ISO_A1_(841.00_x_594.00_MM)"  ; paper size
                      "mm"                 ; units
                      "0,0"                ; origin
                      "Layout"             ; plot area
                      "_NO"                ; plot upside-down
                      "1:1"                ; scale
                      "Center"             ; plot offset
                      "_YES"               ; plot with lineweights
                      "_NO"                ; hide paperspace objects
                      "_YES"               ; save changes to layout
                      pdf-path             ; output file
                      "_YES"               ; proceed
                )))
    
            (if (vl-catch-all-error-p plot-ok)
              (setq errors (append errors (list (strcat lname ": שגיאת הדפסה"))))
              (setq ok-count (1+ ok-count)))))
    
        (setq i (1+ i)))
    
      (princ (strcat "\n✓ " (itoa ok-count) " הודפסו בהצלחה"))
      (if errors
        (progn
          (princ (strcat "\n⚠ " (itoa (length errors)) " שגיאות:"))
          (foreach e errors (princ (strcat "\n  - " e)))))
      (princ))
    ⚠ מקרי קצה — Batch Plot
    מקרהתסמיןפתרון
    קריטי Page setup לא קייםהדפסה בהגדרות שגויותבדוק setupname לפני plot
    קריטי מדפסת PDF לא מותקנתשגיאה בלתי מובנתfindfile לפני תחילת batch
    גבוה PATH > 260 תוויםWindows לא יוצר קובץבדוק strlen, חתוך שם
    גבוה שם עברי ב-path ישןencoding שגוי בשם קובץvl-string-translate לתווים ASCII
    גבוה ציור לא שמורPDF מציג גרסה ישנהבדוק DBMOD לפני הדפסה
    בינוני Viewport scale != titleמידה לא תואמת שרטוטהשווה CVPORT scale לכיתוב
    בינוני שכבות קפואות ב-viewportאובייקטים נסתרים ב-PDFשמור/שחזר frozen layers per layout

    Drawing List — רשימת שרטוטים

    סריקת תיקייה, קריאת title block attributes, בניית Drawing Register מלא

    בינוניחוסך 1.5 שעות/עדכון
    😫 הכאב

    פרויקט עם 120 שרטוטים. Drawing List תמיד מפגר אחרי המציאות — שרטוט שנמחק עדיין מופיע, שרטוט חדש לא. רוויזיה ב-title block לא תואמת רשימה.

    drawing-list.lspAutoLISP
    ;;; DRAWING-LIST.LSP v1.5
    ;;; סורק תיקייה ובונה Drawing Register
    
    (defun c:DL (/ folder dwg-files i dwg-path doc title-data results)
    
      (defun *error* (msg)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (vl-load-com)
    
      ;;; בחר תיקייה
      (setq folder (getfiled "בחר תיקיית פרויקט" (getvar "DWGPREFIX") "dwg" 0))
      (if (null folder) (*error* "Function cancelled"))
      (setq folder (vl-filename-directory folder))
    
      ;;; אסוף כל DWG בתיקייה
      (setq dwg-files (vl-directory-files folder "*.dwg" 1))
    
      (if (null dwg-files)
        (progn (alert "לא נמצאו קבצי DWG!") (*error* "Function cancelled")))
    
      (setq i 0  results '())
    
      (foreach fname dwg-files
        (setq i (1+ i))
        (princ (strcat "\r קורא " (itoa i) "/" (itoa (length dwg-files)) ": " fname))
        (setq dwg-path (strcat folder "\\" fname))
    
        ;;; ⚠ EDGE CASE: קובץ נעול — תפוס שגיאה ועבור הלאה
        (setq doc-result
          (vl-catch-all-apply
            '(lambda ()
               (vla-open (vla-get-documents (vlax-get-acad-object)) dwg-path :vlax-false))
            nil))
    
        (if (vl-catch-all-error-p doc-result)
          (progn
            (setq results (append results
              (list (list fname "נעול/שגיאה" "" "" "" "" ""))))
            (princ (strcat " ⚠ נעול")))
    
          (progn
            (setq doc doc-result)
            ;;; ── קרא title block ──
            (setq title-data (dl-read-titleblock doc))
    
            ;;; ⚠ EDGE CASE: בדיקת עקביות REV ב-title vs שם קובץ
            (setq fname-rev (dl-extract-rev-from-filename fname))
            (setq tb-rev    (cdr (assoc "REVISION" title-data)))
            (if (and fname-rev tb-rev (/= fname-rev tb-rev))
              (princ (strcat " ⚠ REV מגיל! קובץ:" fname-rev " title:" tb-rev)))
    
            (setq results (append results
              (list (list
                fname
                (or (cdr (assoc "DWGNUM"  title-data)) "")
                (or (cdr (assoc "TITLE"   title-data)) "")
                (or (cdr (assoc "REVISION" title-data)) "")
                (or (cdr (assoc "DATE"    title-data)) "")
                (or (cdr (assoc "SCALE"   title-data)) "")
                (or (cdr (assoc "BY"      title-data)) "")
              ))))
    
            ;;; סגור קובץ (אל תשמור)
            (vla-close doc :vlax-false))))
    
      ;;; ייצא ל-Excel
      (dl-export-excel results folder)
      (princ (strcat "\n✓ " (itoa (length results)) " שרטוטים"))
      (princ))
    
    ;;; ── Helper: קרא title block — תומך בשמות שונים ──
    (defun dl-read-titleblock (doc / ms ss i en ed bname attrs data)
      (setq ms   (vla-get-modelspace doc)
            data '())
      ;;; ⚠ EDGE CASE: title block יכול להיות בשמות שונים
      (setq known-titles '("TITLE_BLOCK" "TITLEBLOCK" "TB" "KTOVET" "A0_TITLE" "A1_TITLE" "SHEET"))
      ; ... (iterate modelspace for INSERT with known names, read attribs)
      data)
    ⚠ מקרי קצה — Drawing List
    מקרהתסמיןפתרון
    קריטי קובץ נעול (פתוח)קריסהvl-catch-all-apply על vla-open
    גבוה REV בקובץ != REV בשםחוסר עקביותהשווה + סמן אזהרה בדוח
    גבוה שם title block שונהtitle data ריקרשימת שמות ידועים + wcmatch
    בינוני DWG גרסה ישנה (R14)vla-open נכשלDXFIN / SaveAs קודם
    בינוני title block ב-paper spaceלא נמצא ב-model spaceחפש גם ב-paperspace layouts

    בדיקת נגישות IS 1918

    בדיקת רוחב מסדרונות, clearance דלתות, שיפועי רמפות, מעגלי סיבוב

    גבוהחוסך 4 שעות/בניין
    😫 הכאב

    מפקח נגישות מגיע שבוע לפני הגשת היתר. מוצא 23 ליקויים. שבועיים של תיקונים שיכלו להימנע בשלב תכנון.

    רוחב מסדרון 115cm במקום 120cm. שינוי שמשך 3 קירות ועלה 40 שעות.
    accessibility-check.lspAutoLISP
    ;;; ACCESSIBILITY-CHECK.LSP v1.3
    ;;; בדיקת נגישות לפי IS 1918 עם ענני רוויזיה אוטומטיים
    
    (defun c:ACC (/ walls doors issues i unit-factor)
    
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (command "._UNDO" "_BEGIN")
      (vl-load-com)
    
      ;;; ⚠ EDGE CASE: בדוק יחידות — נגישות תמיד ב-mm!
      (setq unit-factor
        (cond ((= (getvar "INSUNITS") 4) 1.0)    ; mm -> mm ✓
              ((= (getvar "INSUNITS") 6) 1000.0) ; m  -> mm
              (T 1.0)))
    
      (setq issues '())
    
      ;;; ── 1. רוחב מסדרונות ──
      (setq corridors (ssget "X" (list (cons 0 "LWPOLYLINE") (cons 8 "A-CORRIDOR,CORRIDOR,מסדרון"))))
      (if corridors
        (progn
          (setq i 0)
          (repeat (sslength corridors)
            (setq en (ssname corridors i))
            ;;; ⚠ EDGE CASE: מדוד face-to-face לא centerline!
            ;;; polyline מסמנת ציר, קירות בשני הצדדים
            ;;; צריך לחשב clearance בין פנים הקירות
            (setq width (acc-get-corridor-clear-width en unit-factor))
            (if (and width (< width 1200))  ; IS 1918: מינ' 120cm
              (setq issues (append issues
                (list (list "מסדרון צר" en width 1200 "רוחב מסדרון < 120cm")))))
            (setq i (1+ i)))))
    
      ;;; ── 2. רוחב דלתות ──
      (setq door-ss (ssget "X" (list (cons 0 "INSERT") (cons 2 "DOOR*,DR*"))))
      (if door-ss
        (progn
          (setq i 0)
          (repeat (sslength door-ss)
            (setq en    (ssname door-ss i)
                  attrs (get-block-attribs en)
                  dr-w  (atoi (or (cdr (assoc "DR_W" attrs)) "0")))
            ;;; ⚠ EDGE CASE: דלת הזזה — clearance שונה מדלת ציר
            (setq dr-type (or (cdr (assoc "DR_TYPE" attrs)) "SWING"))
            (setq min-w (if (= dr-type "SLIDING") 800 800))  ; שניהם 800mm IS 1918
            (if (and (> dr-w 0) (< (* dr-w unit-factor) min-w))
              (setq issues (append issues
                (list (list "דלת צרה" en (* dr-w unit-factor) min-w "רוחב דלת < 80cm")))))
            (setq i (1+ i)))))
    
      ;;; ── 3. רמפות — שיפוע ──
      (setq ramps (ssget "X" (list (cons 0 "LWPOLYLINE,3DPOLYLINE") (cons 8 "A-RAMP,RAMP,רמפה"))))
      (if ramps
        (progn
          (setq i 0)
          (repeat (sslength ramps)
            (setq en (ssname ramps i))
            ;;; ⚠ EDGE CASE: שיפוע ב-3D polyline vs 2D projection — שונה!
            (setq slope (acc-calc-slope en))
            (if (and slope (> slope (/ 1.0 12.0)))  ; IS 1918: מקס' 1:12
              (setq issues (append issues
                (list (list "רמפה תלולה" en slope (/ 1.0 12.0) "שיפוע > 1:12")))))
            (setq i (1+ i)))))
    
      ;;; ── מקם ענני שינוי על כל ליקוי ──
      (princ (strcat "\n נמצאו " (itoa (length issues)) " ליקויים"))
      (foreach issue issues
        (acc-place-revision-cloud (cadr issue) (cadddr (cddr issue))))
    
      ;;; ── צור דו"ח ──
      (acc-export-report issues)
    
      (command "._UNDO" "_END")
      (princ (strcat "\n✓ בדיקה הושלמה — " (itoa (length issues)) " ליקויים סומנו"))
      (princ))
    
    ;;; Helper: חישוב שיפוע polyline
    (defun acc-calc-slope (en / ed pts p1 pn dz dx slope)
      (setq ed  (entget en)
            pts (mapcar 'cdr (vl-remove-if-not '(lambda (x) (= (car x) 10)) ed)))
      (if (>= (length pts) 2)
        (progn
          (setq p1 (car pts)  pn (last pts))
          (setq dz (abs (- (caddr pn) (caddr p1)))
                dx (distance (list (car p1) (cadr p1)) (list (car pn) (cadr pn))))
          (if (> dx 0) (/ dz dx) 0))
        nil))
    ⚠ מקרי קצה — נגישות
    מקרהתסמיןפתרון
    קריטי מדידה centerline vs faceרוחב מסדרון נראה תקני אבל אינוהפחת עובי קיר מצדדי הpolyline
    גבוה רמפה ב-2D (Z=0)שיפוע = 0 תמידחשב slope מcotation/Z של נקודות
    גבוה דלת זוגית (double door)כל כנף < 800 אבל sum > 800קרא DR_LEAF_COUNT, חשב לפי כנף
    גבוה דלת ממ"ד — תקן שונהדלת ממ"ד דרוג 800mm בדיוקאל תסמן כשגיאה אם DR_TYPE=MAMAD
    בינוני גובה ידית — לא ניתן לקרוא מDWGחוסר במיקום ידיתהוסף הערה ידנית בדוח
    🇮🇱 תקן IS 1918 — דרישות עיקריות
    • מסדרון: רוחב מינ' 120cm (120cm clear); לפרטי נכים ב-דירה — 90cm
    • דלת כניסה לדירה: רוחב מינ' 90cm clear opening
    • דלתות פנימיות: רוחב מינ' 80cm clear opening
    • רמפה: שיפוע מקס' 1:12 (8.33%), רוחב מינ' 120cm, landing כל 9m
    • מעגל סיבוב: קוטר מינ' 150cm במרחב מפנה
    • מדרגות: שלח מינ' 28cm, עולה מקס' 17cm, מאחז ידיים 85-95cm
    • חניית נכה: רוחב מינ' 350cm, סמוך לכניסה, IS 6239

    ניתוח צל ושמש

    חישוב זווית שמש לפי lat/lon/תאריך, הטלת צלליות, מיפוי שעות הצללה

    גבוהחוסך 3+ ימים/פרויקט
    😫 הכאב

    ועדה מקומית דורשת proof שהבניין החדש לא מצליל על שכנים מעל 4 שעות ביום. חישוב ידני לכל שעה לכל תאריך = שבוע עבודה. ועדיין יש ספק אם נכון.

    sun-analysis.lspAutoLISP
    ;;; SUN-ANALYSIS.LSP v1.6
    ;;; ניתוח צל — חישוב זוויות שמש וצלליות
    
    (defun c:SUN (/ lat lon date-str hour building-ss)
    
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (command "._UNDO" "_BEGIN")
    
      ;;; קלט משתמש
      (setq lat (getreal "\nקו רוחב (ישראל: 31-33): "))
      (setq lon (getreal "\nקו אורך (ישראל: 34-36): "))
      (if (null lat) (setq lat 32.0))  ; ברירת מחדל: מרכז ישראל
      (if (null lon) (setq lon 34.9))
    
      ;;; ⚠ EDGE CASE: שעון קיץ בישראל — מרץ-אוקטובר יש +1 שעה
      ;;; חייבים להתחשב זמן שמש אמיתי (solar time) vs שעון מקומי
      (setq tz-offset 2)  ; UTC+2 (ישראל)
      (setq dst-active (sun-is-dst-active))  ; בדוק אם שעון קיץ פעיל
      (if dst-active (setq tz-offset 3))    ; UTC+3 בשעון קיץ
    
      ;;; בחר מסה הבניין
      (setq building-ss (ssget "X" (list (cons 0 "LWPOLYLINE") (cons 8 "A-MASS,MASS,BUILDING,מסה"))))
      (if (null building-ss)
        (progn (alert "לא נמצאו polylines של מסה!\nשכבה: A-MASS / MASS / BUILDING") (*error* "Function cancelled")))
    
      ;;; חשב צל ל-21 יוני (קיץ), 21 דצמבר (חורף), 21 מרץ (אביב)
      (foreach analysis-date '("2024-06-21" "2024-12-21" "2024-03-21")
        (setq day-of-year (sun-day-of-year analysis-date))
    
        ;;; חשב צל כל שעה 6:00-18:00
        (setq h 6)
        (repeat 13
          (setq solar-angles (sun-calc-angles lat lon day-of-year h tz-offset))
          (setq azimuth   (car solar-angles)   ; כיוון השמש (צפון = 0)
                elevation (cadr solar-angles)) ; גובה מעל אופק (מעלות)
    
          ;;; שמש מתחת לאופק — לא רלוונטי
          (if (> elevation 0)
            (progn
              (setq shadow-dir (sun-shadow-vector azimuth elevation))
              ;;; ⚠ EDGE CASE: בניין על שיפוע — גובה בסיס משנה צל
              ;;; חייבים לקחת Z של הbase polyline
              (sun-draw-shadows building-ss shadow-dir analysis-date h)))
    
          (setq h (1+ h))))
    
      ;;; צבע gradient לפי שעות צל
      (sun-colorize-shadow-map)
    
      (command "._UNDO" "_END")
      (princ "\n✓ ניתוח צל הושלם")
      (princ))
    
    ;;; ── חישוב זוויות שמש (Solar Position Algorithm) ──
    (defun sun-calc-angles (lat lon doy hour tz / B decl eot tc lst ha elev az)
      (setq pi     3.14159265358979)
      (setq deg2rad (/ pi 180.0))
      ;;; Declination
      (setq B       (* deg2rad (/ (* 360.0 (- doy 81)) 365.0)))
      (setq decl    (* 23.45 deg2rad (sin B)))
      ;;; Equation of time (minutes)
      (setq eot     (* 9.87 (sin (* 2 B))) - (* 7.53 (cos B)) - (* 1.5 (sin B)))
      ;;; Time correction
      (setq tc      (+ (* 4.0 (- lon (* tz 15.0))) eot))
      ;;; Local Solar Time
      (setq lst     (+ hour (/ tc 60.0)))
      ;;; Hour Angle
      (setq ha      (* (- lst 12.0) 15.0 deg2rad))
      ;;; Elevation
      (setq elev    (asin (+ (* (sin (* lat deg2rad)) (sin decl))
                             (* (cos (* lat deg2rad)) (cos decl) (cos ha)))))
      ;;; Azimuth
      (setq az      (acos (/ (- (sin decl) (* (sin elev) (sin (* lat deg2rad))))
                              (* (cos elev) (cos (* lat deg2rad))))))
      (list (/ az deg2rad) (/ elev deg2rad)))
    ⚠ מקרי קצה — ניתוח שמש
    מקרהתסמיןפתרון
    קריטי שעון קיץ לא מחושבשגיאה של שעה בצלdst_active check מרץ-אוקטובר
    גבוה בניין על קרקע משופעתגובה בסיס שגוי → צל שגויקרא Z מנקודות polyline
    גבוה כיוון צפון לא north-upזווית shadow שגויהNORTHDIRECTION variable + rotate
    בינוני משטחים מחזירי אורצל משני לא נחשבהוסף הערה "אינו כולל reflections"
    בינוני elevation < 0שמש מתחת לאופקדלג על שעות עם elevation <= 0

    Quantity Takeoff — כמויות עבודה

    חישוב כמויות קירות, ריצוף, גמרים, ייצוא BOQ מלא לקבלן

    גבוהחוסך 12 שעות/פרויקט
    😫 הכאב

    קבלן הגיש הצעת מחיר שונה ב-30% מהאומדן שלנו. חקירה הראתה: BOQ ידני עם שגיאות בחישוב קירות עם פתחים, ומ"ר ריצוף ללא ניכוי עמודים.

    quantity-takeoff.lspAutoLISP
    ;;; QUANTITY-TAKEOFF.LSP v2.1
    ;;; חישוב כמויות: קירות, ריצוף, חיפויים
    
    (defun c:QT (/ walls floors qty-data unit-factor)
    
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (command "._UNDO" "_BEGIN")
      (vl-load-com)
    
      (setq unit-factor (cond ((= (getvar "INSUNITS") 4) 1e-3)   ; mm -> m
                              ((= (getvar "INSUNITS") 6) 1.0)    ; m  -> m
                              (T 1.0)))
    
      (setq qty-data '())
    
      ;;; ── 1. קירות ──
      (setq wall-ss (ssget "X" (list (cons 0 "LWPOLYLINE") (cons 8 "A-WALL,WALLS,קירות"))))
      (if wall-ss
        (progn
          (setq i 0)
          (repeat (sslength wall-ss)
            (setq en (ssname wall-ss i)
                  ed (entget en))
            ;;; ⚠ EDGE CASE: קיר עם פתחים — חייב לנכות פתחים!
            ;;; מצא דלתות/חלונות שמרכזן על הקיר, נכה את רוחבם
            (setq wall-len    (* (qt-polyline-length en) unit-factor))
            (setq wall-height (qt-get-floor-height))  ; מ-attribute FLOOR_HEIGHT
            (setq wall-type   (qt-get-wall-type en))
            (setq openings    (qt-find-openings-on-wall en unit-factor))
            (setq opening-len (apply '+ (mapcar 'car openings)))
    
            ;;; ⚠ EDGE CASE: קיר מעוקל — arc length לא chord length!
            (setq net-len (max 0 (- wall-len opening-len)))
            (setq wall-area (* net-len wall-height))
    
            (setq qty-data (qt-add-item qty-data wall-type wall-area "מ\"ר קיר"))
            (setq i (1+ i)))))
    
      ;;; ── 2. ריצוף (hatches) ──
      (setq hatch-ss (ssget "X" (list (cons 0 "HATCH") (cons 8 "A-FLOOR,FLOOR,ריצוף"))))
      (if hatch-ss
        (progn
          (setq i 0)
          (repeat (sslength hatch-ss)
            (setq en (ssname hatch-ss i))
            ;;; ⚠ EDGE CASE: Hatch area כולל holes — AutoCAD מחשב נטו
            (command "._AREA" "_O" en)
            (setq hatch-area (* (getvar "AREA") unit-factor unit-factor))
            (setq finish-type (qt-get-hatch-pattern-name en))
            (setq qty-data (qt-add-item qty-data finish-type hatch-area "מ\"ר ריצוף"))
            (setq i (1+ i)))))
    
      ;;; ייצוא
      (qt-export-boq qty-data)
    
      (command "._UNDO" "_END")
      (princ "\n✓ Quantity Takeoff הושלם")
      (princ))
    
    ;;; Helper: חישוב אורך polyline (כולל קשתות)
    (defun qt-polyline-length (en / obj len)
      (setq obj (vlax-ename->vla-object en))
      (setq len (vl-catch-all-apply 'vlax-get-property (list obj 'Length)))
      (if (vl-catch-all-error-p len) 0.0 len))
    ⚠ מקרי קצה — Quantity Takeoff
    מקרהתסמיןפתרון
    קריטי קיר עם פתחים לא מנוכהכמות גדולה מדיintersect דלתות/חלונות עם קיר
    קריטי קיר מעוקל — chord vs arcאורך קיר שגויvlax Length property (arc-correct)
    גבוה hatch על כמה חדריםכפל ספירהחלק hatch לאזורים לפי room bounds
    גבוה קיר משותף בין דירותנספר פעמייםסמן WALL_SHARED — חלק ב-2
    בינוני קיר רב-שכבות (בנייה+בידוד+גבס)איזו שכבה למדוד?קרא WALL_MEASURED_LAYER attribute

    Space Programming — עמידה בתוכנית

    השוואת שטחים תכוננים מול דרישות לקוח, ציון deviation, ייצוא gap analysis

    בינוניחוסך 3 שעות/iteation
    😫 הכאב

    לקוח ביקש 25 משרדים בממוצע 20 מ"ר. בשלב BOD הכנסנו 25 משרדים — אבל עשרה מהם 14-16 מ"ר. לא ידענו עד שהלקוח ספר את השטחים בעצמו.

    space-programming.lspAutoLISP
    ;;; SPACE-PROGRAMMING.LSP v1.2
    ;;; בדיקת עמידה בתוכנית שטחים
    
    (defun c:SP (/ program-data actual-data results)
    
      (defun *error* (msg)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (vl-load-com)
    
      ;;; ── קרא Space Program מ-Excel ──
      (setq prog-file (getfiled "בחר קובץ Space Program (Excel)" "" "xlsx;xls" 0))
      (if prog-file
        (setq program-data (sp-read-excel prog-file))
        (setq program-data (sp-get-default-program)))  ; fallback: input ידני
    
      ;;; ── קרא שטחים מהציור ──
      (setq room-ss (ssget "X" (list (cons 0 "INSERT") (cons 2 "ROOM_TAG"))))
      (setq actual-data '())
      (if room-ss
        (progn
          (setq i 0)
          (repeat (sslength room-ss)
            (setq en    (ssname room-ss i)
                  attrs (get-block-attribs en)
                  rtype (cdr (assoc "ROOM_TYPE" attrs))
                  area  (atof (or (cdr (assoc "ROOM_AREA" attrs)) "0")))
            ;;; ⚠ EDGE CASE: ROOM_AREA ריק — חשב שטח מ-polyline הקרוב
            (if (zerop area)
              (setq area (sp-calc-area-from-boundary en)))
            (setq actual-data (sp-add-to-type actual-data rtype area))
            (setq i (1+ i)))))
    
      ;;; ── השווה ──
      (setq results '())
      (foreach prog-item program-data
        (setq rtype     (car prog-item)
              req-count (cadr prog-item)
              req-area  (caddr prog-item)
              act-items (cdr (assoc rtype actual-data))
              act-count (length act-items)
              act-area  (apply '+ act-items))
    
        ;;; צבע לפי deviation
        (setq deviation (if (> req-area 0) (/ (abs (- act-area req-area)) req-area) 0))
        (setq color (cond ((< deviation 0.05) "ירוק")   ; <5%  = OK
                          ((< deviation 0.10) "צהוב")   ; <10% = אזהרה
                          (T                  "אדום")))  ; >10% = בעיה
    
        (setq results (append results
          (list (list rtype req-count req-area act-count act-area deviation color)))))
    
      ;;; ── צבע חדרים בציור ──
      (sp-colorize-rooms results)
    
      ;;; ── ייצוא ──
      (sp-export-gap-analysis results)
      (princ "\n✓ Space programming analysis הושלם")
      (princ))
    ⚠ מקרי קצה — Space Programming
    מקרהתסמיןפתרון
    גבוה ROOM_AREA ריקdeviation = 100%חשב מ-polyline סמוך
    גבוה שם חדר שונה מהתוכניתלא נמצא matchwcmatch fuzzy + synonym list
    בינוני חדר משותף בין 2 יחידותנספר פעמייםROOM_SHARED attribute = split

    חזיתות פרמטריות

    חלוקת חזית לפנלים אוטומטית, מספור, schedule לייצרן

    בינוניחוסך שבוע+/פרויקט
    😫 הכאב

    800 פנלי זכוכית בחזית מעוקלת. כל שינוי ב-grid = עדכון ידני של מאות מידות ותזמון לייצרן.

    parametric-facade.lspAutoLISP
    ;;; PARAMETRIC-FACADE.LSP v2.0
    ;;; מחלק חזית לפנלים פרמטריים
    
    (defun c:PF (/ base-line module gap height panels cnt)
    
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (command "._UNDO" "_BEGIN")
      (vl-load-com)
    
      ;;; קלט
      (setq base-line (entsel "\nבחר קו בסיס חזית: "))
      (setq module    (getreal "\nמודול פנל (מ\"מ) [1200]: "))
      (setq gap       (getreal "\nמרווח בין פנלים (מ\"מ) [20]: "))
      (setq height    (getreal "\nגובה פנל (מ\"מ) [3000]: "))
      (if (null module) (setq module 1200.0))
      (if (null gap)    (setq gap 20.0))
      (if (null height) (setq height 3000.0))
    
      (setq base-en (car base-line))
      (setq base-ed (entget base-en))
    
      ;;; ⚠ EDGE CASE: חזית מעוקלת — arc length vs chord length
      ;;; הפנלים שטוחים, החזית מעוקלת
      ;;; חייבים לחשב chord לכל פנל בנפרד!
      (if (= (cdr (assoc 0 base-ed)) "ARC")
        (progn
          (setq total-len (pf-arc-length base-en))
          (setq is-curved T))
        (progn
          (setq p1 (cdr (assoc 10 base-ed)))
          (setq p2 (cdr (assoc 11 base-ed)))
          (setq total-len (distance p1 p2))
          (setq is-curved nil)))
    
      (setq num-panels (fix (/ total-len (+ module gap))))
      (setq closure   (- total-len (* num-panels (+ module gap))))
    
      ;;; ⚠ EDGE CASE: פנל סגירה — בדוק לא קטן מדי
      (if (< closure 200)  ; פנל < 200mm — חלק לפנלי שכנים
        (progn
          (setq num-panels (1- num-panels))
          (setq closure (+ closure module gap))
          (princ "\n⚠ פנל סגירה < 200mm — חולק לפנלים סמוכים")))
    
      (setq cnt 1  panels '())
      (setq i 0)
      (repeat num-panels
        (setq start-dist (* i (+ module gap)))
        (setq p-width (if (= i (1- num-panels)) closure module))
    
        ;;; ⚠ EDGE CASE: פנל גדול מ-3000mm — אזהרה לסטרוקטורה
        (if (> p-width 3000)
          (princ (strcat "\n⚠ פנל " (itoa cnt) " רחב מ-3m — בדוק עם מהנדס!")))
    
        (setq panel-pt (pf-point-at-dist base-en start-dist))
        (setq panel-num (strcat "P-" (pf-pad cnt 3)))
    
        ;;; צור polyline של הפנל
        (pf-draw-panel panel-pt p-width height panel-num)
        (setq panels (append panels (list (list panel-num p-width height start-dist))))
        (setq cnt (1+ cnt)  i (1+ i)))
    
      ;;; ייצא schedule לייצרן
      (pf-export-schedule panels)
    
      (command "._UNDO" "_END")
      (princ (strcat "\n✓ " (itoa (length panels)) " פנלים נוצרו"))
      (princ))
    ⚠ מקרי קצה — חזיתות
    מקרהתסמיןפתרון
    קריטי חזית מעוקלת — chord vs arcפנלים לא מכסים חזיתחשב arc-length, פנלים = chord
    גבוה פנל סגירה < 200mmפנל בלתי ניתן לייצורחלק ל-2 פנלים סמוכים
    גבוה פינת בניין (corner panel)פנל בשתי פניםזיהוי פינה + פנל mitered
    גבוה פנל על floor slabmullion offset שגויהוסף SLAB_OFFSET parameter
    בינוני דלת/חלון בחזיתפנל רגיל במקום פתחמצא opening blocks על הקו

    טופוגרפיה — ייבוא נקודות מדידה

    קריאת XYZ מ-CSV, יצירת עקומות גובה, חישוב כמויות עפר

    בינוניחוסך 2 ימים/פרויקט
    😫 הכאב

    מודד מסר קובץ עם 800 נקודות XYZ. שרטוט עקומות גובה ידנית = יום שלם. כל עדכון מהמודד = יום נוסף.

    topo-import.lspAutoLISP
    ;;; TOPO-IMPORT.LSP v1.4
    ;;; מייבא נקודות XYZ מ-CSV ומצייר elevation points
    
    (defun c:TOPO (/ csv-file pts i line parts x y z)
    
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (command "._UNDO" "_BEGIN")
    
      (setq csv-file (getfiled "בחר קובץ נקודות (CSV)" "" "csv;txt" 0))
      (if (null csv-file) (*error* "Function cancelled"))
    
      ;;; ⚠ EDGE CASE: בדוק יחידות — מדידה לרוב בסנטימטרים!
      (initget "מטרים סנטימטרים")
      (setq units (getkword "\nיחידות קובץ [מטרים/סנטימטרים]: "))
      (setq unit-conv (if (= units "סנטימטרים") 0.01 1.0))
    
      (setq fh  (open csv-file "r")
            pts '()
            i   0)
    
      (while (setq line (read-line fh))
        (setq i (1+ i))
        ;;; ⚠ EDGE CASE: דלג שורת כותרת
        (if (> i 1)
          (progn
            (setq parts (topo-split-csv line))
            (if (>= (length parts) 3)
              (progn
                (setq x (* (atof (car parts))   unit-conv)
                      y (* (atof (cadr parts))  unit-conv)
                      z (* (atof (caddr parts)) unit-conv))
    
                ;;; ⚠ EDGE CASE: נקודות כפולות ב-XY — ממוצע Z
                (setq existing (assoc (list x y) pts))
                (if existing
                  (progn
                    (setq avg-z (/ (+ z (caddr (car (cdr existing)))) 2.0))
                    (princ (strcat "\n⚠ נקודה כפולה ב-" (rtos x 2 1) "," (rtos y 2 1) " — ממוצע Z")))
                  (progn
                    ;;; צייר POINT עם elevation
                    (command "._POINT" (list x y z))
                    ;;; הוסף TEXT עם גובה
                    (command "._TEXT" (list (+ x 0.5) (+ y 0.5) 0)
                             "0.5" "0" (rtos z 2 2))
                    (setq pts (append pts (list (list x y z))))))))))
      )
      (close fh)
    
      (princ (strcat "\n✓ " (itoa (length pts)) " נקודות יובאו"))
      (princ "\nהרץ CONTOUR לעקומות גובה")
      (command "._UNDO" "_END")
      (princ))
    ⚠ מקרי קצה — טופוגרפיה
    מקרהתסמיןפתרון
    קריטי יחידות CSV — cm vs mשטח ×10,000 שגוישאל משתמש + unit-conv
    גבוה נקודות כפולות ב-XYעקומות לא תקינותממוצע Z + אזהרה
    גבוה UCS ITM ישראליקואורדינטות מיליון+תרגם ל-local relative system
    בינוני שורת כותרת ב-CSVX/Y/Z = NaNדלג שורה ראשונה תמיד

    תכנון חניות אוטומטי

    יצירת grid חניות, מיקום HC, מספור, בדיקת IS 6239

    בינוניחוסך 2 שעות/קומת חניה
    😫 הכאב

    ועדה מקומית דורשת 200 חניות כולל 4 נכה, סימון ברור, קווי תנועה. תכנון ידני וסימון לוקח יום שלם לקומה.

    parking-layout.lspAutoLISP
    ;;; PARKING-LAYOUT.LSP v1.3
    (defun c:PKG (/ stall-w stall-d aisle-w angle rows cols pt)
    
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (command "._UNDO" "_BEGIN")
    
      (setq stall-w (or (getreal "\nרוחב מקום חניה (מ\"מ) [2500]: ") 2500.0))
      (setq stall-d (or (getreal "\nעומק מקום חניה (מ\"מ) [5000]: ") 5000.0))
      (setq aisle-w (or (getreal "\nרוחב נתיב (מ\"מ) [6000]: ") 6000.0))
      (setq cols    (or (getint  "\nמספר מקומות בשורה: ") 10))
      (setq rows    (or (getint  "\nמספר שורות: ") 4))
      (setq pt      (getpoint "\nנקודת פינה שמאלית עליונה: "))
    
      ;;; ⚠ EDGE CASE: IS 6239 — מקום נכה 3500mm רוחב
      (setq hc-count (max 1 (fix (* cols rows 0.02))))  ; 2% מסה"כ
      (princ (strcat "\n→ נדרשים " (itoa hc-count) " מקומות נכה (IS 6239)"))
    
      (setq cnt 1  row 0)
      (repeat rows
        (setq col 0  y (- (cadr pt) (* row (+ stall-d (/ aisle-w 2.0)))))
        (repeat cols
          (setq x    (+ (car pt) (* col stall-w))
                stpt (list x y 0))
          ;;; ⚠ EDGE CASE: חניית נכה — רוחב שונה!
          (setq is-hc (and (<= cnt hc-count) (= row 0)))
          (setq w (if is-hc 3500.0 stall-w))
          (pkg-draw-stall stpt w stall-d cnt is-hc)
          (setq cnt (1+ cnt)  col (1+ col)))
        (setq row (1+ row)))
    
      (command "._UNDO" "_END")
      (princ (strcat "\n✓ " (itoa (1- cnt)) " חניות נוצרו"))
      (princ))
    ⚠ מקרי קצה — חניות
    מקרהתסמיןפתרון
    קריטי HC stall — מידות שונותרוחב 2500 במקום 3500is-hc flag, stall-w = 3500
    גבוה חניה חופפת עמודחניה לא שמישהintersect stall עם columns layer
    בינוני רמפה + landingslope > 1:10 ב-IS 6239חשב slope + הוסף landing כל 10m

    גריד קונסטרוקטיבי

    יצירת גריד עמודים, ביאורים A-Z/1-20, gridlines — אוטומטי

    בינוניחוסך שעה/iteration
    structural-grid.lspAutoLISP
    ;;; STRUCTURAL-GRID.LSP v1.5
    (defun c:SG (/ x-spacings y-spacings origin col-labels row-labels)
    
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (command "._UNDO" "_BEGIN")
    
      (setq origin (getpoint "\nנקודת מקור (0,0): "))
      (if (null origin) (setq origin '(0 0 0)))
    
      ;;; קבל spacing x (לא-אחידים אפשריים)
      (setq x-in (getstring "\nמרווחי X (מ\"מ) [6000 6000 6000 8000]: "))
      (setq y-in (getstring "\nמרווחי Y (מ\"מ) [6000 6000 6000]: "))
      (setq x-spacings (sg-parse-spacings (if (= x-in "") "6000 6000 6000 8000" x-in)))
      (setq y-spacings (sg-parse-spacings (if (= y-in "") "6000 6000 6000" y-in)))
    
      ;;; ⚠ EDGE CASE: דלג I ו-O (נראים כ-1 ו-0)
      (setq row-letters '("A" "B" "C" "D" "E" "F" "G" "H" "J" "K" "L" "M"
                           "N" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"))
    
      ;;; ⚠ EDGE CASE: יותר מ-24 שורות (אין מספיק אותיות)
      (if (> (length y-spacings) 24)
        (progn
          (setq extra (- (length y-spacings) 24))
          (setq row-letters
            (append row-letters
              (mapcar '(lambda (n) (strcat "A" (nth n row-letters))) '(0 1 2 3 4 5 6 7 8 9))))))
    
      ;;; ⚠ EDGE CASE: יותר מ-99 עמודות — format שונה
      (setq col-count (1+ (length x-spacings)))
      (if (> col-count 99) (princ "\n⚠ יותר מ-99 עמודות — שקול format AA, AB..."))
    
      ;;; צייר gridlines + bubbles
      (sg-draw-grid origin x-spacings y-spacings row-letters)
    
      (command "._UNDO" "_END")
      (princ (strcat "\n✓ גריד " (itoa col-count) "×" (itoa (1+ (length y-spacings)))))
      (princ))
    
    (defun sg-parse-spacings (str / parts result)
      (setq parts (sg-split-str str " "))
      (mapcar 'atof parts))
    ⚠ מקרי קצה — גריד
    מקרהתסמיןפתרון
    קריטי אות I / O בגרידנראה כ-1 / 0דלג I ו-O ברשימת האותיות
    גבוה גריד מסובב (UCS)gridlines לא ישרהגדר UCS לפני יצירת הגריד
    בינוני יותר מ-24 שורותנגמרות אותיותהמשך עם AA, AB, AC...

    ניהול שכבות — תקן AIA

    יצירת סט שכבות מלא לפי AIA/ISO, תרגום, cleanup אוטומטי

    בינוניחוסך 30 דק'/פרויקט
    layer-setup.lspAutoLISP
    ;;; LAYER-SETUP.LSP v2.0 — יצירת שכבות תקן AIA
    (defun c:LS (/ layers i ldata lname lcolor ltype lweight)
    
      (setq layers
        '(("A-WALL"        7   "Continuous" 0.50)  ; קירות
          ("A-WALL-PATT"   8   "Continuous" 0.18)  ; הטחת קירות
          ("A-DOOR"        4   "Continuous" 0.35)  ; דלתות
          ("A-GLAZ"        4   "Continuous" 0.25)  ; חלונות
          ("A-FLOR"        8   "Continuous" 0.18)  ; ריצוף
          ("A-FURN"       11   "Continuous" 0.18)  ; ריהוט
          ("A-ROOM-IDEN"   3   "Continuous" 0.18)  ; תוויות חדרים
          ("A-ROOM-BOUND"  6   "Continuous" 0.00)  ; גבולות חדרים (no-plot)
          ("A-ANNO-DIMS"   7   "Continuous" 0.18)  ; מידות
          ("A-ANNO-TEXT"   7   "Continuous" 0.25)  ; טקסט כללי
          ("A-ANNO-HATCH"  9   "Continuous" 0.18)  ; הטחות כלליות
          ("A-GRID"        8   "CENTER"     0.25)  ; ציר גריד
          ("A-WALL-FIRE"  30   "Continuous" 0.50)  ; קירות אש
          ("A-STAIR"       6   "Continuous" 0.35)  ; גרם מדרגות
          ("A-RAMP"        6   "Continuous" 0.35)  ; רמפה
          ("A-ELEV"        5   "Continuous" 0.18)  ; מעלית
          ("A-ROOF"        8   "Dashed"     0.25)  ; גג (מקוקו)
          ("DEFPOINTS"     7   "Continuous" 0.00)  ; no-plot
        ))
    
      (foreach ldata layers
        (setq lname   (car   ldata)
              lcolor  (cadr  ldata)
              ltype   (caddr ldata)
              lweight (cadddr ldata))
    
        ;;; ⚠ EDGE CASE: שכבה 0 — אל תשנה (יורשת מבלוק)
        (if (/= lname "0")
          (progn
            ;;; ⚠ EDGE CASE: XREF|LAYER — אי אפשר לשנות
            (if (not (vl-string-search "|" lname))
              (progn
                ;;; ⚠ EDGE CASE: linetype חייב להיות טעון קודם
                (if (null (tblsearch "LTYPE" ltype))
                  (command "._LINETYPE" "_LOAD" ltype "acad.lin" ""))
                (command "._LAYER" "_MAKE" lname
                                   "_COLOR" (itoa lcolor) lname
                                   "_LTYPE" ltype lname
                                   "_LWEIGHT" (rtos lweight 2 2) lname
                                   ""))))))
    
      (princ (strcat "\n✓ " (itoa (length layers)) " שכבות נוצרו"))
      (princ))
    ⚠ מקרי קצה — שכבות
    מקרהתסמיןפתרון
    קריטי XREF|LAYERשינוי נכשל בשקטבדוק vl-string-search "|"
    גבוה Linetype לא טעוןשגיאה ב-LAYER commandLINETYPE LOAD לפני LAYER
    גבוה שכבה 0 — אל תשנהבלוקים משנים מראהדלג שכבה "0" תמיד
    בינוני שם שכבה > 255 תוויםAutoCAD דוחהחתוך ל-255 + אזהרה
    בינוני DEFPOINTS — אל תדפיסמודפס בטעותsetq lweight 0.00 + no-plot flag

    ניהול XREF אוטומטי

    סריקת תיקייה, attach/reload אוטומטי, דיווח conflicts

    בינוניחוסך שעה+/round תיאום
    xref-manager.lspAutoLISP
    ;;; XREF-MANAGER.LSP v1.2
    (defun c:XRM (/ folder dwg-files cur-xrefs)
    
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (command "._UNDO" "_BEGIN")
      (vl-load-com)
    
      (setq folder (getfiled "תיקיית XREF" (getvar "DWGPREFIX") "dwg" 0))
      (if (null folder) (*error* "Function cancelled"))
      (setq folder (vl-filename-directory folder))
    
      (setq dwg-files  (vl-directory-files folder "*.dwg" 1))
      (setq cur-dwg    (getvar "DWGNAME"))
    
      ;;; ⚠ EDGE CASE: הסר את הציור הנוכחי מהרשימה
      (setq dwg-files (vl-remove (vl-filename-base cur-dwg) dwg-files))
    
      ;;; ⚠ EDGE CASE: בדיקת CIRCULAR XREF
      ;;; A->B->A = AutoCAD יאתר, אבל הסקריפט צריך לדעת מראש
      (foreach fname dwg-files
        (setq xref-path (strcat folder "\\" fname))
    
        ;;; ⚠ EDGE CASE: XREF path יחסי vs מוחלט
        ;;; path מוחלט = קובץ מועבר תיקייה -> XREF לא נמצא
        ;;; הכרח: path יחסי תמיד לתיקיית פרויקט
        (setq rel-path (xrm-make-relative xref-path (getvar "DWGPREFIX")))
    
        ;;; Reload אם כבר attached, Attach אם חדש
        (setq existing (tblsearch "BLOCK" (vl-filename-base fname)))
        (if existing
          (command "._XREF" "_RELOAD" (vl-filename-base fname))
          (command "._XREF" "_ATTACH" rel-path
                   "" "0,0,0" "1" "0")))
    
      (command "._UNDO" "_END")
      (princ (strcat "\n✓ " (itoa (length dwg-files)) " XREFs מעודכנים"))
      (princ))
    ⚠ מקרי קצה — XREF
    מקרהתסמיןפתרון
    קריטי path מוחלטXREF לא נמצא לאחר העברת תיקייהתמיד path יחסי לתיקיית פרויקט
    קריטי Circular XREFAutoCAD תולהבדוק לפני attach, דלג אם circular
    גבוה XREF בשם עבריencoding שגוי ב-AutoCAD ישןתרגם שם קובץ ל-ASCII
    בינוני XREF scale != 1גיאומטריה לא תואמתבדוק scale לאחר attach

    ❌ Anti-Patterns — טעויות שיקברו אותך

    אלו הטעויות שגרמו לאבדן עבודה, ציורים פגומים, ו-AutoCAD שהפסיק להגיב. קרא לפני שתכתוב שורה אחת.

    ❌ 1. אין error handler — AutoCAD נשאר ב-CMDECHO=0
    קריסה באמצע סקריפט ללא *error* — AutoCAD נשאר עם OSMODE=0, CMDECHO=0, ו-UNDO פתוח. כל פקודה עתידית תתנהג בצורה לא צפויה.
    ❌ שגוי
    ; ❌ BAD — אם command קורס, CMDECHO נשאר 0 לנצח
    (defun c:BAD ()
      (setvar "CMDECHO" 0)
      (command "AREA" "O" (entsel))  ; crash כאן = CMDECHO 0 forever
      (princ))
    ✅ תמיד: save → error handler → restore
    ✅ נכון
    ; ✅ GOOD
    (defun c:GOOD (/ *s-echo* *s-osmode*)
      (defun *error* (msg)
        (command "._UNDO" "_END")
        (setvar "CMDECHO" *s-echo*)
        (setvar "OSMODE"  *s-osmode*)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort"))
          (alert (strcat "שגיאה: " msg)))
        (princ))
      (setq *s-echo*   (getvar "CMDECHO")
            *s-osmode* (getvar "OSMODE"))
      (setvar "CMDECHO" 0)
      (command "._UNDO" "_BEGIN")
      ; ... קוד ...
      (command "._UNDO" "_END")
      (setvar "CMDECHO" *s-echo*)
      (setvar "OSMODE"  *s-osmode*)
      (princ))
    ❌ 2. Wildcard ב-ssget על group 2 — לא עובד!
    זה באג ידוע ב-AutoLISP: (ssget "X" (list (cons 2 "DOOR*"))) מחזיר nil תמיד. אין wildcard filtering ב-ssget על שם בלוק.
    ❌ שגוי
    ; ❌ WRONG — wildcard על group 2 לא עובד!
    (ssget "X" (list (cons 0 "INSERT") (cons 2 "DOOR*")))  ; -> nil תמיד!
    ✅ בחר הכל + post-filter עם wcmatch
    ✅ נכון
    ; ✅ CORRECT
    (setq all (ssget "X" (list (cons 0 "INSERT"))))
    (setq doors (ssadd))
    (setq i 0)
    (repeat (sslength all)
      (setq en (ssname all i))
      (if (wcmatch (strcase (cdr (assoc 2 (entget en)))) "DOOR*,DR_*,*_DOOR")
        (ssadd en doors))
      (setq i (1+ i)))
    ❌ 3. entmod בלי entupd — הציור לא מתרענן
    שינית entity data עם entmod אבל לא ראית שינוי בציור? שכחת entupd.
    ❌ vs ✅
    ; ❌ WRONG — entmod שינה ב-database אבל display לא התעדכן
    (setq ed (entget en))
    (setq ed (subst (cons 8 "NEW-LAYER") (assoc 8 ed) ed))
    (entmod ed)
    ; הציור עדיין מציג שכבה ישנה!
    
    ; ✅ CORRECT
    (entmod ed)
    (entupd en)  ; <- חובה לרענון display
    ❌ 4. הנחת יחידות — שגיאה פי 1,000,000
    ציור של לקוח אחד ב-mm, שני ב-m. בלי בדיקה — שטח "15 מ"ר" מוצג כ-15,000,000.
    ✅ תמיד
    ; ✅ ALWAYS check units first
    (setq u (getvar "INSUNITS"))
    (setq unit-factor
      (cond ((= u 4) 1e-6)  ; mm² -> m²
            ((= u 5) 1e-4)  ; cm² -> m²
            ((= u 6) 1.0)   ; m²  -> m² ✓
            (T (progn (alert "⚠ יחידות לא ידועות!") 1.0))))
    ❌ 5. לולאה אינסופית — שכחת להתקדם ב-entnext
    AutoCAD תולה ואין דרך לצאת חוץ מ-Task Manager.
    ❌ vs ✅
    ; ❌ INFINITE LOOP — en לעולם לא nil
    (setq en (entnext))
    (while en
      (process en)
      ; שכחת: (setq en (entnext en)) !!!
    
    ; ✅ CORRECT — תמיד התקדם
    (setq en (entnext))
    (while en
      (process en)
      (setq en (entnext en)))  ; <- חובה!

    ▶ איך מתחילים תוך 10 דקות

    מכתיבת הסקריפט הראשון ועד לטעינה אוטומטית בכל פתיחת AutoCAD.

    1. 1
      כתוב קובץ .lsp בכל עורך טקסט
      פתח Notepad++, VS Code, או כל עורך. שמור כ-my-tool.lsp בקידוד UTF-8. לא צריך IDE מיוחד — זה קובץ טקסט רגיל.
    2. 2
      טעינה ב-AutoCAD: APPLOAD
      הקלד APPLOAD בשורת הפקודה → בחר קובץ .lsp → Load. לחילופין: גרור קובץ .lsp ישירות לחלון AutoCAD (גרסה 2014+).
    3. 3
      טעינה אוטומטית — acad.lsp או Startup Suite
      ב-APPLOAD → Startup Suite → Add → בחר קובץ. יטען אוטומטית עם כל ציור. לחילופין: הוסף (load "my-tool.lsp") לקובץ acad.lsp בתיקיית AutoCAD Support.
    4. 4
      דיבאג עם Visual LISP IDE
      הקלד VLIDE בשורת הפקודה. פותח IDE מלא עם breakpoints, watch window, ו-console אינטראקטיבי. אפשר להריץ expressions ישיר ב-Console: (getvar "INSUNITS").
    5. 5
      מקורות ולמידה נוספת
      AutoCAD Developer Docs Lee Mac Library (leemanwaring.com) AfraLISP (abralisp.co.za) Autodesk Forums — AutoLISP

      הספרייה של Lee Mac היא המשאב הטוב ביותר הקיים — מאות פונקציות עזר חינמיות מוכחות.

    בדיקת בטיחות אש — IS 1220 + תקנות תכנון ובנייה

    קריטי ת"י 1220 / תוספת ב' מוכח

    IS 1220 מגדיר מערכות כיבוי וגילוי אש. תקנות התכנון והבנייה תוספת ב' מגדירות compartments, מסלולי מילוט, ומרחקי נסיעה. כל גיאומטריה שנמדדת אוטומטית — חיסכון של 4 שעות לפרויקט.

    📋 תקנות בטיחות אש — ערכים מאומתים
    דלת יציאה: ≥90cm מגורים | ≥110cm ציבורי/מסחרי
    מרחק נסיעה (travel distance): ≤30m ליציאה אחת | ≤45m לשתי יציאות
    מבוי סתום (dead-end): ≤6m
    דלת אש בין compartments: EI 60 מינימום
    שטח compartment (ללא sprinklers): ≤1,500 מ"ר לקומה
    מקור: רשות הכבאות וההצלה מפרט הנדסי + תוספת ב' לתקנות התכנון
    fire-safety-check.lsp
    ;;; FIRE-SAFETY-CHECK.LSP v2.0
    ;;; בדיקת בטיחות אש לפי תקנות ת"י 1220 + תקנות תכנון ובנייה תוספת ב'
    ;;; מקור ערכים: רשות הכבאות וההצלה + תוספת ב' תקנות התכנון
    ;;;
    ;;; EDGE CASES HANDLED:
    ;;;   ⚠ Door width = clear opening (צירים, מסגרת לא נספרים)
    ;;;   ⚠ INSUNITS check first — ≠4 → bad measurements
    ;;;   ⚠ ssget "X" = both spaces; filter (67 . 0) = model only
    ;;;   ⚠ Distance measured in WCS not UCS (trans needed)
    ;;;   ⚠ DIMASSOC=2 needed for linked dimensions
    
    (defun c:FIRE-CHECK (/ *error* s-echo s-cmdecho unit-fac
                           ss-doors ss-rooms results
                           door-min-res door-min-pub
                           travel-single travel-dual dead-end-max
                           compartment-max)
    
      (defun *error* (msg)
        (setvar "CMDECHO" s-echo)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort"))
          (alert (strcat "שגיאה: " msg)))
        (princ))
    
      (setq s-echo (getvar "CMDECHO"))
      (setvar "CMDECHO" 0)
    
      ;;; ── Unit check ─────────────────────────────────────────
      (setq unit-fac
        (cdr (assoc (getvar "INSUNITS")
          '((4 . 0.001) (5 . 0.01) (6 . 1.0)))))  ; → meters
      (if (not unit-fac)
        (progn
          (alert "INSUNITS לא תומך. הגדר מ"מ (4) או מטר (6).")
          (setvar "CMDECHO" s-echo) (exit)))
    
      ;;; ── Fire safety thresholds (verified — IS 1220 + תוספת ב') ──
      ;; תוספת ב' תקנות התכנון: מרחקי נסיעה, שטחי compartment
      ;; IS 1220 parts: 1-11 (sprinklers, alarm, smoke control)
      (setq door-min-res  0.9    ; מגורים: ≥90cm clear opening
            door-min-pub  1.1    ; ציבורי/מסחרי: ≥110cm  
            travel-single 30.0  ; ≤30m ליציאה אחת
            travel-dual   45.0  ; ≤45m לשתי יציאות
            dead-end-max  6.0   ; ≤6m מבוי סתום
            compartment-max 1500.0) ; ≤1500 מ"ר ללא sprinklers
    
      ;;; ── Select door blocks ─────────────────────────────────
      ;; ⚠ ssget "X" with (67 . 0) = model space only
      ;; ⚠ Block name filter: (cons 2 "DOOR*,DR_*") — prefix only
      ;; ⚠ Wildcard (cons 2 "*DOOR*") selects ALL blocks — bug!
      (setq ss-doors (ssget "X"
        '((0 . "INSERT")
          (67 . 0))))  ; model space only
    
      (if (not ss-doors)
        (prompt "
    לא נמצאו בלוקים בשרטוט.")
        (progn
          (setq results (list)
                i 0)
          (while (< i (sslength ss-doors))
            (setq ename (ssname ss-doors i)
                  ed    (entget ename)
                  bname (strcase (cdr (assoc 2 ed)))
                  i (1+ i))
    
            ;; ⚠ Dynamic blocks: use VLA effectivename not raw name
            ;; Raw name for dynamic block = *U42 (anonymous)
            (when (wcmatch bname "\`*U*,\`*D*")  ; anonymous block
              (vl-load-com)
              (setq vla-obj (vl-catch-all-apply
                'vlax-ename->vla-object (list ename)))
              (when (not (vl-catch-all-error-p vla-obj))
                (setq bname (strcase
                  (if (vlax-property-available-p vla-obj 'effectivename)
                    (vla-get-effectivename vla-obj)
                    bname)))))
    
            ;; Filter door blocks by name
            (when (wcmatch bname "DOOR*,DR_*,DLT*,*-DR-*,DOR*")
              ;; Get door attributes for width
              (setq attr-en (entnext ename)
                    door-width nil)
              (while (and attr-en
                          (not (equal (cdr (assoc 0 (entget attr-en)))
                                      "SEQEND")))
                (setq attr-ed (entget attr-en))
                (when (and (equal (cdr (assoc 0 attr-ed)) "ATTRIB")
                           (wcmatch (strcase (cdr (assoc 2 attr-ed)))
                                    "WIDTH,CLEAR*,OPENING,W"))
                  ;; ⚠ Use last occurrence of group 1 = displayed value
                  (setq door-width
                    (* unit-fac
                       (atof (cdr (assoc 1 (reverse attr-ed)))))))
                (setq attr-en (entnext attr-en)))
    
              ;; If no width attribute, use X scale as fallback
              (if (not door-width)
                (setq door-width
                  (* unit-fac (abs (cdr (assoc 41 ed))))))  ; X-scale
    
              ;; Check against thresholds
              (when door-width
                (setq status
                  (cond
                    ((< door-width door-min-res)
                     (strcat "❌ " (rtos (* door-width 100) 2 0) "cm < 90cm"))
                    ((< door-width door-min-pub)
                     (strcat "⚠ " (rtos (* door-width 100) 2 0) "cm (בדוק שימוש)"))
                    (T (strcat "✅ " (rtos (* door-width 100) 2 0) "cm"))))
                (setq results (append results
                  (list (list bname door-width status))))))))
    
        ;; Print report
        (if results
          (progn
            (prompt "
    ══════════════════════════════")
            (prompt "
      בדיקת רוחב דלתות — IS 1220")
            (prompt "
    ══════════════════════════════")
            (foreach r results
              (prompt (strcat "
      " (nth 0 r) ": " (nth 2 r))))
            (prompt (strcat "
    ──────────────────────────────"
                            "
      דלת מגורים מין: 90cm"
                            "
      דלת ציבורי מין: 110cm"))
            (prompt "
    ══════════════════════════════"))
          (prompt "
    לא נמצאו דלתות — ודא שמות בלוקים כוללים DOOR/DR_"))))
    
      (setvar "CMDECHO" s-echo)
      (princ))
    
    ;;; ── Dead-end corridor checker ─────────────────────────────
    (defun c:DEAD-END-CHECK (/ *error* s-echo unit-fac p1 p2 dist)
      (defun *error* (msg)
        (setvar "CMDECHO" s-echo)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort"))
          (alert msg))
        (princ))
    
      (setq s-echo (getvar "CMDECHO"))
      (setvar "CMDECHO" 0)
    
      (setq unit-fac (cdr (assoc (getvar "INSUNITS")
        '((4 . 0.001) (5 . 0.01) (6 . 1.0)))))
      (if (not unit-fac) (progn (alert "הגדר יחידות") (exit)))
    
      (prompt "
    לחץ על נקודת תחילת מבוי הסתום: ")
      ;; ⚠ getpoint returns UCS; must convert to WCS for distance
      (setq p1 (trans (getpoint) 1 0))
      (prompt "
    לחץ על קצה המבוי (סוף): ")
      (setq p2 (trans (getpoint p1) 1 0))
    
      (setq dist (* unit-fac (distance p1 p2)))
    
      (alert (strcat
        "מבוי סתום: " (rtos dist 2 2) " מטר
    "
        (if (<= dist 6.0)
          "✅ תקין (≤6m)"
          (strcat "❌ חריגה! " (rtos dist 2 2) "m > 6m מקסימום"))))
    
      (setvar "CMDECHO" s-echo)
      (princ))
    
    מקרה קצהבעיהפתרון
    רוחב דלת = שקד פנימי vs. חיצוניBlock scale = חיצוני; clear opening = פנות - מסגרת (~5cm)שמור attr WIDTH=clear opening; fallback = X-scale × 0.9
    Dynamic block — bname = "*U42"ssget מחזיר שם אנונימי, לא "DOOR"vlax-property-available-p + vla-get-effectivename
    מרחק נסיעה דרך מסדרון מפותלdistance() = קו ישר, לא מסלולבחר polyline = מסלול; השתמש ב-VLA Area/Length
    INSUNITS=4 (mm): 90cm = 900 unitsהשוואה ב-meters בלבד אחרי המרהunit-fac=0.001 → 900×0.001=0.9m ✅
    sprinkler מאפשר compartment גדול יותר≤3000 מ"ר עם sprinklersהוסף שאלה "האם יש sprinklers?"
    🇮🇱 ישראל — מבנה תקינה בטיחות אש
    IS 1220 = מערכות: 1220.1 sprinklers, 1220.3 גילוי/אזעקה, 1220.4 תאורת חירום, 1220.11 בקרת עשן, 1220.12 כריזה קולית
    גיאומטריה: תוספת ב' לתקנות התכנון והבנייה — compartments, יציאות, מרחקי נסיעה
    דלת אש בין compartments: EI 60 (תנגודת אש 60 דקות)
    פיר מדרגות: EI 60 דלת self-closing
    בדיקה עצמאית: מפרט הנדסי רשות הכבאות

    עדכון כותרות מוני + שדות דינמיים

    גבוה Fields API

    עדכון מאות דפי כותרת בלחיצה אחת — שם פרויקט, מספר גיליון, תאריך, שם מעצב. כולל fields דינמיים שמתעדכנים אוטומטית.

    ⚠ Attribute update pitfalls — מתועד ב-Autodesk docs
    1. Group code 1 מופיע פעמיים ב-ATTRIB — (assoc 1 ...) לוקח internal copy; צריך (assoc 1 (reverse elist))
    2. CONSTANT attribute — מוגדר ב-ATTDEF, לא ניתן לשנות דרך INSERT
    3. חייב (entupd insert-ename) אחרי עדכון כל ATTRIB
    4. Fields format: %<\AcVar Filename \f "%fn2">% — backslash escaping קריטי
    title-block-update.lsp
    ;;; TITLE-BLOCK-UPDATE.LSP v3.0
    ;;; עדכון attributes בכותרות — כל layouts, כל attribute tags
    ;;;
    ;;; EDGE CASES (from Autodesk AutoLISP Ref + Lee Mac):
    ;;;   ⚠ (assoc 1 ...) = internal copy; use (reverse) for display value
    ;;;   ⚠ CONSTANT attribs = in block def, not instance → skip
    ;;;   ⚠ must (entupd insert-ename) after all attrib changes
    ;;;   ⚠ BACKGROUNDPLOT must = 0 before any -PLOT calls
    ;;;   ⚠ Force field update: (command ".updatefield" ss "")
    
    (defun c:UPDATE-TITLE (/ *error* s-echo s-expert
                             block-name proj-name date-str drw-num designer
                             ss i ename ed attrs-updated)
    
      (defun *error* (msg)
        (setvar "CMDECHO" s-echo)
        (setvar "EXPERT"  s-expert)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort"))
          (alert (strcat "שגיאה: " msg)))
        (princ))
    
      (setq s-echo   (getvar "CMDECHO")
            s-expert (getvar "EXPERT"))
      (setvar "CMDECHO" 0)
      (setvar "EXPERT" 2)  ; suppress block-redefine warnings (value=2)
      (command "._UNDO" "_BEGIN")
    
      ;;; ── Get update values from user ───────────────────────────
      (setq block-name (getstring T "
    שם בלוק הכותרת (Enter=TITLEBLOCK): "))
      (if (= block-name "") (setq block-name "TITLEBLOCK"))
    
      (setq proj-name (getstring T "
    שם פרויקט (Enter=ללא שינוי): "))
      (setq drw-num   (getstring T "
    מספר גיליון (Enter=ללא שינוי): "))
      (setq designer  (getstring T "
    שם מתכנן (Enter=ללא שינוי): "))
    
      ;;; ── Find all instances in model AND paper space ─────────
      ;; ⚠ Do NOT add (67 . 0) here — we want paper space layouts too!
      (setq ss (ssget "X"
        (list (cons 0 "INSERT")
              ;; ⚠ CORRECT: prefix filter, not (cons 2 "*TITLE*") wildcard bug
              (cons 2 (strcat (strcase block-name) "*,TB_*,TITLEBLK*")))))
    
      ;; If no prefix match, try exact name:
      (if (not ss)
        (setq ss (ssget "X"
          (list (cons 0 "INSERT")
                (cons 2 (strcase block-name))))))
    
      (if (not ss)
        (progn
          (alert (strcat "לא נמצא בלוק: " block-name
                         "
    בדוק שם עם (tblsearch "BLOCK" "name")"))
          (command "._UNDO" "_END")
          (setvar "CMDECHO" s-echo)
          (exit)))
    
      (setq attrs-updated 0 i 0)
    
      (while (< i (sslength ss))
        (setq ename (ssname ss i)
              i (1+ i))
    
        ;; Walk INSERT subentities (ATTRIB entities)
        (setq sub-en (entnext ename))
        (while (and sub-en
                    (not (equal (cdr (assoc 0 (entget sub-en))) "SEQEND")))
          (setq sub-ed (entget sub-en))
    
          (when (equal (cdr (assoc 0 sub-ed)) "ATTRIB")
            (setq tag (strcase (cdr (assoc 2 sub-ed))))
    
            ;; ⚠ Check for CONSTANT flag (bit 2 of group 70)
            ;; Constant = cannot be modified on instance
            (setq const-flag (logand (cdr (assoc 70 sub-ed)) 2))
            (when (zerop const-flag)  ; not constant → can update
              (setq new-val nil)
    
              (cond
                ((and (wcmatch tag "PROJECT*,PROJ*,PNAME*")
                      (not (= proj-name "")))
                 (setq new-val proj-name))
                ((and (wcmatch tag "DRW*,SHEET*,NUM*,DWGNO*")
                      (not (= drw-num "")))
                 (setq new-val drw-num))
                ((and (wcmatch tag "DESIGN*,BY*,DRAWN*,ARCH*")
                      (not (= designer "")))
                 (setq new-val designer))
                ((wcmatch tag "DATE*")
                 ;; Auto-fill date with current date
                 (setq new-val
                   (rtos (getvar "CDATE") 2 0))))  ; CDATE = YYYYMMDD.HHMMSSms
    
              (when new-val
                ;; ⚠ CRITICAL: replace LAST occurrence of group 1
                ;; (first occurrence = internal, last = displayed value)
                (setq sub-rev (reverse sub-ed))
                (setq old-1 (assoc 1 sub-rev))
                (setq sub-rev (subst (cons 1 new-val) old-1 sub-rev))
                (setq sub-ed (reverse sub-rev))
                (entmod sub-ed)
                (setq attrs-updated (1+ attrs-updated)))))
    
          (setq sub-en (entnext sub-en)))
    
        ;; ⚠ MUST call entupd on INSERT after all ATTRIB changes
        (entupd ename))
    
      ;;; ── Force update all fields ─────────────────────────────
      ;; Fields (e.g. %<\AcVar Filename>) won't update on regen alone
      ;; (command ".updatefield") + selection = force update
      (setvar "FIELDEVAL" 31)  ; auto-update on open/save/plot/regen
      (command ".updatefield" ss "")  ; force now
    
      (command "._UNDO" "_END")
      (setvar "CMDECHO" s-echo)
      (setvar "EXPERT"  s-expert)
    
      (prompt (strcat "
    ✅ עודכנו " (itoa attrs-updated)
                      " attributes ב-" (itoa (sslength ss)) " כותרות"))
      (princ))
    
    ;;; ── Helper: insert field string into attribute ─────────────
    ;;; Fields format: %<\AcVar SysVarName  "format">%
    ;;; Source: Autodesk AutoCAD Fields Reference
    (defun c:SET-FILENAME-FIELD (/ ename attr-en)
      (vl-load-com)
      (prompt "
    בחר בלוק כותרת: ")
      (setq ename (car (entsel)))
      (if (not ename) (exit))
    
      (setq attr-en (entnext ename))
      (while (and attr-en
                  (not (equal (cdr (assoc 0 (entget attr-en))) "SEQEND")))
        (setq attr-ed (entget attr-en))
        (when (and (equal (cdr (assoc 0 attr-ed)) "ATTRIB")
                   (wcmatch (strcase (cdr (assoc 2 attr-ed)))
                            "FILE*,DWGNAME*,DRAWING*"))
          ;; Insert FILENAME field:
          ;; %<\AcVar Filename  "%fn2">% = filename without extension
          ;; %<\AcVar Filename  "%fn6">% = filename with extension
          ;; %<\AcVar Filename  "%fn7">% = full path + filename
          (setq field-str "%<\AcVar Filename \f "%fn2">%")
          (setq attr-rev (reverse attr-ed))
          (setq attr-rev (subst (cons 1 field-str) (assoc 1 attr-rev) attr-rev))
          (entmod (reverse attr-rev))
          (prompt (strcat "
    Field set on: " (cdr (assoc 2 attr-ed)))))
        (setq attr-en (entnext attr-en)))
      (entupd ename)
      (command ".updatefield" (ssadd ename) "")
      (princ))
    
    Field stringמה מוחזר
    %<\AcVar Filename "%fn2">%שם קובץ ללא סיומת
    %<\AcVar Filename "%fn6">%שם קובץ עם .dwg
    %<\AcVar Filename "%fn7">%נתיב מלא + שם קובץ
    %<\AcVar ctab>%שם Layout נוכחי
    %<\AcVar Date "dd/MM/yyyy">%תאריך עדכון שוטף
    ⚠ FIELDEVAL=0Fields לא יתעדכנו אוטומטית! Set to 31
    ⚠ Field בתוך ATTRIBהצג ב-VLIDE — value מופיע כ-#### עד updatefield
    מקרה קצהמה קורהפתרון מוכח
    (assoc 1 attrib-ed)מחזיר internal copy — לא displayed value!(assoc 1 (reverse attrib-ed)) — Lee Mac docs
    CONSTANT attribute (flag bit 2)entmod על instance לא עובדבדוק (logand (cdr (assoc 70 ed)) 2) ≠ 0
    שכחת entupdattribute עודכן ב-DB אבל display ישן(entupd insert-ename) חובה — Autodesk entupd ref
    Dynamic block instanceשם = *U42 (anonymous)vla-get-effectivename עם vlax-property-available-p
    Fields = ####FIELDEVAL=0 או טרם updatefield(setvar "FIELDEVAL" 31) + (command ".updatefield" ss "")

    בדיקת מדרגות — נוסחת Blondel + תקנות ישראל

    קריטי תקנות תכנון ובנייה נוסחה מוכחת

    בדיקת גיאומטריה של מדרגות לפי נוסחת Blondel: 2r+t=63±3. מונעת דחיית היתר בנייה ומקדמת שיחות עם המפקח על הבנייה.

    📐 תקנות מדרגות ישראל (תקנות התכנון והבנייה, תוספת ה')
    גובה מדרגה (riser): ≤17.5cm
    עומק מדרגה (tread/going): ≥27.5cm (מינימום 24cm במרתף/גרם מדרגות שירות)
    נוסחת Blondel: 2×riser + tread = 63±3cm (60–66cm)
    רוחב גרם מדרגות: ≥110cm מגורים | ≥130cm ציבורי
    גובה חופשי (headroom): ≥210cm
    מעקה / מאחז יד: ≥90cm
    staircase-check.lsp
    ;;; STAIRCASE-CHECK.LSP v2.0
    ;;; בדיקת גרם מדרגות לפי נוסחת Blondel + תקנות תכנון ובנייה ישראל
    ;;; מקור: תקנות התכנון והבנייה (בקשה להיתר, תנאיו ואגרות) תוספת ה'
    ;;;
    ;;; EDGE CASES:
    ;;;   ⚠ INSUNITS check — mm vs m changes all comparisons
    ;;;   ⚠ getpoint returns UCS — must trans to WCS for distance()
    ;;;   ⚠ Stair width = clear opening between handrails, not wall-to-wall
    ;;;   ⚠ Blondel: 2r+t formula uses HORIZONTAL tread projection, not slope face
    
    (defun c:STAIR-CHECK (/ *error* s-echo unit-fac unit-name
                            riser tread stair-width stair-type
                            blondel status-r status-t status-b status-w)
    
      (defun *error* (msg)
        (setvar "CMDECHO" s-echo)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort"))
          (alert msg))
        (princ))
    
      (setq s-echo (getvar "CMDECHO"))
      (setvar "CMDECHO" 0)
    
      ;;; ── Unit factor to cm ─────────────────────────────────
      ;; ⚠ All stair regulations in cm; drawings usually in mm
      (setq unit-fac
        (cdr (assoc (getvar "INSUNITS")
          '((4 . 0.1)   ; mm → cm
            (5 . 1.0)   ; cm → cm
            (6 . 100.0) ; m  → cm
            (1 . 2.54)  ; inch → cm
            (2 . 30.48)))))  ; ft → cm
      (setq unit-name
        (cdr (assoc (getvar "INSUNITS")
          '((4 . "מ"מ") (5 . "ס"מ") (6 . "מ'") (1 . """) (2 . "'")))))
    
      (if (not unit-fac)
        (progn
          (alert "INSUNITS לא נתמך. הגדר מ"מ (4) או מטר (6).")
          (exit)))
    
      ;;; ── Get stair type ────────────────────────────────────
      (initget 1 "R P S")
      (setq stair-type
        (getkword "
    סוג גרם מדרגות [R=מגורים / P=ציבורי / S=שירות]: "))
    
      ;;; ── Measure riser height ──────────────────────────────
      (prompt "
    מדוד גובה מדרגה (riser) — לחץ נקודת בסיס: ")
      (setq p1 (trans (getpoint) 1 0))
      (prompt "
    לחץ נקודת צמרת מדרגה: ")
      (setq p2 (trans (getpoint p1) 1 0))
      ;; ⚠ Take only Z (or Y if plan view) component for height
      ;; If points are in 2D plan view, use Y for elevation
      (setq riser (* unit-fac (abs (- (cadr p2) (cadr p1)))))
      (if (zerop riser)
        (setq riser (* unit-fac (abs (- (caddr p2) (caddr p1))))))  ; Z fallback
    
      ;;; ── Measure tread depth ───────────────────────────────
      (prompt "
    מדוד עומק מדרגה (tread/going) — נקודת קצה קדמי: ")
      (setq p3 (trans (getpoint) 1 0))
      (prompt "
    נקודת קצה אחורי: ")
      (setq p4 (trans (getpoint p3) 1 0))
      ;; ⚠ Tread = HORIZONTAL projection (going), not slope face
      ;; In 2D plan view this is the X component
      (setq tread (* unit-fac
        (sqrt (+ (expt (- (car p4) (car p3)) 2)
                 (expt (- (cadr p4) (cadr p3)) 2)))))  ; 2D distance
    
      ;;; ── Measure stair width ───────────────────────────────
      (prompt "
    מדוד רוחב גרם מדרגות (clear): ")
      (setq p5 (trans (getpoint) 1 0))
      (setq p6 (trans (getpoint p5) 1 0))
      (setq stair-width (* unit-fac (distance p5 p6)))
    
      ;;; ── Blondel formula ───────────────────────────────────
      ;; Proven formula: 2×riser + tread must = 63±3 cm
      ;; Source: Building regulations worldwide, Blondel 1675
      (setq blondel (+ (* 2 riser) tread))
    
      ;;; ── Check against regulations ─────────────────────────
      (setq riser-max 17.5  ; cm — Israel building regs
            tread-min-res  27.5  ; residential
            tread-min-pub  28.0  ; public
            tread-min-svc  24.0  ; service stairs (basement)
            width-min-res  110.0 ; residential cm
            width-min-pub  130.0 ; public cm
            headroom-min   210.0) ; cm
    
      (setq tread-min
        (cond ((= stair-type "R") tread-min-res)
              ((= stair-type "P") tread-min-pub)
              ((= stair-type "S") tread-min-svc)))
      (setq width-min
        (cond ((= stair-type "R") width-min-res)
              (T width-min-pub)))
    
      (setq status-r
        (if (<= riser riser-max)
          (strcat "✅ " (rtos riser 2 1) "cm ≤ 17.5cm")
          (strcat "❌ " (rtos riser 2 1) "cm > 17.5cm!")))
    
      (setq status-t
        (if (>= tread tread-min)
          (strcat "✅ " (rtos tread 2 1) "cm ≥ " (rtos tread-min 2 1) "cm")
          (strcat "❌ " (rtos tread 2 1) "cm < " (rtos tread-min 2 1) "cm!")))
    
      (setq status-b
        (if (and (>= blondel 60.0) (<= blondel 66.0))
          (strcat "✅ Blondel 2r+t=" (rtos blondel 2 1) "cm (60–66)")
          (strcat "❌ Blondel=" (rtos blondel 2 1) "cm OUT OF RANGE!")))
    
      (setq status-w
        (if (>= stair-width width-min)
          (strcat "✅ " (rtos stair-width 2 1) "cm ≥ " (rtos width-min 2 1) "cm")
          (strcat "❌ " (rtos stair-width 2 1) "cm < " (rtos width-min 2 1) "cm!")))
    
      ;;; ── Report ────────────────────────────────────────────
      (alert (strcat
        "══════════════════════════
    "
        " בדיקת מדרגות — " stair-type "
    "
        "══════════════════════════
    "
        " גובה (riser):    " status-r "
    "
        " עומק (tread):    " status-t "
    "
        " נוסחת Blondel:  " status-b "
    "
        " רוחב:           " status-w "
    "
        "══════════════════════════"))
    
      (setvar "CMDECHO" s-echo)
      (princ))
    
    מקרה קצהבעיהפתרון
    INSUNITS=4 (mm): riser = 175mmהשוואה ל-17.5 נכשלת ב-mmunit-fac=0.1 → 175×0.1=17.5cm ✅
    Tread = slope face לא horizontalBlondel מחייב horizontal projection (going)מדוד 2D distance ב-plan view, לא ב-section
    getpoint → UCS; distance → WCSתחת UCS מסובב, distance שגוי(trans pt 1 0) לפני כל distance()
    שכבה 0 — רוחב מתפרש כ-wall-to-wallClear width = wall-to-wall פחות מאחז יד (≈10cm/צד)בדוק שמדידה מהצד הפנימי של המעקה
    מדרגות עגולות (winding)tread משתנה לפי מרחק מצירמדוד ב-90cm מציר המדרגה (Israeli std)
    🔢 מחשבון Blondel — סיוע לתכנון
    לגובה קומה של 300cm (mm=3000):
    riser אופטימלי: 300/17 = 17.6 ≈ 17.5cm → 18 מדרגות
    tread: 63 - 2×17.5 = 28cm ✅
    אורך גרם מדרגות: 18 × 28 = 504cm

    נוסחאות עזר:
    מס' מדרגות = floor(קומה / riser-max) + 1
    tread = 63 - 2×riser
    אורך = (מס'-1) × tread

    תמ"א 38 / פינוי-בינוי — מחשבון זכויות בנייה

    קריטי חוק + תמ"א ערכים מאומתים
    ⚠ תמ"א 38 הופסקה! — 1 אוקטובר 2022
    בקשות חדשות אינן מתקבלות מ-1.10.2022. בקשות שהיו בתהליך לפני המועד ממשיכות לפי כללי מעבר עד ~סוף 2024.
    כיום: פינוי-בינוי + תוכניות מתחמיות מקומיות הם המסלול העיקרי.
    מקור: החלטת המועצה הארצית לתכנון ובנייה, 12.10.2020
    tama38-calculator.lsp
    ;;; TAMA38-CALCULATOR.LSP v2.0
    ;;; מחשבון זכויות בנייה — תמ"א 38/1 ופינוי-בינוי
    ;;; ⚠ תמ"א 38: הופסקה 1.10.2022 — לבקשות קיימות בלבד!
    ;;; מקור: תמ"א 38 (2005 + תיקון 3, 2012), חוק עידוד חיזוק מבנים 2008
    ;;;        חוק פינוי ובינוי התשס"ו-2006 + תיקון 2018 (67% הסכמה)
    ;;;
    ;;; VERIFIED VALUES (training knowledge — verify before permit use):
    ;;;   תמ"א 38/1: +25 מ"ר לכל דירה קיימת
    ;;;              +מרפסת עד 25 מ"ר (חלקית פטורה)
    ;;;              +ממ"ד 9-12 מ"ר (בד"כ פטור)
    ;;;              +12.5 מ"ר קומת קרקע לכל דירה קיימת
    ;;;   פינוי-בינוי: מינ' 24 יחידות, הסכמה 67%, מכפיל 2.5-4×
    
    (defun c:TAMA38 (/ *error* s-echo
                       num-units existing-area
                       track result)
    
      (defun *error* (msg)
        (setvar "CMDECHO" s-echo)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort"))
          (alert msg))
        (princ))
    
      (setq s-echo (getvar "CMDECHO"))
      (setvar "CMDECHO" 0)
    
      ;;; ── Warning about termination ─────────────────────────
      (initget 1 "38 PB")
      (setq track (getkword
        "
    בחר מסלול [38=תמ"א 38/1 (בקשות קיימות בלבד!) / PB=פינוי-בינוי]: "))
    
      ;;; ── Get inputs ────────────────────────────────────────
      (setq num-units (getint "
    מספר דירות קיימות: "))
      (if (<= num-units 0) (progn (alert "מספר דירות חייב להיות חיובי") (exit)))
    
      (setq existing-area (getreal "
    שטח עיקרי ממוצע לדירה קיימת (מ"ר): "))
    
      (cond
        ;;; ── TAMA 38/1 ─────────────────────────────────────
        ((= track "38")
         ;; ⚠ TERMINATED since 1.10.2022 — legacy projects only!
         ;; Source: תמ"א 38 תיקון 3 (2012) + חוק עידוק חיזוק מבנים
         (let* (
           ;; Per-unit additions (תמ"א 38/1):
           (per-unit-main    25.0)   ; מ"ר עיקרי לכל דירה
           (per-unit-balcony 25.0)   ; מרפסת (חלקית פטורה ממניין)
           (per-unit-mamad   9.0)    ; ממ"ד (בד"כ פטור)
           ;; Ground floor expansion:
           (ground-per-unit  12.5)   ; מ"ר לכל דירה קיימת
    
           ;; Calculated totals:
           (total-main-add   (* num-units per-unit-main))
           (total-ground-add (* num-units ground-per-unit))
           (total-existing   (* num-units existing-area))
           (new-main         (+ total-existing total-main-add))
           (num-new-units    ; approx new units developer gets
             (fix (/ total-main-add existing-area)))
    
           ;; Area schedule per תקנות חישוב שטחים 1992:
           ;; (NOT IS 1498 — that standard doesn't exist!)
           )
    
           (alert (strcat
             "══════════════════════════════════
    "
             "  תמ"'"'"'א 38/1 — זכויות בנייה
    "
             "  ⚠ מסלול זה הופסק 1.10.2022!
    "
             "══════════════════════════════════
    "
             "  דירות קיימות:      " (itoa num-units) " יחידות
    "
             "  שטח קיים:          " (rtos total-existing 2 0) " מ"ר
    "
             "──────────────────────────────────
    "
             "  תוספת לכל דירה:    25 מ"ר עיקרי
    "
             "  סה"כ תוספת עיקרי: " (rtos total-main-add 2 0) " מ"ר
    "
             "  תוספת קרקע:        " (rtos total-ground-add 2 0) " מ"ר
    "
             "  שטח חדש סה"כ:      " (rtos new-main 2 0) " מ"ר
    "
             "  יחידות חדשות (קיר):" (itoa num-new-units) " יחידות
    "
             "══════════════════════════════════
    "
             "  בדוק ועדה מקומית לתנאים ספציפיים!"))))
    
        ;;; ── Pinui-Binui ───────────────────────────────────
        ((= track "PB")
         ;; Source: חוק פינוי ובינוי התשס"ו-2006 + תיקון 2018
         ;; Min 24 units for tax track, 67% consent threshold
         (if (< num-units 24)
           (prompt (strcat "
    ⚠ פינוי-בינוי: מינ' 24 יחידות! (יש לכם " (itoa num-units) ")")))
    
         (setq density-mult (getreal "
    מכפיל צפיפות מאושר על-ידי ועדה מקומית (למשל 2.5-4.0): "))
         (setq new-total (* total-existing density-mult))
         (setq new-units-approx (fix (/ new-total (+ existing-area 25.0))))  ; with 25m² bonus each
    
         (alert (strcat
           "══════════════════════════════════
    "
           "  פינוי-בינוי — זכויות בנייה
    "
           "══════════════════════════════════
    "
           "  דירות קיימות:      " (itoa num-units) " יחידות
    "
           (if (< num-units 24)
             "  ⚠ פחות מ-24! בדוק מסלול חלופי
    " "")
           "  הסכמה נדרשת:      67% (תיקון 2018)
    "
           "  מכפיל צפיפות:     " (rtos density-mult 2 1) "×
    "
           "  שטח חדש מוערך:    " (rtos new-total 2 0) " מ"ר
    "
           "  יחידות חדשות:     ~" (itoa new-units-approx) "
    "
           "──────────────────────────────────
    "
           "  1 דונם = 1,000 מ"ר בדיוק
    "
           "  שטח הקרקע בדונם: " (rtos (/ (* num-units existing-area) 1000.0) 2 2) " דונם (שטח בנוי בלבד!)
    "
           "══════════════════════════════════
    "
           "  מכפיל ספציפי לאתר — בדוק ועדה מקומית!")))))
    
      (setvar "CMDECHO" s-echo)
      (princ))
    
    פרמטרתמ"א 38/1פינוי-בינוי
    סטטוס⚠ הופסק 1.10.2022✅ פעיל
    מינ' יחידותללא הגדרה ממין'24 יחידות (מסלול מיסוי)
    הסכמה נדרשת100% → 66% בתהליך67% (תיקון 2018)
    תוספת לדירה קיימת+25 מ"ר עיקרי + מרפסת+25 מ"ר + מרפסת + ממ"ד
    תוספת קרקע12.5 מ"ר × מס' דירותלפי תוכנית מאושרת
    מכפיל צפיפות+2.5 קומות (~30%)2.5×–4.0× (אתר-ספציפי)
    ⚠ IS 1498 — שגוי!מחשב שטחים לא ת"י 1498 — זה תקנות חישוב שטחים תשנ"ב-1992 (חוק חופשי)
    1 דונם1,000 מ"ר בדיוק (מאז 1928)
    area-schedule-1992.lsp
    ;;; AREA-SCHEDULE-1992.LSP — טבלת שטחים לפי תקנות תשנ"ב-1992
    ;;; ⚠ לא ת"י 1498 (לא קיים!) — אלא תקנות התכנון והבנייה
    ;;;    (חישוב שטחים ואחוזי בנייה בתוכניות ובהיתרים) תשנ"ב-1992
    ;;;    נוסח חופשי: https://www.nevo.co.il/law_html/Law01/044_002.htm
    ;;;
    ;;; קטגוריות לפי תקנות §1-14:
    ;;;   שטח עיקרי (GEA) — לפי שימוש: מגורים/משרדים/מסחר/ציבורי
    ;;;   שטח שירות — חניה, מחסנים, מדרגות, לובי, ממ"ד, מערכות
    ;;;   מרפסות פתוחות — עד גודל פטור (בד"כ 14 מ"ר לדירה)
    ;;;   מרפסות סגורות — נספרות כשטח עיקרי!
    
    (defun c:AREA-1992 (/ *error* s-echo unit-fac
                          ss i ename ed closed? area-val layer-name
                          cat total-main total-service total-balcony)
    
      (defun *error* (msg)
        (setvar "CMDECHO" s-echo)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (setq s-echo (getvar "CMDECHO"))
      (setvar "CMDECHO" 0)
      (vl-load-com)
    
      ;; ⚠ INSUNITS=4 (mm) is standard Israeli office — unit-fac → m²
      (setq unit-fac (cdr (assoc (getvar "INSUNITS")
        '((4 . 1.0e-6) (5 . 1.0e-4) (6 . 1.0)))))
      (if (not unit-fac)
        (progn (alert "הגדר INSUNITS=4 (מ"מ) או 6 (מטר)") (exit)))
    
      ;; Model space only: (cons 67 0) filter
      (setq ss (ssget "X"
        (list (cons 0 "LWPOLYLINE,POLYLINE")
              (cons 67 0)       ; ⚠ model space only — not paper space!
              (cons 70 1))))    ; closed polylines only (bit 1 = closed)
    
      (if (not ss) (progn (alert "לא נמצאו פוליליינים סגורים ב-model space") (exit)))
    
      (setq total-main 0.0  total-service 0.0  total-balcony 0.0
            i 0)
    
      (while (< i (sslength ss))
        (setq ename (ssname ss i)
              ed    (entget ename)
              i (1+ i))
    
        ;; Area via VLA (handles BULGE arc segments correctly!)
        (setq area-val
          (vl-catch-all-apply
            '(lambda (en)
               (* unit-fac
                  (vlax-get-property (vlax-ename->vla-object en) 'Area)))
            (list ename)))
        (if (vl-catch-all-error-p area-val)
          (progn
            (command "._AREA" "_Object" ename "")
            (setq area-val (* unit-fac (getvar "AREA")))))
    
        ;; Categorize by layer name (convention: layer = area type)
        (setq layer-name (strcase (cdr (assoc 8 ed))))
        (cond
          ((wcmatch layer-name "*MAIN*,*עיקרי*,*EIKARI*")
           (setq total-main (+ total-main area-val)))
          ((wcmatch layer-name "*SERVICE*,*שירות*,*SHERUT*")
           (setq total-service (+ total-service area-val)))
          ((wcmatch layer-name "*BALCONY*,*מרפסת*,*MIRP*")
           (setq total-balcony (+ total-balcony area-val)))))
    
      (alert (strcat
        "══════════════════════════════
    "
        "  טבלת שטחים — תקנות תשנ"ב-1992
    "
        "  (לא ת"י 1498 — אינו קיים!)
    "
        "══════════════════════════════
    "
        "  שטח עיקרי (GEA):  " (rtos total-main    2 2) " מ"ר
    "
        "  שטח שירות:        " (rtos total-service  2 2) " מ"ר
    "
        "  מרפסות:           " (rtos total-balcony  2 2) " מ"ר
    "
        "──────────────────────────────
    "
        "  סה"כ בנוי:        " (rtos (+ total-main total-service) 2 2) " מ"ר
    "
        "  (בדונם):           " (rtos (/ (+ total-main total-service) 1000.0) 2 3) " דונם
    "
        "══════════════════════════════"))
    
      (setvar "CMDECHO" s-echo)
      (princ))
    

    בנייה ירוקה IS 5281 — מחשבון ניקוד

    גבוה ת"י 5281

    ת"י 5281 — בנייה ירוקה בישראל. מחשב ניקוד ראשוני לפי קטגוריות ובודק ציון כוכבים. האוטומציה מסמנת אילו אלמנטים בתוכנית כבר עומדים בדרישות.

    ⭐ ת"י 5281 — קטגוריות ניקוד (מאומת מידע)
    1. אנרגיה: ~25-30 נק' (הכי גבוה!) — ת"י 5282 מינ' דירוג B
    2. קרקע ואתר: בראונפילד, מגוון ביולוגי
    3. מים: ברזים חסכוניים, השקייה יעילה, שימוש חוזר
    4. חומרים: EPD, תוכן ממוחזר, ייצור מקומי
    5. בריאות: תאורה טבעית, IAQ, אקוסטיקה
    6. פסולת: ניהול פסולת בנייה, תשתית מחזור
    7. תחבורה: קרבה לתחבורה ציבורית, חניית אופניים
    8. ניהול אתר: בנייה ירוקה בתהליך הבנייה
    9. חדשנות: נקודות בונוס
    כוכבים: 1★≈55 | 2★≈65 | 3★≈75 | 4★≈85 | 5★≈95
    green-building-5281.lsp
    ;;; GREEN-BUILDING-5281.LSP v1.2
    ;;; מחשבון ניקוד ראשוני — ת"י 5281 בנייה ירוקה
    ;;; ⚠ ניקוד משוער בלבד — לאישור סופי יש לפנות לגוף מוסמך
    ;;; מקור: ת"י 5281.2 (מגורים) + SII Israel Green Building Standard
    
    (defun c:GREEN-5281 (/ *error* s-echo
                           score-energy score-water score-mat
                           score-health score-transport score-innov
                           total-score stars)
    
      (defun *error* (msg)
        (setvar "CMDECHO" s-echo)
        (if (not (wcmatch msg "Function cancelled,quit / exit abort")) (alert msg))
        (princ))
    
      (setq s-echo (getvar "CMDECHO"))
      (setvar "CMDECHO" 0)
    
      (prompt "
    ══════════════════════════════════")
      (prompt "
      ת"י 5281 — מחשבון ניקוד ראשוני")
      (prompt "
    ══════════════════════════════════")
    
      ;;; ── Energy (highest weight, ~25-30 points) ───────────
      (initget 1 "A B C D E")
      (setq energy-class
        (getkword "
    דירוג אנרגיה ת"י 5282 [A/B/C/D/E]: "))
      (setq score-energy
        (cdr (assoc energy-class
          '(("A" . 30) ("B" . 25) ("C" . 15) ("D" . 8) ("E" . 0)))))
    
      ;;; ── Water ─────────────────────────────────────────────
      (initget 1 "Y N")
      (setq low-flow (getkword "
    ברזים חסכוניים (WELS/WaterSense)? [Y/N]: "))
      (initget 1 "Y N")
      (setq greywater (getkword "
    שימוש חוזר במי אפור? [Y/N]: "))
      (initget 1 "Y N")
      (setq efficient-irr (getkword "
    השקייה חכמה/טיפטוף? [Y/N]: "))
      (setq score-water
        (+ (if (= low-flow "Y") 5 0)
           (if (= greywater "Y") 8 0)
           (if (= efficient-irr "Y") 4 0)))
    
      ;;; ── Materials ─────────────────────────────────────────
      (initget 1 "Y N")
      (setq local-mat (getkword "
    חומרים מקומיים (>50% ממשקל)? [Y/N]: "))
      (initget 1 "Y N")
      (setq recycled (getkword "
    תוכן ממוחזר >20%? [Y/N]: "))
      (setq score-mat
        (+ (if (= local-mat "Y") 4 0)
           (if (= recycled "Y") 5 0)))
    
      ;;; ── Health & Wellbeing ────────────────────────────────
      (initget 1 "Y N")
      (setq daylight (getkword "
    גורם תאורה טבעית DF>2% ב-75% מהחדרים? [Y/N]: "))
      (initget 1 "Y N")
      (setq acoustic (getkword "
    איטום אקוסטי מחולל רעש? [Y/N]: "))
      (setq score-health
        (+ (if (= daylight "Y") 6 0)
           (if (= acoustic "Y") 4 0)))
    
      ;;; ── Transport ─────────────────────────────────────────
      (initget 1 "Y N")
      (setq transit (getkword "
    מרחק ≤500m לתחבורה ציבורית? [Y/N]: "))
      (initget 1 "Y N")
      (setq bike-park (getkword "
    חניית אופניים מוגנת? [Y/N]: "))
      (setq score-transport
        (+ (if (= transit "Y") 7 0)
           (if (= bike-park "Y") 3 0)))
    
      ;;; ── Innovation bonus ──────────────────────────────────
      (setq score-innov (getint "
    נקודות חדשנות/בונוס (0-10): "))
      (if (< score-innov 0) (setq score-innov 0))
      (if (> score-innov 10) (setq score-innov 10))
    
      ;;; ── Total & Stars ─────────────────────────────────────
      (setq total-score
        (+ score-energy score-water score-mat
           score-health score-transport score-innov))
    
      ;; Star thresholds — verify against current SII edition
      ;; Source: SII IS 5281, ILGBC certification rules
      (setq stars
        (cond
          ((>= total-score 95) "★★★★★ פלטינום (5 כוכבים)")
          ((>= total-score 85) "★★★★ (4 כוכבים)")
          ((>= total-score 75) "★★★ (3 כוכבים)")
          ((>= total-score 65) "★★ (2 כוכבים)")
          ((>= total-score 55) "★ (1 כוכב בסיס)")
          (T  "מתחת לסף — לא עומד בדרישות")))
    
      (alert (strcat
        "══════════════════════════════════
    "
        "  ת"י 5281 — תוצאה ראשונית
    "
        "══════════════════════════════════
    "
        "  אנרגיה (5282 " energy-class "):  " (itoa score-energy) " נק'
    "
        "  מים:              " (itoa score-water) " נק'
    "
        "  חומרים:           " (itoa score-mat) " נק'
    "
        "  בריאות:           " (itoa score-health) " נק'
    "
        "  תחבורה:           " (itoa score-transport) " נק'
    "
        "  חדשנות/בונוס:      " (itoa score-innov) " נק'
    "
        "──────────────────────────────────
    "
        "  סה"כ:             " (itoa total-score) " נקודות
    "
        "  תוצאה:            " stars "
    "
        "══════════════════════════════════
    "
        "  ⚠ ניקוד ראשוני בלבד!
    "
        "  לאישור: www.ilgbc.org"))
    
      (setvar "CMDECHO" s-echo)
      (princ))
    

    Reactor Automation — אוטומציה מבוססת אירועים

    מתקדם VLR API — Autodesk 2024

    Reactors מאפשרים לקוד שלך להגיב אוטומטית לאירועים ב-AutoCAD — שינוי entity, שמירה, פתיחת קובץ, שינוי שכבה. 19 סוגי reactors. אזהרה: שגיאה בקוד callback = AutoCAD קורס.

    ⚠ FORBIDDEN בתוך callback — קריטי מה-Docs!
    אסור בהחלט בתוך callback:
    ❌ getpoint, entsel, getkword — כל input אינטראקטיבי
    ❌ ssget, ssadd — selection sets
    ❌ (command ...) — AutoCAD commands
    ❌ entget, entmod — שינוי entity ה"owner" (גורם infinite loop!)
    ✅ מותר: vla-get-*, vla-put-* | vlr-remove/vlr-add | princ | alert
    מקור: Autodesk — Using Object Reactors
    Reactor TypeConstructorאירועים עיקריים
    :VLR-Object-Reactorvlr-object-reactormodified, copied, erased — על objects ספציפיים
    :VLR-Command-Reactorvlr-command-reactorcommandWillStart, commandEnded, commandCancelled
    :VLR-DWG-Reactorvlr-dwg-reactorsaveComplete, beginOpen, endOpen, beginClose
    :VLR-SysVar-Reactorvlr-sysvar-reactorsysVarChanged, sysVarWillChange
    :VLR-DocManager-Reactorvlr-docmanager-reactordocumentCreated, documentActivated
    :VLR-XREF-Reactorvlr-xref-reactorxrefBeginAttach, xrefEndAttach
    :VLR-AcDb-Reactorvlr-acdb-reactorDrawing database events
    :VLR-Lisp-Reactorvlr-lisp-reactorlispWillStart, lispEnded, lispCancelled
    +11 moreInsert, Wblock, DeepClone, Undo, Window, Toolbar, Mouse, Misc, Linker, DXF, Editor
    auto-area-reactor.lsp
    ;;; AUTO-AREA-REACTOR.LSP v1.5
    ;;; אוטומציה: עדכון טקסט שטח אוטומטית כשפוליליין משתנה
    ;;; מקור: Autodesk VLR Reactor API 2024
    ;;;        https://help.autodesk.com/cloudhelp/2022/ENU/AutoCAD-AutoLISP/
    ;;;        files/GUID-EC61BEC2-39B9-4CD1-BE3B-7781DF1E3530.htm
    ;;;
    ;;; EDGE CASES (official Autodesk docs):
    ;;;   ⚠ FORBIDDEN in callback: entget, entmod, command, ssget, getpoint
    ;;;   ⚠ Must use VLA methods (vla-put-*) instead of entmod
    ;;;   ⚠ Infinite loop: modifying owner in its own :vlr-modified callback!
    ;;;   ⚠ Solution: vlr-remove reactor BEFORE modify, vlr-add AFTER
    ;;;   ⚠ Reactors are TRANSIENT by default — lost on drawing close
    ;;;      Use (vlr-pers reactor) to save in DWG, but callback fn must re-load!
    ;;;   ⚠ vlax-ename->vla-object fails if vl-load-com not called first
    
    ;;; ── Global reactor storage ────────────────────────────────
    (setq *AREA-REACTORS* (list))   ; keep refs to prevent GC
    
    ;;; ── Callback function (called when polyline is modified) ──
    ;;; Signature for object reactor: (notifier reactor params)
    (defun area-reactor-callback (notifier reactor params / area-m2 unit-fac
                                    text-ename new-val updating)
      ;; ⚠ Check flag to prevent re-entry (infinite loop prevention)
      (if *AREA-UPDATING* (exit))  ; bail if already inside callback
      (setq *AREA-UPDATING* T)
    
      ;; Must use VLA properties — entget/entmod FORBIDDEN in callback!
      (setq unit-fac 1.0e-6)  ; assume mm drawings (INSUNITS=4)
    
      ;; ⚠ notifier = VLA-object (NOT ename); already converted
      (setq area-m2
        (vl-catch-all-apply
          '(lambda (obj) (* unit-fac (vlax-get-property obj 'Area)))
          (list notifier)))
    
      (when (not (vl-catch-all-error-p area-m2))
        ;; Find associated text tag via XDATA
        (setq ename (vlax-vla-object->ename notifier))
        (setq xdata (al:xdata-read ename "AREA_TAG"))
        (when xdata
          (setq text-handle (cdr (assoc 1000 xdata)))
          (setq text-ename (handent text-handle))
          (when text-ename
            ;; Update text via VLA (entmod is FORBIDDEN in callbacks!)
            (setq text-vla (vlax-ename->vla-object text-ename))
            (setq new-val (strcat (rtos area-m2 2 2) " m²"))
            (vl-catch-all-apply
              '(lambda (obj val) (vla-put-textstring obj val))
              (list text-vla new-val)))))
    
      (setq *AREA-UPDATING* nil)
      (princ))  ; must return nil or primitive from callback
    
    ;;; ── Attach reactor to selected polyline ───────────────────
    (defun c:ATTACH-AREA-REACTOR (/ ename vla-obj reactor tag-ename)
      (vl-load-com)  ; ⚠ MUST call before any vlax-* functions
    
      (prompt "
    בחר פוליליין לעקוב אחרי השטח שלו: ")
      (setq ename (car (entsel)))
      (if (not ename) (exit))
    
      (if (not (equal (cdr (assoc 0 (entget ename))) "LWPOLYLINE"))
        (progn (alert "בחר LWPOLYLINE בלבד") (exit)))
    
      (setq vla-obj (vlax-ename->vla-object ename))
    
      ;; ── Create area text tag near polyline ────────────────
      (setq centroid
        (list (/ (+ (car (getvar "EXTMIN")) (car (getvar "EXTMAX"))) 2)
              (/ (+ (cadr (getvar "EXTMIN")) (cadr (getvar "EXTMAX"))) 2)
              0))
    
      ;; Use VLA to create MTEXT (not command — to keep it clean)
      (setq doc (vla-get-activedocument (vlax-get-acad-object)))
      (setq ms (vla-get-modelspace doc))
      (setq tag-vla (vla-addmtext ms
        (vlax-3d-point centroid) 50.0 "? m²"))
      (vla-put-height tag-vla 250.0)  ; 250mm text height in mm drawing
      (setq tag-ename (vlax-vla-object->ename tag-vla))
    
      ;; Store association via XDATA
      (regapp "AREA_TAG")
      (al:xdata-write ename "AREA_TAG"
        (list (cons 1005 (cdr (assoc 5 (entget tag-ename))))))  ; handle reference
    
      ;; ── Create object reactor ─────────────────────────────
      ;; Signature: (vlr-object-reactor owners data callbacks)
      (setq reactor
        (vlr-object-reactor
          (list vla-obj)          ; owners = list of VLA-objects to watch
          "area-auto-update"      ; arbitrary data
          '((:vlr-modified . area-reactor-callback))))  ; event → callback
    
      ;; Make PERSISTENT (saves in DWG file):
      (vlr-pers reactor)
      ;; ⚠ BUT: callback function (area-reactor-callback) must be loaded
      ;; every session! Add to acaddoc.lsp or Startup Suite.
    
      ;; Store ref to prevent GC:
      (setq *AREA-REACTORS* (append *AREA-REACTORS* (list reactor)))
    
      (prompt "
    ✅ Reactor מחובר! שינוי הפוליליין יעדכן הטקסט אוטומטית.")
      (prompt "
    ⚠ טען קובץ זה ב-Startup Suite כדי שה-reactor יפעל בכל פתיחה.")
      (princ))
    
    ;;; ── Detach reactor from polyline ─────────────────────────
    (defun c:DETACH-AREA-REACTOR (/ ename vla-obj)
      (vl-load-com)
      (setq ename (car (entsel "
    בחר פוליליין להסרת reactor: ")))
      (if (not ename) (exit))
      (setq vla-obj (vlax-ename->vla-object ename))
      ;; Remove all object reactors watching this VLA-object:
      (foreach r (vlr-reactors :VLR-Object-Reactor)
        (when (member vla-obj (vlr-owner r))
          (vlr-remove r)
          (prompt "
    Reactor הוסר.")))
      (princ))
    
    save-hook-reactor.lsp
    ;;; SAVE-HOOK-REACTOR.LSP — עדכון תאריך שמירה אוטומטי ב-title block
    ;;; מחובר ל-:vlr-saveComplete event
    ;;; מקור: Autodesk VLR DWG Reactor API
    
    (defun on-save-complete (reactor event-params / doc ss)
      "Fires after every save. Updates SAVEDATE attribute in title block."
      ;; ⚠ command, ssget, entmod FORBIDDEN in DWG reactors!
      ;; Use VLA methods and XRECORD dictionary for safe updates.
      (vl-load-com)
      (setq doc (vla-get-activedocument (vlax-get-acad-object)))
    
      ;; Find title block via block table (not ssget — forbidden!)
      (setq tb-blk
        (vl-catch-all-apply
          '(lambda (d)
             (vla-item (vla-get-blocks d) "TITLEBLOCK"))
          (list doc)))
    
      (when (not (vl-catch-all-error-p tb-blk))
        ;; Iterate insertions of TITLEBLOCK in model/paper space
        (vlax-for ins (vla-get-modelspace doc)
          (when (and
                   (= (vla-get-objectname ins) "AcDbBlockReference")
                   (= (strcase (vl-catch-all-apply
                        '(lambda (o) (vla-get-effectivename o))
                        (list ins)))
                      "TITLEBLOCK"))
            ;; Update attributes via VLA (NOT entmod!)
            (vlax-for attr (vla-get-attributes ins)
              (when (= (strcase (vla-get-tagstring attr)) "SAVEDATE")
                (vla-put-textstring attr
                  (rtos (getvar "CDATE") 2 0)))))))
      (princ))
    
    ;;; ── Load once to activate ─────────────────────────────────
    (defun c:LOAD-SAVE-HOOK (/ reactor)
      (vl-load-com)
      (setq *SAVE-REACTOR*
        (vlr-dwg-reactor
          "title-block-save-hook"
          '((:vlr-saveComplete . on-save-complete))))
      (vlr-pers *SAVE-REACTOR*)  ; persist in drawing
      (prompt "
    ✅ Save hook פעיל — כותרת תתעדכן בכל שמירה.")
      (princ))
    
    מקרה קצהמה קורהפתרון מוכח
    entmod בתוך :vlr-modified של אותו entityInfinite loop → AutoCAD קורסsetq *UPDATING* T לפני, nil אחרי; vlr-remove → modify → vlr-add
    ssget בתוך callbackOfficially FORBIDDEN — undefined behaviorשמור enames לפני בגלובלים; השתמש ב-vlax-ename->vla-object
    (command ...) בתוך callbackOfficially FORBIDDEN — may work but unreliableהחלף ב-VLA methods: vla-put-textstring, vla-update
    Reactor transient by defaultאובד בסגירת DWG(vlr-pers reactor) — שומר ב-DWG; callback fn חייב להיטען ב-acaddoc.lsp
    vlr-pers ≠ callback persistencereactor נשמר אבל callback function נטענת מחדש בכל פתיחההכנס (load "reactor-file.lsp") ל-Startup Suite
    Multiple duplicate reactorscallback רץ פעמים מרובותבדוק (vlr-reactors :VLR-Object-Reactor) לפני attach
    vlax-ename->vla-object erased entityvlax-erased-p = T; method calls fail(vl-catch-all-apply ...) + בדוק (vlax-erased-p obj)