Remove pandas dependency from Bluebeam BCI generator
This commit is contained in:
parent
664e8b44fb
commit
84f07bb3e9
|
|
@ -2,14 +2,14 @@
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import argparse
|
import argparse
|
||||||
import pandas as pd
|
import csv
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parents[1]
|
BASE_DIR = Path(__file__).resolve().parents[1]
|
||||||
DATA_DIR = BASE_DIR / "data"
|
DATA_DIR = BASE_DIR / "data"
|
||||||
OUTPUT_DIR = BASE_DIR / "output"
|
OUTPUT_DIR = BASE_DIR / "output"
|
||||||
|
|
||||||
def clean_value(v):
|
def clean_value(v):
|
||||||
if pd.isna(v):
|
if v is None:
|
||||||
return ""
|
return ""
|
||||||
return str(v).strip()
|
return str(v).strip()
|
||||||
|
|
||||||
|
|
@ -20,89 +20,80 @@ def bci_escape(v):
|
||||||
v = v.replace("\n", "\\n")
|
v = v.replace("\n", "\\n")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
def build_column_dict(row, exclude_cols):
|
def read_csv(path):
|
||||||
data = {}
|
with open(path, newline="", encoding="utf-8-sig") as f:
|
||||||
for col in row.index:
|
return list(csv.DictReader(f))
|
||||||
if col in exclude_cols:
|
|
||||||
continue
|
|
||||||
val = clean_value(row[col])
|
|
||||||
if val != "":
|
|
||||||
data[col] = val
|
|
||||||
return data
|
|
||||||
|
|
||||||
def dict_to_bci_payload(d):
|
def dict_to_bci_payload(d):
|
||||||
parts = []
|
parts = []
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
parts.append(f"'{bci_escape(k)}':'{bci_escape(v)}'")
|
if clean_value(v) != "":
|
||||||
|
parts.append(f"'{bci_escape(k)}':'{bci_escape(v)}'")
|
||||||
return "{" + ",".join(parts) + "}"
|
return "{" + ",".join(parts) + "}"
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Generate Bluebeam BCI script from Markups CSV + update CSV.")
|
parser = argparse.ArgumentParser(description="Generate Bluebeam BCI script from Markups CSV + update CSV.")
|
||||||
parser.add_argument("--bb-csv", default=str(DATA_DIR / "bluebeam_markups.csv"), help="Bluebeam Markups List CSV export")
|
parser.add_argument("--bb-csv", default=str(DATA_DIR / "bluebeam_markups.csv"))
|
||||||
parser.add_argument("--updates-csv", default=str(DATA_DIR / "bluebeam_updates.csv"), help="Your update CSV")
|
parser.add_argument("--updates-csv", default=str(DATA_DIR / "bluebeam_updates.csv"))
|
||||||
parser.add_argument("--out", default=str(OUTPUT_DIR / "update_columns.bci"), help="Output BCI file")
|
parser.add_argument("--out", default=str(OUTPUT_DIR / "update_columns.bci"))
|
||||||
parser.add_argument("--pdf-path", default=r"C:\PATH\TO\TARGET.pdf", help="Windows path to target PDF for the BCI Open() command")
|
parser.add_argument("--pdf-path", default=r"C:\PATH\TO\TARGET.pdf")
|
||||||
parser.add_argument("--match-column", default="Comment", help="Column in Bluebeam CSV to match against match_value")
|
parser.add_argument("--match-column", default="Comment")
|
||||||
parser.add_argument("--page-column", default="Page Index", help="Bluebeam page index column")
|
parser.add_argument("--page-column", default="Page Index")
|
||||||
parser.add_argument("--id-column", default="ID", help="Bluebeam markup ID column")
|
parser.add_argument("--id-column", default="ID")
|
||||||
parser.add_argument("--contains", action="store_true", help="Use contains match instead of exact match")
|
parser.add_argument("--contains", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
bb_csv = Path(args.bb_csv)
|
|
||||||
updates_csv = Path(args.updates_csv)
|
|
||||||
out_path = Path(args.out)
|
|
||||||
|
|
||||||
OUTPUT_DIR.mkdir(exist_ok=True)
|
OUTPUT_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
bb = pd.read_csv(bb_csv, dtype=str).fillna("")
|
bb_rows = read_csv(args.bb_csv)
|
||||||
upd = pd.read_csv(updates_csv, dtype=str).fillna("")
|
update_rows = read_csv(args.updates_csv)
|
||||||
|
|
||||||
|
if not bb_rows:
|
||||||
|
raise ValueError("Bluebeam CSV is empty.")
|
||||||
|
if not update_rows:
|
||||||
|
raise ValueError("Updates CSV is empty.")
|
||||||
|
|
||||||
required_bb = {args.page_column, args.id_column, args.match_column}
|
required_bb = {args.page_column, args.id_column, args.match_column}
|
||||||
missing_bb = required_bb - set(bb.columns)
|
missing_bb = required_bb - set(bb_rows[0].keys())
|
||||||
if missing_bb:
|
if missing_bb:
|
||||||
raise ValueError(f"Bluebeam CSV missing required columns: {sorted(missing_bb)}")
|
raise ValueError(f"Bluebeam CSV missing required columns: {sorted(missing_bb)}")
|
||||||
|
|
||||||
required_upd = {"match_value"}
|
if "match_value" not in update_rows[0]:
|
||||||
missing_upd = required_upd - set(upd.columns)
|
raise ValueError("Updates CSV missing required column: match_value")
|
||||||
if missing_upd:
|
|
||||||
raise ValueError(f"Updates CSV missing required columns: {sorted(missing_upd)}")
|
|
||||||
|
|
||||||
lines = []
|
|
||||||
lines.append(f"Open('{args.pdf_path}', '')")
|
|
||||||
|
|
||||||
|
lines = [f"Open('{args.pdf_path}', '')"]
|
||||||
total_matches = 0
|
total_matches = 0
|
||||||
|
|
||||||
for _, urow in upd.iterrows():
|
for urow in update_rows:
|
||||||
match_value = clean_value(urow["match_value"])
|
match_value = clean_value(urow.get("match_value"))
|
||||||
if not match_value:
|
if not match_value:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if args.contains:
|
col_data = {k: v for k, v in urow.items() if k != "match_value" and clean_value(v) != ""}
|
||||||
mask = bb[args.match_column].astype(str).str.contains(match_value, na=False, regex=False)
|
|
||||||
else:
|
|
||||||
mask = bb[args.match_column].astype(str).str.strip() == match_value
|
|
||||||
|
|
||||||
matches = bb[mask]
|
|
||||||
|
|
||||||
col_data = build_column_dict(urow, exclude_cols={"match_value"})
|
|
||||||
if not col_data:
|
if not col_data:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
payload = dict_to_bci_payload(col_data)
|
payload = dict_to_bci_payload(col_data)
|
||||||
|
|
||||||
for _, brow in matches.iterrows():
|
for brow in bb_rows:
|
||||||
page_index = clean_value(brow[args.page_column])
|
target = clean_value(brow.get(args.match_column))
|
||||||
markup_id = clean_value(brow[args.id_column])
|
|
||||||
|
|
||||||
if page_index == "" or markup_id == "":
|
matched = match_value in target if args.contains else target == match_value
|
||||||
|
if not matched:
|
||||||
|
continue
|
||||||
|
|
||||||
|
page_index = clean_value(brow.get(args.page_column))
|
||||||
|
markup_id = clean_value(brow.get(args.id_column))
|
||||||
|
|
||||||
|
if not page_index or not markup_id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
lines.append(f'ColumnDataSet({page_index},"{markup_id}","{payload}")')
|
lines.append(f'ColumnDataSet({page_index},"{markup_id}","{payload}")')
|
||||||
total_matches += 1
|
total_matches += 1
|
||||||
|
|
||||||
lines.append("Save()")
|
lines += ["Save()", "Close()"]
|
||||||
lines.append("Close()")
|
|
||||||
|
|
||||||
|
out_path = Path(args.out)
|
||||||
out_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
out_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
print(f"WROTE: {out_path}")
|
print(f"WROTE: {out_path}")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue