Table of Contents

Record Set Tool - Complete Technical Specification

Overview

The Record Set tool is a comprehensive multi-file copy and repath system designed for managing Revit project packages. It enables users to collect a set of Revit host models along with their linked files (RVT, DWG, and other external references), copy them to a new target folder structure organized by discipline and building, and automatically update all internal link references to point to the new file locations.

Legacy Python Location (pre-C# port): ./DB Tools/DB Tools.extension/DB Tools.tab/DB Tools Testing.panel/Record Set.pushbutton/


Table of Contents

  1. Core Concepts
  2. Data Models
  3. Complete File Lifecycle
  4. UI Components
  5. Operations Layer
  6. Services Layer
  7. CAD Xref Handling
  8. Safety System
  9. Error Handling
  10. Configuration
  11. LISP Scripts
  12. Functional Requirements
  13. Non-Functional Requirements
  14. UI Control Reference
  15. Interaction Scenarios

1. Core Concepts

1.1 Purpose

Record Set solves the problem of creating portable, self-contained Revit project packages. When Revit models reference external files (linked RVTs, DWGs), those references use absolute paths. Moving files breaks these references. Record Set:

  1. Collects all host files and their dependencies
  2. Copies everything to a structured target folder
  3. Remaps all internal references to use the new locations
  4. Handles CAD files with special Xref processing (discovery, binding, or relative pathing)

1.2 Workflow Stages

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   1. SELECT     │───>│   2. PREVIEW    │───>│   3. EXECUTE    │
│   - Host RVTs   │    │   - Build plan  │    │   - Copy files  │
│   - Target dir  │    │   - Show tree   │    │   - Repath refs │
│   - CAD modes   │    │   - Validate    │    │   - CAD process │
└─────────────────┘    └─────────────────┘    └─────────────────┘

1.3 Preview-Execute Pattern

All operations are planned before execution:

  • Preview phase: Analyze files, discover links, compute new paths, validate constraints
  • Execute phase: Execute the plan atomically with progress tracking and cancellation support

2. Data Models

2.1 RecordSetFile

Represents a single file (host RVT or linked file) in the record set.

RecordSetFile
├── name: string              # Filename (e.g., "Model.rvt")
├── path: string              # Absolute path to source file
├── discipline: string        # Category (e.g., "Structural", "MEP") - empty string allowed
├── building: string          # Building name (e.g., "Building A") - "-" for none
├── link_id: object|null      # Revit ElementId of link (for linked files)
├── link_type: LinkType       # RVT | DWG | OTHER
├── is_common: bool           # True if shared across multiple hosts
├── new_path: string|null     # Computed destination path (set during preview)
├── cad_mode: CadHandlingMode # INHERIT | ADD_XREFS | BIND_ALL
├── bind_type: BindType       # BIND (0) | INSERT (1)
└── children_manual: List<RecordSetFile>  # Manually added Xrefs (DWG only)

2.2 LinkType Enumeration

LinkType
├── RVT   = "rvt"    # Revit model link
├── DWG   = "dwg"    # AutoCAD drawing link
└── OTHER = "other"  # Any other external file (IFC, DGN, etc.)

2.3 CadHandlingMode Enumeration

Controls how DWG files are processed:

CadHandlingMode
├── INHERIT    = "inherit"    # Use parent/global setting
├── ADD_XREFS  = "add_xrefs"  # Discover and copy Xrefs, update paths
└── BIND_ALL   = "bind_all"   # Bind all Xrefs into the DWG

2.4 BindType Enumeration

When binding Xrefs:

BindType
├── BIND   = 0  # Prefix layer/block names with Xref name (preserves uniqueness)
└── INSERT = 1  # Merge without prefix (may cause name conflicts)

2.5 CadRef

Represents an AutoCAD Xref with hierarchical structure:

CadRef
├── host_path: string         # Path to host DWG containing this Xref
├── xref_path: string         # Path to the Xref file
├── xref_name: string         # Block name of the Xref
├── overlay: bool             # True if overlay attachment (vs. attachment)
└── children: List<CadRef>    # Nested Xrefs within this Xref

2.6 PlanItem

Single action in the execution plan:

PlanItem
├── action: string            # Action type (see Action Types below)
└── payload: Dict<string,any> # Parameters for the action

Action Types:

Action Payload Keys Description
fs_create_dir path Create directory
fs_copy src, dst Copy file or directory
cad_copy src, dst Copy CAD file
cad_repath host, new_root Repath Xrefs to relative paths
cad_bind_all host, bindtype Bind all Xrefs into DWG
revit_repath host, mapping Update Revit TransmissionData
ui_warn msg Display warning message
ui_info msg Display info message

2.7 RecordSetPlan

Complete execution blueprint:

RecordSetPlan
├── items: List<PlanItem>     # Ordered list of actions
├── total_bytes: int          # Total size of files to copy
└── summary: Dict             # Statistics (counts, sizes, etc.)

2.8 Context

Execution context container:

Context
├── target_folder: string           # Destination root path
├── selected_hosts: List<RecordSetFile>  # Selected host RVT files
├── ui_flags: Dict                  # UI configuration flags
│   ├── auto_discover_cad_xrefs: bool (default: True)
│   └── allow_cad_repath: bool (default: True)
├── all_links: List<RecordSetFile>  # All discovered links (built during preview)
├── common_links: Set<Tuple>        # Set of (name, link_type) for shared links
├── preview_copy_bytes: int         # Estimated copy size
└── preview_predicted_output_bytes: int  # Estimated output size

2.9 Config (Constants)

Global configuration constants:

Config
├── COMMON_LINKS_FOLDER = "Common Linked Files"
├── MAX_TOTAL_BYTES = 80 GB          # Size limit
├── MAX_TOTAL_ITEMS = 10,000         # Item count limit
├── AUTO_DISCOVER_CAD_XREFS = True   # Default flag
├── ALLOW_CAD_REPATH = True          # Default flag
├── CAD_FALLBACK_DEPTH = 1           # Fallback scan depth
├── CAD_SIBLING_DIRNAMES = ("xrefs", "xref", "refs", "ref", "external", "dwg", "cad")
└── ACCORECONSOLE_HINTS = List of paths to search for AutoCAD Core Console

2.10 SafetyPolicy

Runtime safety constraints (loaded from JSON):

SafetyPolicy
├── allowed_target_roots: List<string>  # Whitelisted target directories
├── delete_mode: string                 # "quarantine" or "delete"
├── require_sentinel_for_delete: bool   # Require sentinel file
├── verify_after_copy: bool             # Checksum verification
├── min_free_bytes: int                 # Minimum free space (default: 10 GB)
├── allowed_extensions: List<string>|null  # Whitelist extensions
├── denied_extensions: List<string>|null   # Blacklist extensions
├── deny_globs: List<string>            # Glob patterns to deny
└── paths: Dict
    └── enable_long_paths: bool         # Windows long path support

2.11 SafetyRuntime

Per-execution metadata:

SafetyRuntime
├── target_root: string      # Target folder path
├── run_id: string           # Format: YYYYMMDD-HHMMSS-XXXX (XXXX = random hex)
├── fingerprint: string      # SHA256[:16] of plan
├── lock_path: string        # {target}/.recordset.lock
├── sentinel: string         # {target}/.recordset_sentinel
├── trash_root: string       # {target}/_RS_TRASH/{run_id}
├── manifest_dir: string     # {target}/_recordset_manifests
└── manifest_path: string    # {manifest_dir}/{run_id}.json

2.12 CancelToken

User cancellation signal:

CancelToken
├── _cancelled: bool = False
├── cancel() -> void    # Sets _cancelled = True
└── is_cancelled() -> bool  # Returns _cancelled

3. Complete File Lifecycle

3.1 Phase 1: Initialization

1. User clicks "Record Set" button on ribbon
   └──> pyRevit executes Record Set_script.py
        └──> Imports and calls _rs_entry.run()

2. run() function:
   └──> Calls _prechecks() - validation stub returning True
   └──> Creates _MainCtl instance
        └──> Loads RecordSetForm.xaml via DBToolsWindow base class
        └──> Initializes empty state:
             - services = Services(app=None)
             - ctx = None
             - plan = None
             - selected_hosts = []
             - target_folder = None
             - ui_flags = {auto_discover_cad_xrefs: True, allow_cad_repath: True}
   └──> Calls ShowDialog() (modal)

3.2 Phase 2: Host File Selection

1. User clicks "Add Host..." button
   └──> add_file() handler

2. add_file():
   └──> _pick_rvt() opens Windows file picker for .rvt files
   └──> Creates RecordSetFile:
        - name = filename
        - path = os.path.normcase(os.path.abspath(selected_path))
        - discipline = ""
        - building = "-"
        - link_type = LinkType.RVT
   └──> Calls services.scan_links_for_host(host)
        └──> RevitOps.read_external_refs(host.path)
             └──> Opens TransmissionData from RVT file
             └──> Iterates ExternalFileReferences
             └──> For each ref:
                  - Extracts absolute path
                  - Determines link_type (rvt/dwg/other) from extension or type
                  - Creates RecordSetFile for link
             └──> Returns list of RecordSetFile links
        └──> Stores links in host.links_to_keep
   └──> Appends host to selected_hosts[]
   └──> Logs action
   └──> Does NOT auto-sync (user must click "Sync Preview")

3.3 Phase 3: Target Folder Selection

1. User clicks "Target Folder..." button
   └──> choose_target_folder() handler

2. choose_target_folder():
   └──> Opens folder picker dialog
   └──> Normalizes path: os.path.normcase(os.path.abspath(...))
   └──> Updates target_folder attribute
   └──> Updates target_tb.Text display

3.4 Phase 4: Preview/Plan Generation

1. User clicks "Sync Preview" button
   └──> sync_tree() handler

2. sync_tree():
   └──> VALIDATION:
        - If target_folder is None: show error, return

   └──> READ UI FLAGS:
        - auto_discover = auto_cad_xref_cb.IsChecked
        - ui_flags["auto_discover_cad_xrefs"] = auto_discover

   └──> BUILD CONTEXT:
        └──> services.build_context(target_folder, selected_hosts, ui_flags)
             └──> Creates Context object
             └──> Calls _mark_common_links():
                  - Counts (name, link_type) pairs across all hosts
                  - For links appearing in 2+ hosts:
                    - Sets link.is_common = True
                    - Adds to ctx.common_links set
             └──> Returns ctx

   └──> GENERATE PLAN:
        └──> services.preview_plan(ctx)
             └──> Creates RecordSetPlan()

             └──> FOR EACH HOST:
                  - new_path = compute_host_target_path(target_root, host)
                    Formula: {target}/{building}/{discipline}/{name}
                    Or if building == "-": {target}/{discipline}/{name}
                  - Add: fs_create_dir(dirname(new_path))
                  - Add: fs_copy(src=host.path, dst=new_path)

                  └──> FOR EACH LINK IN HOST:
                       - new_path = compute_link_target_path(target_root, link, is_common)
                         If is_common: {target}/Common Linked Files/{name}
                         Else if building == "-": {target}/{discipline}/Linked Files/{name}
                         Else: {target}/{building}/{discipline}/Linked Files/{name}
                       - Add: fs_create_dir(dirname(new_path))
                       - If NOT DWG: Add: fs_copy(src, dst)
                       - If DWG: Defer to CAD planning

             └──> CAD PLANNING (for each host):
                  └──> _build_cad_plan_for_host(ctx, plan, host)
                       └──> Find accore = CadXrefOps.find_core_console()

                       └──> FOR EACH DWG LINK:
                            - Add: fs_create_dir(dirname(new_path))
                            - Add: cad_copy(src, dst)

                            └──> IF cad_mode == BIND_ALL:
                                 - Add: cad_bind_all(host=new_path, bindtype=link.bind_type)
                                 - CONTINUE (skip Xref discovery)

                            └──> IF auto_discover AND accore exists:
                                 TRY:
                                   - tree = CadXrefOps.scan_xref_tree(accore, dwg, lsp_dir)
                                   - copy_pairs = CadXrefOps.expand_tree_to_plan(tree)
                                   - For each Xref path (skip host itself):
                                     - Add: fs_create_dir + cad_copy
                                   - If allow_cad_repath:
                                     - Add: cad_repath(host=new_path, new_root=target_root)
                                 EXCEPT:
                                   - Add: ui_warn(error message)
                                   - Fallback to _fallback_cad_copy()

                            └──> ELSE (no accore):
                                 - Add: ui_info("AutoCAD Core Console not found")
                                 - Fallback to _fallback_cad_copy()

             └──> _fallback_cad_copy(plan, dwg):
                  - Heuristic: copy sibling directories named "xrefs", "xref", etc.
                  - For each CAD_SIBLING_DIRNAME:
                    - If exists: Add fs_create_dir + fs_copy for directory

             └──> REVIT REPATH (for each host):
                  - Build mapping: {old_link_path: new_link_path} for all links
                  - Add: revit_repath(host=new_host_path, mapping=mapping)

             └──> CALCULATE SIZES:
                  - total_bytes = sum of all source file sizes
                  - predicted_output = total_bytes + bind expansion estimates

             └──> VALIDATION:
                  - If total_bytes > MAX_TOTAL_BYTES: Add ui_warn
                  - If item_count > MAX_TOTAL_ITEMS: Add ui_warn

             └──> _prevent_name_collisions(plan):
                  - Track all destination paths
                  - If collision (same dst, different src):
                    - Rename to: name (2).ext, name (3).ext, etc.
                    - Add: ui_warn("Renamed due to collision")

             └──> Return (plan, messages)

   └──> DISPLAY PLAN:
        └──> Show messages in plan_msgs_ic ItemsControl
        └──> Show AutoCAD Core Console status in accore_tb TextBlock
        └──> _render_tree():
             - Clear links_tv TreeView
             - For each host:
               - Create TreeViewItem "Host: {name}"
               - For each link:
                 - Create child TreeViewItem "{link_type}: {name}"
                 - If DWG with manual children:
                   - Create grandchild TreeViewItems
             - Set Tag property on each item for object reference

   └──> COMPUTE FINGERPRINT:
        └──> _fingerprint(plan):
             - Concatenate: action + sorted(payload.items())
             - SHA256[:16] of concatenation
        └──> Store in plan_fingerprint

3.5 Phase 5: CAD Mode Configuration (Optional)

1. User selects DWG node in TreeView
   └──> UI enables CAD handling panel

2. User clicks "Keep/Add Xrefs" radio button:
   └──> checked_addxrefs():
        - node = _selected_cad_node()
        - services.set_cad_mode(ctx, node, CadHandlingMode.ADD_XREFS)
        - _set_cad_panel_enabled(addxrefs=True, bindall=False)
        - sync_tree()  // Re-plan

3. User clicks "Bind all Xrefs" radio button:
   └──> checked_bindall():
        - node = _selected_cad_node()
        - services.set_cad_mode(ctx, node, CadHandlingMode.BIND_ALL)
        - Clear node.children_manual[]
        - _set_cad_panel_enabled(addxrefs=False, bindall=True)
        - sync_tree()  // Re-plan

4. User selects BindType (BIND vs INSERT):
   └──> bindtype_changed():
        - services.set_cad_mode(..., bindtype=selected_value)

5. User clicks "Add CAD Xref..." button:
   └──> add_cad_xref():
        - parent = _selected_cad_node()
        - _pick_dwg() opens file picker
        - services.add_manual_cad_xref(ctx, parent, dwg_path)
        - sync_tree()  // Re-plan

3.6 Phase 6: Execution

1. User clicks "Process" button
   └──> process_files() handler

2. process_files():
   └──> VALIDATION:
        - If plan is None: show error, return

   └──> CREATE PROGRESS UI:
        - cancel_token = CancelToken()
        - progress_ctl = _ProgressCtl()
        - progress_ctl.cancel_token = cancel_token
        - progress_ctl.show()  // Non-modal
        - Define log_cb = lambda msg: progress_ctl.log(msg)
        - Define progress_cb = lambda pct: progress_ctl.set_progress(pct)

   └──> EXECUTE:
        └──> services.execute(ctx, plan, progress_cb, log_cb, cancel_token)

             └──> SETUP:
                  - Create Progress tracker with action weights
                  - policy = _load_policy(target_root, log_cb)
                    - Load recordset_policy.json
                    - Return SafetyPolicy object
                  - run_id = _make_run_id()  // YYYYMMDD-HHMMSS-XXXX
                  - fingerprint = _fingerprint_plan(plan)
                  - rt = SafetyRuntime(target_root, run_id, fingerprint)

             └──> VALIDATE:
                  - If target_root not in policy.allowed_target_roots:
                    - Raise Exception("Target not in allowed roots")
                  - If free_space < policy.min_free_bytes + plan.total_bytes:
                    - Raise Exception("Insufficient disk space")

             └──> ACQUIRE LOCK:
                  └──> SafeFsOps.acquire_lock(rt, log_cb):
                       - Create rt.lock_path file with content: run_id + fingerprint
                       - If file already exists: Raise Exception("Concurrent execution")

             └──> WRITE SENTINEL:
                  - SafeFsOps.write_sentinel(rt, log_cb)
                  - Touch file at rt.sentinel

             └──> TRY:
                  └──> FOR EACH PLAN ITEM:
                       - Check cancel_token.is_cancelled(): break if True

                       └──> DISPATCH BY ACTION:

                            fs_create_dir:
                              FsOps.make_dir(payload["path"])

                            fs_copy:
                              FsOps.copy_file(payload["src"], payload["dst"], log_cb)
                              - If src is directory: recursive copy via os.walk()
                              - If src is file: shutil.copy2() (preserves metadata)

                            cad_copy:
                              FsOps.copy_file(payload["src"], payload["dst"], log_cb)

                            cad_repath:
                              accore = CadXrefOps.find_core_console()
                              CadXrefOps.repath_xrefs_relative(
                                accore, payload["host"], lsp_dir, payload["new_root"])
                              - Runs rs_repath_xrefs.lsp via accoreconsole
                              - Converts Xref paths to relative under new_root

                            cad_bind_all:
                              accore = CadXrefOps.find_core_console()
                              CadXrefOps.bind_all(
                                accore, payload["host"], lsp_dir, payload["bindtype"])
                              - Runs rs_bind_all_xrefs.lsp via accoreconsole
                              - Binds all Xrefs with specified BindType
                              - Purges unused 3x
                              - Quick-saves

                            revit_repath:
                              RevitOps.write_transmission(
                                payload["host"], payload["mapping"])
                              - Read TransmissionData from RVT
                              - For each external reference:
                                - old_path = ext_ref.GetPath() or GetAbsolutePath()
                                - new_path = mapping.get(normalize(old_path))
                                - If found: ext_ref.SetDesiredReferenceData(new_path)
                              - Write TransmissionData back if changed

                            ui_warn:
                              log_cb("[WARNING] " + payload["msg"])

                            ui_info:
                              log_cb("[INFO] " + payload["msg"])

                       - progress.step(action_weight)

                  └──> WRITE MANIFEST (success):
                       - SafeFsOps.write_manifest(rt, plan.summary, "ok", log_cb)
                       - JSON at rt.manifest_path:
                         {
                           "run_id": "...",
                           "fingerprint": "...",
                           "target_root": "...",
                           "utc": "2025-01-13T...",
                           "summary": plan.summary,
                           "status": "ok"
                         }

             └──> EXCEPT Exception as e:
                  - SafeFsOps.write_manifest(rt, plan.summary, f"failed:{e}", log_cb)
                  - Re-raise exception

             └──> FINALLY:
                  - SafeFsOps.release_lock(rt, log_cb)
                  - Delete rt.lock_path file

   └──> COMPLETION:
        - progress_ctl.enable_close()
        - User can now close progress window

3.7 Phase 7: Completion

1. Execution completes (success or failure)
   └──> Progress window remains open with logs

2. User interactions:
   └──> "Copy logs" button: Copies step_tb.Text to clipboard
   └──> "Close" button: Closes window (only enabled after completion)

3. Output folder structure:
   {target_folder}/
   ├── {Building}/
   │   └── {Discipline}/
   │       ├── Host.rvt
   │       └── Linked Files/
   │           ├── Link1.rvt
   │           └── Link2.dwg
   ├── Common Linked Files/
   │   ├── SharedLink1.rvt
   │   └── SharedLink2.dwg
   ├── _recordset_manifests/
   │   └── {run_id}.json
   └── .recordset_sentinel

4. UI Components

4.1 Main Window (RecordSetForm.xaml)

Window Properties:

  • Title: "Record Set"
  • Size: 1100 x 720 pixels
  • Resizable, centered on screen
  • Modal display via ShowDialog()
  • Not shown in taskbar

Layout Structure:

┌────────────────────────────────────────────────────────────────────┐
│ [Wizard Bar: 1-Select | 2-Preview | 3-Execute]                     │
├────────────────────────────────────────────────────────────────────┤
│ [Policy Status: Allowed Root | Free Space | AutoCAD | Sentinel]    │
├──────────────────────────────────────────┬─────────────────────────┤
│ [Control Bar]                            │ [Target Folder: ____]   │
│ [Manage Disciplines] [Manage Buildings]  │ [Sync Preview] [Process]│
│ [Add Host] [Remove Selected]             │                         │
│ [x] Auto-discover nested CAD Xrefs       │                         │
├──────────────────────────────────────────┴─────────────────────────┤
│ [Plan Messages: ItemsControl with warnings/info]                   │
├────────────────────────────────┬───────────────────────────────────┤
│ [Files DataGrid]               │ [CAD Panel]                       │
│ ┌─────────────────────────┐    │ ┌─────────────────────────────┐   │
│ │ Host File | Disc | Bldg │    │ │ ( ) Keep/Add Xrefs          │   │
│ ├─────────────────────────┤    │ │ ( ) Bind all Xrefs          │   │
│ │ Model.rvt | STR  | Bld1 │    │ │ [BindType: v] [Add Xref...] │   │
│ │ MEP.rvt   | MEP  | Bld1 │    │ └─────────────────────────────┘   │
│ └─────────────────────────┘    │ [Links TreeView]                  │
│                                │ ├── Host: Model.rvt               │
│                                │ │   ├── rvt: Linked.rvt           │
│                                │ │   └── dwg: CAD.dwg              │
│                                │ │       └── dwg: Xref1.dwg        │
│                                │ └── Host: MEP.rvt                 │
│                                │     └── ...                       │
└────────────────────────────────┴───────────────────────────────────┘

4.2 Manage Dialog (RecordSetFormManage.xaml)

Window Properties:

  • Title: "Record Set - Manage"
  • Size: 520 x 420 pixels
  • Resizable with grip
  • Modal display

Layout:

┌────────────────────────────────────────┐
│ [Add...] [Rename] [Delete]             │
│ Double-click item to rename            │
├────────────────────────────────────────┤
│ [ListBox: items]                       │
│ ┌──────────────────────────────────┐   │
│ │ Structural                       │   │
│ │ MEP                              │   │
│ │ Architectural                    │   │
│ └──────────────────────────────────┘   │
├────────────────────────────────────────┤
│                              [Close]   │
└────────────────────────────────────────┘

Functionality:

  • Add: Prompts for name, appends to list
  • Rename: Prompts for new name, updates in-place
  • Delete: Removes selected item

4.3 Progress Window (RecordSetFormProgress.xaml)

Window Properties:

  • Title: "Record Set - Progress"
  • Size: 820 x 520 pixels
  • Resizable with grip
  • Non-modal display initially
  • Close prevented until execution completes

Layout:

┌────────────────────────────────────────────────────────────────────┐
│ [████████████████████░░░░░░░░░░░░░░░░░░░] 65%                      │
├────────────────────────────────────────────────────────────────────┤
│ [Scrollable Log TextBox]                                           │
│ ┌──────────────────────────────────────────────────────────────┐   │
│ │ Creating directory: C:\Output\Structural                      │   │
│ │ Copying: Model.rvt -> C:\Output\Structural\Model.rvt         │   │
│ │ Copying: Link.rvt -> C:\Output\Structural\Linked Files\...   │   │
│ │ Processing CAD Xrefs for CAD.dwg...                          │   │
│ │ Updating link references in Model.rvt...                     │   │
│ └──────────────────────────────────────────────────────────────┘   │
├────────────────────────────────────────────────────────────────────┤
│ [Recap Panel - collapsed until complete]                           │
│ Copy size: 2.5 GB | Output size: 2.8 GB | Duration: 3m 42s        │
│ Manifest: C:\Output\_recordset_manifests\20250113-154200-a1b2.json│
├────────────────────────────────────────────────────────────────────┤
│ [Copy logs]                                    [Cancel]   [Close]  │
└────────────────────────────────────────────────────────────────────┘

Functionality:

  • Progress bar: 0-100 with percentage text
  • Log area: Scrolling TextBox with auto-scroll
  • Copy logs: Copies log text to clipboard
  • Cancel: Signals cancel_token.cancel()
  • Close: Disabled until execution completes

5. Operations Layer

5.1 FsOps (Filesystem Operations)

Basic filesystem operations without safety validation:

Method Parameters Behavior
make_dir path, log Creates directory if not exists (os.makedirs)
copy_file src, dst, log If directory: recursive copy via os.walk(). If file: shutil.copy2()
remove_path path, log Removes file or directory tree (shutil.rmtree)
calc_size_bytes path Recursively sums file sizes

5.2 SafeFsOps (Safety-Enhanced Operations)

Hardened operations with policy enforcement:

Method Parameters Behavior
_is_under root, path Returns True if path is under root
acquire_lock rt, log Creates exclusive lock file with run_id/fingerprint
release_lock rt, log Removes lock file
write_sentinel rt, log Touches sentinel file
make_dir rt, pol, path, log Delegates to FsOps.make_dir()
copy_file rt, pol, src, dst, log Delegates to FsOps.copy_file()
write_manifest rt, summary, status, log Writes JSON manifest file

5.3 RevitOps (Revit Document Operations)

Revit API operations for document manipulation:

Method Parameters Behavior
open_doc app, path, workshared, detach_preserve Opens RVT with OpenAllWorksets, DetachFromCentral
close_doc doc, save, new_path Closes document, optionally saves to new path
purge_unused doc Stub (not implemented)
delete_views doc, view_ids Transaction: deletes specified views
delete_links doc, link_ids Transaction: deletes specified links
read_external_refs rvt_path Reads TransmissionData, returns list of (abs_path, kind)
write_transmission rvt_path, mapping Updates link paths in TransmissionData

5.4 CadXrefOps (AutoCAD Core Console Operations)

CAD operations via AutoCAD Core Console:

Method Parameters Behavior
find_core_console - Searches for accoreconsole.exe
scan_xref_tree accore, host_dwg, lsp_dir Returns JSON tree of Xrefs
repath_xrefs_relative accore, host, lsp_dir, new_root Converts Xref paths to relative
bind_all accore, host, lsp_dir, bindtype Binds all Xrefs with purge
expand_tree_to_plan tree_json, copy_pairs Extracts all unique paths from tree

6. Services Layer

6.1 Context Management

Method Parameters Behavior
build_context target_folder, selected_hosts, ui_flags Creates Context, marks common links
set_cad_mode ctx, parent_dwg, mode, bindtype Sets CAD handling mode for DWG
add_manual_cad_xref ctx, parent_link, dwg_path Adds manual Xref to DWG
Method Parameters Behavior
scan_links_for_host host Reads TransmissionData, creates RecordSetFile for each link
_mark_common_links ctx Identifies links shared across multiple hosts

6.3 Plan Generation

Method Parameters Behavior
preview_plan ctx Generates complete execution plan with validation
_build_cad_plan_for_host ctx, plan, host Generates CAD-specific plan items
_fallback_cad_copy plan, dwg Heuristic copy of sibling CAD directories
_prevent_name_collisions plan Renames duplicate destinations

6.4 Execution

Method Parameters Behavior
execute ctx, plan, progress_cb, log_cb, cancel_token Executes plan with progress tracking

7. CAD Xref Handling

7.1 CAD Handling Modes

INHERIT:

  • Uses parent/global setting
  • Default mode for newly discovered DWGs

ADD_XREFS (Keep/Add Xrefs):

  1. Discover Xref tree via AutoCAD Core Console (rs_list_xrefs.lsp)
  2. Copy host DWG and all discovered Xrefs to target
  3. Repath Xrefs to use relative paths (rs_repath_xrefs.lsp)
  4. Allow manual addition of extra Xrefs

BIND_ALL:

  1. Copy host DWG to target
  2. Bind all Xrefs into the DWG (rs_bind_all_xrefs.lsp)
  3. Purge unused content (3x)
  4. Result: Self-contained DWG with no external dependencies

7.2 BindType Options

BIND (0):

  • Prefixes layer/block names with Xref name
  • Example: "0" layer becomes "XrefName$0$0"
  • Preserves uniqueness, avoids conflicts

INSERT (1):

  • Merges layers/blocks without prefix
  • Example: "0" layer merges with existing "0"
  • May cause naming conflicts, but cleaner result

7.3 Fallback Behavior

When AutoCAD Core Console is not available:

  1. Log info message about missing accore
  2. Heuristically copy sibling directories with known CAD names:
    • xrefs, xref, refs, ref, external, dwg, cad
  3. No path repathing (Xrefs may still break)

8. Safety System

8.1 Lock File Mechanism

Prevents concurrent execution:

File: {target}/.recordset.lock
Content: {run_id}\n{fingerprint}
  • Created at execution start
  • Deleted at execution end (finally block)
  • If exists when starting: throw exception

8.2 Sentinel File

Marks target as managed by Record Set:

File: {target}/.recordset_sentinel
Content: (empty - just touched)
  • Created/touched at execution start
  • Required before any delete operations (configurable)
  • Never deleted

8.3 Manifest Files

Execution audit trail:

File: {target}/_recordset_manifests/{run_id}.json
Content:
{
  "run_id": "20250113-154200-a1b2",
  "fingerprint": "abc123def456...",
  "target_root": "C:\\Output",
  "utc": "2025-01-13T15:42:00Z",
  "summary": { ... },
  "status": "ok" | "failed:..."
}

8.4 Safety Policy

Configurable constraints in recordset_policy.json:

  • allowed_target_roots: Whitelist of valid target directories
  • min_free_bytes: Minimum free disk space (default: 10 GB)
  • denied_extensions: File types to skip (.dwl, .dwl2, .tmp, .bak, .lnk, .exe)
  • deny_globs: Patterns to skip (backup.rvt, .old, ~$)

9. Error Handling

9.1 Error Scenarios

Scenario Handling
No target folder selected UI error message, abort preview
Target not in allowed roots Exception during execute
Insufficient disk space Exception during execute
Lock file exists Exception during execute
File copy fails Exception propagates, manifest written with failure status
CAD Core Console not found Fallback to heuristic copy, continue
CAD script fails Warning logged, continue with partial plan
TransmissionData read fails Empty link list returned, continue
User cancels Break execution loop, release lock, exit

9.2 Recovery

  • Lock file: Manually delete .recordset.lock if stale
  • Failed execution: Check manifest for failure status and details
  • Partial copy: Target folder may contain incomplete files; re-run after cleanup

10. Configuration

10.1 recordset_policy.json

Default location: Same directory as script

{
  "allowed_target_roots": [
    "\\\\fileserver\\Projects"
  ],
  "delete_mode": "quarantine",
  "require_sentinel_for_delete": true,
  "verify_after_copy": false,
  "min_free_bytes": 10737418240,
  "denied_extensions": [".dwl", ".dwl2", ".tmp", ".bak", ".lnk", ".exe"],
  "deny_globs": ["*backup*.rvt", "*.old", "~$*"],
  "paths": {
    "enable_long_paths": false
  }
}

10.2 Config Constants

Hardcoded in _rs_models.py:

Constant Value Description
COMMON_LINKS_FOLDER "Common Linked Files" Folder name for shared links
MAX_TOTAL_BYTES 80 GB Maximum total copy size
MAX_TOTAL_ITEMS 10,000 Maximum item count
CAD_SIBLING_DIRNAMES ("xrefs", "xref", ...) Fallback CAD directory names
ACCORECONSOLE_HINTS [...paths...] AutoCAD Core Console search paths

11. LISP Scripts

11.1 rs_list_xrefs.lsp

Purpose: Discover Xref tree for a DWG

Input: Drawing path (via accoreconsole /i flag)

Output: JSON structure:

{
  "host": "path/to/drawing.dwg",
  "xrefs": [
    {
      "name": "xref_name",
      "path": "path/to/xref.dwg",
      "overlay": false,
      "children": [...]
    }
  ]
}

Algorithm:

  1. Open drawing (context from accoreconsole)
  2. Iterate Blocks collection
  3. For each Xref block:
    • Extract name and path
    • Recursively open child and scan
    • Build nested structure
  4. Output JSON to stdout

11.2 rs_repath_xrefs.lsp

Purpose: Convert Xref paths to relative

Input: "host.dwg|target_root" (pipe-separated)

Output: {"ok":true}

Algorithm:

  1. Parse argument
  2. For each Xref:
    • If path starts with target_root: strip root, make relative
  3. Save drawing

11.3 rs_bind_all_xrefs.lsp

Purpose: Bind all Xrefs into DWG

Input: "BIND|host.dwg" or "INSERT|host.dwg"

Output: {"ok":true}

Algorithm:

  1. Parse mode (BIND=0, INSERT=1)
  2. Set BINDTYPE variable
  3. Execute: -xref bind *
  4. Execute: -purge a * n (3x)
  5. Execute: _.qsave

12. Functional Requirements

12.1 Host File Management

ID Requirement
FR-1.1 Add host RVT files via file picker
FR-1.2 Remove selected host files
FR-1.3 Automatically scan host for linked files
FR-1.4 Assign discipline to host files
FR-1.5 Assign building to host files
FR-1.6 Display host files in editable DataGrid

12.2 Target Folder Management

ID Requirement
FR-2.1 Select target folder via folder picker
FR-2.2 Display selected target folder path
FR-2.3 Validate target folder against allowed roots

12.3 Preview System

ID Requirement
FR-3.1 Generate execution plan on demand
FR-3.2 Display file hierarchy in TreeView
FR-3.3 Show validation warnings/info messages
FR-3.4 Detect and report name collisions
FR-3.5 Calculate and display total copy size
FR-3.6 Compute plan fingerprint for verification

12.4 CAD Handling

ID Requirement
FR-4.1 Support ADD_XREFS mode (discover + copy + repath)
FR-4.2 Support BIND_ALL mode (bind into single DWG)
FR-4.3 Support BIND vs INSERT bind types
FR-4.4 Allow manual Xref addition for DWGs
FR-4.5 Auto-discover nested Xrefs via AutoCAD Core Console
FR-4.6 Fallback to heuristic copy when accore unavailable

12.5 Execution

ID Requirement
FR-5.1 Copy all files to target folder
FR-5.2 Create organized folder structure
FR-5.3 Update Revit TransmissionData for all hosts
FR-5.4 Process CAD files according to mode
FR-5.5 Display real-time progress
FR-5.6 Support user cancellation
FR-5.7 Generate execution manifest

12.6 Safety

ID Requirement
FR-6.1 Enforce allowed target roots
FR-6.2 Check minimum free disk space
FR-6.3 Prevent concurrent execution via lock file
FR-6.4 Create sentinel file for managed folders
FR-6.5 Filter denied file extensions
FR-6.6 Filter denied glob patterns

13. Non-Functional Requirements

13.1 Performance

ID Requirement
NFR-1.1 Handle projects up to 80 GB total size
NFR-1.2 Support up to 10,000 items in plan
NFR-1.3 Provide responsive UI during execution
NFR-1.4 Weighted progress tracking for accurate estimates

13.2 Reliability

ID Requirement
NFR-2.1 Atomic execution with lock file
NFR-2.2 Manifest generation for audit trail
NFR-2.3 Graceful handling of missing AutoCAD
NFR-2.4 Error recovery via manifest status

13.3 Usability

ID Requirement
NFR-3.1 Three-step wizard workflow
NFR-3.2 Real-time log display
NFR-3.3 Copy logs to clipboard
NFR-3.4 Clear validation messages

13.4 Security

ID Requirement
NFR-4.1 Policy-based target root restriction
NFR-4.2 Extension and glob filtering
NFR-4.3 Sentinel requirement for destructive operations

14. UI Control Reference

14.1 Main Window Controls

Control Name Type Binding/Handler Purpose
target_tb TextBox Read-only display Shows selected target folder
auto_cad_xref_cb CheckBox ui_flags Toggle auto CAD Xref discovery
files_dg DataGrid selected_hosts Host file list with discipline/building
links_tv TreeView plan hierarchy Host/link hierarchy display
plan_msgs_ic ItemsControl plan messages Validation warnings/info
accore_tb TextBlock display AutoCAD Core Console path
rb_addxrefs RadioButton checked_addxrefs Select ADD_XREFS mode
rb_bindall RadioButton checked_bindall Select BIND_ALL mode
bindtype_cb ComboBox bindtype_changed BIND vs INSERT selection
add_cad_xref_b Button add_cad_xref Add manual Xref button

14.2 Manage Dialog Controls

Control Name Type Handler Purpose
list_lb ListBox items binding Display discipline/building list
Add button Button button_add Add new item
Rename button Button button_rename Rename selected item
Delete button Button button_del Delete selected item

14.3 Progress Window Controls

Control Name Type Binding/Handler Purpose
overall_pb ProgressBar set_progress Visual progress (0-100)
progress_tb TextBlock set_progress Percentage text
step_tb TextBox log Scrolling log output
step_sv ScrollViewer auto-scroll Log container
Copy logs button Button button_copylogs Copy log to clipboard
Cancel button Button button_cancel Cancel execution
Close button Button button_close Close window

15. Interaction Scenarios

15.1 Basic Workflow

  1. User opens Record Set tool
  2. Clicks "Add Host..." and selects Model.rvt
  3. System scans links, finds Link.rvt and CAD.dwg
  4. User assigns discipline "Structural" in DataGrid
  5. User clicks "Target Folder..." and selects C:\Output
  6. User clicks "Sync Preview"
  7. System shows tree: Host: Model.rvt -> Link.rvt, CAD.dwg
  8. User clicks "Process"
  9. Progress window shows copying progress
  10. Files copied, links repathed
  11. User closes progress window

15.2 CAD Bind Scenario

  1. User adds host with DWG links
  2. User syncs preview (auto-discovers Xrefs)
  3. User selects CAD.dwg in TreeView
  4. User clicks "Bind all Xrefs" radio button
  5. User selects "Insert (merge names)" in BindType dropdown
  6. User syncs preview again
  7. Plan now shows cad_bind_all action instead of cad_copy + cad_repath
  8. User executes
  9. CAD.dwg is copied, all Xrefs bound and purged

15.3 Manual Xref Addition

  1. User adds host with DWG link
  2. User syncs preview
  3. User selects CAD.dwg in TreeView
  4. Ensures "Keep/Add Xrefs" is selected
  5. User clicks "Add CAD Xref..."
  6. Selects additional DWG file
  7. New Xref appears as child of CAD.dwg in TreeView
  8. User syncs preview
  9. New Xref included in plan
  10. User executes

15.4 Policy Violation Handling

  1. User adds hosts and selects target folder
  2. Target folder is NOT in allowed_target_roots
  3. User clicks "Process"
  4. System throws exception: "Target not in allowed roots"
  5. User updates target to allowed location
  6. User re-executes successfully

15.5 Cancellation Scenario

  1. User starts execution
  2. Progress shows 30% complete
  3. User clicks "Cancel" button
  4. cancel_token.cancel() called
  5. Current action completes
  6. Loop breaks on next iteration
  7. Lock released, manifest written with partial status
  8. User can close window

Appendix A: Python File Summary

File Lines Purpose
Record Set_script.py 6 Entry point
_rs_entry.py 448 UI controllers (3 window classes)
_rs_models.py 115 Data models and enums
_rs_ops.py 388 Operations layer (Fs, Safe, Revit, CAD)
_rs_services.py 422 Services layer (orchestration)
_rs_utils.py 68 Utility functions
RecordSetForm.xaml 98 Main window UI
RecordSetFormManage.xaml 23 Manage dialog UI
RecordSetFormProgress.xaml 48 Progress window UI
rs_list_xrefs.lsp 56 LISP: Xref discovery
rs_repath_xrefs.lsp 32 LISP: Xref repathing
rs_bind_all_xrefs.lsp 17 LISP: Xref binding

Total Python Lines: ~1,447 Total XAML Lines: ~169 Total LISP Lines: ~105


Appendix B: Path Computation Formulas

compute_host_target_path(target_root, host)

IF host.building IN ("-", ""):
    RETURN {target_root}/{host.discipline}/{host.name}
ELSE:
    RETURN {target_root}/{host.building}/{host.discipline}/{host.name}
IF is_common:
    RETURN {target_root}/Common Linked Files/{link.name}
ELSE IF link.building IN ("-", ""):
    RETURN {target_root}/{link.discipline}/Linked Files/{link.name}
ELSE:
    RETURN {target_root}/{link.building}/{link.discipline}/Linked Files/{link.name}

Appendix C: Fingerprint Algorithm

def _fingerprint(plan):
    parts = []
    for item in plan.items:
        parts.append(item.action)
        for k, v in sorted(item.payload.items()):
            parts.append(f"{k}={v}")
    content = "|".join(parts)
    return hashlib.sha256(content.encode()).hexdigest()[:16]

Document Version: 1.0 Generated: 2026-01-14 Source: Python Record Set tool analysis