Added Additonal scripts
This commit is contained in:
parent
4035dc0588
commit
4bc442fd75
Binary file not shown.
Binary file not shown.
91844
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM01-As-Built.pdf
Normal file
91844
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM01-As-Built.pdf
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
79022
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM02-As-Built.pdf
Normal file
79022
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM02-As-Built.pdf
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
31588
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM03-As-Built.pdf
Normal file
31588
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM03-As-Built.pdf
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
55850
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM04-As-Built.pdf
Normal file
55850
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM04-As-Built.pdf
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
54490
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM05-As-Built.pdf
Normal file
54490
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM05-As-Built.pdf
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
48229
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM06-As-Built.pdf
Normal file
48229
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM06-As-Built.pdf
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
70115
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM07-As-Built.pdf
Normal file
70115
Additional/duplicate_PDF/2429_AMAZON-MTN6-MCM07-As-Built.pdf
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
316
Additional/duplicate_PDF/check_duplicate.py
Normal file
316
Additional/duplicate_PDF/check_duplicate.py
Normal file
@ -0,0 +1,316 @@
|
||||
"""
|
||||
Scan each PDF in this folder and find duplicate connection tags (inside the SAME PDF)
|
||||
based on user-provided wildcard patterns (using '*' as "anything").
|
||||
|
||||
Output: one Excel file per PDF: <PDF_NAME>_duplicates.xlsx
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Set, Tuple
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# USER PATTERNS (wildcards)
|
||||
# -----------------------------
|
||||
# '*' means "any characters". Everything else is treated literally.
|
||||
WILDCARD_PATTERNS: List[str] = [
|
||||
"*_*_TPE*",
|
||||
"*_*_S*_PB",
|
||||
"*_*_S*_PB_LT",
|
||||
"*_*_JR*_PB",
|
||||
"*_*_JR*_PB_LT",
|
||||
"*_*_SS*_SPB",
|
||||
"*_*_SS*_STPB",
|
||||
"*_*_SS*_SPB_LT",
|
||||
"*_*_EN*_PB",
|
||||
"*_*_EN*_PB_LT",
|
||||
"*_*_PE*",
|
||||
"*_*_LPE*",
|
||||
"*_*_FPE*",
|
||||
"*_*_BCN*_R",
|
||||
"*_*_BCN*_B",
|
||||
"*_*_BCN*_A",
|
||||
"*_*_BCN*_G",
|
||||
"*_*_BCN*_H",
|
||||
"*_*_EPC*_1",
|
||||
"*_*_EPC*_2",
|
||||
"*_*_VFD1_DISC",
|
||||
"*_*_*_STO1",
|
||||
"*_*_*_ESTOP1",
|
||||
"*_*_LS*",
|
||||
"*_*_ENC*",
|
||||
"*_*_ENW*",
|
||||
"*_*_ENS*",
|
||||
"*_*_PX*",
|
||||
"*_*_SOL*",
|
||||
"*_*_DIV*",
|
||||
"*_*_PS*",
|
||||
"*_*_BDS*",
|
||||
"*_*_TS*",
|
||||
]
|
||||
|
||||
# -----------------------------
|
||||
# CABLE PATTERNS (separate check)
|
||||
# -----------------------------
|
||||
# Rule: if a cable label appears more than 2 times in the SAME PDF => duplicated/overused.
|
||||
CABLE_WILDCARD_PATTERNS: List[str] = [
|
||||
"*_*_VFD*_I0",
|
||||
"*_*_VFD*_I1",
|
||||
"*_*_VFD*_I2",
|
||||
"*_*_VFD*_I3",
|
||||
"*_*_VFD*_IO0",
|
||||
"*_*_VFD*_IO1",
|
||||
"*_*_VFD*_SI0",
|
||||
"*_*_VFD*_SI1",
|
||||
"*_*_VFD*_SI2",
|
||||
"*_*_VFD*_SI3",
|
||||
"*_*_VFD*_SO0",
|
||||
"*_FIO*_P0_C0",
|
||||
"*_FIO*_P0_C1",
|
||||
"*_FIO*_P1_C2",
|
||||
"*_FIO*_P1_C3",
|
||||
"*_FIO*_P2_C4",
|
||||
"*_FIO*_P2_C5",
|
||||
"*_FIO*_P3_C6",
|
||||
"*_FIO*_P3_C7",
|
||||
"*_FIO*_P4_C8",
|
||||
"*_FIO*_P4_C9",
|
||||
"*_FIO*_P5_C10",
|
||||
"*_FIO*_P5_C11",
|
||||
"*_FIO*_P6_C12",
|
||||
"*_FIO*_P6_C13",
|
||||
"*_FIO*_P7_C14",
|
||||
"*_FIO*_P7_C15",
|
||||
"*_FIOH*_C7_A",
|
||||
"*_FIOH*_C7_B",
|
||||
"*_FIOH*_C5_A",
|
||||
"*_FIOH*_C5_B",
|
||||
"*_FIOH*_C3_A",
|
||||
"*_FIOH*_C3_B",
|
||||
"*_FIOH*_C1_A",
|
||||
"*_FIOH*_C1_B",
|
||||
"*_FIOH*_C8_A",
|
||||
"*_FIOH*_C8_B",
|
||||
"*_FIOH*_C6_A",
|
||||
"*_FIOH*_C6_B",
|
||||
"*_FIOH*_C4_A",
|
||||
"*_FIOH*_C4_B",
|
||||
"*_FIOH*_C2_A",
|
||||
"*_FIOH*_C2_B",
|
||||
]
|
||||
|
||||
|
||||
# Candidate token: something like "PS3_2_VFD1_DISC" (>= 2 underscore-separated parts)
|
||||
TOKEN_RE = re.compile(r"\b[A-Z0-9]+(?:_[A-Z0-9]+)+\b", re.IGNORECASE)
|
||||
|
||||
|
||||
def _compile_wildcard_patterns(patterns: Iterable[str]) -> List[re.Pattern]:
|
||||
compiled: List[re.Pattern] = []
|
||||
for p in patterns:
|
||||
# Treat everything literally except '*' which becomes '.*'
|
||||
parts = [re.escape(x) for x in p.split("*")]
|
||||
regex = ".*".join(parts)
|
||||
# Match full token
|
||||
compiled.append(re.compile(rf"^{regex}$", re.IGNORECASE))
|
||||
return compiled
|
||||
|
||||
|
||||
def _tokenize(text: str) -> List[str]:
|
||||
# Normalize common oddities
|
||||
text = text.replace("\u00ad", "") # soft hyphen
|
||||
# PDFs sometimes insert whitespace/newlines around underscores; normalize that.
|
||||
# Example: "PS3_2_VFD1_\nDISC" -> "PS3_2_VFD1_DISC"
|
||||
text = re.sub(r"\s*_\s*", "_", text)
|
||||
return [m.group(0).upper() for m in TOKEN_RE.finditer(text)]
|
||||
|
||||
|
||||
def _ensure_deps() -> Tuple[object, object]:
|
||||
"""
|
||||
Returns (fitz_module, pandas_module). Exits with helpful message if missing.
|
||||
"""
|
||||
try:
|
||||
import fitz # PyMuPDF
|
||||
except Exception:
|
||||
print(
|
||||
"Missing dependency: PyMuPDF\n"
|
||||
"Install with:\n"
|
||||
" python -m pip install --upgrade pip\n"
|
||||
" python -m pip install pymupdf\n",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise
|
||||
|
||||
try:
|
||||
import pandas as pd
|
||||
except Exception:
|
||||
print(
|
||||
"Missing dependency: pandas (and openpyxl for Excel)\n"
|
||||
"Install with:\n"
|
||||
" python -m pip install --upgrade pip\n"
|
||||
" python -m pip install pandas openpyxl\n",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise
|
||||
|
||||
return fitz, pd
|
||||
|
||||
|
||||
@dataclass
|
||||
class Occurrence:
|
||||
token: str
|
||||
pages: Set[int] # 1-based page numbers
|
||||
count: int
|
||||
|
||||
|
||||
def find_duplicates_in_pdf(pdf_path: Path, compiled_patterns: List[re.Pattern]) -> List[Occurrence]:
|
||||
fitz, _ = _ensure_deps()
|
||||
|
||||
occurrences: Dict[str, Occurrence] = {}
|
||||
|
||||
with fitz.open(pdf_path) as doc:
|
||||
for page_index in range(doc.page_count):
|
||||
page = doc.load_page(page_index)
|
||||
text = page.get_text("text") or ""
|
||||
tokens = _tokenize(text)
|
||||
if not tokens:
|
||||
continue
|
||||
|
||||
for t in tokens:
|
||||
if not any(r.match(t) for r in compiled_patterns):
|
||||
continue
|
||||
if t not in occurrences:
|
||||
occurrences[t] = Occurrence(token=t, pages=set(), count=0)
|
||||
occurrences[t].pages.add(page_index + 1)
|
||||
occurrences[t].count += 1
|
||||
|
||||
# "Duplicate" = appears on more than one page (what you asked for)
|
||||
dups = [o for o in occurrences.values() if len(o.pages) > 1]
|
||||
dups.sort(key=lambda o: (-len(o.pages), -o.count, o.token))
|
||||
return dups
|
||||
|
||||
|
||||
def find_cable_overuse_in_pdf(
|
||||
pdf_path: Path,
|
||||
compiled_patterns: List[re.Pattern],
|
||||
*,
|
||||
allowed_occurrences: int = 2,
|
||||
) -> List[Occurrence]:
|
||||
"""
|
||||
Separate check from page-duplicate logic:
|
||||
- Cable labels are often printed twice (both ends) => OK up to allowed_occurrences (default 2).
|
||||
- If total occurrences > allowed_occurrences, flag it.
|
||||
"""
|
||||
fitz, _ = _ensure_deps()
|
||||
|
||||
occurrences: Dict[str, Occurrence] = {}
|
||||
|
||||
with fitz.open(pdf_path) as doc:
|
||||
for page_index in range(doc.page_count):
|
||||
page = doc.load_page(page_index)
|
||||
text = page.get_text("text") or ""
|
||||
tokens = _tokenize(text)
|
||||
if not tokens:
|
||||
continue
|
||||
|
||||
for t in tokens:
|
||||
if not any(r.match(t) for r in compiled_patterns):
|
||||
continue
|
||||
if t not in occurrences:
|
||||
occurrences[t] = Occurrence(token=t, pages=set(), count=0)
|
||||
occurrences[t].pages.add(page_index + 1)
|
||||
occurrences[t].count += 1
|
||||
|
||||
overused = [o for o in occurrences.values() if o.count > allowed_occurrences]
|
||||
overused.sort(key=lambda o: (-o.count, -len(o.pages), o.token))
|
||||
return overused
|
||||
|
||||
|
||||
def write_excel_for_pdf(
|
||||
pdf_path: Path,
|
||||
duplicates: List[Occurrence],
|
||||
cable_overuse: List[Occurrence],
|
||||
) -> Path:
|
||||
_, pd = _ensure_deps()
|
||||
|
||||
out_path = pdf_path.with_name(pdf_path.stem + "_duplicates.xlsx")
|
||||
rows = []
|
||||
for d in duplicates:
|
||||
rows.append(
|
||||
{
|
||||
"Token": d.token,
|
||||
"Pages": ", ".join(map(str, sorted(d.pages))),
|
||||
"UniquePagesCount": len(d.pages),
|
||||
"TotalOccurrences": d.count,
|
||||
}
|
||||
)
|
||||
|
||||
df = pd.DataFrame(rows, columns=["Token", "Pages", "UniquePagesCount", "TotalOccurrences"])
|
||||
|
||||
cable_rows = []
|
||||
for c in cable_overuse:
|
||||
cable_rows.append(
|
||||
{
|
||||
"CableLabel": c.token,
|
||||
"Pages": ", ".join(map(str, sorted(c.pages))),
|
||||
"UniquePagesCount": len(c.pages),
|
||||
"TotalOccurrences": c.count,
|
||||
}
|
||||
)
|
||||
cable_df = pd.DataFrame(
|
||||
cable_rows, columns=["CableLabel", "Pages", "UniquePagesCount", "TotalOccurrences"]
|
||||
)
|
||||
|
||||
with pd.ExcelWriter(out_path, engine="openpyxl") as writer:
|
||||
df.to_excel(writer, index=False, sheet_name="Duplicates")
|
||||
cable_df.to_excel(writer, index=False, sheet_name="CableOveruse")
|
||||
|
||||
summary = pd.DataFrame(
|
||||
[
|
||||
{
|
||||
"PDF": pdf_path.name,
|
||||
"DuplicateTokens": len(duplicates),
|
||||
"CableOverusedLabels": len(cable_overuse),
|
||||
},
|
||||
]
|
||||
)
|
||||
summary.to_excel(writer, index=False, sheet_name="Summary")
|
||||
|
||||
return out_path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
base_dir = Path(__file__).resolve().parent
|
||||
pdfs = sorted(base_dir.glob("*.pdf"))
|
||||
if not pdfs:
|
||||
print(f"No PDFs found in: {base_dir}")
|
||||
return 1
|
||||
|
||||
compiled_patterns = _compile_wildcard_patterns(WILDCARD_PATTERNS)
|
||||
compiled_cable_patterns = _compile_wildcard_patterns(CABLE_WILDCARD_PATTERNS)
|
||||
|
||||
print(f"Found {len(pdfs)} PDF(s). Checking duplicates INSIDE each PDF only...")
|
||||
for pdf in pdfs:
|
||||
print(f"\n--- {pdf.name} ---")
|
||||
try:
|
||||
dups = find_duplicates_in_pdf(pdf, compiled_patterns)
|
||||
cable_overuse = find_cable_overuse_in_pdf(pdf, compiled_cable_patterns, allowed_occurrences=2)
|
||||
out_xlsx = write_excel_for_pdf(pdf, dups, cable_overuse)
|
||||
print(f"Duplicate tokens (appear on >1 page): {len(dups)}")
|
||||
print(f"Cable labels overused (total occurrences > 2): {len(cable_overuse)}")
|
||||
print(f"Excel written: {out_xlsx.name}")
|
||||
except Exception as e:
|
||||
print(f"ERROR processing {pdf.name}: {e}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
print("\nDone.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
BIN
Additional/format/Amazon CDW5_IP Addresses_Local.xlsx
Normal file
BIN
Additional/format/Amazon CDW5_IP Addresses_Local.xlsx
Normal file
Binary file not shown.
BIN
Additional/format/Amazon CDW5_IP Addresses_Local_formatted.xlsx
Normal file
BIN
Additional/format/Amazon CDW5_IP Addresses_Local_formatted.xlsx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
336
AutoCAD/Attribute/AS_AttrBatch.lsp
Normal file
336
AutoCAD/Attribute/AS_AttrBatch.lsp
Normal file
@ -0,0 +1,336 @@
|
||||
;; AS_AttrBatch.lsp
|
||||
;; Batch-edit a single attribute tag for all matching AS* blocks inside a user selection.
|
||||
;; Command: ASBATCHATTR
|
||||
;;
|
||||
;; Command-line version (no dialog/DCL).
|
||||
|
||||
(defun asba:_unique (lst / out)
|
||||
(foreach x lst
|
||||
(if (not (member x out))
|
||||
(setq out (cons x out))
|
||||
)
|
||||
)
|
||||
(reverse out)
|
||||
)
|
||||
|
||||
(defun asba:_ss->list (ss / i out)
|
||||
(setq i 0 out '())
|
||||
(if ss
|
||||
(while (< i (sslength ss))
|
||||
(if (= (type (ssname ss i)) 'ENAME)
|
||||
(setq out (cons (ssname ss i) out))
|
||||
)
|
||||
(setq i (1+ i))
|
||||
)
|
||||
)
|
||||
(reverse out)
|
||||
)
|
||||
|
||||
(defun asba:_effname (ename / ed nm r obj)
|
||||
;; Try to get the "EffectiveName" (handles dynamic blocks).
|
||||
;; Fully guarded: if anything fails, fall back to DXF group 2.
|
||||
(setq nm nil)
|
||||
(if (= (type ename) 'ENAME)
|
||||
(progn
|
||||
;; COM path (safe) -> try to set nm
|
||||
(setq r (vl-catch-all-apply 'vl-load-com '()))
|
||||
(setq r (vl-catch-all-apply 'vlax-ename->vla-object (list ename)))
|
||||
(if (not (vl-catch-all-error-p r))
|
||||
(progn
|
||||
(setq obj r)
|
||||
(setq r (vl-catch-all-apply 'vlax-property-available-p (list obj 'EffectiveName)))
|
||||
(if (and (not (vl-catch-all-error-p r)) r)
|
||||
(progn
|
||||
(setq r (vl-catch-all-apply 'vla-get-EffectiveName (list obj)))
|
||||
(if (and (not (vl-catch-all-error-p r)) (= (type r) 'STR))
|
||||
(setq nm r)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; DXF fallback if nm not found
|
||||
(if (not nm)
|
||||
(progn
|
||||
(setq ed (entget ename))
|
||||
(setq r (cdr (assoc 2 ed)))
|
||||
(if (= (type r) 'STR) (setq nm r))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
nm
|
||||
)
|
||||
|
||||
(defun asba:_attrib-tags-of-insert (ins / e ed tags tagTmp)
|
||||
;; Read attribute TAGs using DXF traversal (avoids VLA objects/variants).
|
||||
(setq tags '())
|
||||
(setq e (entnext ins))
|
||||
(while (and e (setq ed (entget e)) (= (cdr (assoc 0 ed)) "ATTRIB"))
|
||||
(setq tagTmp (cdr (assoc 2 ed)))
|
||||
(if (= (type tagTmp) 'STR)
|
||||
(setq tags (cons (strcase tagTmp) tags))
|
||||
)
|
||||
(setq e (entnext e))
|
||||
)
|
||||
tags
|
||||
)
|
||||
|
||||
(defun asba:_find-first-insert-by-name (ss blockName / enames e nm)
|
||||
(setq enames (asba:_ss->list ss))
|
||||
(setq e nil)
|
||||
(foreach x enames
|
||||
(if (and (null e)
|
||||
(= (type x) 'ENAME)
|
||||
(= (cdr (assoc 0 (entget x))) "INSERT"))
|
||||
(progn
|
||||
(setq nm (asba:_effname x))
|
||||
(if (and (= (type nm) 'STR) (= (strcase nm) (strcase blockName)))
|
||||
(setq e x)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
e
|
||||
)
|
||||
|
||||
(defun asba:_set-attrib-value-on-insert (ins tag newValue / e ed curTag)
|
||||
;; Update only the chosen TAG on a single INSERT.
|
||||
;; Returns T if changed, NIL otherwise.
|
||||
(setq e (entnext ins))
|
||||
(while (and e (setq ed (entget e)) (= (cdr (assoc 0 ed)) "ATTRIB"))
|
||||
(setq curTag (cdr (assoc 2 ed)))
|
||||
(if (and (= (type curTag) 'STR) (= (strcase curTag) (strcase tag)))
|
||||
(progn
|
||||
(if (assoc 1 ed)
|
||||
(setq ed (subst (cons 1 newValue) (assoc 1 ed) ed))
|
||||
(setq ed (append ed (list (cons 1 newValue))))
|
||||
)
|
||||
(entmod ed)
|
||||
(entupd ins)
|
||||
(setq e nil) ;; stop after first match
|
||||
(setq ed T) ;; reuse as "changed" flag
|
||||
)
|
||||
(setq e (entnext e))
|
||||
)
|
||||
)
|
||||
(if (= ed T) T nil)
|
||||
)
|
||||
|
||||
(defun asba:_blocks-in-selection (ss / enames names nm)
|
||||
;; Kept for backward compatibility (returns unique names only).
|
||||
(mapcar 'car (asba:_block-counts-in-selection ss))
|
||||
)
|
||||
|
||||
(defun asba:_block-counts-in-selection (ss / enames counts nm cell)
|
||||
;; Returns alist: (("AS_VFD" . 50) ("AS_PMM" . 1) ...)
|
||||
(setq enames (asba:_ss->list ss))
|
||||
(setq counts '())
|
||||
(foreach e enames
|
||||
(if (= (cdr (assoc 0 (entget e))) "INSERT")
|
||||
(progn
|
||||
(setq nm (asba:_effname e))
|
||||
(if (and (= (type nm) 'STR) (wcmatch (strcase nm) "AS*"))
|
||||
(progn
|
||||
(setq nm (strcase nm))
|
||||
(setq cell (assoc nm counts))
|
||||
(if cell
|
||||
(setq counts (subst (cons nm (1+ (cdr cell))) cell counts))
|
||||
(setq counts (cons (cons nm 1) counts))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
;; sort by name
|
||||
(vl-sort counts '(lambda (a b) (< (car a) (car b))))
|
||||
)
|
||||
|
||||
(defun asba:_counts->display (alist / out)
|
||||
(setq out '())
|
||||
(foreach p alist
|
||||
(setq out (cons (strcat (car p) " (" (itoa (cdr p)) ")") out))
|
||||
)
|
||||
(reverse out)
|
||||
)
|
||||
|
||||
(defun asba:_attrs-for-block (ss blockName / ins tags)
|
||||
;; Attributes are normally consistent across block references.
|
||||
;; To avoid scanning every insert (and any weird/proxy entities), read tags from the first valid insert.
|
||||
(setq ins (asba:_find-first-insert-by-name ss blockName))
|
||||
(if ins
|
||||
(progn
|
||||
(setq tags (asba:_attrib-tags-of-insert ins))
|
||||
(setq tags (asba:_unique tags))
|
||||
(vl-sort tags '<)
|
||||
)
|
||||
'()
|
||||
)
|
||||
)
|
||||
|
||||
(defun asba:_apply-attr (ss blockName tag newValue / enames changed nm attrefs att)
|
||||
(setq enames (asba:_ss->list ss))
|
||||
(setq changed 0)
|
||||
(foreach e enames
|
||||
(if (= (cdr (assoc 0 (entget e))) "INSERT")
|
||||
(progn
|
||||
(setq nm (asba:_effname e))
|
||||
(if (and (= (type nm) 'STR) (= (strcase nm) (strcase blockName)))
|
||||
(progn
|
||||
(if (asba:_set-attrib-value-on-insert e tag newValue)
|
||||
(setq changed (1+ changed))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
changed
|
||||
)
|
||||
|
||||
(defun asba:_print-numbered (title items / i)
|
||||
(prompt (strcat "\n" title))
|
||||
(setq i 1)
|
||||
(foreach it items
|
||||
(prompt (strcat "\n " (itoa i) ") " it))
|
||||
(setq i (1+ i))
|
||||
)
|
||||
)
|
||||
|
||||
(defun asba:_choose-index (promptText maxN / n)
|
||||
(setq n nil)
|
||||
(while (not n)
|
||||
(setq n (getint (strcat "\n" promptText " (1-" (itoa maxN) ", Enter to cancel): ")))
|
||||
(cond
|
||||
((null n) (setq n 0))
|
||||
((or (< n 1) (> n maxN))
|
||||
(prompt "\nInvalid number.")
|
||||
(setq n nil)
|
||||
)
|
||||
)
|
||||
)
|
||||
n
|
||||
)
|
||||
|
||||
(defun asba:_ss-has (ss ename)
|
||||
;; returns T if ename is in selection set ss
|
||||
(if (and ss ename)
|
||||
(if (ssmemb ename ss) T nil)
|
||||
nil
|
||||
)
|
||||
)
|
||||
|
||||
(defun asba:_pick-block-from-ss (ss / sel ename nm)
|
||||
(setq nm nil)
|
||||
(while (not nm)
|
||||
(setq sel (entsel "\nClick a block (AS*) inside the selected zone (Enter to cancel): "))
|
||||
(cond
|
||||
((null sel) (setq nm "")) ;; cancelled
|
||||
(t
|
||||
(setq ename (car sel))
|
||||
(if (and ename (= (cdr (assoc 0 (entget ename))) "INSERT") (asba:_ss-has ss ename))
|
||||
(progn
|
||||
(setq nm (asba:_effname ename))
|
||||
(if (not (and (= (type nm) 'STR) (wcmatch (strcase nm) "AS*")))
|
||||
(progn (prompt "\nThat block name does not start with AS*.") (setq nm nil))
|
||||
)
|
||||
)
|
||||
(progn (prompt "\nPlease click a block INSERT that is inside your selected zone.") (setq nm nil))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(if (= nm "") nil nm)
|
||||
)
|
||||
|
||||
(defun asba:_getstring-safe (msg / r)
|
||||
;; Some AutoCAD builds do not support (getstring T ...). Use the simplest form.
|
||||
(setq r (getstring msg))
|
||||
r
|
||||
)
|
||||
|
||||
(defun c:ASBATCHATTR (/ ss blockCounts blockNames blockDisplay mode block bIdx attrs tag aIdx newVal changed blkTotal)
|
||||
(vl-load-com)
|
||||
|
||||
(prompt "\nSelect a zone/area (window/crossing allowed). Press Enter when done...")
|
||||
(setq ss (ssget))
|
||||
(if (not ss)
|
||||
(progn (prompt "\nNothing selected.") (princ))
|
||||
(progn
|
||||
(setq blockCounts (asba:_block-counts-in-selection ss))
|
||||
(setq blockNames (mapcar 'car blockCounts))
|
||||
(setq blockDisplay (asba:_counts->display blockCounts))
|
||||
(if (not blockNames)
|
||||
(progn (prompt "\nNo blocks found with name starting with AS* in the selection.") (princ))
|
||||
(progn
|
||||
;; Choose block: Pick (mouse) or List (numbered)
|
||||
(initget "Pick List")
|
||||
(setq mode (getkword "\nChoose block by [Pick/List] <Pick>: "))
|
||||
(if (null mode) (setq mode "Pick"))
|
||||
|
||||
(cond
|
||||
((= mode "Pick")
|
||||
(setq block (asba:_pick-block-from-ss ss))
|
||||
)
|
||||
(t
|
||||
(asba:_print-numbered "Unique blocks (AS*) with counts:" blockDisplay)
|
||||
(setq bIdx (asba:_choose-index "Choose block number" (length blockNames)))
|
||||
(setq block (if (= bIdx 0) nil (nth (1- bIdx) blockNames)))
|
||||
)
|
||||
)
|
||||
|
||||
(if (not block)
|
||||
(progn (prompt "\nCancelled.") (princ))
|
||||
(progn
|
||||
(setq blkTotal (cdr (assoc (strcase block) blockCounts)))
|
||||
(setq attrs (asba:_attrs-for-block ss block))
|
||||
(if (not attrs)
|
||||
(progn (prompt (strcat "\nBlock " block " has no attributes in the selection.")) (princ))
|
||||
(progn
|
||||
;; Choose attribute: numbered list only (reliable + matches requested workflow)
|
||||
(asba:_print-numbered (strcat "Unique attribute tags for " block ":") attrs)
|
||||
(setq aIdx (asba:_choose-index "Choose attribute number" (length attrs)))
|
||||
(setq tag (if (= aIdx 0) nil (nth (1- aIdx) attrs)))
|
||||
|
||||
(if (not tag)
|
||||
(progn (prompt "\nCancelled.") (princ))
|
||||
(progn
|
||||
(setq newVal (asba:_getstring-safe (strcat "\nNew value for tag " tag " (Enter to cancel): ")))
|
||||
;; If user presses Enter immediately, AutoCAD may return "".
|
||||
(if (or (null newVal) (= newVal ""))
|
||||
(progn (prompt "\nCancelled.") (princ))
|
||||
(progn
|
||||
(setq changed (asba:_apply-attr ss block tag newVal))
|
||||
(command "_.REGEN")
|
||||
(prompt
|
||||
(strcat
|
||||
"\nDone. Changed "
|
||||
(itoa changed)
|
||||
" block(s) out of "
|
||||
(itoa (if blkTotal blkTotal 0))
|
||||
" selected "
|
||||
block
|
||||
" block(s)."
|
||||
)
|
||||
)
|
||||
(princ)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(princ "\nLoaded AS_AttrBatch. Run command: ASBATCHATTR")
|
||||
(princ)
|
||||
|
||||
|
||||
25
AutoCAD/Attribute/README.md
Normal file
25
AutoCAD/Attribute/README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# AS Batch Attribute Editor (AutoLISP)
|
||||
|
||||
This tool lets you select an area in your drawing, list **unique block names starting with `AS*`**, list **unique attribute tags** for a chosen block, and then **change one attribute value for all matching blocks** inside the selected area.
|
||||
|
||||
## Files
|
||||
- `AS_AttrBatch.lsp` — AutoLISP logic (**this is the only required file**)
|
||||
No `.dcl` file is used — this is a command-line tool.
|
||||
|
||||
## Install / Run
|
||||
1. Copy `AS_AttrBatch.lsp` into any folder.
|
||||
2. In AutoCAD run `APPLOAD` and load `AS_AttrBatch.lsp`.
|
||||
3. Run command: `ASBATCHATTR`
|
||||
|
||||
## How to use
|
||||
1. Select a zone/part of the drawing (window/crossing selection is fine), then press **Enter**.
|
||||
2. Choose the block:
|
||||
- Option **Pick**: click a block in the selected zone
|
||||
- Option **List**: choose from a numbered list
|
||||
- The list shows **unique block names** with a **count** like `AS_VFD (50)`
|
||||
3. Choose the attribute tag from the **numbered list**.
|
||||
4. Type the new value and press **Enter**.
|
||||
|
||||
Only the chosen attribute tag is updated. All other attributes remain unchanged.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user