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
- Core Concepts
- Data Models
- Complete File Lifecycle
- UI Components
- Operations Layer
- Services Layer
- CAD Xref Handling
- Safety System
- Error Handling
- Configuration
- LISP Scripts
- Functional Requirements
- Non-Functional Requirements
- UI Control Reference
- 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:
- Collects all host files and their dependencies
- Copies everything to a structured target folder
- Remaps all internal references to use the new locations
- 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 |
6.2 Link Discovery
| 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):
- Discover Xref tree via AutoCAD Core Console (rs_list_xrefs.lsp)
- Copy host DWG and all discovered Xrefs to target
- Repath Xrefs to use relative paths (rs_repath_xrefs.lsp)
- Allow manual addition of extra Xrefs
BIND_ALL:
- Copy host DWG to target
- Bind all Xrefs into the DWG (rs_bind_all_xrefs.lsp)
- Purge unused content (3x)
- 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:
- Log info message about missing accore
- Heuristically copy sibling directories with known CAD names:
- xrefs, xref, refs, ref, external, dwg, cad
- 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 directoriesmin_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.lockif 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:
- Open drawing (context from accoreconsole)
- Iterate Blocks collection
- For each Xref block:
- Extract name and path
- Recursively open child and scan
- Build nested structure
- 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:
- Parse argument
- For each Xref:
- If path starts with target_root: strip root, make relative
- 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:
- Parse mode (BIND=0, INSERT=1)
- Set BINDTYPE variable
- Execute:
-xref bind * - Execute:
-purge a * n(3x) - 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
- User opens Record Set tool
- Clicks "Add Host..." and selects Model.rvt
- System scans links, finds Link.rvt and CAD.dwg
- User assigns discipline "Structural" in DataGrid
- User clicks "Target Folder..." and selects C:\Output
- User clicks "Sync Preview"
- System shows tree: Host: Model.rvt -> Link.rvt, CAD.dwg
- User clicks "Process"
- Progress window shows copying progress
- Files copied, links repathed
- User closes progress window
15.2 CAD Bind Scenario
- User adds host with DWG links
- User syncs preview (auto-discovers Xrefs)
- User selects CAD.dwg in TreeView
- User clicks "Bind all Xrefs" radio button
- User selects "Insert (merge names)" in BindType dropdown
- User syncs preview again
- Plan now shows cad_bind_all action instead of cad_copy + cad_repath
- User executes
- CAD.dwg is copied, all Xrefs bound and purged
15.3 Manual Xref Addition
- User adds host with DWG link
- User syncs preview
- User selects CAD.dwg in TreeView
- Ensures "Keep/Add Xrefs" is selected
- User clicks "Add CAD Xref..."
- Selects additional DWG file
- New Xref appears as child of CAD.dwg in TreeView
- User syncs preview
- New Xref included in plan
- User executes
15.4 Policy Violation Handling
- User adds hosts and selects target folder
- Target folder is NOT in allowed_target_roots
- User clicks "Process"
- System throws exception: "Target not in allowed roots"
- User updates target to allowed location
- User re-executes successfully
15.5 Cancellation Scenario
- User starts execution
- Progress shows 30% complete
- User clicks "Cancel" button
- cancel_token.cancel() called
- Current action completes
- Loop breaks on next iteration
- Lock released, manifest written with partial status
- 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}
compute_link_target_path(target_root, link, is_common)
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