← Macro & add-in library

Drawings & BOM · V8.12

Automated Cope Detail Drawing Generator

A SolidWorks VBA workflow that generates cope-detail views for weldment cut-list items and adds BOM balloons.

ENGINEERING CONTRIBUTION

Developed geometry classification, view competition, orientation logic, placement, sorting, and BOM-balloon workflows.

Prerequisites

  • SolidWorks VBA with an active drawing and suitable referenced weldment/cut-list geometry.
  • A BOM / Weldment Cut List table that resolves item numbers for balloons, plus compatible drawing sheet setup.
  • Review output-sheet, balloon and logging constants before adapting the workflow.

Additional setup

  • Requires a drawing BOM or weldment cut-list table with resolvable item numbers for balloon annotations.

SOURCE WALKTHROUGH

How the workflow fits together.

  1. 01

    Validate drawing and cut lists

    main connects to the active drawing, initializes logging and dispatches through guarded cut-list/body discovery.

  2. 02

    Compete candidate views

    Body fingerprints and categories influence scores across standard and optional custom orientations. Temporary views supply geometry evidence.

  3. 03

    Orient and place details

    Projected spans, linear edges and staged fallbacks control drawing-view rotation. Accepted views are sorted and placed in rows.

  4. 04

    Annotate and restore

    The workflow adds BOM balloons and restores temporary model/view state where possible. Failures are logged rather than treated as a completed detail.

Output & model changes

  • RESET_OUTPUT_SHEET defaults to True: an existing COPE DETAILS sheet is deleted and recreated, including its prior contents.
  • Creates drawing views and BOM balloons; uses temporary model/view orientation state and rebuilds during analysis.
  • Writes diagnostics to the configured log folder; logs can contain document details.

CODE & ENTRY POINTS

Read the implementation.

Find a procedure, follow an API call or download the module for your SolidWorks setup.

CopeDetailGenerator.bas

Weldment body classification, category-specific view competition, orientation fallback logic, drawing placement and BOM-balloon annotation in one exported module.

VBA · 11,332 lines

Download .bas

Entry points
API & integration calls
Selected line 1
Option Explicit

'====================================================================================
' AUTO_COPE_DETAIL_GENERATOR_V8_11_PLATE_FACE_CORNER_AND_CUSTOM_VIEW_TRUST
'
' PURPOSE (FAST MODE)
'   - Generate one cope-detail drawing view per weldment cut-list item
'   - FAST "simple stack" placement (row-by-row / wrap) for speed
'   - Sort items ASCENDING by Part Number before placing
'   - Notes OFF
'   - Post-pass: add REAL BOM balloons ("bubbles") per view
'       * Anchored to a VISIBLE entity (edge/vertex/face) inside each view
'       * Then reposition bubble near top-right of that view
'
' REQUIREMENT
'   - The drawing MUST contain a BOM / Weldment Cut List table that resolves item numbers.
'     Without it, InsertBOMBalloon2 can return blank/incorrect bubbles or fail.
'
' VERSION NOTES (V8.12)
'   - Preserves existing behavior
'   - Keeps strengthened configuration assignment / validation from V8.4
'   - Preserves existing behavior
'   - Keeps strengthened configuration assignment / validation from V8.4
'   - Replaces edge-only orientation logic with category-based body analysis
'   - Isolates each cut-list body, fingerprints its geometry, and classifies it
'       LINEAR STRUCTURAL / CURVED STRUCTURAL / SHEET-PLATE / ROUND STOCK / IRREGULAR
'   - Still tries ALL six standard views first, then user-created named/custom model views
'   - Scores candidate views using category-specific signals:
'       projected straight-direction families, projected body point-cloud span, projected box area,
'       visible edge evidence, and outline quality
'   - Rotation now uses category-specific reference selection with staged discrete search:
'       90 deg -> 45 deg -> 30 deg -> 15 deg -> 10 deg -> 5 deg -> 2 deg -> 1 deg
'   - For parts with no reliable straight edge, falls back to projected body-span / chord logic
'   - Keeps visible-linear-edge and bbox sweep as final fallbacks
'   - Adds detailed logging around body classification, view competition, and rotation testing
'   - Adds body-box-corner projected-point fallback when round/cylindrical bodies return zero sampled points
'   - Adds round-stock outline fallback so solid rods are not falsely rejected when projected principal metrics are unavailable
'   - Treats ActivateModelConfigurationSafe as successful when the requested configuration is confirmed active after rebuild
'
'====================================================================================

'==========================
' USER SETTINGS
'==========================

Private Const MACRO_REVISION As String = "V8.12_ROUND_STOCK_PROJECTED_FALLBACK"
Private Const MACRO_TITLE As String = "AUTO_COPE_DETAIL_GENERATOR_V8_12_ROUND_STOCK_PROJECTED_FALLBACK"

Private Const OUTPUT_SHEET_NAME   As String = "COPE DETAILS"
Private Const RESET_OUTPUT_SHEET  As Boolean = True

' Permitted region margins (meters)
Private Const MARGIN_LEFT         As Double = 0.025
Private Const MARGIN_RIGHT        As Double = 0.025
Private Const MARGIN_TOP          As Double = 0.025
Private Const MARGIN_BOTTOM       As Double = 0.065    ' title block clearance

' FAST STACK placement gaps (meters)
Private Const STACK_GAP_X         As Double = 0.01
Private Const STACK_GAP_Y         As Double = 0.012

' Band above each view (reserved; we keep it even if notes are off)
Private Const NOTE_BAND_H         As Double = 0.01
Private Const NOTE_TO_VIEW_GAP    As Double = 0.004

Private Const STACK_WRAP_ROWS     As Boolean = True
Private Const ALLOW_VERTICAL_OVERFLOW As Boolean = False

' === manual parking strip mode ===
Private Const MANUAL_PARK_STRIP_MODE      As Boolean = True
Private Const PARK_STRIP_OFFSET_BELOW_SHEET As Double = 0.12   ' meters below sheet bottom
Private Const PARK_STRIP_ALLOW_WRAP       As Boolean = True
Private Const PARK_STRIP_WRAP_WIDTH       As Double = 0.75     ' width of parking row before wrapping
Private Const PARK_STRIP_ROW_GAP          As Double = 0.03     ' gap between parking rows

' Scale behavior
'   If DETAIL_SCALE_DECIMAL <= 0, use selected/reference view scale
'   Else force this scale for all generated views
Private Const DETAIL_SCALE_DECIMAL As Double = 0#

' Optional scale limits if using reference scale (0 = disabled)
Private Const MIN_SCALE_DECIMAL   As Double = 0#
Private Const MAX_SCALE_DECIMAL   As Double = 0#

' Rotation / orientation
Private Const AUTO_ROTATE_TO_HORIZONTAL As Boolean = False  ' candidate views are rotated during validation; do not rotate again after placement

' View-selection logic
Private Const ORIENT_TRY_ALL_STANDARD_VIEWS     As Boolean = True
Private Const ORIENT_INCLUDE_CUSTOM_MODEL_VIEWS As Boolean = True
Private Const ORIENT_VIEWSEL_MIN_EDGE_LEN       As Double = 0.0005   ' meters on sheet
Private Const ORIENT_VIEWSEL_LEN_EPS            As Double = 0.0002
Private Const ORIENT_VIEWSEL_SCORE_EPS          As Double = 0.5
Private Const ORIENT_VIEWSEL_ANGLE_EPS_DEG      As Double = 0.5
Private Const ORIENT_LOG_VIEW_CANDIDATES        As Boolean = True
Private Const TEMP_MODEL_VIEW_PREFIX            As String = "AUTO_COPE_TMP_"
Private Const ALLOW_GENERATED_BODY_FRAME_VIEWS  As Boolean = False    ' production default: do not create temp named model views for orientation
Private Const BODY_ORIENT_MAX_CANDIDATES        As Long = 12
Private Const BODY_ORIENT_FACE_DOT_LEN_MAX      As Double = 0.35
Private Const STRICT_AXIS_HORIZONTAL_TOL_DEG    As Double = 4#
Private Const STRICT_PLATE_PROFILE_RATIO_MIN    As Double = 0.06
Private Const STRICT_STRUCT_PROFILE_RATIO_MIN   As Double = 0.012
Private Const STRICT_MIN_PROFILE_ABS            As Double = 0.003

' Primary rotation search = direct 3D-reference projection; the staged sweep is kept only as dormant legacy support.
Private Const ROT_USE_STAGED_DISCRETE_SEARCH    As Boolean = False
Private Const ROT_DISCRETE_SUCCESS_TOL_DEG      As Double = 3#
Private Const ROT_MIN_VISIBLE_EDGE_LEN          As Double = 0.0005   ' meters on sheet
Private Const ROT_LOG_EDGE_CANDIDATES           As Boolean = True

' Secondary / fallback rotation paths
Private Const ROT_USE_VISIBLE_LINEAR_EDGE_ALIGN As Boolean = True
Private Const ROT_SNAP_TOL_DEG                  As Double = 3#
Private Const ORIENT_SNAP_HORIZONTAL_BONUS      As Double = 25000000#
Private Const ORIENT_SNAP_VERTICAL_BONUS        As Double = 15000000#
Private Const SNAP_REF_USE_VISIBLE_EDGE_FIRST   As Boolean = False
Private Const ROT_EDGE_CONFIRM_TOL_DEG          As Double = 0.5
Private Const ANGLE_EDGE_FAMILY_TOL_DEG         As Double = 1#
Private Const ANGLE_EDGE_FAMILY_FINAL_TOL_DEG   As Double = 0.5
Private Const ANGLE_EDGE_MIN_PROJECTED_LEN      As Double = 0.0005
Private Const ANGLE_EDGE_FAMILY_TOP_LOG_COUNT   As Long = 3

Private Const PLATE_PERP_DOT_TOL                 As Double = 0.001
Private Const PLATE_POINT_TOL                    As Double = 0.000001
Private Const PLATE_FACE_NORMAL_EPS              As Double = 0.0000001
Private Const PLATE_CUSTOM_VIEW_PENALTY          As Double = 2500#
Private Const PLATE_CUSTOM_VIEW_STRICT_PENALTY   As Double = 6000#


' Final fallback = bbox sweep
Private Const ROT_SWEEP_COARSE_STEP_DEG As Double = 10#
Private Const ROT_SWEEP_FINE_STEP_DEG   As Double = 1#

' Category-based orientation engine
Private Const CAT_LOG_ANALYSIS                As Boolean = True
Private Const CAT_FAMILY_ANGLE_TOL_DEG        As Double = 10#
Private Const CAT_MIN_LINE_LEN_MODEL          As Double = 0.000001
Private Const CAT_POINT_KEY_DECIMALS          As Long = 6
Private Const CAT_PLATE_THIN_RATIO_MAX        As Double = 0.16
Private Const CAT_CURVED_EDGE_RATIO_TRIGGER   As Double = 0.6
Private Const CAT_REF_SUCCESS_TOL_DEG         As Double = 1#

Private Const CAT_NAME_LINEAR_STRUCTURAL      As String = "LINEAR_STRUCTURAL"
Private Const CAT_NAME_CURVED_STRUCTURAL      As String = "CURVED_STRUCTURAL"
Private Const CAT_NAME_SHEET_PLATE            As String = "SHEET_PLATE"
Private Const CAT_NAME_ROUND_STOCK            As String = "ROUND_STOCK"
Private Const CAT_NAME_IRREGULAR              As String = "IRREGULAR"
Private Const CAT_SUBTYPE_UNKNOWN             As String = "UNKNOWN"
Private Const CAT_SUBTYPE_CHANNEL             As String = "CHANNEL"
Private Const CAT_SUBTYPE_ANGLE               As String = "ANGLE"
Private Const CAT_SUBTYPE_PLATE               As String = "PLATE"
Private Const CAT_SUBTYPE_GUSSET              As String = "GUSSET"
Private Const CAT_SUBTYPE_RECT_MEMBER         As String = "RECT_MEMBER"
Private Const CAT_SUBTYPE_ROUND               As String = "ROUND"
Private Const CAT_LINEAR_PROFILE_RATIO_MIN    As Double = 0.05
Private Const CAT_LINEAR_PROFILE_ABS_MIN      As Double = 0.001
Private Const CAT_ANGLE_PROFILE_RATIO_MIN     As Double = 0.018
Private Const CAT_CHANNEL_PROFILE_RATIO_MIN   As Double = 0.025
Private Const CAT_SECTION_PROFILE_ABS_MIN     As Double = 0.0015
Private Const CAT_PLATE_FACE_RATIO_MIN        As Double = 0.08
Private Const CAT_PLATE_FACE_ABS_MIN          As Double = 0.001
Private Const CAT_MAJOR_HORIZONTAL_TOL_DEG    As Double = 3#
Private Const CAT_OUTLINE_HORIZONTAL_RATIO_MIN As Double = 0.9
Private Const CAT_REJECT_PENALTY              As Double = 50000000#


' Notes OFF
Private Const ADD_LABEL_NOTE              As Boolean = False

' BOM balloons (REAL bubbles) - requires BOM / Cut List table in drawing
Private Const ADD_BOM_BALLOON_AFTER_PLACE As Boolean = True
Private Const BALLOON_OFFSET_X            As Double = 0.018
Private Const BALLOON_OFFSET_Y            As Double = 0.01
Private Const BALLOON_RETRY_COUNT         As Long = 3

' NEW: stronger configuration assignment / validation
Private Const VIEW_CFG_RETRY_COUNT        As Long = 3

' Placement tolerance / retries
Private Const POS_TOL             As Double = 0.003
Private Const POS_RETRY           As Long = 3

' Staging (off-sheet) placement for temporary manipulation
Private Const STAGING_OFFSET_X    As Double = 0.35

' Cut-list property candidates
Private Const PROP_ITEMNO_1 As String = "ITEM NO."
Private Const PROP_ITEMNO_2 As String = "Item"
Private Const PROP_ITEMNO_3 As String = "Item Number"
Private Const PROP_ITEMNO_4 As String = "SW-CutListItemNumber"

Private Const PROP_PN_1 As String = "PartNo"
Private Const PROP_PN_2 As String = "Part Number"
Private Const PROP_PN_3 As String = "PART NUMBER"
Private Const PROP_PN_4 As String = "SW-Part Number"

Private Const PROP_QTY_1 As String = "QTY"
Private Const PROP_QTY_2 As String = "Quantity"
Private Const PROP_QTY_3 As String = "SW-Quantity"

' Feature-tree traversal guardrails. SolidWorks feature/subfeature links can
' revisit folders through multiple paths; never recurse without a visited set.
Private Const CUTLIST_MAX_FEATURE_DEPTH As Long = 24
Private Const CUTLIST_MAX_FEATURE_SCAN  As Long = 5000

'==========================
' MODULE GLOBALS
'==========================
Private g_swApp            As SldWorks.SldWorks
Private g_swDrwModel       As SldWorks.ModelDoc2
Private g_swDraw           As SldWorks.DrawingDoc

Private g_refView          As SldWorks.View
Private g_modelPath        As String
Private g_refCfgRaw        As String
Private g_refCfg           As String
Private g_refModel         As SldWorks.ModelDoc2

Private g_openedModel      As Boolean
Private g_openedModelTitle As String
Private g_tempViewNames    As Collection
Private g_keepTempViewNames As Object

' File-based debug logger.
' All run logs are written to the required project folder:
' C:\Public\AutoCopeLogs
Private Const DEBUG_LOG_FOLDER As String = "C:\Public\AutoCopeLogs"
Private Const DEBUG_LOG_PREFIX As String = "AUTO_COPE_DETAIL_GENERATO_"
Private g_debugLogPath      As String
Private g_debugLogFileNo    As Integer
Private g_debugLogEnabled   As Boolean
Private g_debugLogInitDone  As Boolean
Private g_debugLogFailed    As Boolean

Private Const PI As Double = 3.14159265358979

'====================================================================================
' ENTRY POINT
'====================================================================================
Public Sub main()
    On Error GoTo EH

    Set g_swApp = Application.SldWorks
    Set g_swDrwModel = g_swApp.ActiveDoc
    Set g_tempViewNames = New Collection
    Set g_keepTempViewNames = CreateObject("Scripting.Dictionary")

    InitDebugLog
    LogDivider "MACRO START"
    LogInfo "START | " & MACRO_TITLE & " | revision=" & MACRO_REVISION
    LogStage "ENTRY", "main entry reached; dispatcher=main; legacyPath=False"
    LogKeyValue "ENTRY", "solidWorksAppAvailable", BoolWord(Not g_swApp Is Nothing)
    LogKeyValue "ENTRY", "activeDocAvailable", BoolWord(Not g_swDrwModel Is Nothing)

    '--- guards ----------------------------------------------------------------------
    If g_swDrwModel Is Nothing Then
        LogError "ENTRY | no active document; terminating before drawing validation"
        MsgBox "No active document. Open a drawing and try again.", vbExclamation
        GoTo TidyUp
    End If

    LogKeyValue "ENTRY", "activeDocTitle", ModelTitleSafe(g_swDrwModel)
    LogKeyValue "ENTRY", "activeDocPath", ModelPathSafe(g_swDrwModel)
    LogKeyValue "ENTRY", "activeDocType", DocumentTypeNameSafe(g_swDrwModel.GetType)
    LogKeyValue "ENTRY", "activeDocSaved", BoolWord(IsModelSavedSafe(g_swDrwModel))

    If g_swDrwModel.GetType <> swDocumentTypes_e.swDocDRAWING Then
        LogError "ENTRY | active document type is not drawing | type=" & DocumentTypeNameSafe(g_swDrwModel.GetType)
        MsgBox "Active document is not a drawing (.SLDDRW).", vbExclamation
        GoTo TidyUp
    End If

    Set g_swDraw = g_swDrwModel
    LogStage "DRAWING", "active drawing document accepted"

    '--- reference view --------------------------------------------------------------
    LogStage "REFERENCE_VIEW", "resolving selected or first model drawing view"
    Set g_refView = GetReferenceDrawingView(g_swDraw, g_swDrwModel)
    If g_refView Is Nothing Then
        LogError "REFERENCE_VIEW | no source model view found on active drawing"
        MsgBox "No model drawing view found." & vbCrLf & _
               "Select a drawing view on the source sheet, then run again.", vbExclamation
        GoTo TidyUp
    End If

    g_modelPath = SafeStr(g_refView.GetReferencedModelName)
    g_refCfgRaw = Trim$(SafeStr(g_refView.ReferencedConfiguration))

    LogInfo "Ref view=" & ViewGetNameSafe(g_refView) & _
            " | model=" & g_modelPath & _
            " | rawCfg=" & g_refCfgRaw
    LogKeyValue "REFERENCE_VIEW", "viewName", ViewGetNameSafe(g_refView)
    LogKeyValue "REFERENCE_VIEW", "referencedModelPath", g_modelPath
    LogKeyValue "REFERENCE_VIEW", "rawReferencedConfiguration", g_refCfgRaw

    If Len(g_modelPath) = 0 Then
        LogError "REFERENCE_VIEW | referenced model path is blank"
        MsgBox "Cannot resolve model path from selected view.", vbExclamation
        GoTo TidyUp
    End If

    '--- open referenced part --------------------------------------------------------
    LogStage "MODEL_OPEN", "opening referenced model"
    ' Open without forcing the possibly-bad drawing config token.
    If Not EnsureModelOpen(g_refView, g_modelPath, "", _
                           g_refModel, g_openedModel, g_openedModelTitle) Then
        LogError "MODEL_OPEN | failed to open referenced model | path=" & g_modelPath
        MsgBox "Failed to open referenced model: " & g_modelPath, vbExclamation
        GoTo TidyUp
    End If

    If g_refModel.GetType <> swDocumentTypes_e.swDocPART Then
        LogError "MODEL_OPEN | referenced model is not a part | title=" & ModelTitleSafe(g_refModel) & _
                 " | type=" & DocumentTypeNameSafe(g_refModel.GetType)
        MsgBox "Referenced model is not a Part. Weldment part required.", vbExclamation
        GoTo TidyUp
    End If
    LogKeyValue "MODEL_OPEN", "openedModelTitle", ModelTitleSafe(g_refModel)
    LogKeyValue "MODEL_OPEN", "openedModelPath", ModelPathSafe(g_refModel)
    LogKeyValue "MODEL_OPEN", "openedByMacro", BoolWord(g_openedModel)

    '--- resolve source configuration robustly ---------------------------------------
    LogStage "CONFIG", "resolving source configuration"
    g_refCfg = ResolvePreferredReferencedConfiguration(g_refModel, g_refCfgRaw)

    If Len(g_refCfg) = 0 Then
        LogError "CONFIG | failed to resolve valid configuration | raw=" & g_refCfgRaw
        MsgBox "Could not resolve a valid model configuration from the source view." & vbCrLf & _
               "Raw source configuration: " & g_refCfgRaw, vbExclamation
        GoTo TidyUp
    End If

    LogInfo "Resolved configuration | raw=" & g_refCfgRaw & " | final=" & g_refCfg
    LogKeyValue "CONFIG", "resolvedConfiguration", g_refCfg

    ' Activate the resolved config in the model before making views.
    If Not ActivateModelConfigurationSafe(g_refModel, g_refCfg) Then
        LogWarn "Proceeding even though ActivateModelConfigurationSafe returned False | cfg=" & g_refCfg
    End If
    LogStage "TEMP_VIEW_CLEANUP", "deferred stale macro-owned model view cleanup until output sheet reset is complete"

    '--- prepare output sheet --------------------------------------------------------
    LogStage "SHEET", "preparing output sheet"
    Dim templateSheet As SldWorks.Sheet
    Set templateSheet = g_swDraw.GetCurrentSheet
    If Not templateSheet Is Nothing Then
        LogKeyValue "SHEET", "templateSheetName", SafeStr(templateSheet.GetName)
        LogKeyValue "SHEET", "templatePath", SafeStr(templateSheet.GetTemplateName)
    End If

    If RESET_OUTPUT_SHEET Then
        If SheetExists(g_swDraw, OUTPUT_SHEET_NAME) Then
            On Error Resume Next
            g_swDraw.DeleteSheet OUTPUT_SHEET_NAME
            On Error GoTo 0
        End If
        EnsureSheetExistsCopyProps g_swDraw, templateSheet, OUTPUT_SHEET_NAME
    Else
        If Not SheetExists(g_swDraw, OUTPUT_SHEET_NAME) Then
            EnsureSheetExistsCopyProps g_swDraw, templateSheet, OUTPUT_SHEET_NAME
        End If
    End If

    g_swDraw.ActivateSheet OUTPUT_SHEET_NAME
    ForceSheetContext
    LogKeyValue "SHEET", "activeOutputSheet", OUTPUT_SHEET_NAME

    LogStage "TEMP_VIEW_CLEANUP", "removing stale macro-owned model views after output sheet reset"
    CleanupMacroTempModelViews g_refModel, "startup"

    '--- exact sheet bounds ----------------------------------------------------------
    LogStage "SHEET_BOUNDS", "reading exact sheet outline"
    Dim sx0 As Double, sy0 As Double, sx1 As Double, sy1 As Double
    If Not GetSheetBoundsExact(g_swDraw, sx0, sy0, sx1, sy1) Then
        LogError "SHEET_BOUNDS | failed to read exact sheet bounds from sheet view"
        MsgBox "Failed to read exact sheet bounds from sheet view outline.", vbExclamation
        GoTo TidyUp
    End If

    Dim sheetW As Double, sheetH As Double
    sheetW = sx1 - sx0
    sheetH = sy1 - sy0

    LogInfo "SheetBounds | xmin=" & Fmt(sx0) & " ymin=" & Fmt(sy0) & _
            " xmax=" & Fmt(sx1) & " ymax=" & Fmt(sy1) & _
            " | W=" & Fmt(sheetW) & " H=" & Fmt(sheetH)

    '--- permitted placement rectangle -----------------------------------------------
    Dim px0 As Double, py0 As Double, px1 As Double, py1 As Double
    px0 = sx0 + MARGIN_LEFT
    px1 = sx1 - MARGIN_RIGHT
    py0 = sy0 + MARGIN_BOTTOM
    py1 = sy1 - MARGIN_TOP

    If px1 <= px0 Or py1 <= py0 Then
        LogError "PLACEMENT_ZONE | invalid permitted rectangle | px0=" & Fmt(px0) & " px1=" & Fmt(px1) & _
                 " py0=" & Fmt(py0) & " py1=" & Fmt(py1)
        MsgBox "Invalid permitted placement rectangle (margins too large).", vbExclamation
        GoTo TidyUp
    End If

    LogInfo "PermRect | x=[" & Fmt(px0) & "," & Fmt(px1) & "] y=[" & Fmt(py0) & "," & Fmt(py1) & "]"

    '--- cut-list items --------------------------------------------------------------
    LogStage "CUTLIST", "starting cut-list/body discovery"
    Dim items As Collection
    Set items = GetCutListItems(g_refModel)

    If items Is Nothing Or items.Count = 0 Then
        LogError "CUTLIST | no cut-list or fallback solid-body items found"
        MsgBox "No Cut List items found. Update weldment cut list and try again.", vbExclamation
        GoTo TidyUp
    End If
    LogInfo "Cut-list items = " & items.Count
    LogStage "CUTLIST", "DONE | cut-list/body discovery | itemCount=" & CStr(items.Count)

    '--- sort by Part Number ascending -----------------------------------------------
    LogStage "SORT", "sorting discovered items by PartNo and ItemNo"
    Set items = SortCutListItemsByPartNo(items)
    LogInfo "Sort | order = ASC PartNo"

    '--- target scale (once) ---------------------------------------------------------
    LogStage "SCALE", "resolving target drawing view scale"
    Dim targetScale As Double
    targetScale = ResolveTargetScale(g_refView)
    LogInfo "Target scale = " & Fmt(targetScale)
    LogKeyValue "SCALE", "targetScaleDecimal", Fmt(targetScale)

    '--- staging coords --------------------------------------------------------------
    Dim stagingX As Double, stagingY As Double
    stagingX = sx1 + STAGING_OFFSET_X
    stagingY = sy0 + (sheetH / 2#)
    LogInfo "Staging | x=" & Fmt(stagingX) & " y=" & Fmt(stagingY)

    '--- placement cursors -----------------------------------------------------------
    Dim curLeft As Double, curTop As Double
    Dim rowUsedH As Double

    ' Manual parking strip (for user manual placement later)
    Dim parkX0 As Double, parkX1 As Double
    parkX0 = sx0 + MARGIN_LEFT
    parkX1 = parkX0 + PARK_STRIP_WRAP_WIDTH

    If MANUAL_PARK_STRIP_MODE Then
        curLeft = parkX0
        curTop = sy0 - PARK_STRIP_OFFSET_BELOW_SHEET
        rowUsedH = 0#
        LogInfo "MANUAL PARK START | x0=" & Fmt(parkX0) & " x1=" & Fmt(parkX1) & _
                " | curLeft=" & Fmt(curLeft) & " curTop=" & Fmt(curTop)
    Else
        curLeft = px0
        curTop = py1
        rowUsedH = 0#
        LogInfo "FAST STACK START | curLeft=" & Fmt(curLeft) & " curTop=" & Fmt(curTop)
    End If

    '--- processing ------------------------------------------------------------------
    Dim i As Long
    Dim okCt As Long, failCt As Long, overflowCt As Long
    Dim placedViews As New Collection
    Dim placedItems As New Collection
    okCt = 0: failCt = 0: overflowCt = 0

    For i = 1 To items.Count
        Dim item As Object
        Set item = items(i)

        LogDivider "ITEM " & CStr(i)
        LogInfo "-- Item[" & i & "] | itemNo=" & SafeStr(item("ItemNo")) & _
                " | pn=" & SafeStr(item("PartNo")) & " | qty=" & SafeStr(item("Qty"))
        LogKeyValue "ITEM", "index", CStr(i)
        LogKeyValue "ITEM", "itemNo", SafeStr(item("ItemNo"))
        LogKeyValue "ITEM", "partNo", SafeStr(item("PartNo"))
        LogKeyValue "ITEM", "qty", SafeStr(item("Qty"))
        LogKeyValue "ITEM", "bbox", Fmt(SafeCDbl(item("dx"))) & " x " & Fmt(SafeCDbl(item("dy"))) & " x " & Fmt(SafeCDbl(item("dz")))

        ForceSheetContext

        ' Keep source model on intended config before creating each view.
        Call ActivateModelConfigurationSafe(g_refModel, g_refCfg)

        ' 1) Create best prepared base view from standard/custom candidate competition.
        '    The 3D body analysis defines fabrication intent, but normal production
        '    placement uses existing model views rather than generated temp views.
        LogStage "ITEM_ORIENTATION", "START | itemIdx=" & CStr(i) & _
                 " | strategy=standard/custom view scored by 3D body signature | generatedTempViews=" & BoolWord(ALLOW_GENERATED_BODY_FRAME_VIEWS)
        Dim baseViewName As String
        Dim swNewView As SldWorks.View
        If Not CreatePreparedBestViewForItem(item, stagingX, stagingY, i, swNewView, baseViewName) Then
            LogError "CreatePreparedBestViewForItem failed | itemIdx=" & i
            failCt = failCt + 1
            GoTo NextItem
        End If

        ' 4) The accepted candidate has already been rotated by direct 3D-reference
        '    projection. This flag remains as a guarded legacy escape hatch only.
        If AUTO_ROTATE_TO_HORIZONTAL Then
            Call RotateViewToBestHorizontal(g_swDrwModel, swNewView, item, "Place itemIdx=" & CStr(i))
        End If

        ' 5) Apply scale
        LogStage "ITEM_SCALE", "applying target scale | itemIdx=" & CStr(i) & " | scale=" & Fmt(targetScale)
        If Not ApplyViewScale(swNewView, targetScale) Then
            LogError "ApplyViewScale failed | itemIdx=" & i
            SafeDeleteView g_swDrwModel, swNewView
            failCt = failCt + 1
            GoTo NextItem
        End If

        ' Re-assert config after scale / rebuild-sensitive changes
        If Not EnsureViewUsesConfiguration(swNewView, g_refCfg, "post-scale itemIdx=" & CStr(i)) Then
            LogWarn "EnsureViewUsesConfiguration failed after scale | itemIdx=" & i & _
                    " | continuing but bubble may not resolve"
        End If

        g_swDrwModel.EditRebuild3

        ' 6) Measure view at staging
        Dim vW As Double, vH As Double
        If Not GetViewWH(swNewView, vW, vH) Then
            LogError "GetViewWH failed | itemIdx=" & i
            SafeDeleteView g_swDrwModel, swNewView
            failCt = failCt + 1
            GoTo NextItem
        End If

        If vW <= 0# Or vH <= 0# Then
            LogError "Invalid view size | itemIdx=" & i & " | w=" & Fmt(vW) & " h=" & Fmt(vH)
            SafeDeleteView g_swDrwModel, swNewView
            failCt = failCt + 1
            GoTo NextItem
        End If
        LogKeyValue "ITEM_VIEW", "measuredSize", Fmt(vW) & " x " & Fmt(vH)

        Dim itemBlockH As Double
        itemBlockH = NOTE_BAND_H + NOTE_TO_VIEW_GAP + vH

        ' 7) wrap / parking logic
        If MANUAL_PARK_STRIP_MODE Then
            If PARK_STRIP_ALLOW_WRAP Then
                If (curLeft + vW) > parkX1 Then
                    LogWarn "PLACEMENT | park strip wrap | itemIdx=" & CStr(i) & _
                            " | curLeft=" & Fmt(curLeft) & " | vW=" & Fmt(vW) & " | parkX1=" & Fmt(parkX1)
                    curLeft = parkX0
                    curTop = curTop - rowUsedH - PARK_STRIP_ROW_GAP
                    rowUsedH = 0#
                    LogInfo "   park wrap row | new curLeft=" & Fmt(curLeft) & " curTop=" & Fmt(curTop)
                End If
            End If
        Else
            If STACK_WRAP_ROWS Then
                If (curLeft + vW) > px1 Then
                    LogWarn "PLACEMENT | sheet row wrap | itemIdx=" & CStr(i) & _
                            " | curLeft=" & Fmt(curLeft) & " | vW=" & Fmt(vW) & " | px1=" & Fmt(px1)
                    curLeft = px0
                    curTop = curTop - rowUsedH - STACK_GAP_Y
                    rowUsedH = 0#
                    LogInfo "   wrap row | new curLeft=" & Fmt(curLeft) & " curTop=" & Fmt(curTop)
                End If
            End If

            ' 8) bottom overflow (only for on-sheet mode)
            Dim nextBottom As Double
            nextBottom = curTop - itemBlockH
            If nextBottom < py0 Then
                overflowCt = overflowCt + 1
                LogWarn "   placement below permitted area | itemIdx=" & i & _
                        " | nextBottom=" & Fmt(nextBottom) & " < py0=" & Fmt(py0)

                If Not ALLOW_VERTICAL_OVERFLOW Then
                    LogError "Stopping due to overflow policy."
                    SafeDeleteView g_swDrwModel, swNewView
                    failCt = failCt + 1
                    Exit For
                End If
            End If
        End If

        ' 9) compute target positions
        Dim slotCX As Double, viewY As Double
        slotCX = curLeft + (vW / 2#)
        viewY = curTop - NOTE_BAND_H - NOTE_TO_VIEW_GAP - (vH / 2#)

        ' 10) final position
        LogStage "ITEM_PLACEMENT", "placing view | itemIdx=" & CStr(i) & _
                 " | targetCenter=(" & Fmt(slotCX) & "," & Fmt(viewY) & ")"
        If Not ForceViewPosition(swNewView, slotCX, viewY, POS_TOL, POS_RETRY) Then
            LogError "ForceViewPosition failed | itemIdx=" & i
            SafeDeleteView g_swDrwModel, swNewView
            failCt = failCt + 1
            GoTo NextItem
        End If

        ' Final config assertion before accepting placed view
        If Not EnsureViewUsesConfiguration(swNewView, g_refCfg, "final-place itemIdx=" & CStr(i)) Then
            LogWarn "Final configuration validation failed | itemIdx=" & i & _
                    " | cfg=" & g_refCfg & " | current=" & SafeStr(swNewView.ReferencedConfiguration)
        End If

        ' 11) rename
        On Error Resume Next
        swNewView.SetName2 "COPE_" & Format$(i, "0000")
        On Error GoTo 0

        placedViews.Add swNewView
        placedItems.Add item

        ' 12) advance cursor
        curLeft = curLeft + vW + STACK_GAP_X
        If itemBlockH > rowUsedH Then rowUsedH = itemBlockH

        okCt = okCt + 1

        LogInfo "   placed OK | view=" & ViewGetNameSafe(swNewView) & _
                " | base=" & baseViewName & _
                " | cfg=" & SafeStr(swNewView.ReferencedConfiguration) & _
                " | size=" & Fmt(vW) & "x" & Fmt(vH) & _
                " | pos=(" & Fmt(slotCX) & "," & Fmt(viewY) & ")"
        LogStage "ITEM_COMPLETE", "DONE | placed item | itemIdx=" & CStr(i) & _
                 " | view=" & ViewGetNameSafe(swNewView)

NextItem:
        ForceSheetContext
        DoEvents
    Next i

    ' Final rebuild once
    g_swDrwModel.EditRebuild3
    ForceSheetContext

    ' Pre-balloon config normalization pass
    If placedViews.Count > 0 Then
        Call NormalizePlacedViewConfigurations(placedViews, g_refCfg)
    End If

    ' Post-pass: insert BOM balloons per view
    If ADD_BOM_BALLOON_AFTER_PLACE Then
        LogStage "BALLOONS", "starting BOM balloon insertion | viewCount=" & CStr(placedViews.Count)
        Dim balloonCt As Long
        balloonCt = AddBOMBalloonsForPlacedViews(placedViews, placedItems)
        LogInfo "BOM balloons inserted = " & CStr(balloonCt) & " / " & CStr(placedViews.Count)
    End If

    LogStage "SAVE", "drawing save not performed by this macro iteration; currentDrawingPath=" & ModelPathSafe(g_swDrwModel)

    ' Summary
    If failCt > 0 Or overflowCt > 0 Then
        LogWarn "DONE WITH WARNINGS | OK=" & okCt & " FAIL=" & failCt & " OVERFLOW=" & overflowCt
        MsgBox "Completed (FAST STACK)." & vbCrLf & _
               "OK = " & okCt & vbCrLf & _
               "FAIL = " & failCt & vbCrLf & _
               "Overflow placements = " & overflowCt & vbCrLf & _
               "See Immediate Window (Ctrl+G) for details.", vbExclamation
    Else
        LogInfo "DONE | OK=" & okCt & " FAIL=" & failCt & " OVERFLOW=" & overflowCt
    End If

    LogStage "MACRO_END", "normal completion reached | OK=" & CStr(okCt) & _
             " | FAIL=" & CStr(failCt) & " | OVERFLOW=" & CStr(overflowCt)
    LogDivider "MACRO END"

TidyUp:
    LogStage "TIDYUP", "starting cleanup"
    If Not g_refModel Is Nothing Then
        CleanupMacroTempModelViews g_refModel, "tidyup_keep_accepted"
    End If

    If g_openedModel And Len(g_openedModelTitle) > 0 Then
        On Error Resume Next
        If ProtectedTempModelViewCount() > 0 Then
            LogWarn "TIDYUP | leaving model open because accepted drawing views still depend on temporary named views | title=" & _
                    g_openedModelTitle & " | keptTempViews=" & CStr(ProtectedTempModelViewCount())
        Else
            LogStage "TIDYUP", "closing model opened by macro | title=" & g_openedModelTitle
            g_swApp.CloseDoc g_openedModelTitle
        End If
        On Error GoTo 0
    End If

    LogStage "TIDYUP", "clearing module globals"
    Set g_refModel = Nothing
    Set g_refView = Nothing
    Set g_tempViewNames = Nothing
    Set g_keepTempViewNames = Nothing
    Set g_swDraw = Nothing
    Set g_swDrwModel = Nothing
    Set g_swApp = Nothing
    LogStage "TIDYUP", "cleanup complete"
    CloseDebugLog
    Exit Sub

EH:
    LogError "EXCEPTION | " & Err.Number & " | " & Err.Description
    LogError "FATAL | main terminated by top-level error | errNumber=" & CStr(Err.Number) & _
             " | errDescription=" & Err.Description
    MsgBox "Macro error: " & Err.Description, vbExclamation
    Resume TidyUp
End Sub

'====================================================================================
' SCALE RESOLUTION
'====================================================================================
Private Function ResolveTargetScale(ByVal refV As SldWorks.View) As Double
    On Error GoTo EH

    Dim s As Double
    s = DETAIL_SCALE_DECIMAL

    If s <= 0# Then
        s = 1#
        On Error Resume Next
        s = refV.ScaleDecimal
        On Error GoTo 0
        If s <= 0# Then s = 1#
    End If

    If MIN_SCALE_DECIMAL > 0# Then If s < MIN_SCALE_DECIMAL Then s = MIN_SCALE_DECIMAL
    If MAX_SCALE_DECIMAL > 0# Then If s > MAX_SCALE_DECIMAL Then s = MAX_SCALE_DECIMAL

    If s <= 0# Then s = 1#
    ResolveTargetScale = s
    Exit Function

EH:
    ResolveTargetScale = 1#
End Function

Private Function BuildBodyOrientationCandidates(ByVal item As Object) As Collection
    On Error GoTo EH

    Dim outCol As New Collection
    Set BuildBodyOrientationCandidates = outCol

    If item Is Nothing Then Exit Function
    Call EnsureItemCategoryAnalysis(item)

    Dim swBody As SldWorks.Body2
    Set swBody = Nothing
    On Error Resume Next
    Set swBody = item("RepBody_Model")
    On Error GoTo EH
    If swBody Is Nothing Then Exit Function

    Dim axisX As Double, axisY As Double, axisZ As Double
    If Not GetItemPrimaryAxis(item, axisX, axisY, axisZ) Then
        axisX = 1#: axisY = 0#: axisZ = 0#
    End If

    Dim seen As Object
    Set seen = CreateObject("Scripting.Dictionary")

    Dim catName As String
    catName = UCase$(SafeStr(item("BodyCategory")))

    Dim faceIdx As Long
    For faceIdx = 1 To 6
        Dim nx As Double, ny As Double, nz As Double
        Dim faceArea As Double
        nx = SafeCDbl(item("Sig_FaceDir" & CStr(faceIdx) & "X"))
        ny = SafeCDbl(item("Sig_FaceDir" & CStr(faceIdx) & "Y"))
        nz = SafeCDbl(item("Sig_FaceDir" & CStr(faceIdx) & "Z"))
        faceArea = SafeCDbl(item("Sig_FaceDir" & CStr(faceIdx) & "Area"))

        If faceArea > 0# And NormalizeVector3(nx, ny, nz) Then
            Dim dotLen As Double
            dotLen = Abs(Dot3(axisX, axisY, axisZ, nx, ny, nz))

            If catName = UCase$(CAT_NAME_SHEET_PLATE) Or dotLen <= BODY_ORIENT_FACE_DOT_LEN_MAX Then
                Dim px As Double, py As Double, pz As Double
                px = axisX: py = axisY: pz = axisZ
                If Not ProjectVectorOffAxis(px, py, pz, nx, ny, nz) Then
                    If Not GetBestPerpendicularWorldAxis(nx, ny, nz, px, py, pz) Then
                        px = 1#: py = 0#: pz = 0#
                    End If
                End If

                Dim baseScore As Double
                baseScore = (faceArea * 100000000#) + (SafeCDbl(item("Sig_PrimaryDirLen")) * 1000#) - (dotLen * 1000000#)
                If catName = UCase$(CAT_NAME_SHEET_PLATE) Then baseScore = baseScore + 5000000#
                If faceIdx = 1 Then baseScore = baseScore + 2500000#

                AddBodyOrientationCandidate outCol, seen, item, _
                                            "FACE_NORMAL_" & CStr(faceIdx) & "_A", _
                                            px, py, pz, nx, ny, nz, baseScore, _
                                            "planar face family rank=" & CStr(faceIdx) & " area=" & Fmt(faceArea) & " dotLength=" & Fmt(dotLen)

                AddBodyOrientationCandidate outCol, seen, item, _
                                            "FACE_NORMAL_" & CStr(faceIdx) & "_B", _
                                            px, py, pz, -nx, -ny, -nz, baseScore - 1#, _
                                            "opposite planar face family rank=" & CStr(faceIdx) & " area=" & Fmt(faceArea) & " dotLength=" & Fmt(dotLen)
            End If
        End If
    Next faceIdx

    Dim fx As Double, fy As Double, fz As Double
    If GetBestPerpendicularWorldAxis(axisX, axisY, axisZ, fx, fy, fz) Then
        AddBodyOrientationCandidate outCol, seen, item, _
                                    "AXIS_PERP_WORLD_A", _
                                    axisX, axisY, axisZ, fx, fy, fz, 1000#, _
                                    "fallback perpendicular world axis"
        AddBodyOrientationCandidate outCol, seen, item, _
                                    "AXIS_PERP_WORLD_B", _
                                    axisX, axisY, axisZ, -fx, -fy, -fz, 999#, _
                                    "fallback opposite perpendicular world axis"
    End If

    Dim sx As Double, sy As Double, sz As Double
    sx = SafeCDbl(item("Sig_SecondaryDirX"))
    sy = SafeCDbl(item("Sig_SecondaryDirY"))
    sz = SafeCDbl(item("Sig_SecondaryDirZ"))
    If NormalizeVector3(sx, sy, sz) Then
        If Abs(Dot3(axisX, axisY, axisZ, sx, sy, sz)) <= 0.8 Then
            AddBodyOrientationCandidate outCol, seen, item, _
                                        "AXIS_PERP_SECONDARY", _
                                        axisX, axisY, axisZ, sx, sy, sz, 800#, _
                                        "fallback secondary direction"
        End If
    End If

    LogInfo "BodyOrientation | built candidates | itemNo=" & SafeStr(item("ItemNo")) & _
            " | pn=" & SafeStr(item("PartNo")) & _
            " | cat=" & SafeStr(item("BodyCategory")) & _
            " | count=" & CStr(outCol.Count)
    Exit Function

EH:
    LogError "BuildBodyOrientationCandidates exception | " & Err.Number & " | " & Err.Description & _
             " | itemNo=" & SafeStr(item("ItemNo"))
    Set BuildBodyOrientationCandidates = New Collection
End Function

Private Sub AddBodyOrientationCandidate(ByVal outCol As Collection, _
                                        ByVal seen As Object, _
                                        ByVal item As Object, _
                                        ByVal kind As String, _
                                        ByVal axisX As Double, ByVal axisY As Double, ByVal axisZ As Double, _
                                        ByVal viewX As Double, ByVal viewY As Double, ByVal viewZ As Double, _
                                        ByVal baseScore As Double, _
                                        ByVal reason As String)
    On Error GoTo EH

    If outCol Is Nothing Then Exit Sub
    If seen Is Nothing Then Exit Sub

    If Not NormalizeVector3(axisX, axisY, axisZ) Then Exit Sub
    If Not NormalizeVector3(viewX, viewY, viewZ) Then Exit Sub

    If Not ProjectVectorOffAxis(axisX, axisY, axisZ, viewX, viewY, viewZ) Then Exit Sub

    Dim upX As Double, upY As Double, upZ As Double
    Cross3 viewX, viewY, viewZ, axisX, axisY, axisZ, upX, upY, upZ
    If Not NormalizeVector3(upX, upY, upZ) Then Exit Sub

    ' Rebuild X from Y/Z so the final triad is cleanly orthonormal.
    Cross3 upX, upY, upZ, viewX, viewY, viewZ, axisX, axisY, axisZ
    If Not NormalizeVector3(axisX, axisY, axisZ) Then Exit Sub

    Dim key As String
    key = MakeOrientationCandidateKey(axisX, axisY, axisZ, viewX, viewY, viewZ)
    If Len(key) = 0 Then Exit Sub
    If seen.Exists(key) Then Exit Sub
    seen.Add key, True

    Dim xSpan As Double, ySpan As Double, zSpan As Double
    Call GetBodyFrameExtentMetrics(item, axisX, axisY, axisZ, upX, upY, upZ, viewX, viewY, viewZ, xSpan, ySpan, zSpan)

    Dim cand As Object
    Set cand = CreateObject("Scripting.Dictionary")
    cand("Kind") = kind
    cand("Score") = baseScore + (ySpan * 100000#) - (zSpan * 1000#)
    cand("Reason") = reason & " | modelSpanXYZ=" & Fmt(xSpan) & "/" & Fmt(ySpan) & "/" & Fmt(zSpan)
    cand("Xx") = axisX: cand("Xy") = axisY: cand("Xz") = axisZ
    cand("Yx") = upX: cand("Yy") = upY: cand("Yz") = upZ
    cand("Zx") = viewX: cand("Zy") = viewY: cand("Zz") = viewZ
    cand("ModelXSpan") = xSpan
    cand("ModelYSpan") = ySpan
    cand("ModelZSpan") = zSpan

    InsertCandidateSorted outCol, cand
    If outCol.Count > BODY_ORIENT_MAX_CANDIDATES Then outCol.Remove outCol.Count
    Exit Sub

EH:
    LogWarn "AddBodyOrientationCandidate exception | " & Err.Number & " | " & Err.Description & _
            " | kind=" & kind & " | itemNo=" & SafeStr(item("ItemNo"))
End Sub

Private Sub InsertCandidateSorted(ByVal outCol As Collection, ByVal cand As Object)
    On Error GoTo EH

    If outCol Is Nothing Then Exit Sub
    If cand Is Nothing Then Exit Sub

    Dim i As Long
    For i = 1 To outCol.Count
        If SafeCDbl(cand("Score")) > SafeCDbl(outCol(i)("Score")) Then
            outCol.Add cand, , i
            Exit Sub
        End If
    Next i

    outCol.Add cand
    Exit Sub

EH:
    LogWarn "InsertCandidateSorted exception | " & Err.Number & " | " & Err.Description
End Sub

Private Function MakeOrientationCandidateKey(ByVal xx As Double, ByVal xy As Double, ByVal xz As Double, _
                                             ByVal zx As Double, ByVal zy As Double, ByVal zz As Double) As String
    On Error GoTo EH
    MakeOrientationCandidateKey = Format$(xx, "0.000") & "|" & Format$(xy, "0.000") & "|" & Format$(xz, "0.000") & "|" & _
                                  Format$(zx, "0.000") & "|" & Format$(zy, "0.000") & "|" & Format$(zz, "0.000")
    Exit Function
EH:
    MakeOrientationCandidateKey = ""
End Function

Private Function GetItemPrimaryAxis(ByVal item As Object, _
                                    ByRef axisX As Double, _
                                    ByRef axisY As Double, _
                                    ByRef axisZ As Double) As Boolean
    On Error GoTo EH

    axisX = SafeCDbl(item("Sig_PrimaryDirX"))
    axisY = SafeCDbl(item("Sig_PrimaryDirY"))
    axisZ = SafeCDbl(item("Sig_PrimaryDirZ"))

    If NormalizeVector3(axisX, axisY, axisZ) Then
        GetItemPrimaryAxis = True
        Exit Function
    End If

    Dim dx As Double, dy As Double, dz As Double
    dx = SafeCDbl(item("dx"))
    dy = SafeCDbl(item("dy"))
    dz = SafeCDbl(item("dz"))

    If dx >= dy And dx >= dz Then
        axisX = 1#: axisY = 0#: axisZ = 0#
    ElseIf dy >= dx And dy >= dz Then
        axisX = 0#: axisY = 1#: axisZ = 0#
    Else
        axisX = 0#: axisY = 0#: axisZ = 1#
    End If

    GetItemPrimaryAxis = True
    Exit Function

EH:
    axisX = 1#: axisY = 0#: axisZ = 0#
    GetItemPrimaryAxis = True
End Function

Private Function TryGetBodyFabricationFrame(ByVal item As Object, _
                                            ByRef primaryX As Double, _
                                            ByRef primaryY As Double, _
                                            ByRef primaryZ As Double, _
                                            ByRef secondaryX As Double, _
                                            ByRef secondaryY As Double, _
                                            ByRef secondaryZ As Double, _
                                            ByRef hasSecondary As Boolean, _
                                            ByRef frameReason As String) As Boolean
    On Error GoTo EH

    TryGetBodyFabricationFrame = False
    primaryX = 0#: primaryY = 0#: primaryZ = 0#
    secondaryX = 0#: secondaryY = 0#: secondaryZ = 0#
    hasSecondary = False
    frameReason = ""

    If item Is Nothing Then Exit Function
    Call EnsureItemCategoryAnalysis(item)

    Dim primaryLen As Double
    primaryLen = SafeCDbl(item("Sig_PrimaryDirLen"))

    primaryX = SafeCDbl(item("Sig_PrimaryDirX"))
    primaryY = SafeCDbl(item("Sig_PrimaryDirY"))
    primaryZ = SafeCDbl(item("Sig_PrimaryDirZ"))

    Dim primaryReason As String
    If NormalizeVector3(primaryX, primaryY, primaryZ) And primaryLen > 0# Then
        primaryReason = "strongest straight-edge direction family | len=" & Fmt(primaryLen)
    ElseIf GetItemPrimaryAxis(item, primaryX, primaryY, primaryZ) Then
        primaryReason = "fallback item primary axis/bbox axis"
    Else
        frameReason = "no usable primary length axis"
        Exit Function
    End If

    Dim secondaryReason As String
    hasSecondary = TryChooseFabricationSecondaryAxis(item, primaryX, primaryY, primaryZ, _
                                                     secondaryX, secondaryY, secondaryZ, secondaryReason)

    Dim secondaryFrameReason As String
    If hasSecondary Then
        secondaryFrameReason = secondaryReason
    Else
        secondaryFrameReason = "not available"
    End If

    frameReason = "primary=" & primaryReason & _
                  " | secondary=" & secondaryFrameReason

    LogInfo "RotationBasis3D | itemNo=" & SafeStr(item("ItemNo")) & _
            " | pn=" & SafeStr(item("PartNo")) & _
            " | cat=" & SafeStr(item("BodyCategory")) & _
            " | subtype=" & SafeStr(item("BodySubtype")) & _
            " | primary=(" & Fmt(primaryX) & "," & Fmt(primaryY) & "," & Fmt(primaryZ) & ")" & _
            " | secondary=(" & Fmt(secondaryX) & "," & Fmt(secondaryY) & "," & Fmt(secondaryZ) & ")" & _
            " | hasSecondary=" & BoolWord(hasSecondary) & _
            " | reason=" & frameReason

    TryGetBodyFabricationFrame = True
    Exit Function

EH:
    frameReason = "TryGetBodyFabricationFrame exception | " & Err.Number & " | " & Err.Description
    LogWarn frameReason & " | itemNo=" & SafeStr(item("ItemNo"))
    TryGetBodyFabricationFrame = False
End Function

Private Function TryChooseFabricationSecondaryAxis(ByVal item As Object, _
                                                   ByVal primaryX As Double, _
                                                   ByVal primaryY As Double, _
                                                   ByVal primaryZ As Double, _
                                                   ByRef secondaryX As Double, _
                                                   ByRef secondaryY As Double, _
                                                   ByRef secondaryZ As Double, _
                                                   ByRef secondaryReason As String) As Boolean
    On Error GoTo EH

    TryChooseFabricationSecondaryAxis = False
    secondaryX = 0#: secondaryY = 0#: secondaryZ = 0#
    secondaryReason = ""

    If item Is Nothing Then Exit Function
    If Not NormalizeVector3(primaryX, primaryY, primaryZ) Then Exit Function

    Dim bestScore As Double
    Dim bestIdx As Long
    Dim bestDot As Double
    bestScore = -1#
    bestIdx = 0
    bestDot = 1#

    Dim faceIdx As Long
    For faceIdx = 1 To 6
        Dim nx As Double, ny As Double, nz As Double
        Dim faceArea As Double
        nx = SafeCDbl(item("Sig_FaceDir" & CStr(faceIdx) & "X"))
        ny = SafeCDbl(item("Sig_FaceDir" & CStr(faceIdx) & "Y"))
        nz = SafeCDbl(item("Sig_FaceDir" & CStr(faceIdx) & "Z"))
        faceArea = SafeCDbl(item("Sig_FaceDir" & CStr(faceIdx) & "Area"))

        If faceArea > 0# And NormalizeVector3(nx, ny, nz) Then
            Dim dotLen As Double
            dotLen = Abs(Dot3(primaryX, primaryY, primaryZ, nx, ny, nz))

            If dotLen < 0.95 Then
                Dim tx As Double, ty As Double, tz As Double
                tx = nx: ty = ny: tz = nz

                If ProjectVectorOffAxis(tx, ty, tz, primaryX, primaryY, primaryZ) Then
                    Dim score As Double
                    score = (faceArea * (1# - dotLen)) + (0.000001 * CDbl(7 - faceIdx))

                    If score > bestScore Then
                        bestScore = score
                        bestIdx = faceIdx
                        bestDot = dotLen
                        secondaryX = tx: secondaryY = ty: secondaryZ = tz
                    End If
                End If
            End If
        End If
    Next faceIdx

    If bestIdx > 0 Then
        TryChooseFabricationSecondaryAxis = True
        secondaryReason = "planar face family " & CStr(bestIdx) & _
                          " projected off primary | score=" & Fmt(bestScore) & _
                          " | dotPrimary=" & Fmt(bestDot)
        Exit Function
    End If

    If TryGetItemDirectionFamilyOffPrimary(item, "Sig_SecondaryDir", primaryX, primaryY, primaryZ, _
                                           secondaryX, secondaryY, secondaryZ, secondaryReason) Then
        TryChooseFabricationSecondaryAxis = True
        Exit Function
    End If

    If TryGetItemDirectionFamilyOffPrimary(item, "Sig_TertiaryDir", primaryX, primaryY, primaryZ, _
                                           secondaryX, secondaryY, secondaryZ, secondaryReason) Then
        TryChooseFabricationSecondaryAxis = True
        Exit Function
    End If

    Exit Function

EH:
    secondaryReason = "TryChooseFabricationSecondaryAxis exception | " & Err.Number & " | " & Err.Description
    LogWarn secondaryReason & " | itemNo=" & SafeStr(item("ItemNo"))
    TryChooseFabricationSecondaryAxis = False
End Function

Private Function TryGetItemDirectionFamilyOffPrimary(ByVal item As Object, _
                                                     ByVal keyPrefix As String, _
                                                     ByVal primaryX As Double, _
                                                     ByVal primaryY As Double, _
                                                     ByVal primaryZ As Double, _
                                                     ByRef outX As Double, _
                                                     ByRef outY As Double, _
                                                     ByRef outZ As Double, _
                                                     ByRef reasonText As String) As Boolean
    On Error GoTo EH

    TryGetItemDirectionFamilyOffPrimary = False
    outX = SafeCDbl(item(keyPrefix & "X"))
    outY = SafeCDbl(item(keyPrefix & "Y"))
    outZ = SafeCDbl(item(keyPrefix & "Z"))

    Dim famLen As Double
    famLen = SafeCDbl(item(keyPrefix & "Len"))

    If famLen <= 0# Then Exit Function
    If Not NormalizeVector3(outX, outY, outZ) Then Exit Function

    Dim dotLen As Double
    dotLen = Abs(Dot3(primaryX, primaryY, primaryZ, outX, outY, outZ))
    If dotLen >= 0.9 Then Exit Function

    If Not ProjectVectorOffAxis(outX, outY, outZ, primaryX, primaryY, primaryZ) Then Exit Function

    reasonText = keyPrefix & " projected off primary | len=" & Fmt(famLen) & _
                 " | dotPrimary=" & Fmt(dotLen)
    TryGetItemDirectionFamilyOffPrimary = True
    Exit Function

EH:
    TryGetItemDirectionFamilyOffPrimary = False
End Function

Private Function GetBestPerpendicularWorldAxis(ByVal nx As Double, ByVal ny As Double, ByVal nz As Double, _
                                               ByRef outX As Double, ByRef outY As Double, ByRef outZ As Double) As Boolean
    On Error GoTo EH

    If Not NormalizeVector3(nx, ny, nz) Then Exit Function

    Dim ax As Double, ay As Double, az As Double
    ax = 1#: ay = 0#: az = 0#
    If Abs(Dot3(nx, ny, nz, ax, ay, az)) > 0.85 Then
        ax = 0#: ay = 1#: az = 0#
    End If
    If Abs(Dot3(nx, ny, nz, ax, ay, az)) > 0.85 Then
        ax = 0#: ay = 0#: az = 1#
    End If

    outX = ax: outY = ay: outZ = az
    GetBestPerpendicularWorldAxis = ProjectVectorOffAxis(outX, outY, outZ, nx, ny, nz)
    Exit Function

EH:
    GetBestPerpendicularWorldAxis = False
End Function

Private Function ProjectVectorOffAxis(ByRef vx As Double, ByRef vy As Double, ByRef vz As Double, _
                                      ByVal nx As Double, ByVal ny As Double, ByVal nz As Double) As Boolean
    On Error GoTo EH

    If Not NormalizeVector3(nx, ny, nz) Then Exit Function

    Dim d As Double
    d = Dot3(vx, vy, vz, nx, ny, nz)
    vx = vx - (d * nx)
    vy = vy - (d * ny)
    vz = vz - (d * nz)

    ProjectVectorOffAxis = NormalizeVector3(vx, vy, vz)
    Exit Function

EH:
    ProjectVectorOffAxis = False
End Function

Private Function CreatePreparedBodyCandidateView(ByVal item As Object, _
                                                 ByVal cand As Object, _
                                                 ByVal stagingX As Double, _
                                                 ByVal stagingY As Double, _
                                                 ByVal itemIdx As Long, _
                                                 ByVal candIdx As Long, _
                                                 ByRef swOutView As SldWorks.View, _
                                                 ByRef tempViewName As String) As Boolean
    On Error GoTo EH

    CreatePreparedBodyCandidateView = False
    Set swOutView = Nothing
    tempViewName = ""

    ForceSheetContext
    Call ActivateModelConfigurationSafe(g_refModel, g_refCfg)

    tempViewName = TEMP_MODEL_VIEW_PREFIX & Format$(itemIdx, "0000") & "_" & Format$(candIdx, "0000") & "_" & Format$(Timer * 1000#, "00000000")
    If Not CreateTemporaryModelViewFromCandidate(cand, tempViewName) Then
        LogWarn "CreatePreparedBodyCandidateView | temp model view failed | itemIdx=" & CStr(itemIdx) & _
                " | candIdx=" & CStr(candIdx) & " | name=" & tempViewName
        RestoreDrawingDocument "temp view create failed"
        Exit Function
    End If

    RestoreDrawingDocument "create body candidate drawing view"
    ForceSheetContext

    Set swOutView = g_swDraw.CreateDrawViewFromModelView3(g_modelPath, tempViewName, stagingX, stagingY, 0#)
    If swOutView Is Nothing Then
        LogWarn "CreatePreparedBodyCandidateView | CreateDrawViewFromModelView3 failed | itemIdx=" & CStr(itemIdx) & _
                " | tempView=" & tempViewName
        Exit Function
    End If

    On Error Resume Next
    swOutView.PositionLocked = False
    swOutView.SetName2 "COPE_CAND_" & Format$(itemIdx, "0000") & "_" & Format$(candIdx, "0000")
    On Error GoTo EH

    If Not EnsureViewUsesConfiguration(swOutView, g_refCfg, "body-cand post-create itemIdx=" & CStr(itemIdx) & " view=" & tempViewName) Then
        LogWarn "CreatePreparedBodyCandidateView | EnsureViewUsesConfiguration failed post-create | itemIdx=" & CStr(itemIdx) & _
                " | tempView=" & tempViewName & " | cfg=" & g_refCfg
        SafeDeleteView g_swDrwModel, swOutView
        Set swOutView = Nothing
        Exit Function
    End If

    If Not IsolateBodyFast(swOutView, item) Then
        LogWarn "CreatePreparedBodyCandidateView | IsolateBodyFast failed | itemIdx=" & CStr(itemIdx) & _
                " | tempView=" & tempViewName
        SafeDeleteView g_swDrwModel, swOutView
        Set swOutView = Nothing
        Exit Function
    End If

    g_swDrwModel.EditRebuild3
    ForceSheetContext

    CreatePreparedBodyCandidateView = True
    Exit Function

EH:
    LogError "CreatePreparedBodyCandidateView exception | " & Err.Number & " | " & Err.Description & _
             " | itemIdx=" & CStr(itemIdx) & " | tempView=" & tempViewName
    If Not swOutView Is Nothing Then SafeDeleteView g_swDrwModel, swOutView
    Set swOutView = Nothing
    CreatePreparedBodyCandidateView = False
End Function

Private Function CreateTemporaryModelViewFromCandidate(ByVal cand As Object, ByVal tempViewName As String) As Boolean
    On Error GoTo EH

    CreateTemporaryModelViewFromCandidate = False
    Dim swView As SldWorks.ModelView
    Dim originalOrientation As SldWorks.MathTransform
    Dim didChangeModelView As Boolean

    If cand Is Nothing Then Exit Function
    If Len(Trim$(tempViewName)) = 0 Then Exit Function
    If g_refModel Is Nothing Then Exit Function

    If Not ActivateModelDocumentForViewSetup("CreateTemporaryModelViewFromCandidate") Then Exit Function

    On Error Resume Next
    g_refModel.DeleteNamedView tempViewName
    Err.Clear
    On Error GoTo EH

    Set swView = g_refModel.ActiveView
    If swView Is Nothing Then
        LogWarn "CreateTemporaryModelViewFromCandidate | model ActiveView is Nothing | name=" & tempViewName
        Exit Function
    End If

    On Error Resume Next
    Set originalOrientation = swView.Orientation3
    If Err.Number <> 0 Then
        LogWarn "CreateTemporaryModelViewFromCandidate | could not capture original model view orientation | " & _
                Err.Number & " | " & Err.Description & " | name=" & tempViewName
        Err.Clear
    Else
        LogInfo "TempViewCreate | captured original model view orientation | name=" & tempViewName & _
                " | ok=" & BoolWord(Not (originalOrientation Is Nothing))
    End If
    g_refModel.ClearSelection2 True
    On Error GoTo EH

    Dim swXf As SldWorks.MathTransform
    Set swXf = CreateOrientationTransformFromFrame(SafeCDbl(cand("Xx")), SafeCDbl(cand("Xy")), SafeCDbl(cand("Xz")), _
                                                   SafeCDbl(cand("Yx")), SafeCDbl(cand("Yy")), SafeCDbl(cand("Yz")), _
                                                   SafeCDbl(cand("Zx")), SafeCDbl(cand("Zy")), SafeCDbl(cand("Zz")))
    If swXf Is Nothing Then
        LogWarn "CreateTemporaryModelViewFromCandidate | transform is Nothing | name=" & tempViewName
        Exit Function
    End If

    swView.Orientation3 = swXf
    didChangeModelView = True
    On Error Resume Next
    ' SOLIDWORKS stores the named view camera extents. Zoom-to-fit is required here;
    ' otherwise drawing views created from this temporary named view can appear empty.
    g_refModel.ViewZoomtofit2
    If Err.Number <> 0 Then
        LogWarn "TempViewCreate | ViewZoomtofit2 failed before NameView | name=" & tempViewName & _
                " | " & Err.Number & " | " & Err.Description
        Err.Clear
    Else
        LogInfo "TempViewCreate | zoom-to-fit applied before NameView | name=" & tempViewName
    End If
    g_refModel.GraphicsRedraw2
    On Error GoTo EH

    g_refModel.NameView tempViewName
    TrackTempModelView tempViewName

    CreateTemporaryModelViewFromCandidate = ModelViewNameExists(g_refModel, tempViewName)
    LogInfo "TempViewCreate | name=" & tempViewName & _
            " | ok=" & BoolWord(CreateTemporaryModelViewFromCandidate)
    RestoreModelViewStateAfterTemporaryView swView, originalOrientation, didChangeModelView, tempViewName, "normal"
    Exit Function

EH:
    LogError "CreateTemporaryModelViewFromCandidate exception | " & Err.Number & " | " & Err.Description & _
             " | name=" & tempViewName
    RestoreModelViewStateAfterTemporaryView swView, originalOrientation, didChangeModelView, tempViewName, "error"
    CreateTemporaryModelViewFromCandidate = False
End Function

Private Sub RestoreModelViewStateAfterTemporaryView(ByVal swView As SldWorks.ModelView, _
                                                    ByVal originalOrientation As SldWorks.MathTransform, _
                                                    ByVal didChangeModelView As Boolean, _
                                                    ByVal tempViewName As String, _
                                                    ByVal tag As String)
    On Error Resume Next

    If Not didChangeModelView Then Exit Sub

    If Not swView Is Nothing And Not originalOrientation Is Nothing Then
        Err.Clear
        swView.Orientation3 = originalOrientation
        If Err.Number <> 0 Then
            LogWarn "TempViewCreate | failed to restore original model view orientation | tag=" & tag & _
                    " | name=" & tempViewName & " | " & Err.Number & " | " & Err.Description
            Err.Clear
        Else
            LogInfo "TempViewCreate | restored original model view orientation | tag=" & tag & _
                    " | name=" & tempViewName
        End If
    Else
        LogWarn "TempViewCreate | original model view orientation unavailable; display may remain changed until redraw/reopen | tag=" & tag & _
                " | name=" & tempViewName
    End If

    If Not g_refModel Is Nothing Then
        g_refModel.ClearSelection2 True
        g_refModel.GraphicsRedraw2
    End If
    On Error GoTo 0
End Sub

Private Function CreateOrientationTransformFromFrame(ByVal xx As Double, ByVal xy As Double, ByVal xz As Double, _
                                                     ByVal yx As Double, ByVal yy As Double, ByVal yz As Double, _
                                                     ByVal zx As Double, ByVal zy As Double, ByVal zz As Double) As SldWorks.MathTransform
    On Error GoTo EH

    If Not NormalizeVector3(xx, xy, xz) Then Exit Function
    If Not NormalizeVector3(yx, yy, yz) Then Exit Function
    If Not NormalizeVector3(zx, zy, zz) Then Exit Function

    Dim swMu As SldWorks.MathUtility
    Set swMu = g_swApp.GetMathUtility
    If swMu Is Nothing Then Exit Function

    Dim vx As SldWorks.MathVector
    Dim vy As SldWorks.MathVector
    Dim vz As SldWorks.MathVector
    Dim vOrigin As SldWorks.MathVector
    Dim swOriginPt As SldWorks.MathPoint
    Dim swXf As SldWorks.MathTransform

    Set vx = CreateMathVector3(swMu, xx, xy, xz)
    Set vy = CreateMathVector3(swMu, yx, yy, yz)
    ' SOLIDWORKS model-view orientation expects the composed Z vector inverted before inverse.
    Set vz = CreateMathVector3(swMu, -zx, -zy, -zz)
    Set swOriginPt = CreateMathPoint3(swMu, 0#, 0#, 0#)
    If swOriginPt Is Nothing Then Exit Function
    Set vOrigin = swOriginPt.ConvertToVector

    If vx Is Nothing Or vy Is Nothing Or vz Is Nothing Or vOrigin Is Nothing Then Exit Function

    Set swXf = swMu.ComposeTransform(vx, vy, vz, vOrigin, 1#)
    If swXf Is Nothing Then Exit Function

    Set CreateOrientationTransformFromFrame = swXf.Inverse
    Exit Function

EH:
    LogWarn "CreateOrientationTransformFromFrame exception | " & Err.Number & " | " & Err.Description
    Set CreateOrientationTransformFromFrame = Nothing
End Function

Private Function CreateMathVector3(ByVal swMu As SldWorks.MathUtility, _
                                   ByVal x As Double, ByVal y As Double, ByVal z As Double) As SldWorks.MathVector
    On Error GoTo EH
    Dim v(0 To 2) As Double
    v(0) = x: v(1) = y: v(2) = z
    Set CreateMathVector3 = swMu.CreateVector(v)
    Exit Function
EH:
    Set CreateMathVector3 = Nothing
End Function

Private Function CreateMathPoint3(ByVal swMu As SldWorks.MathUtility, _
                                  ByVal x As Double, ByVal y As Double, ByVal z As Double) As SldWorks.MathPoint
    On Error GoTo EH
    Dim p(0 To 2) As Double
    p(0) = x: p(1) = y: p(2) = z
    Set CreateMathPoint3 = swMu.CreatePoint(p)
    Exit Function
EH:
    Set CreateMathPoint3 = Nothing
End Function

Private Function ActivateModelDocumentForViewSetup(ByVal tag As String) As Boolean
    On Error GoTo EH

    ActivateModelDocumentForViewSetup = False
    If g_swApp Is Nothing Then Exit Function
    If g_refModel Is Nothing Then Exit Function

    Dim errs As Long
    Dim title As String
    title = g_refModel.GetTitle
    If Len(title) = 0 Then Exit Function

    Dim actDoc As SldWorks.ModelDoc2
    Set actDoc = g_swApp.ActivateDoc3(title, False, 0, errs)
    ActivateModelDocumentForViewSetup = Not (actDoc Is Nothing)

    LogInfo "ActivateModelDocumentForViewSetup | tag=" & tag & _
            " | title=" & title & _
            " | errs=" & CStr(errs) & _
            " | ok=" & BoolWord(ActivateModelDocumentForViewSetup)
    Exit Function

EH:
    LogWarn "ActivateModelDocumentForViewSetup exception | " & Err.Number & " | " & Err.Description & _
            " | tag=" & tag
    ActivateModelDocumentForViewSetup = False
End Function

Private Sub RestoreDrawingDocument(ByVal tag As String)
    On Error Resume Next
    If Not g_swApp Is Nothing And Not g_swDrwModel Is Nothing Then
        Dim errs As Long
        g_swApp.ActivateDoc3 g_swDrwModel.GetTitle, False, 0, errs
        If Not g_swDraw Is Nothing Then g_swDraw.ActivateSheet OUTPUT_SHEET_NAME
        ForceSheetContext
        LogInfo "RestoreDrawingDocument | tag=" & tag & " | errs=" & CStr(errs)
    End If
    On Error GoTo 0
End Sub

Private Sub TrackTempModelView(ByVal tempViewName As String)
    On Error Resume Next
    If g_tempViewNames Is Nothing Then Set g_tempViewNames = New Collection
    If Len(Trim$(tempViewName)) > 0 Then g_tempViewNames.Add tempViewName
    On Error GoTo 0
End Sub

Private Sub ProtectTempModelView(ByVal tempViewName As String)
    On Error Resume Next

    If Len(Trim$(tempViewName)) = 0 Then Exit Sub
    If g_keepTempViewNames Is Nothing Then Set g_keepTempViewNames = CreateObject("Scripting.Dictionary")

    Dim key As String
    key = UCase$(Trim$(tempViewName))
    If Not g_keepTempViewNames.Exists(key) Then
        g_keepTempViewNames.Add key, tempViewName
        LogInfo "TempViewKeep | accepted drawing view still depends on temp named view | name=" & tempViewName
    End If
    On Error GoTo 0
End Sub

Private Function ShouldKeepTempModelView(ByVal tempViewName As String, ByVal tag As String) As Boolean
    On Error GoTo EH

    ShouldKeepTempModelView = False
    If Len(Trim$(tempViewName)) = 0 Then Exit Function
    If InStr(1, tag, "keep_accepted", vbTextCompare) = 0 Then Exit Function
    If g_keepTempViewNames Is Nothing Then Exit Function

    ShouldKeepTempModelView = g_keepTempViewNames.Exists(UCase$(Trim$(tempViewName)))
    Exit Function

EH:
    ShouldKeepTempModelView = False
End Function

Private Function ProtectedTempModelViewCount() As Long
    On Error GoTo EH

    ProtectedTempModelViewCount = 0
    If g_keepTempViewNames Is Nothing Then Exit Function
    ProtectedTempModelViewCount = CLng(g_keepTempViewNames.Count)
    Exit Function

EH:
    ProtectedTempModelViewCount = 0
End Function

Private Function ModelViewNameExists(ByVal swModel As SldWorks.ModelDoc2, ByVal viewName As String) As Boolean
    On Error GoTo EH

    Dim vNames As Variant
    vNames = GetModelViewNamesSafe(swModel)
    If Not IsArray(vNames) Then Exit Function

    Dim i As Long
    For i = LBound(vNames) To UBound(vNames)
        If StrComp(SafeStr(vNames(i)), viewName, vbTextCompare) = 0 Then
            ModelViewNameExists = True
            Exit Function
        End If
    Next i
    Exit Function

EH:
    ModelViewNameExists = False
End Function

Private Sub CleanupMacroTempModelViews(ByVal swModel As SldWorks.ModelDoc2, ByVal tag As String)
    On Error GoTo EH

    If swModel Is Nothing Then Exit Sub

    Dim namesToDelete As Object
    Set namesToDelete = CreateObject("Scripting.Dictionary")

    Dim vNames As Variant
    vNames = GetModelViewNamesSafe(swModel)
    If IsArray(vNames) Then
        Dim i As Long
        For i = LBound(vNames) To UBound(vNames)
            Dim nm As String
            nm = SafeStr(vNames(i))
            If Left$(UCase$(nm), Len(TEMP_MODEL_VIEW_PREFIX)) = UCase$(TEMP_MODEL_VIEW_PREFIX) Then
                If Not namesToDelete.Exists(UCase$(nm)) Then namesToDelete.Add UCase$(nm), nm
            End If
        Next i
    End If

    If Not g_tempViewNames Is Nothing Then
        Dim j As Long
        For j = 1 To g_tempViewNames.Count
            nm = SafeStr(g_tempViewNames(j))
            If Len(nm) > 0 Then
                If Not namesToDelete.Exists(UCase$(nm)) Then namesToDelete.Add UCase$(nm), nm
            End If
        Next j
    End If

    Dim key As Variant
    For Each key In namesToDelete.Keys
        nm = SafeStr(namesToDelete(key))
        If Len(nm) > 0 Then
            If ShouldKeepTempModelView(nm, tag) Then
                LogInfo "TempViewCleanup | keeping accepted temp named view because drawing view depends on it | tag=" & tag & _
                        " | name=" & nm
                GoTo NextNameToDelete
            End If

            Dim ok As Boolean
            On Error Resume Next
            ok = swModel.DeleteNamedView(nm)
            If Err.Number <> 0 Then
                LogWarn "TempViewCleanup | delete error | tag=" & tag & _
                        " | name=" & nm & _
                        " | " & Err.Number & " | " & Err.Description
                Err.Clear
            Else
                LogInfo "TempViewCleanup | tag=" & tag & " | name=" & nm & " | ok=" & BoolWord(ok)
            End If
            On Error GoTo EH
        End If
NextNameToDelete:
    Next key

    If InStr(1, tag, "keep_accepted", vbTextCompare) = 0 Then
        Set g_tempViewNames = New Collection
    End If
    Exit Sub

EH:
    LogWarn "CleanupMacroTempModelViews exception | " & Err.Number & " | " & Err.Description & _
            " | tag=" & tag
End Sub

Private Function StrictValidatePreparedBodyView(ByVal swView As SldWorks.View, _
                                                ByVal item As Object, _
                                                ByVal cand As Object, _
                                                ByRef validationDetail As String) As Boolean
    On Error GoTo EH

    StrictValidatePreparedBodyView = False
    validationDetail = ""
    If swView Is Nothing Then Exit Function
    If item Is Nothing Then Exit Function
    If cand Is Nothing Then Exit Function

    Dim bodyCt As Long
    bodyCt = GetViewBodyCountSafe(swView)

    Dim axisMag As Double
    Dim axisAngRad As Double
    Dim axisProjectionOk As Boolean
    Dim axisProjectionSource As String
    axisProjectionOk = TryProjectModelDirectionToView(swView, SafeCDbl(cand("Xx")), SafeCDbl(cand("Xy")), SafeCDbl(cand("Xz")), axisMag, axisAngRad)
    If axisProjectionOk Then
        axisProjectionSource = "ModelToViewTransform"
    Else
        ' The view was created directly from this model-view basis. If SOLIDWORKS has not
        ' exposed a usable transform yet, keep validating body isolation and readable profile
        ' instead of rejecting a potentially correct fabrication view only because transform
        ' projection was unavailable.
        axisMag = 0#
        axisAngRad = 0#
        axisProjectionSource = "CandidateBasisFallback"
        LogWarn "StrictValidate | model-to-view axis projection unavailable; using candidate-frame fallback | view=" & _
                ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
                " | kind=" & SafeStr(cand("Kind")) & " | bodyCt=" & CStr(bodyCt)
    End If

    Dim axisRemainDeg As Double
    axisRemainDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(axisAngRad)))

    Dim xSpan As Double, ySpan As Double, zSpan As Double
    Call GetBodyFrameExtentMetrics(item, SafeCDbl(cand("Xx")), SafeCDbl(cand("Xy")), SafeCDbl(cand("Xz")), _
                                   SafeCDbl(cand("Yx")), SafeCDbl(cand("Yy")), SafeCDbl(cand("Yz")), _
                                   SafeCDbl(cand("Zx")), SafeCDbl(cand("Zy")), SafeCDbl(cand("Zz")), _
                                   xSpan, ySpan, zSpan)

    Dim profileRatio As Double
    If xSpan > 0# Then profileRatio = ySpan / xSpan

    Dim minProfile As Double
    Dim catName As String
    catName = UCase$(SafeStr(item("BodyCategory")))

    If catName = UCase$(CAT_NAME_SHEET_PLATE) Then
        minProfile = MaxD(STRICT_MIN_PROFILE_ABS, xSpan * STRICT_PLATE_PROFILE_RATIO_MIN)
    ElseIf catName = UCase$(CAT_NAME_LINEAR_STRUCTURAL) Then
        minProfile = MaxD(STRICT_MIN_PROFILE_ABS, xSpan * STRICT_STRUCT_PROFILE_RATIO_MIN)
    Else
        minProfile = STRICT_MIN_PROFILE_ABS
    End If

    Dim broadReadable As Boolean
    broadReadable = (ySpan >= minProfile)

    Dim bodyOk As Boolean
    bodyOk = (bodyCt = 1)

    Dim axisOk As Boolean
    axisOk = ((Not axisProjectionOk) Or (axisRemainDeg <= STRICT_AXIS_HORIZONTAL_TOL_DEG))

    validationDetail = "bodyCt=" & CStr(bodyCt) & _
                       " | axisRemainDeg=" & Format$(axisRemainDeg, "0.000") & _
                       " | axisMag=" & Fmt(axisMag) & _
                       " | axisProjection=" & axisProjectionSource & _
                       " | modelSpanXYZ=" & Fmt(xSpan) & "/" & Fmt(ySpan) & "/" & Fmt(zSpan) & _
                       " | profileRatio=" & Fmt(profileRatio) & _
                       " | minProfile=" & Fmt(minProfile) & _
                       " | bodyOk=" & BoolWord(bodyOk) & _
                       " | axisOk=" & BoolWord(axisOk) & _
                       " | readable=" & BoolWord(broadReadable) & _
                       " | kind=" & SafeStr(cand("Kind"))

    LogInfo "StrictValidate | view=" & ViewGetNameSafe(swView) & _
            " | itemNo=" & SafeStr(item("ItemNo")) & _
            " | pn=" & SafeStr(item("PartNo")) & _
            " | cat=" & SafeStr(item("BodyCategory")) & _
            " | " & validationDetail

    StrictValidatePreparedBodyView = (bodyOk And axisOk And broadReadable)
    Exit Function

EH:
    validationDetail = "StrictValidate exception | " & Err.Number & " | " & Err.Description
    LogWarn validationDetail & " | view=" & ViewGetNameSafe(swView)
    StrictValidatePreparedBodyView = False
End Function

Private Function ValidateHybridCandidateNotEmpty(ByVal swView As SldWorks.View, _
                                                 ByVal item As Object, _
                                                 ByRef validationDetail As String) As Boolean
    On Error GoTo EH

    ValidateHybridCandidateNotEmpty = False
    validationDetail = ""
    If swView Is Nothing Then
        validationDetail = "view is Nothing"
        Exit Function
    End If
    If item Is Nothing Then
        validationDetail = "item is Nothing"
        Exit Function
    End If

    Dim bodyCt As Long
    bodyCt = GetViewBodyCountSafe(swView)

    Dim outlineW As Double, outlineH As Double
    Dim outlineOk As Boolean
    outlineOk = TryGetViewOutlineWH(swView, outlineW, outlineH)

    Dim edgeCt As Long, vertexCt As Long, faceCt As Long
    GetVisibleEntityCountsInView swView, edgeCt, vertexCt, faceCt

    Dim ptCt As Long
    Dim spanLen As Double, spanAng As Double
    Dim cloudW As Double, cloudH As Double, cloudArea As Double, cloudDetail As String
    Call GetBodyProjectedPointCloudMetrics(swView, item, ptCt, spanLen, spanAng, cloudW, cloudH, cloudArea, cloudDetail)

    Dim pcPtCt As Long
    Dim pcMajor As Double, pcMajorAng As Double, pcMinor As Double, pcMinorAng As Double, pcRatio As Double, pcDetail As String
    Call GetBodyProjectedPrincipalMetrics(swView, item, pcPtCt, pcMajor, pcMajorAng, pcMinor, pcMinorAng, pcRatio, pcDetail)

    Dim evidenceCt As Long
    evidenceCt = edgeCt + vertexCt + faceCt + ptCt + pcPtCt

    validationDetail = "bodyCt=" & CStr(bodyCt) & _
                       " | outlineOk=" & BoolWord(outlineOk) & _
                       " | outline=" & Fmt(outlineW) & "x" & Fmt(outlineH) & _
                       " | visibleEdges=" & CStr(edgeCt) & _
                       " | visibleVertices=" & CStr(vertexCt) & _
                       " | visibleFaces=" & CStr(faceCt) & _
                       " | projectedPts=" & CStr(ptCt) & _
                       " | principalPts=" & CStr(pcPtCt) & _
                       " | cloud=" & Fmt(cloudW) & "x" & Fmt(cloudH) & _
                       " | cloudArea=" & Fmt(cloudArea) & _
                       " | pcMajor=" & Fmt(pcMajor) & _
                       " | pcMinor=" & Fmt(pcMinor) & _
                       " | evidenceCt=" & CStr(evidenceCt)

    If bodyCt <> 1 Then
        validationDetail = validationDetail & " | reject=body isolation count is not exactly one"
        Exit Function
    End If

    If Not outlineOk Or outlineW <= 0.0000001 Or outlineH <= 0.0000001 Then
        validationDetail = validationDetail & " | reject=invalid or zero drawing outline"
        Exit Function
    End If

    If evidenceCt <= 0 Then
        validationDetail = validationDetail & " | reject=no visible/projected body evidence"
        Exit Function
    End If

    ValidateHybridCandidateNotEmpty = True
    validationDetail = validationDetail & " | ok=True"
    Exit Function

EH:
    validationDetail = "ValidateHybridCandidateNotEmpty exception | " & Err.Number & " | " & Err.Description
    LogWarn validationDetail & " | view=" & ViewGetNameSafe(swView)
    ValidateHybridCandidateNotEmpty = False
End Function

Private Function ApplyRoundStockProjectedOutlineFallback(ByVal swView As SldWorks.View, _
                                                       ByVal item As Object, _
                                                       ByRef ptCt As Long, _
                                                       ByRef spanLen As Double, _
                                                       ByRef cloudW As Double, _
                                                       ByRef cloudH As Double, _
                                                       ByRef cloudArea As Double, _
                                                       ByRef pcPtCt As Long, _
                                                       ByRef pcMajor As Double, _
                                                       ByRef pcMajorAng As Double, _
                                                       ByRef pcMinor As Double, _
                                                       ByRef pcMinorAng As Double, _
                                                       ByRef pcRatio As Double, _
                                                       ByRef detail As String) As Boolean
    On Error GoTo EH

    ApplyRoundStockProjectedOutlineFallback = False
    detail = ""
    If swView Is Nothing Then Exit Function
    If item Is Nothing Then Exit Function

    Dim outlineW As Double, outlineH As Double
    outlineW = 0#: outlineH = 0#
    If Not TryGetViewOutlineWH(swView, outlineW, outlineH) Then Exit Function
    If outlineW <= 0.0000001 Or outlineH <= 0.0000001 Then Exit Function

    Dim majorLen As Double, minorLen As Double
    Dim majorAng As Double, minorAng As Double

    If outlineW >= outlineH Then
        majorLen = outlineW
        minorLen = outlineH
        majorAng = 0#
        minorAng = PI / 2#
    Else
        majorLen = outlineH
        minorLen = outlineW
        majorAng = PI / 2#
        minorAng = 0#
    End If

    If majorLen <= 0.0000001 Then Exit Function

    If pcMajor <= 0# Then pcMajor = majorLen
    If pcMinor <= 0# Then pcMinor = minorLen
    If pcMajorAng = 0# And pcMinorAng = 0# Then
        pcMajorAng = majorAng
        pcMinorAng = minorAng
    End If

    If pcMajor > 0# And pcRatio <= 0# Then pcRatio = pcMinor / pcMajor

    If cloudW <= 0# Then cloudW = outlineW
    If cloudH <= 0# Then cloudH = outlineH
    If cloudArea <= 0# Then cloudArea = outlineW * outlineH
    If spanLen <= 0# Then spanLen = Sqr((outlineW * outlineW) + (outlineH * outlineH))

    detail = "round-stock outline fallback | outline=" & Fmt(outlineW) & "x" & Fmt(outlineH) & _
             " | major=" & Fmt(pcMajor) & _
             " | minor=" & Fmt(pcMinor) & _
             " | ratio=" & Fmt(pcRatio)

    ApplyRoundStockProjectedOutlineFallback = True
    Exit Function

EH:
    LogWarn "ApplyRoundStockProjectedOutlineFallback exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
    ApplyRoundStockProjectedOutlineFallback = False
End Function

Private Function ValidateHybridCandidateReadablePreRotation(ByVal swView As SldWorks.View, _
                                                            ByVal item As Object, _
                                                            ByVal cand As Object, _
                                                            ByRef validationScore As Double, _
                                                            ByRef validationDetail As String) As Boolean
    On Error GoTo EH

    ValidateHybridCandidateReadablePreRotation = False
    validationScore = -1E+30
    validationDetail = ""
    If swView Is Nothing Then Exit Function
    If item Is Nothing Then Exit Function

    Call EnsureItemCategoryAnalysis(item)

    Dim catName As String
    catName = UCase$(SafeStr(item("BodyCategory")))

    Dim subtypeName As String
    subtypeName = UCase$(SafeStr(item("BodySubtype")))

    Dim famBest As Double, famAng As Double, famSecond As Double, famDetail As String
    Dim spanLen As Double, spanAng As Double, cloudW As Double, cloudH As Double, cloudArea As Double, cloudDetail As String
    Dim ptCt As Long
    Dim pcPtCt As Long, pcMajor As Double, pcMajorAng As Double, pcMinor As Double, pcMinorAng As Double, pcRatio As Double, pcDetail As String
    Dim profileMetric As Double, faceMetric As Double
    Dim minReadable As Double
    Dim readable As Boolean
    Dim rejectReason As String

    Call GetProjectedDominantDirectionMetrics(swView, item, famBest, famAng, famSecond, famDetail)
    Call GetBodyProjectedPointCloudMetrics(swView, item, ptCt, spanLen, spanAng, cloudW, cloudH, cloudArea, cloudDetail)
    Call GetBodyProjectedPrincipalMetrics(swView, item, pcPtCt, pcMajor, pcMajorAng, pcMinor, pcMinorAng, pcRatio, pcDetail)

    Dim edgeCt As Long, vertexCt As Long, faceCt As Long
    GetVisibleEntityCountsInView swView, edgeCt, vertexCt, faceCt

    Dim roundFallbackDetail As String
    roundFallbackDetail = ""

    If pcMajor <= 0# And Not cand Is Nothing Then
        If UCase$(SafeStr(cand("Source"))) = "BODY_FRAME" Then
            pcMajor = SafeCDbl(cand("ModelXSpan"))
            pcMinor = SafeCDbl(cand("ModelYSpan"))
            pcRatio = 0#
            If pcMajor > 0# Then pcRatio = pcMinor / pcMajor
            pcDetail = "body-frame span fallback because projected principal metrics unavailable"
        End If
    End If

    If catName = UCase$(CAT_NAME_ROUND_STOCK) Then
        If (pcMajor <= 0# Or spanLen <= 0#) And (edgeCt > 0 Or faceCt > 0) Then
            If ApplyRoundStockProjectedOutlineFallback(swView, item, ptCt, spanLen, cloudW, cloudH, cloudArea, _
                                                       pcPtCt, pcMajor, pcMajorAng, pcMinor, pcMinorAng, pcRatio, _
                                                       roundFallbackDetail) Then
                LogInfo "RoundStockFallback | pre-rotation readability metrics restored from outline | view=" & _
                        ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
                        " | detail={" & roundFallbackDetail & "}"
            End If
        End If
    End If

    profileMetric = MaxD(pcMinor, famSecond)
    faceMetric = pcMajor * pcMinor
    readable = False
    rejectReason = ""

    Select Case subtypeName
        Case CAT_SUBTYPE_CHANNEL
            minReadable = MaxD(CAT_SECTION_PROFILE_ABS_MIN, pcMajor * CAT_CHANNEL_PROFILE_RATIO_MIN)
            readable = (pcMajor > 0# And profileMetric >= minReadable And pcMajor >= (profileMetric * 1.1))
            validationScore = (pcMajor * 1200000#) + (profileMetric * 1800000#) + _
                              (famSecond * 1200000#) + (faceMetric * 600000#) + _
                              (CDbl(edgeCt) * 25#) + (CDbl(faceCt) * 100#)
            If Not readable Then rejectReason = "channel view does not show readable web/open-leg profile; side/edge projection rejected"

        Case CAT_SUBTYPE_ANGLE
            minReadable = MaxD(CAT_SECTION_PROFILE_ABS_MIN, pcMajor * CAT_ANGLE_PROFILE_RATIO_MIN)
            readable = (pcMajor > 0# And profileMetric >= minReadable And pcMajor >= (profileMetric * 1.05))
            validationScore = (pcMajor * 1250000#) + (profileMetric * 1700000#) + _
                              (famSecond * 1400000#) + (faceMetric * 500000#) + _
                              (CDbl(edgeCt) * 25#) + (CDbl(faceCt) * 100#)
            If Not readable Then rejectReason = "angle view does not preserve L/profile relationship with one leg on sheet and projecting leg visible"

        Case Else
            Select Case catName
                Case UCase$(CAT_NAME_SHEET_PLATE)
                    minReadable = MaxD(CAT_PLATE_FACE_ABS_MIN, pcMajor * CAT_PLATE_FACE_RATIO_MIN)
                    readable = (pcMajor > 0# And pcMinor >= minReadable)
                    validationScore = (faceMetric * 2500000#) + (pcMinor * 1200000#) + (cloudArea * 250000#) + (spanLen * 50000#)
                    If Not readable Then rejectReason = "sheet/plate broad face is edge-on or unreadable"

                Case UCase$(CAT_NAME_LINEAR_STRUCTURAL)
                    minReadable = MaxD(CAT_LINEAR_PROFILE_ABS_MIN, pcMajor * CAT_LINEAR_PROFILE_RATIO_MIN)
                    readable = (pcMajor > 0# And profileMetric >= minReadable)
                    validationScore = (pcMajor * 1000000#) + (profileMetric * 800000#) + (cloudArea * 150000#) + (famBest * 100000#)
                    If Not readable Then rejectReason = "linear member profile/detail face is edge-on or unreadable"

                Case UCase$(CAT_NAME_CURVED_STRUCTURAL), UCase$(CAT_NAME_ROUND_STOCK), UCase$(CAT_NAME_IRREGULAR)
                    readable = (pcMajor > 0# Or famBest > 0# Or spanLen > 0#)
                    validationScore = (pcMajor * 700000#) + (pcMinor * 250000#) + (famBest * 200000#) + (cloudArea * 100000#)
                    If Not readable Then rejectReason = "no usable projected geometry for category"

                Case Else
                    readable = (pcMajor > 0# Or famBest > 0# Or spanLen > 0#)
                    validationScore = (pcMajor * 500000#) + (pcMinor * 200000#) + (cloudArea * 100000#)
                    If Not readable Then rejectReason = "no usable projected geometry"
            End Select
    End Select

    validationDetail = "cat=" & SafeStr(item("BodyCategory")) & _
                       " | subtype=" & SafeStr(item("BodySubtype")) & _
                       " | readable=" & BoolWord(readable) & _
                       " | minReadable=" & Fmt(minReadable) & _
                       " | pcMajor=" & Fmt(pcMajor) & "@ang=" & Format$(RadToDeg(pcMajorAng), "0.000") & _
                       " | pcMinor=" & Fmt(pcMinor) & _
                       " | pcRatio=" & Fmt(pcRatio) & _
                       " | profileMetric=" & Fmt(profileMetric) & _
                       " | faceMetric=" & Fmt(faceMetric) & _
                       " | famBest=" & Fmt(famBest) & _
                       " | famSecond=" & Fmt(famSecond) & _
                       " | spanLen=" & Fmt(spanLen) & _
                       " | cloud=" & Fmt(cloudW) & "x" & Fmt(cloudH) & _
                       " | cloudArea=" & Fmt(cloudArea) & _
                       " | pts=" & CStr(ptCt) & _
                       " | principalPts=" & CStr(pcPtCt) & _
                       " | visibleEdges=" & CStr(edgeCt) & _
                       " | visibleFaces=" & CStr(faceCt) & _
                       " | score=" & Format$(validationScore, "0.000")

    If Len(roundFallbackDetail) > 0 Then
        validationDetail = validationDetail & " | fallback={" & roundFallbackDetail & "}"
    End If

    If Not readable Then
        validationDetail = validationDetail & " | reject=" & rejectReason
        Exit Function
    End If

    ValidateHybridCandidateReadablePreRotation = True
    Exit Function

EH:
    validationDetail = "ValidateHybridCandidateReadablePreRotation exception | " & Err.Number & " | " & Err.Description
    LogWarn validationDetail & " | view=" & ViewGetNameSafe(swView)
    ValidateHybridCandidateReadablePreRotation = False
End Function

Private Function RotateHybridCandidateAfterFaceValidation(ByVal swView As SldWorks.View, _
                                                          ByVal item As Object, _
                                                          ByVal tag As String, _
                                                          ByRef rotateDetail As String) As Boolean
    On Error GoTo EH

    RotateHybridCandidateAfterFaceValidation = False
    rotateDetail = ""
    If swView Is Nothing Then
        rotateDetail = "view is Nothing"
        Exit Function
    End If

    Dim beforeAngle As Double
    Dim afterAngle As Double
    Dim beforeScore As Double
    Dim beforeDetail As String
    Dim beforeValid As Boolean
    Dim subtypeName As String

    subtypeName = UCase$(SafeStr(item("BodySubtype")))
    beforeAngle = swView.Angle
    beforeValid = ValidateOrientationResultByCategory(swView, item, beforeScore, beforeDetail)

    If beforeValid Then
        If subtypeName = CAT_SUBTYPE_ANGLE Then
            Dim alreadyEdgeDetail As String
            If Not IsAngleProjectedEdgeFamilyHorizontal(swView, item, alreadyEdgeDetail) Then
                LogWarn "RotationEdgeValidation | angle projected model edge is not exactly horizontal; overriding point-cloud-valid state | view=" & _
                        ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
                        " | before={" & beforeDetail & "} | edge={" & alreadyEdgeDetail & "}"
            Else
                rotateDetail = "already valid horizontal | angle projected edge confirmed | angleDeg=" & Format$(RadToDeg(beforeAngle), "0.000") & _
                               " | before={" & beforeDetail & "} | edge={" & alreadyEdgeDetail & "}"
                Exit Function
            End If
        Else
            rotateDetail = "already valid horizontal | angleDeg=" & Format$(RadToDeg(beforeAngle), "0.000") & _
                           " | before={" & beforeDetail & "}"
            Exit Function
        End If
    End If

    Dim rotOk As Boolean
    Dim fabDetail As String
    Dim edgeFamilyDetail As String
    Dim edgeFamilyAvailable As Boolean
    Dim fallbackDetail As String
    Dim flipDetail As String
    Dim usedFallback As Boolean

    rotOk = False
    fabDetail = "not used"
    edgeFamilyDetail = "not used"
    edgeFamilyAvailable = False
    fallbackDetail = "not used"
    flipDetail = "not used"
    usedFallback = False

    If subtypeName = CAT_SUBTYPE_ANGLE Then
        rotOk = TryRotateAngleViewByProjectedEdgeFamily(g_swDrwModel, swView, item, _
                                                        tag & " | angle projected model edge-family rotation", _
                                                        edgeFamilyDetail, edgeFamilyAvailable)
        fabDetail = edgeFamilyDetail

        If Not rotOk And edgeFamilyAvailable Then
            LogWarn "RotationFallback | blocked legacy fallback because angle model edge-family existed but failed strict validation | view=" & _
                    ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
                    " | detail={" & edgeFamilyDetail & "}"
            fallbackDetail = "blocked; angle edge-family existed but did not validate"
            GoTo BuildRotateDetail
        End If

        If Not rotOk Then
            LogWarn "RotationFallback | angle projected edge-family unavailable; allowing older fallback paths only as last resort | view=" & _
                    ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
                    " | detail={" & edgeFamilyDetail & "}"
        End If
    End If

    If Not rotOk Then
        rotOk = TryRotateViewByFabricationFrame(g_swDrwModel, swView, item, tag & " | primary 3D fabrication frame", fabDetail)
    End If

    If Not rotOk Then
        usedFallback = True
        LogWarn "RotateHybridCandidateAfterFaceValidation | deterministic fabrication-frame rotation did not validate; using legacy category fallback | view=" & _
                ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
                " | detail={" & fabDetail & "}"

        rotOk = TryRotateViewByCategoryReferenceAlignment(g_swDrwModel, swView, item, tag & " | legacy category fallback")
        fallbackDetail = "method=legacy-category-fallback | ok=" & BoolWord(rotOk)

        ' Point-cloud mass flipping is now only a legacy fallback polarity aid. The primary
        ' deterministic path chooses thetaA/thetaB from a 3D secondary/profile vector.
        If ApplySubtypePresentationFlipIfNeeded(g_swDrwModel, swView, item, tag & " | legacy fallback polarity", flipDetail) Then
            rotOk = True
        End If
    End If

BuildRotateDetail:
    afterAngle = swView.Angle
    RotateHybridCandidateAfterFaceValidation = (Abs(NormalizeAngleRad(afterAngle - beforeAngle)) > DegToRad(0.01))

    Dim afterScore As Double
    Dim afterDetail As String
    Dim afterValid As Boolean
    afterValid = ValidateOrientationResultByCategory(swView, item, afterScore, afterDetail)

    rotateDetail = "rotOk=" & BoolWord(rotOk) & _
                   " | rotated=" & BoolWord(RotateHybridCandidateAfterFaceValidation) & _
                   " | beforeAngleDeg=" & Format$(RadToDeg(beforeAngle), "0.000") & _
                   " | afterAngleDeg=" & Format$(RadToDeg(afterAngle), "0.000") & _
                   " | beforeValid=" & BoolWord(beforeValid) & _
                   " | afterValid=" & BoolWord(afterValid) & _
                   " | usedFallback=" & BoolWord(usedFallback) & _
                   " | angleEdgeFamily={" & edgeFamilyDetail & "}" & _
                   " | fab={" & fabDetail & "}" & _
                   " | fallback={" & fallbackDetail & "}" & _
                   " | legacyFlip={" & flipDetail & "}" & _
                   " | before={" & beforeDetail & "}" & _
                   " | after={" & afterDetail & "}"
    Exit Function

EH:
    rotateDetail = "RotateHybridCandidateAfterFaceValidation exception | " & Err.Number & " | " & Err.Description
    LogWarn rotateDetail & " | view=" & ViewGetNameSafe(swView)
    RotateHybridCandidateAfterFaceValidation = False
End Function

Private Function ApplySubtypePresentationFlipIfNeeded(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                                      ByVal swView As SldWorks.View, _
                                                      ByVal item As Object, _
                                                      ByVal tag As String, _
                                                      ByRef flipDetail As String) As Boolean
    On Error GoTo EH

    ApplySubtypePresentationFlipIfNeeded = False
    flipDetail = "not required"

    If swDrawModel Is Nothing Then Exit Function
    If swView Is Nothing Then Exit Function
    If item Is Nothing Then Exit Function

    Dim subtypeName As String
    subtypeName = UCase$(SafeStr(item("BodySubtype")))

    ' Angle fabrication convention for this workflow:
    ' one leg reads on the sheet and the other leg projects toward the viewer,
    ' preferably to the bottom side. A 180-degree flip preserves length
    ' horizontality while moving an upward-biased L/profile to the bottom.
    If subtypeName <> CAT_SUBTYPE_ANGLE Then
        flipDetail = "subtype=" & subtypeName & " | no presentation flip rule"
        Exit Function
    End If

    Dim pts As Collection
    Set pts = New Collection
    If Not CollectProjectedBodySamplePoints(swView, item, pts) Then
        flipDetail = "subtype=ANGLE | no projected point cloud for bottom-leg decision"
        Exit Function
    End If
    If pts.Count < 3 Then
        flipDetail = "subtype=ANGLE | insufficient projected points=" & CStr(pts.Count)
        Exit Function
    End If

    Dim minY As Double, maxY As Double
    Dim firstPt As Variant
    firstPt = pts(1)
    minY = CDbl(firstPt(1))
    maxY = CDbl(firstPt(1))

    Dim i As Long
    For i = 1 To pts.Count
        Dim pv As Variant
        pv = pts(i)
        If CDbl(pv(1)) < minY Then minY = CDbl(pv(1))
        If CDbl(pv(1)) > maxY Then maxY = CDbl(pv(1))
    Next i

    Dim spanY As Double
    spanY = maxY - minY
    If spanY <= 0.0000001 Then
        flipDetail = "subtype=ANGLE | no vertical profile span"
        Exit Function
    End If

    Dim centerY As Double
    centerY = (minY + maxY) / 2#

    Dim aboveScore As Double, belowScore As Double
    aboveScore = 0#: belowScore = 0#

    For i = 1 To pts.Count
        pv = pts(i)
        Dim dy As Double
        dy = CDbl(pv(1)) - centerY
        If dy > 0# Then
            aboveScore = aboveScore + dy
        Else
            belowScore = belowScore + Abs(dy)
        End If
    Next i

    Dim shouldFlip As Boolean
    shouldFlip = (aboveScore > (belowScore * 1.15)) And ((aboveScore - belowScore) > (spanY * 0.05))

    If shouldFlip Then
        Dim beforeDeg As Double
        Dim targetAngle As Double
        beforeDeg = RadToDeg(swView.Angle)
        targetAngle = NormalizeAngleRad(swView.Angle + PI)
        SetViewAngleAndRefresh swDrawModel, swView, targetAngle, tag & " | angle bottom-leg 180 flip"
        ApplySubtypePresentationFlipIfNeeded = True
        flipDetail = "subtype=ANGLE | applied=True | reason=profile mass above center; prefer projecting leg/bottom side" & _
                     " | beforeDeg=" & Format$(beforeDeg, "0.000") & _
                     " | afterDeg=" & Format$(RadToDeg(swView.Angle), "0.000") & _
                     " | aboveScore=" & Fmt(aboveScore) & _
                     " | belowScore=" & Fmt(belowScore) & _
                     " | spanY=" & Fmt(spanY)
        LogInfo "AnglePresentationFlip | view=" & ViewGetNameSafe(swView) & " | " & flipDetail
    Else
        flipDetail = "subtype=ANGLE | applied=False | bottom-side profile already acceptable or ambiguous" & _
                     " | aboveScore=" & Fmt(aboveScore) & _
                     " | belowScore=" & Fmt(belowScore) & _
                     " | spanY=" & Fmt(spanY)
        LogInfo "AnglePresentationFlip | view=" & ViewGetNameSafe(swView) & " | " & flipDetail
    End If

    Exit Function

EH:
    flipDetail = "ApplySubtypePresentationFlipIfNeeded exception | " & Err.Number & " | " & Err.Description
    LogWarn flipDetail & " | view=" & ViewGetNameSafe(swView)
    ApplySubtypePresentationFlipIfNeeded = False
End Function

Private Function ValidateHybridFinalCandidate(ByVal swView As SldWorks.View, _
                                              ByVal item As Object, _
                                              ByVal cand As Object, _
                                              ByRef validationScore As Double, _
                                              ByRef validationDetail As String) As Boolean
    On Error GoTo EH

    ValidateHybridFinalCandidate = False
    validationScore = -1E+30
    validationDetail = ""

    If ValidateOrientationResultByCategory(swView, item, validationScore, validationDetail) Then
        validationDetail = "method=projected-category | " & validationDetail
        ValidateHybridFinalCandidate = True
        Exit Function
    End If

    If Not cand Is Nothing Then
        If UCase$(SafeStr(cand("Source"))) = "BODY_FRAME" Then
            Dim strictDetail As String
            If StrictValidatePreparedBodyView(swView, item, cand, strictDetail) Then
                validationScore = 500000# + SafeCDbl(cand("Score"))
                validationDetail = "method=body-frame-strict-fallback | " & strictDetail
                ValidateHybridFinalCandidate = True
                Exit Function
            End If

            validationDetail = "projected-category failed; body-frame strict fallback failed | projected={" & validationDetail & "} | strict={" & strictDetail & "}"
            Exit Function
        End If
    End If

    validationDetail = "projected-category failed | " & validationDetail
    Exit Function

EH:
    validationDetail = "ValidateHybridFinalCandidate exception | " & Err.Number & " | " & Err.Description
    LogWarn validationDetail & " | view=" & ViewGetNameSafe(swView)
    ValidateHybridFinalCandidate = False
End Function

Private Function HybridCandidateFinalScore(ByVal sourceName As String, _
                                           ByVal preScore As Double, _
                                           ByVal readableScore As Double, _
                                           ByVal finalValidationScore As Double, _
                                           ByVal rotateApplied As Boolean) As Double
    On Error GoTo EH

    Dim sourceBonus As Double
    Select Case UCase$(Trim$(sourceName))
        Case "BODY_FRAME"
            If ALLOW_GENERATED_BODY_FRAME_VIEWS Then
                sourceBonus = -25000#
            Else
                sourceBonus = -100000000#
            End If
        Case "CUSTOM_VIEW"
            sourceBonus = 52500#
        Case "STANDARD_VIEW"
            sourceBonus = 55000#
        Case Else
            sourceBonus = 0#
    End Select

    HybridCandidateFinalScore = finalValidationScore + (readableScore * 0.25) + (preScore * 0.05) + sourceBonus
    If rotateApplied Then HybridCandidateFinalScore = HybridCandidateFinalScore - 250#
    Exit Function

EH:
    HybridCandidateFinalScore = finalValidationScore
End Function

Private Sub GetVisibleEntityCountsInView(ByVal swView As SldWorks.View, _
                                         ByRef edgeCt As Long, _
                                         ByRef vertexCt As Long, _
                                         ByRef faceCt As Long)
    On Error GoTo EH

    edgeCt = 0
    vertexCt = 0
    faceCt = 0
    If swView Is Nothing Then Exit Sub

    Dim vVisComps As Variant
    On Error Resume Next
    vVisComps = swView.GetVisibleComponents
    On Error GoTo EH

    If IsArray(vVisComps) Then
        Dim i As Long
        For i = LBound(vVisComps) To UBound(vVisComps)
            AddVisibleEntityCountsFromComp swView, vVisComps(i), edgeCt, vertexCt, faceCt
        Next i
    End If

    AddVisibleEntityCountsFromComp swView, Nothing, edgeCt, vertexCt, faceCt
    Exit Sub

EH:
    LogWarn "GetVisibleEntityCountsInView exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
End Sub

Private Sub AddVisibleEntityCountsFromComp(ByVal swView As SldWorks.View, _
                                           ByVal visComp As Variant, _
                                           ByRef edgeCt As Long, _
                                           ByRef vertexCt As Long, _
                                           ByRef faceCt As Long)
    On Error GoTo EH

    edgeCt = edgeCt + CountVisibleEntitiesByType(swView, visComp, swViewEntityType_e.swViewEntityType_Edge)
    vertexCt = vertexCt + CountVisibleEntitiesByType(swView, visComp, swViewEntityType_e.swViewEntityType_Vertex)
    faceCt = faceCt + CountVisibleEntitiesByType(swView, visComp, swViewEntityType_e.swViewEntityType_Face)
    Exit Sub

EH:
    LogWarn "AddVisibleEntityCountsFromComp exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
End Sub

Private Function CountVisibleEntitiesByType(ByVal swView As SldWorks.View, _
                                            ByVal visComp As Variant, _
                                            ByVal entType As Long) As Long
    On Error GoTo EH

    CountVisibleEntitiesByType = 0
    If swView Is Nothing Then Exit Function

    Dim vEnts As Variant
    On Error Resume Next
    vEnts = swView.GetVisibleEntities2(visComp, entType)
    If Err.Number <> 0 Then
        Err.Clear
        On Error GoTo EH
        Exit Function
    End If
    On Error GoTo EH

    If IsArray(vEnts) Then CountVisibleEntitiesByType = SafeArrayCount(vEnts)
    Exit Function

EH:
    CountVisibleEntitiesByType = 0
End Function

Private Sub GetBodyFrameExtentMetrics(ByVal item As Object, _
                                      ByVal xx As Double, ByVal xy As Double, ByVal xz As Double, _
                                      ByVal yx As Double, ByVal yy As Double, ByVal yz As Double, _
                                      ByVal zx As Double, ByVal zy As Double, ByVal zz As Double, _
                                      ByRef xSpan As Double, _
                                      ByRef ySpan As Double, _
                                      ByRef zSpan As Double)
    On Error GoTo EH

    xSpan = 0#: ySpan = 0#: zSpan = 0#
    If item Is Nothing Then Exit Sub

    Dim swBody As SldWorks.Body2
    Set swBody = Nothing
    On Error Resume Next
    Set swBody = item("RepBody_Model")
    On Error GoTo EH
    If swBody Is Nothing Then Exit Sub

    xSpan = GetBodyExtentAlongDirection(swBody, xx, xy, xz)
    ySpan = GetBodyExtentAlongDirection(swBody, yx, yy, yz)
    zSpan = GetBodyExtentAlongDirection(swBody, zx, zy, zz)
    Exit Sub

EH:
    LogWarn "GetBodyFrameExtentMetrics exception | " & Err.Number & " | " & Err.Description
    xSpan = 0#: ySpan = 0#: zSpan = 0#
End Sub

Private Function GetBodyExtentAlongDirection(ByVal swBody As SldWorks.Body2, _
                                             ByVal ux As Double, ByVal uy As Double, ByVal uz As Double) As Double
    On Error GoTo EH

    GetBodyExtentAlongDirection = 0#
    If swBody Is Nothing Then Exit Function
    If Not NormalizeVector3(ux, uy, uz) Then Exit Function

    Dim gotAny As Boolean
    Dim minP As Double, maxP As Double

    Dim vEdges As Variant
    vEdges = swBody.GetEdges
    If IsArray(vEdges) Then
        Dim i As Long
        For i = LBound(vEdges) To UBound(vEdges)
            Dim swEdge As SldWorks.Edge
            Set swEdge = vEdges(i)
            If Not swEdge Is Nothing Then
                Dim swV1 As SldWorks.Vertex
                Dim swV2 As SldWorks.Vertex
                Set swV1 = swEdge.GetStartVertex
                Set swV2 = swEdge.GetEndVertex
                AccumVertexProjection swV1, ux, uy, uz, gotAny, minP, maxP
                AccumVertexProjection swV2, ux, uy, uz, gotAny, minP, maxP
            End If
        Next i
    End If

    If gotAny Then
        GetBodyExtentAlongDirection = maxP - minP
        Exit Function
    End If

    GetBodyExtentAlongDirection = GetBodyBoxExtentAlongDirection(swBody, ux, uy, uz)
    Exit Function

EH:
    LogWarn "GetBodyExtentAlongDirection exception | " & Err.Number & " | " & Err.Description
    GetBodyExtentAlongDirection = 0#
End Function

Private Sub AccumVertexProjection(ByVal swVtx As SldWorks.Vertex, _
                                  ByVal ux As Double, ByVal uy As Double, ByVal uz As Double, _
                                  ByRef gotAny As Boolean, _
                                  ByRef minP As Double, _
                                  ByRef maxP As Double)
    On Error GoTo EH
    If swVtx Is Nothing Then Exit Sub

    Dim p As Variant
    p = swVtx.GetPoint
    If Not IsArray(p) Then Exit Sub

    Dim d As Double
    d = (CDbl(p(0)) * ux) + (CDbl(p(1)) * uy) + (CDbl(p(2)) * uz)
    If Not gotAny Then
        minP = d: maxP = d: gotAny = True
    Else
        If d < minP Then minP = d
        If d > maxP Then maxP = d
    End If
    Exit Sub

EH:
    LogWarn "AccumVertexProjection exception | " & Err.Number & " | " & Err.Description
End Sub

Private Function GetBodyBoxExtentAlongDirection(ByVal swBody As SldWorks.Body2, _
                                                ByVal ux As Double, ByVal uy As Double, ByVal uz As Double) As Double
    On Error GoTo EH

    GetBodyBoxExtentAlongDirection = 0#
    Dim bb As Variant
    bb = swBody.GetBodyBox
    If Not IsArray(bb) Then Exit Function

    Dim xs(0 To 1) As Double
    Dim ys(0 To 1) As Double
    Dim zs(0 To 1) As Double
    xs(0) = CDbl(bb(0)): xs(1) = CDbl(bb(3))
    ys(0) = CDbl(bb(1)): ys(1) = CDbl(bb(4))
    zs(0) = CDbl(bb(2)): zs(1) = CDbl(bb(5))

    Dim gotAny As Boolean
    Dim minP As Double, maxP As Double
    Dim ix As Long, iy As Long, iz As Long
    For ix = 0 To 1
        For iy = 0 To 1
            For iz = 0 To 1
                Dim d As Double
                d = (xs(ix) * ux) + (ys(iy) * uy) + (zs(iz) * uz)
                If Not gotAny Then
                    minP = d: maxP = d: gotAny = True
                Else
                    If d < minP Then minP = d
                    If d > maxP Then maxP = d
                End If
            Next iz
        Next iy
    Next ix

    If gotAny Then GetBodyBoxExtentAlongDirection = maxP - minP
    Exit Function

EH:
    GetBodyBoxExtentAlongDirection = 0#
End Function

'====================================================================================
' SORT CUT-LIST ITEMS BY PART NUMBER ASCENDING
'====================================================================================
Private Function SortCutListItemsByPartNo(ByVal src As Collection) As Collection
    On Error GoTo EH

    Dim outCol As New Collection
    If src Is Nothing Then
        Set SortCutListItemsByPartNo = outCol
        Exit Function
    End If

    If src.Count <= 1 Then
        Dim k As Long
        For k = 1 To src.Count
            outCol.Add src(k)
        Next k
        Set SortCutListItemsByPartNo = outCol
        Exit Function
    End If

    Dim arr() As Object
    ReDim arr(1 To src.Count)

    Dim i As Long, j As Long
    For i = 1 To src.Count
        Set arr(i) = src(i)
    Next i

    ' Insertion sort
    For i = 2 To UBound(arr)
        Dim tmp As Object
        Set tmp = arr(i)

        j = i - 1
        Do While j >= 1
            If CompareItemsByPartNo(arr(j), tmp) > 0 Then
                Set arr(j + 1) = arr(j)
                j = j - 1
            Else
                Exit Do
            End If
        Loop
        Set arr(j + 1) = tmp
    Next i

    For i = 1 To UBound(arr)
        outCol.Add arr(i)
        LogInfo "Sort[" & i & "] | pn=" & SafeStr(arr(i)("PartNo")) & " | itemNo=" & SafeStr(arr(i)("ItemNo"))
    Next i

    Set SortCutListItemsByPartNo = outCol
    Exit Function

EH:
    LogWarn "SortCutListItemsByPartNo exception | " & Err.Number & " | " & Err.Description & " | using unsorted order"
    Set SortCutListItemsByPartNo = src
End Function

Private Function CompareItemsByPartNo(ByVal a As Object, ByVal b As Object) As Long
    On Error GoTo EH

    Dim pa As String, pb As String
    pa = Trim$(SafeStr(a("PartNo")))
    pb = Trim$(SafeStr(b("PartNo")))

    If Len(pa) = 0 Then pa = "~"
    If Len(pb) = 0 Then pb = "~"

    CompareItemsByPartNo = ComparePartNoSmart(pa, pb)
    If CompareItemsByPartNo <> 0 Then Exit Function

    CompareItemsByPartNo = CompareItemNo(a, b)
    If CompareItemsByPartNo <> 0 Then Exit Function

    CompareItemsByPartNo = StrComp(SafeStr(a("Qty")), SafeStr(b("Qty")), vbTextCompare)
    Exit Function

EH:
    CompareItemsByPartNo = 0
End Function

Private Function ComparePartNoSmart(ByVal s1 As String, ByVal s2 As String) As Long
    On Error GoTo EH

    Dim stem1 As String, stem2 As String
    Dim n1 As Double, n2 As Double
    Dim hasNum1 As Boolean, hasNum2 As Boolean

    ParseTrailingNumber s1, stem1, n1, hasNum1
    ParseTrailingNumber s2, stem2, n2, hasNum2

    ComparePartNoSmart = StrComp(UCase$(stem1), UCase$(stem2), vbTextCompare)
    If ComparePartNoSmart <> 0 Then Exit Function

    If hasNum1 And hasNum2 Then
        If n1 < n2 Then
            ComparePartNoSmart = -1
        ElseIf n1 > n2 Then
            ComparePartNoSmart = 1
        Else
            ComparePartNoSmart = 0
        End If
        Exit Function
    End If

    ComparePartNoSmart = StrComp(UCase$(s1), UCase$(s2), vbTextCompare)
    Exit Function

EH:
    ComparePartNoSmart = StrComp(UCase$(s1), UCase$(s2), vbTextCompare)
End Function

Private Sub ParseTrailingNumber(ByVal s As String, _
                                ByRef stem As String, _
                                ByRef numVal As Double, _
                                ByRef hasNum As Boolean)
    On Error GoTo EH

    Dim i As Long
    Dim t As String
    t = Trim$(s)

    hasNum = False
    numVal = 0#
    stem = t

    If Len(t) = 0 Then Exit Sub

    i = Len(t)
    Do While i >= 1
        Dim ch As String
        ch = Mid$(t, i, 1)
        If (ch >= "0" And ch <= "9") Then
            i = i - 1
        Else
            Exit Do
        End If
    Loop

    If i < Len(t) Then
        Dim tail As String
        tail = Mid$(t, i + 1)
        If Len(tail) > 0 And IsNumeric(tail) Then
            hasNum = True
            numVal = CDbl(tail)
            stem = Left$(t, i)
            Exit Sub
        End If
    End If

    Exit Sub
EH:
    hasNum = False
    numVal = 0#
    stem = s
End Sub

Private Function CompareItemNo(ByVal a As Object, ByVal b As Object) As Long
    On Error GoTo EH

    Dim sa As String, sb As String
    sa = Trim$(SafeStr(a("ItemNo")))
    sb = Trim$(SafeStr(b("ItemNo")))

    If IsNumeric(sa) And IsNumeric(sb) Then
        Dim da As Double, db As Double
        da = CDbl(sa): db = CDbl(sb)
        If da < db Then
            CompareItemNo = -1
        ElseIf da > db Then
            CompareItemNo = 1
        Else
            CompareItemNo = 0
        End If
    Else
        CompareItemNo = StrComp(sa, sb, vbTextCompare)
    End If
    Exit Function

EH:
    CompareItemNo = 0
End Function


'====================================================================================
' CANDIDATE VIEW SELECTION (standard first, then user custom views)
'====================================================================================
Private Function CreatePreparedBestViewForItem(ByVal item As Object, _
                                               ByVal stagingX As Double, _
                                               ByVal stagingY As Double, _
                                               ByVal itemIdx As Long, _
                                               ByRef swBestView As SldWorks.View, _
                                               ByRef bestViewName As String) As Boolean
    On Error GoTo EH

    CreatePreparedBestViewForItem = False
    Set swBestView = Nothing
    bestViewName = ""

    Call EnsureItemCategoryAnalysis(item)

    Dim catName As String
    catName = SafeStr(item("BodyCategory"))

    Dim subtypeName As String
    subtypeName = UCase$(SafeStr(item("BodySubtype")))

    Dim cands As Collection
    Set cands = BuildHybridOrientationCandidates(item)

    If cands Is Nothing Or cands.Count = 0 Then
        LogError "HybridOrientation | no candidates | itemIdx=" & CStr(itemIdx) & _
                 " | itemNo=" & SafeStr(item("ItemNo")) & " | cat=" & catName & _
                 " | subtype=" & subtypeName
        Exit Function
    End If

    Dim candIdx As Long
    Dim bestScore As Double
    Dim bestDetail As String
    Dim bestSource As String
    Dim bestKind As String
    Dim bestTransportName As String
    Dim bestNeedsProtect As Boolean
    bestScore = -1E+300

    For candIdx = 1 To cands.Count
        Dim cand As Object
        Set cand = cands(candIdx)

        LogHybridCandidate itemIdx, candIdx, item, cand

        Dim swCandView As SldWorks.View
        Set swCandView = Nothing
        Dim transportName As String
        Dim createdOk As Boolean
        Dim needsProtect As Boolean
        Dim sourceName As String
        Dim kindName As String

        transportName = ""
        sourceName = UCase$(SafeStr(cand("Source")))
        kindName = SafeStr(cand("Kind"))
        needsProtect = False

        If sourceName = "BODY_FRAME" And Not ALLOW_GENERATED_BODY_FRAME_VIEWS Then
            LogWarn "HybridCandidateResult | REJECT_GENERATED_VIEW_DISABLED | itemIdx=" & CStr(itemIdx) & _
                    " | candIdx=" & CStr(candIdx) & _
                    " | kind=" & kindName
            GoTo NextHybridCandidate
        End If

        If sourceName = "BODY_FRAME" Then
            createdOk = CreatePreparedBodyCandidateView(item, cand, stagingX, stagingY, itemIdx, candIdx, swCandView, transportName)
            needsProtect = True
        Else
            transportName = SafeStr(cand("ViewName"))
            createdOk = CreatePreparedCandidateView(item, stagingX, stagingY, itemIdx, candIdx, transportName, swCandView)
        End If

        If Not createdOk Then
            LogWarn "HybridCandidateResult | CREATE_FAILED | itemIdx=" & CStr(itemIdx) & _
                    " | candIdx=" & CStr(candIdx) & _
                    " | source=" & sourceName & _
                    " | kind=" & kindName & _
                    " | transport=" & transportName
            GoTo NextHybridCandidate
        End If

        Dim emptyDetail As String
        If Not ValidateHybridCandidateNotEmpty(swCandView, item, emptyDetail) Then
            LogWarn "HybridCandidateResult | REJECT_EMPTY | itemIdx=" & CStr(itemIdx) & _
                    " | candIdx=" & CStr(candIdx) & _
                    " | source=" & sourceName & _
                    " | kind=" & kindName & _
                    " | transport=" & transportName & _
                    " | detail=" & emptyDetail
            SafeDeleteView g_swDrwModel, swCandView
            GoTo NextHybridCandidate
        End If

        Dim readableDetail As String
        Dim readableScore As Double
        If Not ValidateHybridCandidateReadablePreRotation(swCandView, item, cand, readableScore, readableDetail) Then
            LogWarn "HybridCandidateResult | REJECT_FACE | itemIdx=" & CStr(itemIdx) & _
                    " | candIdx=" & CStr(candIdx) & _
                    " | source=" & sourceName & _
                    " | kind=" & kindName & _
                    " | transport=" & transportName & _
                    " | empty={" & emptyDetail & "}" & _
                    " | detail=" & readableDetail
            SafeDeleteView g_swDrwModel, swCandView
            GoTo NextHybridCandidate
        End If

        Dim rotateApplied As Boolean
        Dim rotateDetail As String
        rotateApplied = RotateHybridCandidateAfterFaceValidation(swCandView, item, "hybrid itemIdx=" & CStr(itemIdx) & " candIdx=" & CStr(candIdx), rotateDetail)

        Dim postEmptyDetail As String
        If Not ValidateHybridCandidateNotEmpty(swCandView, item, postEmptyDetail) Then
            LogWarn "HybridCandidateResult | REJECT_EMPTY_AFTER_ROTATE | itemIdx=" & CStr(itemIdx) & _
                    " | candIdx=" & CStr(candIdx) & _
                    " | source=" & sourceName & _
                    " | kind=" & kindName & _
                    " | transport=" & transportName & _
                    " | rotateApplied=" & BoolWord(rotateApplied) & _
                    " | detail=" & postEmptyDetail
            SafeDeleteView g_swDrwModel, swCandView
            GoTo NextHybridCandidate
        End If

        Dim finalValidationScore As Double
        Dim finalValidationDetail As String
        If Not ValidateHybridFinalCandidate(swCandView, item, cand, finalValidationScore, finalValidationDetail) Then
            LogWarn "HybridCandidateResult | REJECT_FINAL | itemIdx=" & CStr(itemIdx) & _
                    " | candIdx=" & CStr(candIdx) & _
                    " | source=" & sourceName & _
                    " | kind=" & kindName & _
                    " | transport=" & transportName & _
                    " | rotateApplied=" & BoolWord(rotateApplied) & _
                    " | rotate={" & rotateDetail & "}" & _
                    " | pre={" & readableDetail & "}" & _
                    " | final=" & finalValidationDetail
            SafeDeleteView g_swDrwModel, swCandView
            GoTo NextHybridCandidate
        End If

        Dim candidateScore As Double
        Dim trustAdjustment As Double
        candidateScore = HybridCandidateFinalScore(sourceName, SafeCDbl(cand("Score")), readableScore, finalValidationScore, rotateApplied)
        trustAdjustment = HybridCandidateTrustAdjustment(sourceName, item, rotateDetail, finalValidationDetail)
        candidateScore = candidateScore + trustAdjustment

        LogInfo "HybridCandidateResult | VALID | itemIdx=" & CStr(itemIdx) & _
                " | candIdx=" & CStr(candIdx) & _
                " | source=" & sourceName & _
                " | kind=" & kindName & _
                " | transport=" & transportName & _
                " | view=" & ViewGetNameSafe(swCandView) & _
                " | rotateApplied=" & BoolWord(rotateApplied) & _
                " | trustAdj=" & Format$(trustAdjustment, "0.000") & _
                " | score=" & Format$(candidateScore, "0.000") & _
                " | empty={" & postEmptyDetail & "}" & _
                " | pre={" & readableDetail & "}" & _
                " | rotate={" & rotateDetail & "}" & _
                " | final={" & finalValidationDetail & "}"

        If swBestView Is Nothing Or candidateScore > bestScore Then
            If Not swBestView Is Nothing Then SafeDeleteView g_swDrwModel, swBestView
            Set swBestView = swCandView
            bestScore = candidateScore
            bestViewName = transportName
            bestTransportName = transportName
            bestSource = sourceName
            bestKind = kindName
            bestNeedsProtect = needsProtect
            bestDetail = "score=" & Format$(candidateScore, "0.000") & _
                         " | rotateApplied=" & BoolWord(rotateApplied) & _
                         " | rotate={" & rotateDetail & "}" & _
                         " | final={" & finalValidationDetail & "}"
        Else
            SafeDeleteView g_swDrwModel, swCandView
        End If

        If ShouldStopHybridCandidateSearchAfterValid(item, candIdx, cands.Count, bestSource, bestKind, bestDetail) Then
            LogInfo "HybridCandidateSearch | EARLY_STOP | itemIdx=" & CStr(itemIdx) & _
                    " | itemNo=" & SafeStr(item("ItemNo")) & _
                    " | subtype=" & SafeStr(item("BodySubtype")) & _
                    " | candIdx=" & CStr(candIdx) & _
                    " | totalCandidates=" & CStr(cands.Count) & _
                    " | winnerSource=" & bestSource & _
                    " | winnerKind=" & bestKind & _
                    " | reason=angle front/back projected model edge-family candidate already produced a valid fabrication view"
            Exit For
        End If

NextHybridCandidate:
        ForceSheetContext
        DoEvents
    Next candIdx

    If Not swBestView Is Nothing Then
        If bestNeedsProtect And ALLOW_GENERATED_BODY_FRAME_VIEWS Then ProtectTempModelView bestTransportName
        LogInfo "HybridOrientationWinner | itemIdx=" & CStr(itemIdx) & _
                " | itemNo=" & SafeStr(item("ItemNo")) & _
                " | pn=" & SafeStr(item("PartNo")) & _
                " | subtype=" & SafeStr(item("BodySubtype")) & _
                " | source=" & bestSource & _
                " | kind=" & bestKind & _
                " | transport=" & bestTransportName & _
                " | view=" & ViewGetNameSafe(swBestView) & _
                " | " & bestDetail
        CreatePreparedBestViewForItem = True
        Exit Function
    End If

    LogError "HybridOrientation | all candidates failed validation | itemIdx=" & CStr(itemIdx) & _
             " | itemNo=" & SafeStr(item("ItemNo")) & _
             " | pn=" & SafeStr(item("PartNo")) & _
             " | cat=" & catName & _
             " | candidateCount=" & CStr(cands.Count)
    Exit Function

EH:
    LogError "CreatePreparedBestViewForItem exception | " & Err.Number & " | " & Err.Description & _
             " | itemIdx=" & CStr(itemIdx)
    If Not swBestView Is Nothing Then SafeDeleteView g_swDrwModel, swBestView
    Set swBestView = Nothing
    CreatePreparedBestViewForItem = False
End Function

Private Function ShouldStopHybridCandidateSearchAfterValid(ByVal item As Object, _
                                                           ByVal candIdx As Long, _
                                                           ByVal totalCandidates As Long, _
                                                           ByVal bestSource As String, _
                                                           ByVal bestKind As String, _
                                                           ByVal bestDetail As String) As Boolean
    On Error GoTo EH

    ShouldStopHybridCandidateSearchAfterValid = False
    If item Is Nothing Then Exit Function

    Dim subtypeName As String
    subtypeName = UCase$(SafeStr(item("BodySubtype")))

    ' Keep custom/user views in the competition when they exist. For the common
    ' standard-only angle case, front/back are the two likely profile views; once
    ' both have been tested and one is valid by projected model edge-family rotation,
    ' the remaining top/bottom/left/right candidates mostly add runtime without
    ' improving fabrication intent. Never early-stop on the legacy point-cloud path.
    If subtypeName = CAT_SUBTYPE_ANGLE Then
        If totalCandidates = 6 And candIdx >= 2 Then
            If Len(bestSource) > 0 Then
                If InStr(1, bestDetail, "method=model-edge-family", vbTextCompare) > 0 And _
                   InStr(1, bestDetail, "valid=True", vbTextCompare) > 0 And _
                   InStr(1, bestDetail, "usedFallback=True", vbTextCompare) = 0 Then
                    ShouldStopHybridCandidateSearchAfterValid = True
                End If
            End If
        End If
    End If

    Exit Function

EH:
    ShouldStopHybridCandidateSearchAfterValid = False
End Function

Private Function CreatePreparedCandidateView(ByVal item As Object, _
                                             ByVal stagingX As Double, _
                                             ByVal stagingY As Double, _
                                             ByVal itemIdx As Long, _
                                             ByVal candSeq As Long, _
                                             ByVal viewName As String, _
                                             ByRef swOutView As SldWorks.View) As Boolean
    On Error GoTo EH

    CreatePreparedCandidateView = False
    Set swOutView = Nothing

    ForceSheetContext
    Call ActivateModelConfigurationSafe(g_refModel, g_refCfg)

    Set swOutView = g_swDraw.CreateDrawViewFromModelView3(g_modelPath, viewName, stagingX, stagingY, 0#)

    If swOutView Is Nothing Then
        LogWarn "CreatePreparedCandidateView | CreateDrawViewFromModelView3 failed | itemIdx=" & CStr(itemIdx) & _
                " | viewName=" & viewName
        Exit Function
    End If

    On Error Resume Next
    swOutView.PositionLocked = False
    swOutView.SetName2 "COPE_CAND_" & Format$(itemIdx, "0000") & "_" & Format$(candSeq, "0000")
    On Error GoTo EH

    If Not EnsureViewUsesConfiguration(swOutView, g_refCfg, "cand post-create itemIdx=" & CStr(itemIdx) & " view=" & viewName) Then
        LogWarn "CreatePreparedCandidateView | EnsureViewUsesConfiguration failed post-create | itemIdx=" & CStr(itemIdx) & _
                " | viewName=" & viewName & " | cfg=" & g_refCfg
        SafeDeleteView g_swDrwModel, swOutView
        Set swOutView = Nothing
        Exit Function
    End If

    If Not IsolateBodyFast(swOutView, item) Then
        LogWarn "CreatePreparedCandidateView | IsolateBodyFast failed | itemIdx=" & CStr(itemIdx) & _
                " | viewName=" & viewName
        SafeDeleteView g_swDrwModel, swOutView
        Set swOutView = Nothing
        Exit Function
    End If

    If Not EnsureViewUsesConfiguration(swOutView, g_refCfg, "cand post-isolate itemIdx=" & CStr(itemIdx) & " view=" & viewName) Then
        LogWarn "CreatePreparedCandidateView | EnsureViewUsesConfiguration failed post-isolate | itemIdx=" & CStr(itemIdx) & _
                " | viewName=" & viewName & " | cfg=" & g_refCfg
    End If

    g_swDrwModel.EditRebuild3
    ForceSheetContext

    CreatePreparedCandidateView = True
    Exit Function

EH:
    LogError "CreatePreparedCandidateView exception | " & Err.Number & " | " & Err.Description & _
             " | itemIdx=" & CStr(itemIdx) & " | viewName=" & viewName
    If Not swOutView Is Nothing Then SafeDeleteView g_swDrwModel, swOutView
    Set swOutView = Nothing
    CreatePreparedCandidateView = False
End Function

Private Function GetPrioritizedCandidateViewNames(ByVal swModel As SldWorks.ModelDoc2) As Collection
    On Error GoTo EH

    Dim outCol As New Collection
    Dim seen As Object
    Set seen = CreateObject("Scripting.Dictionary")

    If ORIENT_TRY_ALL_STANDARD_VIEWS Then
        AddCandidateViewName outCol, seen, "*Front"
        AddCandidateViewName outCol, seen, "*Back"
        AddCandidateViewName outCol, seen, "*Top"
        AddCandidateViewName outCol, seen, "*Bottom"
        AddCandidateViewName outCol, seen, "*Right"
        AddCandidateViewName outCol, seen, "*Left"
    End If

    If ORIENT_INCLUDE_CUSTOM_MODEL_VIEWS Then
        Dim vNames As Variant
        vNames = GetModelViewNamesSafe(swModel)

        If IsArray(vNames) Then
            Dim i As Long
            For i = LBound(vNames) To UBound(vNames)
                Dim nm As String
                nm = Trim$(SafeStr(vNames(i)))

                If Len(nm) > 0 Then
                    If Not IsStandardCandidateViewName(nm) Then
                        ' User-created named views typically do not start with "*".
                        If Left$(nm, 1) <> "*" Then
                            AddCandidateViewName outCol, seen, nm
                        End If
                    End If
                End If
            Next i
        End If
    End If

    Set GetPrioritizedCandidateViewNames = outCol
    Exit Function

EH:
    LogError "GetPrioritizedCandidateViewNames exception | " & Err.Number & " | " & Err.Description
    Set GetPrioritizedCandidateViewNames = New Collection
End Function

Private Sub AddCandidateViewName(ByVal outCol As Collection, ByVal seen As Object, ByVal nm As String)
    On Error GoTo EH

    Dim key As String
    key = UCase$(Trim$(nm))

    If Len(key) = 0 Then Exit Sub
    If seen.Exists(key) Then Exit Sub

    seen.Add key, True
    outCol.Add nm
    Exit Sub

EH:
    LogWarn "AddCandidateViewName exception | " & Err.Number & " | " & Err.Description & " | name=" & nm
End Sub

Private Function IsStandardCandidateViewName(ByVal nm As String) As Boolean
    Dim u As String
    u = UCase$(Trim$(nm))

    Select Case u
        Case "*FRONT", "*BACK", "*TOP", "*BOTTOM", "*RIGHT", "*LEFT"
            IsStandardCandidateViewName = True
        Case Else
            IsStandardCandidateViewName = False
    End Select
End Function

Private Function BuildHybridOrientationCandidates(ByVal item As Object) As Collection
    On Error GoTo EH

    Dim outCol As New Collection
    Set BuildHybridOrientationCandidates = outCol

    If item Is Nothing Then Exit Function

    Dim seen As Object
    Set seen = CreateObject("Scripting.Dictionary")

    Dim standardStartCt As Long
    standardStartCt = outCol.Count

    AddHybridNamedViewCandidate outCol, seen, "STANDARD_VIEW", "*Front", 600#, "standard orthographic candidate"
    AddHybridNamedViewCandidate outCol, seen, "STANDARD_VIEW", "*Back", 590#, "standard orthographic candidate"
    AddHybridNamedViewCandidate outCol, seen, "STANDARD_VIEW", "*Top", 580#, "standard orthographic candidate"
    AddHybridNamedViewCandidate outCol, seen, "STANDARD_VIEW", "*Bottom", 570#, "standard orthographic candidate"
    AddHybridNamedViewCandidate outCol, seen, "STANDARD_VIEW", "*Right", 560#, "standard orthographic candidate"
    AddHybridNamedViewCandidate outCol, seen, "STANDARD_VIEW", "*Left", 550#, "standard orthographic candidate"

    Dim standardCt As Long
    standardCt = outCol.Count - standardStartCt

    Dim customStartCt As Long
    customStartCt = outCol.Count

    Dim vNames As Variant
    vNames = GetModelViewNamesSafe(g_refModel)

    If IsArray(vNames) Then
        Dim j As Long
        For j = LBound(vNames) To UBound(vNames)
            Dim nm As String
            nm = Trim$(SafeStr(vNames(j)))

            If Len(nm) > 0 Then
                If Not IsStandardCandidateViewName(nm) Then
                    If Left$(nm, 1) <> "*" Then
                        If Not IsMacroTempModelViewName(nm) Then
                            AddHybridNamedViewCandidate outCol, seen, "CUSTOM_VIEW", nm, 700#, "user/custom named view candidate"
                        Else
                            LogInfo "HybridCandidatePool | skipped macro temp custom view | name=" & nm
                        End If
                    End If
                End If
            End If
        Next j
    End If

    Dim customCt As Long
    customCt = outCol.Count - customStartCt

    Dim bodyCandCount As Long
    bodyCandCount = 0

    If ALLOW_GENERATED_BODY_FRAME_VIEWS Then
        Dim bodyCands As Collection
        Set bodyCands = BuildBodyOrientationCandidates(item)

        If Not bodyCands Is Nothing Then
            Dim i As Long
            For i = 1 To bodyCands.Count
                Dim bodyCand As Object
                Set bodyCand = bodyCands(i)
                If Not bodyCand Is Nothing Then
                    bodyCand("Source") = "BODY_FRAME"
                    bodyCand("ViewName") = ""
                    bodyCand("CandidateLabel") = SafeStr(bodyCand("Kind"))
                    outCol.Add bodyCand
                    bodyCandCount = bodyCandCount + 1
                End If
            Next i
        End If
    Else
        LogInfo "HybridCandidatePool | generated body-frame named-view candidates disabled | itemNo=" & SafeStr(item("ItemNo")) & _
                " | pn=" & SafeStr(item("PartNo"))
    End If

    LogInfo "HybridCandidatePool | itemNo=" & SafeStr(item("ItemNo")) & _
            " | pn=" & SafeStr(item("PartNo")) & _
            " | cat=" & SafeStr(item("BodyCategory")) & _
            " | subtype=" & SafeStr(item("BodySubtype")) & _
            " | standardCandidates=" & CStr(standardCt) & _
            " | customCandidates=" & CStr(customCt) & _
            " | bodyCandidates=" & CStr(bodyCandCount) & _
            " | totalCandidates=" & CStr(outCol.Count)

    Exit Function

EH:
    LogError "BuildHybridOrientationCandidates exception | " & Err.Number & " | " & Err.Description & _
             " | itemNo=" & SafeStr(item("ItemNo"))
    Set BuildHybridOrientationCandidates = New Collection
End Function

Private Sub AddHybridNamedViewCandidate(ByVal outCol As Collection, _
                                        ByVal seen As Object, _
                                        ByVal sourceName As String, _
                                        ByVal viewName As String, _
                                        ByVal preScore As Double, _
                                        ByVal reason As String)
    On Error GoTo EH

    If outCol Is Nothing Then Exit Sub
    If seen Is Nothing Then Exit Sub
    If Len(Trim$(viewName)) = 0 Then Exit Sub

    Dim key As String
    key = UCase$(Trim$(sourceName)) & "|" & UCase$(Trim$(viewName))
    If seen.Exists(key) Then Exit Sub
    seen.Add key, True

    Dim cand As Object
    Set cand = CreateObject("Scripting.Dictionary")
    cand("Source") = UCase$(Trim$(sourceName))
    cand("ViewName") = viewName
    cand("Kind") = UCase$(Trim$(sourceName)) & ":" & viewName
    cand("CandidateLabel") = viewName
    cand("Score") = preScore
    cand("Reason") = reason
    outCol.Add cand
    Exit Sub

EH:
    LogWarn "AddHybridNamedViewCandidate exception | " & Err.Number & " | " & Err.Description & _
            " | source=" & sourceName & " | viewName=" & viewName
End Sub

Private Function IsMacroTempModelViewName(ByVal viewName As String) As Boolean
    IsMacroTempModelViewName = (Left$(UCase$(Trim$(viewName)), Len(TEMP_MODEL_VIEW_PREFIX)) = UCase$(TEMP_MODEL_VIEW_PREFIX))
End Function

Private Sub LogHybridCandidate(ByVal itemIdx As Long, ByVal candIdx As Long, ByVal item As Object, ByVal cand As Object)
    On Error GoTo EH

    If cand Is Nothing Then Exit Sub

    Dim sourceName As String
    sourceName = UCase$(SafeStr(cand("Source")))

    If sourceName = "BODY_FRAME" Then
        LogInfo "HybridCandidate | itemIdx=" & CStr(itemIdx) & _
                " | candIdx=" & CStr(candIdx) & _
                " | source=BODY_FRAME" & _
                " | cat=" & SafeStr(item("BodyCategory")) & _
                " | kind=" & SafeStr(cand("Kind")) & _
                " | preScore=" & Format$(SafeCDbl(cand("Score")), "0.000") & _
                " | x=(" & Fmt(SafeCDbl(cand("Xx"))) & "," & Fmt(SafeCDbl(cand("Xy"))) & "," & Fmt(SafeCDbl(cand("Xz"))) & ")" & _
                " | y=(" & Fmt(SafeCDbl(cand("Yx"))) & "," & Fmt(SafeCDbl(cand("Yy"))) & "," & Fmt(SafeCDbl(cand("Yz"))) & ")" & _
                " | z=(" & Fmt(SafeCDbl(cand("Zx"))) & "," & Fmt(SafeCDbl(cand("Zy"))) & "," & Fmt(SafeCDbl(cand("Zz"))) & ")" & _
                " | reason=" & SafeStr(cand("Reason"))
    Else
        LogInfo "HybridCandidate | itemIdx=" & CStr(itemIdx) & _
                " | candIdx=" & CStr(candIdx) & _
                " | source=" & sourceName & _
                " | cat=" & SafeStr(item("BodyCategory")) & _
                " | viewName=" & SafeStr(cand("ViewName")) & _
                " | kind=" & SafeStr(cand("Kind")) & _
                " | preScore=" & Format$(SafeCDbl(cand("Score")), "0.000") & _
                " | reason=" & SafeStr(cand("Reason"))
    End If
    Exit Sub

EH:
    LogWarn "LogHybridCandidate exception | " & Err.Number & " | " & Err.Description & _
            " | itemIdx=" & CStr(itemIdx) & " | candIdx=" & CStr(candIdx)
End Sub

Private Function GetModelViewNamesSafe(ByVal swModel As SldWorks.ModelDoc2) As Variant
    On Error GoTo EH

    GetModelViewNamesSafe = Empty
    If swModel Is Nothing Then Exit Function

    Dim vNames As Variant

    Err.Clear
    On Error Resume Next
    vNames = CallByName(swModel, "GetModelViewNames", VbMethod)
    If Err.Number <> 0 Then
        LogWarn "GetModelViewNamesSafe | CallByName(GetModelViewNames) failed | " & Err.Number & " | " & Err.Description
        Err.Clear
        On Error GoTo EH
        Exit Function
    End If
    On Error GoTo EH

    If IsArray(vNames) Then
        GetModelViewNamesSafe = vNames
    Else
        LogWarn "GetModelViewNamesSafe | returned non-array / empty result"
    End If

    Exit Function

EH:
    LogWarn "GetModelViewNamesSafe exception | " & Err.Number & " | " & Err.Description
    GetModelViewNamesSafe = Empty
End Function

Private Function EvaluateOrientationCandidateView(ByVal item As Object, _
                                                  ByVal swView As SldWorks.View, _
                                                  ByVal viewName As String, _
                                                  ByRef hasRef As Boolean, _
                                                  ByRef bestMetric As Double, _
                                                  ByRef absAngDeg As Double, _
                                                  ByRef outlineW As Double, _
                                                  ByRef outlineH As Double, _
                                                  ByRef outlineScore As Double, _
                                                  ByRef compositeScore As Double, _
                                                  ByRef detail As String) As Boolean
    On Error GoTo EH

    EvaluateOrientationCandidateView = False
    hasRef = False
    bestMetric = 0#
    absAngDeg = 9999#
    outlineW = 0#
    outlineH = 0#
    outlineScore = 0#
    compositeScore = -1E+300
    detail = ""

    If swView Is Nothing Then Exit Function

    Call EnsureItemCategoryAnalysis(item)

    Dim catName As String
    Dim reason As String
    catName = SafeStr(item("BodyCategory"))
    reason = SafeStr(item("BodyCategoryReason"))

    Dim famBest As Double, famAng As Double, famSecond As Double, famDetail As String
    Dim spanLen As Double, spanAng As Double, cloudW As Double, cloudH As Double, cloudArea As Double, cloudDetail As String
    Dim ptCt As Long
    Dim visLen As Double, visAng As Double, visDesc As String
    Dim edgeCt As Long, lineCt As Long

    Dim pcPtCt As Long
    Dim pcMajor As Double, pcMajorAng As Double
    Dim pcMinor As Double, pcMinorAng As Double
    Dim pcRatio As Double, pcDetail As String

    Dim profileMetric As Double
    Dim faceMetric As Double
    Dim penalty As Double
    Dim rejectNote As String

    Dim snapMetric As Double
    Dim snapAngRad As Double
    Dim snapQuality As Double
    Dim snapDesc As String
    Dim snapDeltaH As Double
    Dim snapDeltaV As Double
    Dim snapState As String
    Dim snapRank As Long

    Call GetProjectedDominantDirectionMetrics(swView, item, famBest, famAng, famSecond, famDetail)
    Call GetBodyProjectedPointCloudMetrics(swView, item, ptCt, spanLen, spanAng, cloudW, cloudH, cloudArea, cloudDetail)
    Call GetBodyProjectedPrincipalMetrics(swView, item, pcPtCt, pcMajor, pcMajorAng, pcMinor, pcMinorAng, pcRatio, pcDetail)

    visLen = 0#: visAng = 0#: visDesc = ""
    edgeCt = 0: lineCt = 0
    Call GetBestVisibleLinearEdgeInView(swView, visLen, visAng, visDesc, edgeCt, lineCt)

    If TryGetViewOutlineWH(swView, outlineW, outlineH) Then
        outlineScore = HorizontalScoreWH(outlineW, outlineH)
    Else
        outlineScore = 0#
    End If

    profileMetric = MaxD(pcMinor, famSecond)
    faceMetric = pcMajor * pcMinor
    penalty = 0#
    rejectNote = ""

    Select Case UCase$(catName)
        Case UCase$(CAT_NAME_LINEAR_STRUCTURAL)
            If pcMajor > 0# Then
                hasRef = True
                bestMetric = pcMajor
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(pcMajorAng)))
            ElseIf famBest > 0# Then
                hasRef = True
                bestMetric = famBest
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(famAng)))
            ElseIf spanLen > 0# Then
                hasRef = True
                bestMetric = spanLen
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(spanAng)))
            ElseIf visLen > 0# Then
                hasRef = True
                bestMetric = visLen
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(visAng)))
            End If

            compositeScore = (pcMajor * 1400000#) + _
                             (profileMetric * 900000#) + _
                             (famBest * 400000#) + _
                             (famSecond * 300000#) + _
                             (cloudArea * 150000#) + _
                             (spanLen * 100000#) + _
                             (visLen * 1000#) + _
                             (outlineScore * 0.02)

            If pcMajor > 0# Then
                If profileMetric < MaxD(CAT_LINEAR_PROFILE_ABS_MIN, pcMajor * CAT_LINEAR_PROFILE_RATIO_MIN) Then
                    penalty = penalty + CAT_REJECT_PENALTY
                    rejectNote = AppendRejectNote(rejectNote, "linear end-on/readability penalty")
                End If
            End If

        Case UCase$(CAT_NAME_SHEET_PLATE)
            If pcMajor > 0# Then
                hasRef = True
                bestMetric = pcMajor
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(pcMajorAng)))
            ElseIf spanLen > 0# Then
                hasRef = True
                bestMetric = spanLen
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(spanAng)))
            ElseIf famBest > 0# Then
                hasRef = True
                bestMetric = famBest
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(famAng)))
            ElseIf visLen > 0# Then
                hasRef = True
                bestMetric = visLen
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(visAng)))
            End If

            compositeScore = (faceMetric * 2500000#) + _
                             (pcMinor * 1500000#) + _
                             (pcMajor * 500000#) + _
                             (cloudArea * 300000#) + _
                             (spanLen * 50000#) + _
                             (visLen * 500#) + _
                             (outlineScore * 0.02)

            If pcMajor > 0# Then
                If pcMinor < MaxD(CAT_PLATE_FACE_ABS_MIN, pcMajor * CAT_PLATE_FACE_RATIO_MIN) Then
                    penalty = penalty + CAT_REJECT_PENALTY
                    rejectNote = AppendRejectNote(rejectNote, "sheet edge-on penalty")
                End If
            End If

        Case UCase$(CAT_NAME_CURVED_STRUCTURAL)
            If pcMajor > 0# Then
                hasRef = True
                bestMetric = pcMajor
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(pcMajorAng)))
            ElseIf spanLen > 0# Then
                hasRef = True
                bestMetric = spanLen
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(spanAng)))
            ElseIf visLen > 0# Then
                hasRef = True
                bestMetric = visLen
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(visAng)))
            ElseIf famBest > 0# Then
                hasRef = True
                bestMetric = famBest
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(famAng)))
            End If

            compositeScore = (pcMajor * 900000#) + _
                             (pcMinor * 400000#) + _
                             (cloudArea * 500000#) + _
                             (spanLen * 300000#) + _
                             (visLen * 500#) + _
                             (outlineScore * 0.02)

        Case UCase$(CAT_NAME_ROUND_STOCK)
            If famBest > 0# Then
                hasRef = True
                bestMetric = famBest
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(famAng)))
            ElseIf pcMajor > 0# Then
                hasRef = True
                bestMetric = pcMajor
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(pcMajorAng)))
            ElseIf spanLen > 0# Then
                hasRef = True
                bestMetric = spanLen
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(spanAng)))
            ElseIf visLen > 0# Then
                hasRef = True
                bestMetric = visLen
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(visAng)))
            End If

            compositeScore = (famBest * 1000000#) + _
                             (pcMajor * 250000#) + _
                             (cloudArea * 100000#) + _
                             (visLen * 1000#) + _
                             (outlineScore * 0.02)

        Case Else
            If pcMajor > 0# Then
                hasRef = True
                bestMetric = pcMajor
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(pcMajorAng)))
            ElseIf spanLen > 0# Then
                hasRef = True
                bestMetric = spanLen
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(spanAng)))
            ElseIf famBest > 0# Then
                hasRef = True
                bestMetric = famBest
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(famAng)))
            ElseIf visLen > 0# Then
                hasRef = True
                bestMetric = visLen
                absAngDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(visAng)))
            End If

            compositeScore = (faceMetric * 500000#) + _
                             (pcMajor * 350000#) + _
                             (pcMinor * 250000#) + _
                             (cloudArea * 200000#) + _
                             (spanLen * 120000#) + _
                             (visLen * 1000#) + _
                             (outlineScore * 0.02)
    End Select

    If penalty > 0# Then compositeScore = compositeScore - penalty

    snapMetric = 0#: snapAngRad = 0#: snapQuality = -1E+30: snapDesc = ""
    snapDeltaH = 9999#: snapDeltaV = 9999#: snapState = "NO_REF": snapRank = 0

    If GetSnapReferenceForView(swView, item, snapMetric, snapAngRad, snapQuality, snapDesc) Then
        snapRank = GetOrientationSnapState(snapAngRad, ROT_SNAP_TOL_DEG, snapDeltaH, snapDeltaV, snapState)

        If (penalty <= 0#) Or (penalty < CAT_REJECT_PENALTY) Then
            Select Case snapRank
                Case 2
                    compositeScore = compositeScore + ORIENT_SNAP_HORIZONTAL_BONUS
                Case 1
                    compositeScore = compositeScore + ORIENT_SNAP_VERTICAL_BONUS
            End Select
        End If

        If Not hasRef Then
            hasRef = True
            bestMetric = snapMetric
            absAngDeg = MinD(snapDeltaH, snapDeltaV)
        End If
    End If

    If Len(rejectNote) > 0 Then
        detail = "reason=" & reason & _
                 " | famBest=" & Fmt(famBest) & _
                 " | famSecond=" & Fmt(famSecond) & _
                 " | spanLen=" & Fmt(spanLen) & _
                 " | cloud=" & Fmt(cloudW) & "x" & Fmt(cloudH) & _
                 " | cloudArea=" & Fmt(cloudArea) & _
                 " | pts=" & CStr(ptCt) & _
                 " | pcMajor=" & Fmt(pcMajor) & "@ang=" & Format$(RadToDeg(pcMajorAng), "0.0") & _
                 " | pcMinor=" & Fmt(pcMinor) & _
                 " | pcRatio=" & Fmt(pcRatio) & _
                 " | faceMetric=" & Fmt(faceMetric) & _
                 " | visLen=" & Fmt(visLen) & _
                 " | edgesScanned=" & CStr(edgeCt) & _
                 " | lineCandidates=" & CStr(lineCt) & _
                 " | snap=" & snapState & _
                 " | snapDeltaH=" & Format$(snapDeltaH, "0.000") & _
                 " | snapDeltaV=" & Format$(snapDeltaV, "0.000") & _
                 " | fam={" & famDetail & "}" & _
                 " | cloud={" & cloudDetail & "}" & _
                 " | pc={" & pcDetail & "}" & _
                 " | rejectHint=" & rejectNote
    Else
        detail = "reason=" & reason & _
                 " | famBest=" & Fmt(famBest) & _
                 " | famSecond=" & Fmt(famSecond) & _
                 " | spanLen=" & Fmt(spanLen) & _
                 " | cloud=" & Fmt(cloudW) & "x" & Fmt(cloudH) & _
                 " | cloudArea=" & Fmt(cloudArea) & _
                 " | pts=" & CStr(ptCt) & _
                 " | pcMajor=" & Fmt(pcMajor) & "@ang=" & Format$(RadToDeg(pcMajorAng), "0.0") & _
                 " | pcMinor=" & Fmt(pcMinor) & _
                 " | pcRatio=" & Fmt(pcRatio) & _
                 " | faceMetric=" & Fmt(faceMetric) & _
                 " | visLen=" & Fmt(visLen) & _
                 " | edgesScanned=" & CStr(edgeCt) & _
                 " | lineCandidates=" & CStr(lineCt) & _
                 " | snap=" & snapState & _
                 " | snapDeltaH=" & Format$(snapDeltaH, "0.000") & _
                 " | snapDeltaV=" & Format$(snapDeltaV, "0.000") & _
                 " | fam={" & famDetail & "}" & _
                 " | cloud={" & cloudDetail & "}" & _
                 " | pc={" & pcDetail & "}"
    End If

    EvaluateOrientationCandidateView = True
    Exit Function

EH:
    LogWarn "EvaluateOrientationCandidateView exception | " & Err.Number & " | " & Err.Description & _
            " | itemNo=" & SafeStr(item("ItemNo")) & _
            " | cand=" & viewName
    EvaluateOrientationCandidateView = False
End Function

Private Function IsOrientationCandidateBetter(ByVal candCompositeScore As Double, _
                                              ByVal candIsStd As Boolean, _
                                              ByVal candMetric As Double, _
                                              ByVal candAbsAngDeg As Double, _
                                              ByVal bestCompositeScore As Double, _
                                              ByVal bestIsStd As Boolean, _
                                              ByVal bestMetric As Double, _
                                              ByVal bestAbsAngDeg As Double) As Boolean
    On Error GoTo EH

    IsOrientationCandidateBetter = False

    If candCompositeScore > (bestCompositeScore + 0.0001) Then
        IsOrientationCandidateBetter = True
        Exit Function
    End If

    If bestCompositeScore > (candCompositeScore + 0.0001) Then
        Exit Function
    End If

    If candIsStd And (Not bestIsStd) Then
        IsOrientationCandidateBetter = True
        Exit Function
    End If

    If bestIsStd And (Not candIsStd) Then
        Exit Function
    End If

    If candMetric > (bestMetric + 0.0001) Then
        IsOrientationCandidateBetter = True
        Exit Function
    End If

    If bestMetric > (candMetric + 0.0001) Then
        Exit Function
    End If

    If candAbsAngDeg < (bestAbsAngDeg - 0.05) Then
        IsOrientationCandidateBetter = True
        Exit Function
    End If

    Exit Function

EH:
    IsOrientationCandidateBetter = False
End Function

Private Function ChooseBestStdViewFromBBoxFast(ByVal item As Object) As String
    On Error GoTo EH

    Dim dx As Double, dy As Double, dz As Double
    dx = CDbl(item("dx"))
    dy = CDbl(item("dy"))
    dz = CDbl(item("dz"))

    If dx <= 0# Or dy <= 0# Or dz <= 0# Then
        ChooseBestStdViewFromBBoxFast = "*Front"
        Exit Function
    End If

    Dim sFront As Double, sTopV As Double, sRight As Double
    sFront = ScoreProjectedViewFastV1(dx, dy)
    sTopV = ScoreProjectedViewFastV1(dx, dz)
    sRight = ScoreProjectedViewFastV1(dy, dz)

    If sFront >= sTopV And sFront >= sRight Then
        ChooseBestStdViewFromBBoxFast = "*Front"
    ElseIf sTopV >= sFront And sTopV >= sRight Then
        ChooseBestStdViewFromBBoxFast = "*Top"
    Else
        ChooseBestStdViewFromBBoxFast = "*Right"
    End If
    Exit Function

EH:
    ChooseBestStdViewFromBBoxFast = "*Front"
End Function

Private Function ScoreProjectedViewFastV1(ByVal a As Double, ByVal b As Double) As Double
    On Error GoTo EH

    Dim longD As Double, shortD As Double
    Dim area As Double, ratio As Double

    longD = a: shortD = b
    If shortD > longD Then SwapD longD, shortD

    area = a * b

    If shortD > 0# Then
        ratio = longD / shortD
    Else
        ratio = 1E+30
    End If

    ScoreProjectedViewFastV1 = (area * 1000#) - (ratio * 5#) + (longD * 25#) + (shortD * 10#)
    Exit Function

EH:
    ScoreProjectedViewFastV1 = 0#
End Function

'====================================================================================
' FAST BODY ISOLATION
'====================================================================================
Private Function IsolateBodyFast(ByVal swV As SldWorks.View, _
                                 ByVal item As Object) As Boolean
    On Error GoTo EH

    Dim repB As SldWorks.Body2
    Set repB = item("RepBody_Model")

    If repB Is Nothing Then
        LogError "IsolateBodyFast | RepBody_Model is Nothing | item=" & SafeStr(item("ItemNo"))
        IsolateBodyFast = False
        Exit Function
    End If

    LogInfo "   IsolateBodyFast | start | item=" & SafeStr(item("ItemNo")) & _
            " | view=" & ViewGetNameSafe(swV)

    If TrySetSingleBodyInView(swV, repB, "direct") Then
        LogInfo "   IsolateBodyFast | direct OK | item=" & SafeStr(item("ItemNo"))
        IsolateBodyFast = True
        Exit Function
    End If

    LogWarn "   IsolateBodyFast | direct path failed | trying fallback selection path"
    IsolateBodyFast = IsolateBodyFallback(swV, item)
    Exit Function

EH:
    LogError "IsolateBodyFast exception | " & Err.Number & " | " & Err.Description
    IsolateBodyFast = False
End Function

Private Function IsolateBodyFallback(ByVal swV As SldWorks.View, _
                                     ByVal item As Object) As Boolean
    On Error GoTo EH

    Dim repB As SldWorks.Body2
    Set repB = item("RepBody_Model")
    If repB Is Nothing Then
        LogError "IsolateBodyFallback | RepBody_Model is Nothing | item=" & SafeStr(item("ItemNo"))
        IsolateBodyFallback = False
        Exit Function
    End If

    Dim vName As String
    vName = ViewGetNameSafe(swV)

    g_swDrwModel.ClearSelection2 True

    If Not g_swDrwModel.Extension.SelectByID2(vName, "DRAWINGVIEW", 0#, 0#, 0#, False, 0, Nothing, 0) Then
        LogWarn "   IsolateBodyFallback | failed to select drawing view | view=" & vName
    Else
        LogInfo "   IsolateBodyFallback | selected drawing view | view=" & vName
    End If

    IsolateBodyFallback = TrySetSingleBodyInView(swV, repB, "fallback")

    g_swDrwModel.ClearSelection2 True

    LogInfo "   IsolateBodyFallback | item=" & SafeStr(item("ItemNo")) & _
            " | result=" & CStr(IsolateBodyFallback) & _
            " | finalBodyCount=" & CStr(GetViewBodyCountSafe(swV))
    Exit Function

EH:
    LogError "IsolateBodyFallback exception | " & Err.Number & " | " & Err.Description
    IsolateBodyFallback = False
End Function

Private Function TrySetSingleBodyInView(ByVal swV As SldWorks.View, _
                                        ByVal repB As SldWorks.Body2, _
                                        ByVal stageTag As String) As Boolean
    On Error GoTo EH

    Dim ct As Long

    TrySetSingleBodyInView = False

    If swV Is Nothing Then
        LogError "TrySetSingleBodyInView | view is Nothing | stage=" & stageTag
        Exit Function
    End If

    If repB Is Nothing Then
        LogError "TrySetSingleBodyInView | body is Nothing | stage=" & stageTag
        Exit Function
    End If

    Dim viewBody As SldWorks.Body2
    Set viewBody = ResolveViewReferencedBody(swV, repB)
    If viewBody Is Nothing Then
        LogWarn "   TrySetSingleBodyInView | could not resolve representative body against view referenced document | stage=" & stageTag & _
                " | repBody=" & GetBodyNameSafe(repB)
        Set viewBody = repB
    End If

    Dim bodyObjs(0) As Object
    Dim bodiesIn As Variant
    Set bodyObjs(0) = viewBody
    bodiesIn = bodyObjs

    ' VBA requires an Object array variant here; a Variant() array can be
    ' accepted syntactically but leave the view showing every body.
    Err.Clear
    On Error Resume Next
    swV.Bodies = (bodiesIn)
    If Err.Number <> 0 Then
        LogWarn "   TrySetSingleBodyInView | Bodies property failed | stage=" & stageTag & _
                " | " & Err.Number & " | " & Err.Description
        Err.Clear
    End If
    swV.Update
    On Error GoTo EH

    g_swDrwModel.EditRebuild3

    ct = GetViewBodyCountSafe(swV)
    LogInfo "   TrySetSingleBodyInView | after Bodies property | stage=" & stageTag & _
            " | bodyCount=" & CStr(ct) & _
            " | repBody=" & GetBodyNameSafe(repB) & _
            " | viewBody=" & GetBodyNameSafe(viewBody)

    If ct = 1 Then
        TrySetSingleBodyInView = True
        Exit Function
    End If

    LogWarn "   TrySetSingleBodyInView | Bodies property did not isolate exactly one body; failing view instead of using unsupported alternate VBA body path | stage=" & stageTag & _
            " | bodyCount=" & CStr(ct)
    Exit Function

EH:
    LogError "TrySetSingleBodyInView exception | " & Err.Number & " | " & Err.Description & _
             " | stage=" & stageTag & " | view=" & ViewGetNameSafe(swV)
    TrySetSingleBodyInView = False
End Function

Private Function ResolveViewReferencedBody(ByVal swV As SldWorks.View, _
                                           ByVal repB As SldWorks.Body2) As SldWorks.Body2
    On Error GoTo EH

    Set ResolveViewReferencedBody = Nothing
    If swV Is Nothing Then Exit Function
    If repB Is Nothing Then Exit Function

    Dim refDoc As SldWorks.ModelDoc2
    Set refDoc = Nothing
    On Error Resume Next
    Set refDoc = swV.ReferencedDocument
    On Error GoTo EH

    If refDoc Is Nothing Then
        Set ResolveViewReferencedBody = repB
        Exit Function
    End If

    If refDoc.GetType <> swDocumentTypes_e.swDocPART Then
        Set ResolveViewReferencedBody = repB
        Exit Function
    End If

    Dim swPart As SldWorks.PartDoc
    Set swPart = refDoc
    If swPart Is Nothing Then
        Set ResolveViewReferencedBody = repB
        Exit Function
    End If

    Dim vBodies As Variant
    vBodies = swPart.GetBodies2(swBodyType_e.swSolidBody, True)
    If Not IsArray(vBodies) Then
        Set ResolveViewReferencedBody = repB
        Exit Function
    End If

    Dim repName As String
    repName = UCase$(Trim$(GetBodyNameSafe(repB)))

    If Len(repName) > 0 Then
        Dim i As Long
        For i = LBound(vBodies) To UBound(vBodies)
            Dim byName As SldWorks.Body2
            Set byName = vBodies(i)
            If Not byName Is Nothing Then
                If StrComp(UCase$(Trim$(GetBodyNameSafe(byName))), repName, vbTextCompare) = 0 Then
                    Set ResolveViewReferencedBody = byName
                    LogInfo "ResolveViewReferencedBody | matched by name | body=" & GetBodyNameSafe(byName)
                    Exit Function
                End If
            End If
        Next i
    End If

    Dim rBox As Variant
    rBox = Empty
    On Error Resume Next
    rBox = repB.GetBodyBox
    If Err.Number <> 0 Then
        Err.Clear
        rBox = Empty
    End If
    On Error GoTo EH

    Dim bestBody As SldWorks.Body2
    Dim bestDelta As Double
    bestDelta = 1E+30

    For i = LBound(vBodies) To UBound(vBodies)
        Dim candB As SldWorks.Body2
        Set candB = vBodies(i)
        If Not candB Is Nothing Then
            Dim delta As Double
            delta = GetBodyBoxArrayDelta(rBox, candB)
            If delta < bestDelta Then
                bestDelta = delta
                Set bestBody = candB
            End If
        End If
    Next i

    If Not bestBody Is Nothing Then
        Set ResolveViewReferencedBody = bestBody
        LogInfo "ResolveViewReferencedBody | matched by bbox | rep=" & GetBodyNameSafe(repB) & _
                " | resolved=" & GetBodyNameSafe(bestBody) & _
                " | delta=" & Fmt(bestDelta)
    Else
        Set ResolveViewReferencedBody = repB
    End If
    Exit Function

EH:
    LogWarn "ResolveViewReferencedBody exception | " & Err.Number & " | " & Err.Description
    Set ResolveViewReferencedBody = repB
End Function

Private Function GetBodyBoxArrayDelta(ByVal refBox As Variant, ByVal candB As SldWorks.Body2) As Double
    On Error GoTo EH

    GetBodyBoxArrayDelta = 1E+30
    If candB Is Nothing Then Exit Function
    If Not IsArray(refBox) Then Exit Function
    If UBound(refBox) - LBound(refBox) < 5 Then Exit Function

    Dim candBox As Variant
    candBox = candB.GetBodyBox
    If Not IsArray(candBox) Then Exit Function
    If UBound(candBox) - LBound(candBox) < 5 Then Exit Function

    Dim i As Long
    Dim d As Double
    For i = 0 To 5
        d = d + Abs(CDbl(refBox(i)) - CDbl(candBox(i)))
    Next i

    GetBodyBoxArrayDelta = d
    Exit Function

EH:
    GetBodyBoxArrayDelta = 1E+30
End Function

Private Function GetViewBodyCountSafe(ByVal swV As SldWorks.View) As Long
    On Error GoTo EH

    GetViewBodyCountSafe = -1
    If swV Is Nothing Then Exit Function

    On Error Resume Next
    GetViewBodyCountSafe = swV.GetBodiesCount
    If Err.Number <> 0 Then
        LogWarn "GetViewBodyCountSafe | GetBodiesCount failed | " & Err.Number & " | " & Err.Description
        Err.Clear
        GetViewBodyCountSafe = -1
    End If
    On Error GoTo 0

    Exit Function

EH:
    LogError "GetViewBodyCountSafe exception | " & Err.Number & " | " & Err.Description
    GetViewBodyCountSafe = -1
End Function

'====================================================================================
' ROTATION
'====================================================================================

Private Function RotateViewToBestHorizontal(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                            ByVal swView As SldWorks.View, _
                                            ByVal item As Object, _
                                            ByVal tag As String) As Boolean
    On Error GoTo EH

    RotateViewToBestHorizontal = False
    If swView Is Nothing Then Exit Function

    Call EnsureItemCategoryAnalysis(item)

    LogInfo "RotateViewToBestHorizontal | start | view=" & ViewGetNameSafe(swView) & _
            " | cat=" & SafeStr(item("BodyCategory")) & _
            " | tag=" & tag

    If TryRotateViewByCategoryReferenceAlignment(swDrawModel, swView, item, tag & " | direct category first") Then
        RotateViewToBestHorizontal = True
        Exit Function
    End If

    LogInfo "RotateViewToBestHorizontal | direct category alignment did not finish orientation | view=" & ViewGetNameSafe(swView)

    If TryApplySnapFirstOrientation(swDrawModel, swView, item, tag) Then
        RotateViewToBestHorizontal = True
        Exit Function
    End If

    LogInfo "RotateViewToBestHorizontal | snap-first did not finish orientation | continue guarded fallbacks | view=" & ViewGetNameSafe(swView)

    If ROT_USE_STAGED_DISCRETE_SEARCH Then
        If TryRotateViewByDiscreteHorizontalSearch(swDrawModel, swView, item, tag) Then
            RotateViewToBestHorizontal = True
            Exit Function
        End If

        LogWarn "RotateViewToBestHorizontal | staged discrete category search did not fully solve orientation | fallback=category direct align | view=" & ViewGetNameSafe(swView)
    End If

    If TryRotateViewByCategoryReferenceAlignment(swDrawModel, swView, item, tag) Then
        RotateViewToBestHorizontal = True
        Exit Function
    End If

    If ROT_USE_VISIBLE_LINEAR_EDGE_ALIGN Then
        If TryRotateViewByLongestVisibleLinearEdge(swDrawModel, swView, tag) Then
            RotateViewToBestHorizontal = True
            Exit Function
        End If

        LogWarn "RotateViewToBestHorizontal | direct visible-edge align path did not produce a usable result | fallback=bbox sweep | view=" & ViewGetNameSafe(swView)
    End If

    LogWarn "RotateViewToBestHorizontal | no deterministic rotation reference succeeded; refusing bbox sweep rescue | view=" & ViewGetNameSafe(swView)
    RotateViewToBestHorizontal = False
    Exit Function

EH:
    LogError "RotateViewToBestHorizontal exception | " & Err.Number & " | " & Err.Description & " | " & tag
    RotateViewToBestHorizontal = False
End Function

Private Function TryRotateViewByDiscreteHorizontalSearch(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                                         ByVal swView As SldWorks.View, _
                                                         ByVal item As Object, _
                                                         ByVal tag As String) As Boolean
    On Error GoTo EH

    TryRotateViewByDiscreteHorizontalSearch = False
    If swView Is Nothing Then Exit Function

    Dim baseAngleRad As Double
    baseAngleRad = swView.Angle

    Dim tested As Object
    Set tested = CreateObject("Scripting.Dictionary")

    Dim stepList As Variant
    stepList = Array(90#, 45#, 30#, 15#, 10#, 5#, 2#, 1#)

    Dim stepVar As Variant

    Dim anyFound As Boolean
    Dim anyBestAngleRad As Double
    Dim anyBestRemainDeg As Double
    Dim anyBestMetric As Double
    Dim anyBestQuality As Double
    Dim anyBestDesc As String
    Dim anyBestStepDeg As Double

    anyBestRemainDeg = 1E+30
    anyBestMetric = 0#
    anyBestQuality = -1E+30
    anyBestAngleRad = baseAngleRad
    anyBestDesc = ""

    For Each stepVar In stepList
        Dim stepDeg As Double
        stepDeg = CDbl(stepVar)

        Dim grpFound As Boolean
        Dim grpBestAngleRad As Double
        Dim grpBestRemainDeg As Double
        Dim grpBestMetric As Double
        Dim grpBestQuality As Double
        Dim grpBestDesc As String

        If EvaluateDiscreteRotationGroup(swDrawModel, swView, item, baseAngleRad, stepDeg, tested, _
                                         grpFound, grpBestAngleRad, grpBestRemainDeg, grpBestMetric, grpBestQuality, grpBestDesc, tag) Then

            LogInfo "RotDiscrete | group summary | cat=" & SafeStr(item("BodyCategory")) & _
                    " | stepDeg=" & Format$(stepDeg, "0.###") & _
                    " | bestRemainDeg=" & Format$(grpBestRemainDeg, "0.000") & _
                    " | metric=" & Fmt(grpBestMetric) & _
                    " | quality=" & Format$(grpBestQuality, "0.000") & _
                    " | ref=" & grpBestDesc

            If (Not anyFound) Or IsRotationCandidateBetter(grpBestRemainDeg, grpBestMetric, grpBestQuality, _
                                                           anyBestRemainDeg, anyBestMetric, anyBestQuality) Then
                anyFound = True
                anyBestAngleRad = grpBestAngleRad
                anyBestRemainDeg = grpBestRemainDeg
                anyBestMetric = grpBestMetric
                anyBestQuality = grpBestQuality
                anyBestDesc = grpBestDesc
                anyBestStepDeg = stepDeg
            End If

            If grpFound And grpBestRemainDeg <= ROT_DISCRETE_SUCCESS_TOL_DEG Then
                Dim succValid As Boolean
                Dim succScore As Double
                Dim succDetail As String

                succValid = ValidateOrientationResultByCategory(swView, item, succScore, succDetail)

                If succValid Then
                    LogInfo "RotDiscrete | SUCCESS | cat=" & SafeStr(item("BodyCategory")) & _
                            " | stepDeg=" & Format$(stepDeg, "0.###") & _
                            " | remainDeg=" & Format$(grpBestRemainDeg, "0.000") & _
                            " | metric=" & Fmt(grpBestMetric) & _
                            " | ref=" & grpBestDesc & _
                            " | verify=" & succDetail
                    TryRotateViewByDiscreteHorizontalSearch = True
                    Exit Function
                Else
                    LogWarn "RotDiscrete | false-positive prevented by validation | cat=" & SafeStr(item("BodyCategory")) & _
                            " | stepDeg=" & Format$(stepDeg, "0.###") & _
                            " | remainDeg=" & Format$(grpBestRemainDeg, "0.000") & _
                            " | ref=" & grpBestDesc & _
                            " | verify=" & succDetail
                End If
            End If
        End If
    Next stepVar

    If anyFound Then
        SetViewAngleAndRefresh swDrawModel, swView, anyBestAngleRad, _
                               tag & " | best discrete cat=" & SafeStr(item("BodyCategory")) & _
                               " step=" & Format$(anyBestStepDeg, "0.###")

        Dim finalValid As Boolean
        Dim finalScore As Double
        Dim finalDetail As String
        Dim finalRefMetric As Double
        Dim finalRefAng As Double
        Dim finalRefQual As Double
        Dim finalRefDesc As String
        Dim finalRemain As Double

        finalValid = ValidateOrientationResultByCategory(swView, item, finalScore, finalDetail)
        If GetSnapReferenceForView(swView, item, finalRefMetric, finalRefAng, finalRefQual, finalRefDesc) Then
            finalRemain = Abs(RadToDeg(NormalizeAngleToHorizontal(finalRefAng)))
        Else
            finalRemain = 9999#
        End If

        If finalValid And finalRemain <= ROT_DISCRETE_SUCCESS_TOL_DEG Then
            LogInfo "RotDiscrete | final validated best | cat=" & SafeStr(item("BodyCategory")) & _
                    " | remainDeg=" & Format$(finalRemain, "0.000") & _
                    " | metric=" & Fmt(finalRefMetric) & _
                    " | verify=" & finalDetail
            TryRotateViewByDiscreteHorizontalSearch = True
            Exit Function
        End If

        LogWarn "RotDiscrete | best-effort only; validation failed or angle still off | cat=" & SafeStr(item("BodyCategory")) & _
                " | remainDeg=" & Format$(finalRemain, "0.000") & _
                " | metric=" & Fmt(anyBestMetric) & _
                " | ref=" & anyBestDesc & _
                " | verify=" & finalDetail
    End If

    Exit Function

EH:
    LogError "TryRotateViewByDiscreteHorizontalSearch exception | " & Err.Number & " | " & Err.Description & _
             " | view=" & ViewGetNameSafe(swView)
    TryRotateViewByDiscreteHorizontalSearch = False
End Function


Private Function EvaluateDiscreteRotationGroup(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                               ByVal swView As SldWorks.View, _
                                               ByVal item As Object, _
                                               ByVal baseAngleRad As Double, _
                                               ByVal stepDeg As Double, _
                                               ByVal tested As Object, _
                                               ByRef grpFound As Boolean, _
                                               ByRef grpBestAngleRad As Double, _
                                               ByRef grpBestRemainDeg As Double, _
                                               ByRef grpBestMetric As Double, _
                                               ByRef grpBestQuality As Double, _
                                               ByRef grpBestDesc As String, _
                                               ByVal tag As String) As Boolean
    On Error GoTo EH

    EvaluateDiscreteRotationGroup = False
    grpFound = False
    grpBestRemainDeg = 1E+30
    grpBestMetric = 0#
    grpBestQuality = -1E+30
    grpBestAngleRad = baseAngleRad
    grpBestDesc = ""

    Dim angleList As Collection
    Set angleList = BuildDiscreteAngleList(baseAngleRad, stepDeg)

    Dim angVar As Variant
    For Each angVar In angleList
        Dim candAngRad As Double
        candAngRad = CDbl(angVar)

        Dim key As String
        key = Format$(NormalizeDeg360(RadToDeg(candAngRad)), "0.000")

        If tested.Exists(key) Then GoTo NextAngle
        tested.Add key, True

        SetViewAngleAndRefresh swDrawModel, swView, candAngRad, _
                               tag & " | discrete cat=" & SafeStr(item("BodyCategory")) & " step=" & Format$(stepDeg, "0.###")

        Dim hasRef As Boolean
        Dim refMetric As Double
        Dim remainDeg As Double
        Dim qualityScore As Double
        Dim refDesc As String
        Dim validNow As Boolean
        Dim validScore As Double
        Dim validDetail As String

        If EvaluateRotationCandidateAtCurrentAngle(swView, item, hasRef, refMetric, remainDeg, qualityScore, refDesc) Then
            validNow = ValidateOrientationResultByCategory(swView, item, validScore, validDetail)

            If validNow Then
                qualityScore = qualityScore + (CAT_REJECT_PENALTY / 2#)
            Else
                qualityScore = qualityScore - (CAT_REJECT_PENALTY / 4#)
            End If

            LogInfo "RotDiscreteCandidate | cat=" & SafeStr(item("BodyCategory")) & _
                    " | stepDeg=" & Format$(stepDeg, "0.###") & _
                    " | viewDeg=" & Format$(RadToDeg(candAngRad), "0.000") & _
                    " | hasRef=" & BoolWord(hasRef) & _
                    " | remainDeg=" & Format$(remainDeg, "0.000") & _
                    " | metric=" & Fmt(refMetric) & _
                    " | quality=" & Format$(qualityScore, "0.000") & _
                    " | valid=" & BoolWord(validNow) & _
                    " | valScore=" & Format$(validScore, "0.000") & _
                    " | ref=" & refDesc & _
                    " | verify=" & validDetail

            If hasRef Then
                If (Not grpFound) Or IsRotationCandidateBetter(remainDeg, refMetric, qualityScore, _
                                                               grpBestRemainDeg, grpBestMetric, grpBestQuality) Then
                    grpFound = True
                    grpBestAngleRad = candAngRad
                    grpBestRemainDeg = remainDeg
                    grpBestMetric = refMetric
                    grpBestQuality = qualityScore
                    grpBestDesc = refDesc & " | verify=" & validDetail
                End If

                If remainDeg <= ROT_DISCRETE_SUCCESS_TOL_DEG And validNow Then
                    EvaluateDiscreteRotationGroup = True
                    Exit Function
                End If
            End If
        End If

NextAngle:
        DoEvents
    Next angVar

    If grpFound Then
        SetViewAngleAndRefresh swDrawModel, swView, grpBestAngleRad, _
                               tag & " | best in group cat=" & SafeStr(item("BodyCategory")) & " step=" & Format$(stepDeg, "0.###")
        EvaluateDiscreteRotationGroup = True
        Exit Function
    End If

    SetViewAngleAndRefresh swDrawModel, swView, baseAngleRad, _
                           tag & " | restore group start cat=" & SafeStr(item("BodyCategory")) & " step=" & Format$(stepDeg, "0.###")
    EvaluateDiscreteRotationGroup = False
    Exit Function

EH:
    LogError "EvaluateDiscreteRotationGroup exception | " & Err.Number & " | " & Err.Description & _
             " | view=" & ViewGetNameSafe(swView) & " | stepDeg=" & Format$(stepDeg, "0.###")
    EvaluateDiscreteRotationGroup = False
End Function

Private Function EvaluateRotationCandidateAtCurrentAngle(ByVal swView As SldWorks.View, _
                                                         ByVal item As Object, _
                                                         ByRef hasRef As Boolean, _
                                                         ByRef bestMetric As Double, _
                                                         ByRef remainDeg As Double, _
                                                         ByRef qualityScore As Double, _
                                                         ByRef bestDesc As String) As Boolean
    On Error GoTo EH

    EvaluateRotationCandidateAtCurrentAngle = False
    hasRef = False
    bestMetric = 0#
    remainDeg = 9999#
    qualityScore = -1E+30
    bestDesc = ""

    If swView Is Nothing Then Exit Function

    Dim refAngRad As Double
    If GetSnapReferenceForView(swView, item, bestMetric, refAngRad, qualityScore, bestDesc) Then
        hasRef = True
        remainDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(refAngRad)))
    End If

    If hasRef Then
        EvaluateRotationCandidateAtCurrentAngle = True
    End If

    Exit Function

EH:
    LogWarn "EvaluateRotationCandidateAtCurrentAngle exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
    EvaluateRotationCandidateAtCurrentAngle = False
End Function

Private Function IsRotationCandidateBetter(ByVal candRemainDeg As Double, _
                                           ByVal candMetric As Double, _
                                           ByVal candQualityScore As Double, _
                                           ByVal bestRemainDeg As Double, _
                                           ByVal bestMetric As Double, _
                                           ByVal bestQualityScore As Double) As Boolean
    On Error GoTo EH

    IsRotationCandidateBetter = False

    If candRemainDeg < (bestRemainDeg - 0.05) Then
        IsRotationCandidateBetter = True
        Exit Function
    End If

    If bestRemainDeg < (candRemainDeg - 0.05) Then
        Exit Function
    End If

    If candMetric > (bestMetric + 0.0001) Then
        IsRotationCandidateBetter = True
        Exit Function
    End If

    If bestMetric > (candMetric + 0.0001) Then
        Exit Function
    End If

    If candQualityScore > (bestQualityScore + 0.5) Then
        IsRotationCandidateBetter = True
        Exit Function
    End If

    Exit Function

EH:
    IsRotationCandidateBetter = False
End Function

Private Function NormalizeDeg360(ByVal degVal As Double) As Double
    Do While degVal < 0#
        degVal = degVal + 360#
    Loop
    Do While degVal >= 360#
        degVal = degVal - 360#
    Loop
    NormalizeDeg360 = degVal
End Function

Private Function TryRotateViewByLongestVisibleLinearEdge(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                                         ByVal swView As SldWorks.View, _
                                                         ByVal tag As String) As Boolean
    On Error GoTo EH

    TryRotateViewByLongestVisibleLinearEdge = False
    If swView Is Nothing Then Exit Function

    Dim bestLen As Double
    Dim bestAngRad As Double
    Dim bestDesc As String
    Dim edgeCt As Long
    Dim lineCt As Long

    If Not GetBestVisibleLinearEdgeInView(swView, bestLen, bestAngRad, bestDesc, edgeCt, lineCt) Then
        LogWarn "TryRotateViewByLongestVisibleLinearEdge | no visible linear edge found | edgesScanned=" & CStr(edgeCt) & _
                " | lineCandidates=" & CStr(lineCt) & " | view=" & ViewGetNameSafe(swView)
        Exit Function
    End If

    If bestLen < ROT_MIN_VISIBLE_EDGE_LEN Then
        LogWarn "TryRotateViewByLongestVisibleLinearEdge | best edge below minimum usable length | len=" & Fmt(bestLen) & _
                " | min=" & Fmt(ROT_MIN_VISIBLE_EDGE_LEN) & " | desc=" & bestDesc & " | view=" & ViewGetNameSafe(swView)
        Exit Function
    End If

    Dim currentViewAngle As Double
    Dim deltaToHorizontal As Double
    Dim targetAngle As Double

    currentViewAngle = swView.Angle
    deltaToHorizontal = NormalizeAngleToHorizontal(bestAngRad)
    targetAngle = NormalizeAngleRad(currentViewAngle - deltaToHorizontal)

    LogInfo "   RotEdgePick | pass=1 | view=" & ViewGetNameSafe(swView) & _
            " | edgesScanned=" & CStr(edgeCt) & " | lineCandidates=" & CStr(lineCt) & _
            " | bestLen=" & Fmt(bestLen) & " | bestAngDeg=" & Format$(RadToDeg(bestAngRad), "0.000") & _
            " | deltaDeg=" & Format$(RadToDeg(deltaToHorizontal), "0.000") & _
            " | currentViewDeg=" & Format$(RadToDeg(currentViewAngle), "0.000") & _
            " | targetViewDeg=" & Format$(RadToDeg(targetAngle), "0.000") & _
            " | edge=" & bestDesc

    SetViewAngleAndRefresh swDrawModel, swView, targetAngle, tag & " edge pass1"

    ' Confirm after first pass. Visible edge order can change slightly after rotation.
    Dim confirmLen As Double
    Dim confirmAngRad As Double
    Dim confirmDesc As String
    Dim confirmEdgeCt As Long
    Dim confirmLineCt As Long
    Dim remainingDeg As Double

    If GetBestVisibleLinearEdgeInView(swView, confirmLen, confirmAngRad, confirmDesc, confirmEdgeCt, confirmLineCt) Then
        remainingDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(confirmAngRad)))

        LogInfo "   RotEdgeConfirm | pass=1 | view=" & ViewGetNameSafe(swView) & _
                " | bestLen=" & Fmt(confirmLen) & " | bestAngDeg=" & Format$(RadToDeg(confirmAngRad), "0.000") & _
                " | remainingDeg=" & Format$(remainingDeg, "0.000") & " | edge=" & confirmDesc

        If remainingDeg > ROT_EDGE_CONFIRM_TOL_DEG Then
            currentViewAngle = swView.Angle
            deltaToHorizontal = NormalizeAngleToHorizontal(confirmAngRad)
            targetAngle = NormalizeAngleRad(currentViewAngle - deltaToHorizontal)

            LogInfo "   RotEdgePick | pass=2 | view=" & ViewGetNameSafe(swView) & _
                    " | deltaDeg=" & Format$(RadToDeg(deltaToHorizontal), "0.000") & _
                    " | currentViewDeg=" & Format$(RadToDeg(currentViewAngle), "0.000") & _
                    " | targetViewDeg=" & Format$(RadToDeg(targetAngle), "0.000") & _
                    " | edge=" & confirmDesc

            SetViewAngleAndRefresh swDrawModel, swView, targetAngle, tag & " edge pass2"

            If GetBestVisibleLinearEdgeInView(swView, confirmLen, confirmAngRad, confirmDesc, confirmEdgeCt, confirmLineCt) Then
                remainingDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(confirmAngRad)))
                LogInfo "   RotEdgeConfirm | pass=2 | view=" & ViewGetNameSafe(swView) & _
                        " | bestLen=" & Fmt(confirmLen) & " | bestAngDeg=" & Format$(RadToDeg(confirmAngRad), "0.000") & _
                        " | remainingDeg=" & Format$(remainingDeg, "0.000") & " | edge=" & confirmDesc
            End If
        End If
    End If

    TryRotateViewByLongestVisibleLinearEdge = True
    Exit Function

EH:
    LogError "TryRotateViewByLongestVisibleLinearEdge exception | " & Err.Number & " | " & Err.Description & " | " & tag
    TryRotateViewByLongestVisibleLinearEdge = False
End Function

Private Function GetBestVisibleLinearEdgeInView(ByVal swView As SldWorks.View, _
                                                ByRef bestLen As Double, _
                                                ByRef bestAngRad As Double, _
                                                ByRef bestDesc As String, _
                                                ByRef edgeCt As Long, _
                                                ByRef lineCt As Long) As Boolean
    On Error GoTo EH

    GetBestVisibleLinearEdgeInView = False
    bestLen = 0#
    bestAngRad = 0#
    bestDesc = ""
    edgeCt = 0
    lineCt = 0

    If swView Is Nothing Then Exit Function

    Dim swXf As SldWorks.MathTransform
    Set swXf = swView.ModelToViewTransform
    If swXf Is Nothing Then
        LogWarn "GetBestVisibleLinearEdgeInView | ModelToViewTransform is Nothing | view=" & ViewGetNameSafe(swView)
        Exit Function
    End If

    Dim seen As Object
    Set seen = CreateObject("Scripting.Dictionary")

    Dim vVisComps As Variant
    On Error Resume Next
    vVisComps = swView.GetVisibleComponents
    On Error GoTo EH

    If IsArray(vVisComps) Then
        Dim i As Long
        For i = LBound(vVisComps) To UBound(vVisComps)
            ScanVisibleLinearEdgesFromComponent swView, vVisComps(i), swXf, seen, bestLen, bestAngRad, bestDesc, edgeCt, lineCt
        Next i
    End If

    ' Fallback: some SW builds can still return entities with a Nothing component.
    ScanVisibleLinearEdgesFromComponent swView, Nothing, swXf, seen, bestLen, bestAngRad, bestDesc, edgeCt, lineCt

    If lineCt > 0 And bestLen > 0# Then
        GetBestVisibleLinearEdgeInView = True
    End If

    Exit Function

EH:
    LogError "GetBestVisibleLinearEdgeInView exception | " & Err.Number & " | " & Err.Description & _
             " | view=" & ViewGetNameSafe(swView)
    GetBestVisibleLinearEdgeInView = False
End Function

Private Function GetBestVisibleLinearEdgeMatchingModelAxis(ByVal swView As SldWorks.View, _
                                                           ByVal axisX As Double, _
                                                           ByVal axisY As Double, _
                                                           ByVal axisZ As Double, _
                                                           ByRef bestLen As Double, _
                                                           ByRef bestAngRad As Double, _
                                                           ByRef bestDesc As String, _
                                                           ByRef edgeCt As Long, _
                                                           ByRef lineCt As Long, _
                                                           ByRef matchedCt As Long) As Boolean
    On Error GoTo EH

    GetBestVisibleLinearEdgeMatchingModelAxis = False
    bestLen = 0#
    bestAngRad = 0#
    bestDesc = ""
    edgeCt = 0
    lineCt = 0
    matchedCt = 0

    If swView Is Nothing Then Exit Function
    If Not NormalizeVector3(axisX, axisY, axisZ) Then Exit Function

    Dim swXf As SldWorks.MathTransform
    Set swXf = swView.ModelToViewTransform
    If swXf Is Nothing Then Exit Function

    Dim seen As Object
    Set seen = CreateObject("Scripting.Dictionary")

    Dim vVisComps As Variant
    On Error Resume Next
    vVisComps = swView.GetVisibleComponents
    On Error GoTo EH

    If IsArray(vVisComps) Then
        Dim i As Long
        For i = LBound(vVisComps) To UBound(vVisComps)
            ScanVisibleLinearEdgesMatchingAxisFromComponent swView, vVisComps(i), swXf, seen, _
                                                            axisX, axisY, axisZ, bestLen, bestAngRad, bestDesc, _
                                                            edgeCt, lineCt, matchedCt
        Next i
    End If

    ScanVisibleLinearEdgesMatchingAxisFromComponent swView, Nothing, swXf, seen, _
                                                    axisX, axisY, axisZ, bestLen, bestAngRad, bestDesc, _
                                                    edgeCt, lineCt, matchedCt

    If matchedCt > 0 And bestLen > 0# Then
        GetBestVisibleLinearEdgeMatchingModelAxis = True
    End If

    Exit Function

EH:
    LogWarn "GetBestVisibleLinearEdgeMatchingModelAxis exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
    GetBestVisibleLinearEdgeMatchingModelAxis = False
End Function

Private Sub ScanVisibleLinearEdgesMatchingAxisFromComponent(ByVal swView As SldWorks.View, _
                                                           ByVal visComp As Variant, _
                                                           ByVal swXf As SldWorks.MathTransform, _
                                                           ByVal seen As Object, _
                                                           ByVal axisX As Double, _
                                                           ByVal axisY As Double, _
                                                           ByVal axisZ As Double, _
                                                           ByRef bestLen As Double, _
                                                           ByRef bestAngRad As Double, _
                                                           ByRef bestDesc As String, _
                                                           ByRef edgeCt As Long, _
                                                           ByRef lineCt As Long, _
                                                           ByRef matchedCt As Long)
    On Error GoTo EH

    If swView Is Nothing Then Exit Sub
    If seen Is Nothing Then Exit Sub

    Dim vVisEnts As Variant
    On Error Resume Next
    vVisEnts = swView.GetVisibleEntities2(visComp, swViewEntityType_e.swViewEntityType_Edge)
    If Err.Number <> 0 Then
        Err.Clear
        On Error GoTo EH
        Exit Sub
    End If
    On Error GoTo EH

    If Not IsArray(vVisEnts) Then Exit Sub

    Dim cosTol As Double
    cosTol = Cos(DegToRad(CAT_FAMILY_ANGLE_TOL_DEG))

    Dim i As Long
    For i = LBound(vVisEnts) To UBound(vVisEnts)
        Dim objEnt As Object
        Set objEnt = Nothing

        On Error Resume Next
        Set objEnt = vVisEnts(i)
        On Error GoTo EH

        If Not objEnt Is Nothing Then
            If TypeOf objEnt Is SldWorks.Edge Then
                Dim swEdge As SldWorks.Edge
                Set swEdge = objEnt

                Dim edgeKey As String
                edgeKey = CStr(ObjPtr(swEdge))

                If Not seen.Exists(edgeKey) Then
                    seen.Add edgeKey, True
                    edgeCt = edgeCt + 1

                    Dim ux As Double, uy As Double, uz As Double, modelLen As Double
                    If TryGetModelLineEdgeDirection(swEdge, ux, uy, uz, modelLen) Then
                        lineCt = lineCt + 1

                        Dim dotAbs As Double
                        dotAbs = Abs(Dot3(axisX, axisY, axisZ, ux, uy, uz))

                        If dotAbs >= cosTol Then
                            Dim projectedLen As Double, angRad As Double, edgeDesc As String
                            If TryGetProjectedVisibleLinearEdgeData(swView, swEdge, swXf, projectedLen, angRad, edgeDesc) Then
                                matchedCt = matchedCt + 1

                                If projectedLen > bestLen Then
                                    bestLen = projectedLen
                                    bestAngRad = angRad
                                    bestDesc = edgeDesc & _
                                               " | modelLen=" & Fmt(modelLen) & _
                                               " | dotPrimary=" & Fmt(dotAbs)
                                End If
                            End If
                        End If
                    End If
                End If
            End If
        End If
    Next i

    Exit Sub

EH:
    LogWarn "ScanVisibleLinearEdgesMatchingAxisFromComponent exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
End Sub

Private Sub ScanVisibleLinearEdgesFromComponent(ByVal swView As SldWorks.View, _
                                                ByVal visComp As Variant, _
                                                ByVal swXf As SldWorks.MathTransform, _
                                                ByVal seen As Object, _
                                                ByRef bestLen As Double, _
                                                ByRef bestAngRad As Double, _
                                                ByRef bestDesc As String, _
                                                ByRef edgeCt As Long, _
                                                ByRef lineCt As Long)
    On Error GoTo EH

    If swView Is Nothing Then Exit Sub

    Dim vVisEnts As Variant
    On Error Resume Next
    vVisEnts = swView.GetVisibleEntities2(visComp, swViewEntityType_e.swViewEntityType_Edge)
    If Err.Number <> 0 Then
        Err.Clear
        On Error GoTo EH
        Exit Sub
    End If
    On Error GoTo EH

    If Not IsArray(vVisEnts) Then Exit Sub

    Dim i As Long
    For i = LBound(vVisEnts) To UBound(vVisEnts)
        Dim objEnt As Object
        Set objEnt = Nothing

        On Error Resume Next
        Set objEnt = vVisEnts(i)
        On Error GoTo EH

        If Not objEnt Is Nothing Then
            If TypeOf objEnt Is SldWorks.Edge Then
                Dim swEdge As SldWorks.Edge
                Set swEdge = objEnt

                Dim edgeKey As String
                edgeKey = CStr(ObjPtr(swEdge))

                If Not seen.Exists(edgeKey) Then
                    seen.Add edgeKey, True
                    edgeCt = edgeCt + 1

                    Dim edgeLen As Double
                    Dim edgeAngRad As Double
                    Dim edgeDesc As String

                    If TryGetProjectedVisibleLinearEdgeData(swView, swEdge, swXf, edgeLen, edgeAngRad, edgeDesc) Then
                        lineCt = lineCt + 1

                        If ROT_LOG_EDGE_CANDIDATES Then
                            LogInfo "   RotEdgeCandidate | view=" & ViewGetNameSafe(swView) & _
                                    " | len=" & Fmt(edgeLen) & " | angDeg=" & Format$(RadToDeg(edgeAngRad), "0.000") & _
                                    " | edge=" & edgeDesc
                        End If

                        If edgeLen > bestLen Then
                            bestLen = edgeLen
                            bestAngRad = edgeAngRad
                            bestDesc = edgeDesc
                        End If
                    End If
                End If
            End If
        End If
    Next i

    Exit Sub

EH:
    LogWarn "ScanVisibleLinearEdgesFromComponent exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
End Sub

Private Function TryGetProjectedVisibleLinearEdgeData(ByVal swView As SldWorks.View, _
                                                      ByVal swEdge As SldWorks.Edge, _
                                                      ByVal swXf As SldWorks.MathTransform, _
                                                      ByRef projectedLen As Double, _
                                                      ByRef angRad As Double, _
                                                      ByRef edgeDesc As String) As Boolean
    On Error GoTo EH

    TryGetProjectedVisibleLinearEdgeData = False
    projectedLen = 0#
    angRad = 0#
    edgeDesc = ""

    If swView Is Nothing Then Exit Function
    If swEdge Is Nothing Then Exit Function
    If swXf Is Nothing Then Exit Function

    Dim swCurve As SldWorks.Curve
    Set swCurve = swEdge.GetCurve
    If swCurve Is Nothing Then Exit Function

    Dim isLine As Boolean
    isLine = False
    On Error Resume Next
    isLine = swCurve.isLine
    Err.Clear
    On Error GoTo EH
    If Not isLine Then Exit Function

    Dim swStartVtx As SldWorks.Vertex
    Dim swEndVtx As SldWorks.Vertex
    Set swStartVtx = swEdge.GetStartVertex
    Set swEndVtx = swEdge.GetEndVertex

    If swStartVtx Is Nothing Or swEndVtx Is Nothing Then Exit Function

    Dim vStart As Variant
    Dim vEnd As Variant
    vStart = swStartVtx.GetPoint
    vEnd = swEndVtx.GetPoint

    Dim x1 As Double, y1 As Double
    Dim x2 As Double, y2 As Double

    If Not TryTransformModelPointToViewXY(vStart, swXf, x1, y1) Then Exit Function
    If Not TryTransformModelPointToViewXY(vEnd, swXf, x2, y2) Then Exit Function

    Dim dx As Double, dy As Double
    dx = x2 - x1
    dy = y2 - y1

    projectedLen = Sqr((dx * dx) + (dy * dy))
    If projectedLen <= 0# Then Exit Function

    angRad = Atn2Safe(dy, dx)

    edgeDesc = "p1=(" & Fmt(x1) & "," & Fmt(y1) & ")" & _
               " p2=(" & Fmt(x2) & "," & Fmt(y2) & ")" & _
               " len=" & Fmt(projectedLen)

    TryGetProjectedVisibleLinearEdgeData = True
    Exit Function

EH:
    LogWarn "TryGetProjectedVisibleLinearEdgeData exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
    TryGetProjectedVisibleLinearEdgeData = False
End Function

Private Function TryTransformModelPointToViewXY(ByVal modelPt As Variant, _
                                                ByVal swXf As SldWorks.MathTransform, _
                                                ByRef x As Double, _
                                                ByRef y As Double) As Boolean
    On Error GoTo EH

    TryTransformModelPointToViewXY = False
    x = 0#: y = 0#

    If IsEmpty(modelPt) Then Exit Function
    If Not IsArray(modelPt) Then Exit Function
    If swXf Is Nothing Then Exit Function

    Dim swMu As SldWorks.MathUtility
    Dim swPt As SldWorks.MathPoint
    Dim swPtOut As SldWorks.MathPoint
    Dim vOut As Variant

    Set swMu = g_swApp.GetMathUtility
    If swMu Is Nothing Then Exit Function

    Set swPt = swMu.CreatePoint(modelPt)
    If swPt Is Nothing Then Exit Function

    Set swPtOut = swPt.MultiplyTransform(swXf)
    If swPtOut Is Nothing Then Exit Function

    vOut = swPtOut.ArrayData
    If Not IsArray(vOut) Then Exit Function
    If UBound(vOut) - LBound(vOut) < 1 Then Exit Function

    x = CDbl(vOut(0))
    y = CDbl(vOut(1))

    TryTransformModelPointToViewXY = True
    Exit Function

EH:
    LogWarn "TryTransformModelPointToViewXY exception | " & Err.Number & " | " & Err.Description
    TryTransformModelPointToViewXY = False
End Function

Private Function RotateViewToBestHorizontal_BBoxFallback(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                                         ByVal swView As SldWorks.View, _
                                                         ByVal tag As String) As Boolean
    On Error GoTo EH

    RotateViewToBestHorizontal_BBoxFallback = False
    If swView Is Nothing Then Exit Function

    Dim bestScore As Double
    Dim bestAngDeg As Double
    Dim bestW As Double, bestH As Double
    bestScore = -1E+30
    bestAngDeg = 0#

    Dim degVal As Double
    Dim angRad As Double
    Dim w As Double, h As Double
    Dim sc As Double

    ' coarse sweep
    For degVal = 0# To 179# Step ROT_SWEEP_COARSE_STEP_DEG
        angRad = DegToRad(degVal)
        SetViewAngleAndRefresh swDrawModel, swView, angRad, tag & " coarse " & Format$(degVal, "0")

        If TryGetViewOutlineWH(swView, w, h) Then
            sc = HorizontalScoreWH(w, h)
            If sc > bestScore Then
                bestScore = sc
                bestAngDeg = degVal
                bestW = w
                bestH = h
            End If
        End If
    Next degVal

    ' fine sweep around coarse winner
    Dim fineStart As Double, fineEnd As Double
    fineStart = MaxD(0#, bestAngDeg - ROT_SWEEP_COARSE_STEP_DEG)
    fineEnd = MinD(179#, bestAngDeg + ROT_SWEEP_COARSE_STEP_DEG)

    For degVal = fineStart To fineEnd Step ROT_SWEEP_FINE_STEP_DEG
        angRad = DegToRad(degVal)
        SetViewAngleAndRefresh swDrawModel, swView, angRad, tag & " fine " & Format$(degVal, "0.0")

        If TryGetViewOutlineWH(swView, w, h) Then
            sc = HorizontalScoreWH(w, h)
            If sc > bestScore Then
                bestScore = sc
                bestAngDeg = degVal
                bestW = w
                bestH = h
            End If
        End If
    Next degVal

    angRad = DegToRad(bestAngDeg)
    SetViewAngleAndRefresh swDrawModel, swView, angRad, tag & " final " & Format$(bestAngDeg, "0.0")

    LogInfo "   RotPickBBoxFallback | angle=" & Format$(bestAngDeg, "0.0") & _
            "deg | w=" & Fmt(bestW) & " h=" & Fmt(bestH)

    RotateViewToBestHorizontal_BBoxFallback = True
    Exit Function

EH:
    LogError "RotateViewToBestHorizontal_BBoxFallback exception | " & Err.Number & " | " & Err.Description & " | " & tag
    RotateViewToBestHorizontal_BBoxFallback = False
End Function

Private Function HorizontalScoreWH(ByVal w As Double, ByVal h As Double) As Double
    If w <= 0# Or h <= 0# Then
        HorizontalScoreWH = -1E+30
        Exit Function
    End If

    HorizontalScoreWH = ((w / h) * 10000#) + (w * 500#) - (h * 100#)
End Function

Private Sub SetViewAngleAndRefresh(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                   ByVal swView As SldWorks.View, _
                                   ByVal angRad As Double, _
                                   ByVal tag As String)
    On Error GoTo EH

    If swView Is Nothing Then Exit Sub
    swView.Angle = angRad

    On Error Resume Next
    swView.Update
    On Error GoTo 0

    swDrawModel.EditRebuild3
    LogInfo "SetViewAngleAndRefresh | " & tag & " | angle(rad)=" & Fmt(angRad)
    Exit Sub

EH:
    LogError "SetViewAngleAndRefresh | " & tag & " | " & Err.Number & " | " & Err.Description
End Sub

Private Function TryGetViewOutlineWH(ByVal swView As SldWorks.View, ByRef w As Double, ByRef h As Double) As Boolean
    On Error GoTo EH

    Dim o As Variant
    TryGetViewOutlineWH = False
    w = 0#: h = 0#

    If swView Is Nothing Then Exit Function

    o = swView.GetOutline
    If IsEmpty(o) Then Exit Function
    If Not IsArray(o) Then Exit Function
    If UBound(o) - LBound(o) < 3 Then Exit Function

    w = Abs(CDbl(o(2)) - CDbl(o(0)))
    h = Abs(CDbl(o(3)) - CDbl(o(1)))

    If w > 0# And h > 0# Then TryGetViewOutlineWH = True
    Exit Function

EH:
    LogError "TryGetViewOutlineWH | " & Err.Number & " | " & Err.Description
End Function

'====================================================================================
' VIEW POSITIONING
'====================================================================================

'====================================================================================
' CATEGORY-BASED BODY ANALYSIS / VIEW SELECTION / ROTATION REFERENCE
'====================================================================================
Private Sub EnsureItemCategoryAnalysis(ByVal item As Object)
    On Error GoTo EH

    If item Is Nothing Then Exit Sub

    On Error Resume Next
    If item.Exists("BodyCategory") And item.Exists("BodySubtype") Then Exit Sub
    On Error GoTo EH

    AnalyzeItemCategory item
    Exit Sub

EH:
    LogWarn "EnsureItemCategoryAnalysis exception | " & Err.Number & " | " & Err.Description
End Sub

Private Sub AnalyzeItemCategory(ByVal item As Object)
    On Error GoTo EH

    Dim swBody As SldWorks.Body2
    Set swBody = Nothing
    On Error Resume Next
    Set swBody = item("RepBody_Model")
    On Error GoTo EH

    item("BodyCategory") = CAT_NAME_IRREGULAR
    item("BodyCategoryReason") = "No representative body"
    item("BodySubtype") = CAT_SUBTYPE_UNKNOWN
    item("BodySubtypeReason") = "No representative body"
    item("BodySubtypeHint") = ""

    item("Sig_LineEdgeCt") = 0
    item("Sig_CircleEdgeCt") = 0
    item("Sig_OtherEdgeCt") = 0
    item("Sig_PlanarFaceCt") = 0
    item("Sig_CylFaceCt") = 0
    item("Sig_OtherFaceCt") = 0

    Call ResetDirectionFamilySlots(item)
    Call ResetPlanarFaceFamilySlots(item)

    If swBody Is Nothing Then Exit Sub

    Dim fams As Collection
    Set fams = New Collection

    Dim lineEdgeCt As Long, circleEdgeCt As Long, otherEdgeCt As Long
    Dim planarFaceCt As Long, cylFaceCt As Long, otherFaceCt As Long

    AnalyzeBodyEdgesAndDirections swBody, fams, lineEdgeCt, circleEdgeCt, otherEdgeCt
    AnalyzeBodyFaces swBody, planarFaceCt, cylFaceCt, otherFaceCt
    AnalyzeBodyPlanarFaceFamilies swBody, item
    CaptureTopDirectionFamiliesToItem item, fams

    item("Sig_LineEdgeCt") = lineEdgeCt
    item("Sig_CircleEdgeCt") = circleEdgeCt
    item("Sig_OtherEdgeCt") = otherEdgeCt
    item("Sig_PlanarFaceCt") = planarFaceCt
    item("Sig_CylFaceCt") = cylFaceCt
    item("Sig_OtherFaceCt") = otherFaceCt

    Dim d1 As Double, d2 As Double, d3 As Double
    d1 = Abs(CDbl(item("dx")))
    d2 = Abs(CDbl(item("dy")))
    d3 = Abs(CDbl(item("dz")))

    Dim sMin As Double, sMid As Double, sMax As Double
    Sort3Doubles d1, d2, d3, sMin, sMid, sMax

    Dim thinRatio As Double
    thinRatio = 0#
    If sMax > 0# Then thinRatio = sMin / sMax

    Dim primaryLen As Double, secondaryLen As Double
    primaryLen = SafeCDbl(item("Sig_PrimaryDirLen"))
    secondaryLen = SafeCDbl(item("Sig_SecondaryDirLen"))

    Dim catName As String
    Dim catReason As String
    catName = CAT_NAME_IRREGULAR
    catReason = "Fallback irregular"

    Dim subtypeName As String
    Dim subtypeReason As String
    Dim subtypeHint As String
    DetectBodySubtype item, swBody, subtypeName, subtypeReason, subtypeHint

    If thinRatio > 0# And thinRatio <= CAT_PLATE_THIN_RATIO_MAX And planarFaceCt >= 2 Then
        catName = CAT_NAME_SHEET_PLATE
        catReason = "Thin ratio=" & Fmt(thinRatio) & " with planar faces"

    ElseIf cylFaceCt >= 1 And circleEdgeCt >= 1 And lineEdgeCt <= (circleEdgeCt + 4) Then
        catName = CAT_NAME_ROUND_STOCK
        catReason = "Cylindrical faces dominate"

    ElseIf (circleEdgeCt + otherEdgeCt) > 0 And lineEdgeCt > 0 Then
        If (CDbl(circleEdgeCt + otherEdgeCt) / MaxD(1#, CDbl(lineEdgeCt + circleEdgeCt + otherEdgeCt))) >= CAT_CURVED_EDGE_RATIO_TRIGGER Then
            catName = CAT_NAME_CURVED_STRUCTURAL
            catReason = "Curved edges significant"
        Else
            catName = CAT_NAME_LINEAR_STRUCTURAL
            catReason = "Straight edges still dominant"
        End If

    ElseIf lineEdgeCt >= 4 And primaryLen > 0# Then
        catName = CAT_NAME_LINEAR_STRUCTURAL
        catReason = "Straight-edge family detected"

    ElseIf circleEdgeCt > 0 Or cylFaceCt > 0 Then
        catName = CAT_NAME_CURVED_STRUCTURAL
        catReason = "No strong straight family but curved geometry exists"

    ElseIf primaryLen > 0# Then
        catName = CAT_NAME_LINEAR_STRUCTURAL
        catReason = "Projected direction family fallback"

    End If

    Select Case UCase$(subtypeName)
        Case CAT_SUBTYPE_CHANNEL, CAT_SUBTYPE_ANGLE, CAT_SUBTYPE_RECT_MEMBER
            If catName <> CAT_NAME_LINEAR_STRUCTURAL Then
                catReason = catReason & "; subtype override to linear structural (" & subtypeName & ")"
            End If
            catName = CAT_NAME_LINEAR_STRUCTURAL

        Case CAT_SUBTYPE_PLATE, CAT_SUBTYPE_GUSSET
            If catName <> CAT_NAME_SHEET_PLATE And thinRatio > 0# And thinRatio <= (CAT_PLATE_THIN_RATIO_MAX * 1.5) Then
                catReason = catReason & "; subtype override to sheet/plate (" & subtypeName & ")"
                catName = CAT_NAME_SHEET_PLATE
            End If

        Case CAT_SUBTYPE_ROUND
            If catName <> CAT_NAME_ROUND_STOCK Then
                catReason = catReason & "; subtype override to round stock"
                catName = CAT_NAME_ROUND_STOCK
            End If
    End Select

    item("BodyCategory") = catName
    item("BodyCategoryReason") = catReason
    item("BodySubtype") = subtypeName
    item("BodySubtypeReason") = subtypeReason
    item("BodySubtypeHint") = subtypeHint

    If CAT_LOG_ANALYSIS Then
        LogInfo "CategoryDetect | itemNo=" & SafeStr(item("ItemNo")) & _
                " | pn=" & SafeStr(item("PartNo")) & _
                " | cat=" & catName & _
                " | subtype=" & subtypeName & _
                " | reason=" & catReason & _
                " | subtypeReason=" & subtypeReason & _
                " | hint=" & subtypeHint & _
                " | bbox=" & Fmt(d1) & "x" & Fmt(d2) & "x" & Fmt(d3) & _
                " | thinRatio=" & Fmt(thinRatio) & _
                " | edges L/C/O=" & CStr(lineEdgeCt) & "/" & CStr(circleEdgeCt) & "/" & CStr(otherEdgeCt) & _
                " | faces P/C/O=" & CStr(planarFaceCt) & "/" & CStr(cylFaceCt) & "/" & CStr(otherFaceCt) & _
                " | primaryDirLen=" & Fmt(primaryLen) & _
                " | secondaryDirLen=" & Fmt(secondaryLen)
    End If

    Exit Sub

EH:
    LogWarn "AnalyzeItemCategory exception | " & Err.Number & " | " & Err.Description & _
            " | itemNo=" & SafeStr(item("ItemNo")) & " | pn=" & SafeStr(item("PartNo"))
End Sub

Private Sub DetectBodySubtype(ByVal item As Object, _
                              ByVal swBody As SldWorks.Body2, _
                              ByRef subtypeName As String, _
                              ByRef subtypeReason As String, _
                              ByRef hintText As String)
    On Error GoTo EH

    subtypeName = CAT_SUBTYPE_UNKNOWN
    subtypeReason = "No section-family hint matched"
    hintText = BuildSubtypeHintText(item, swBody)

    Dim u As String
    u = UCase$(hintText)

    If InStr(1, u, "GUSSET", vbTextCompare) > 0 Then
        subtypeName = CAT_SUBTYPE_GUSSET
        subtypeReason = "Name hint contains GUSSET"

    ElseIf LooksLikeChannelName(u) Then
        subtypeName = CAT_SUBTYPE_CHANNEL
        subtypeReason = "Name hint matches channel pattern (C/MC/channel)"

    ElseIf LooksLikeAngleName(u) Then
        subtypeName = CAT_SUBTYPE_ANGLE
        subtypeReason = "Name hint matches angle pattern (L/angle)"

    ElseIf LooksLikePlateName(u) Then
        subtypeName = CAT_SUBTYPE_PLATE
        subtypeReason = "Name hint matches plate pattern (PL/plate)"

    ElseIf InStr(1, u, "ROUND", vbTextCompare) > 0 Or _
           InStr(1, u, "ROD", vbTextCompare) > 0 Or _
           InStr(1, u, "PIPE", vbTextCompare) > 0 Or _
           InStr(1, u, "HSS ROUND", vbTextCompare) > 0 Then
        subtypeName = CAT_SUBTYPE_ROUND
        subtypeReason = "Name hint matches round/pipe/rod"

    ElseIf InStr(1, u, "HSS", vbTextCompare) > 0 Or _
           InStr(1, u, "TUBE", vbTextCompare) > 0 Or _
           InStr(1, u, "RECT", vbTextCompare) > 0 Then
        subtypeName = CAT_SUBTYPE_RECT_MEMBER
        subtypeReason = "Name hint matches rectangular structural member"
    End If

    Exit Sub

EH:
    subtypeName = CAT_SUBTYPE_UNKNOWN
    subtypeReason = "DetectBodySubtype exception | " & Err.Number & " | " & Err.Description
    hintText = ""
    LogWarn subtypeReason
End Sub

Private Function BuildSubtypeHintText(ByVal item As Object, ByVal swBody As SldWorks.Body2) As String
    On Error GoTo EH

    Dim s As String
    s = ""

    s = AppendHintPart(s, DictValueString(item, "CutListFeatureName"))
    s = AppendHintPart(s, DictValueString(item, "PartNo"))
    s = AppendHintPart(s, DictValueString(item, "BodyName"))
    s = AppendHintPart(s, DictValueString(item, "ItemNo"))
    If Not swBody Is Nothing Then s = AppendHintPart(s, GetBodyNameSafe(swBody))

    BuildSubtypeHintText = Trim$(s)
    Exit Function

EH:
    BuildSubtypeHintText = ""
End Function

Private Function AppendHintPart(ByVal baseText As String, ByVal addText As String) As String
    addText = Trim$(addText)
    If Len(addText) = 0 Then
        AppendHintPart = baseText
    ElseIf Len(Trim$(baseText)) = 0 Then
        AppendHintPart = addText
    Else
        AppendHintPart = baseText & " | " & addText
    End If
End Function

Private Function DictValueString(ByVal d As Object, ByVal keyName As String) As String
    On Error GoTo EH

    DictValueString = ""
    If d Is Nothing Then Exit Function
    If d.Exists(keyName) Then DictValueString = SafeStr(d(keyName))
    Exit Function

EH:
    DictValueString = ""
End Function

Private Function LooksLikeChannelName(ByVal u As String) As Boolean
    On Error GoTo EH

    If InStr(1, u, "CHANNEL", vbTextCompare) > 0 Then
        LooksLikeChannelName = True
        Exit Function
    End If

    Dim toks As Variant
    toks = Split(TokenizedHintText(u), " ")

    Dim i As Long
    For i = LBound(toks) To UBound(toks)
        Dim t As String
        t = Trim$(SafeStr(toks(i)))
        If Len(t) >= 2 Then
            If Left$(t, 2) = "MC" Then
                If Len(t) >= 3 Then
                    If IsDigitChar(Mid$(t, 3, 1)) Then
                        LooksLikeChannelName = True
                        Exit Function
                    End If
                End If
            ElseIf Left$(t, 1) = "C" Then
                If IsDigitChar(Mid$(t, 2, 1)) Then
                    LooksLikeChannelName = True
                    Exit Function
                End If
            End If
        End If
    Next i

    Exit Function

EH:
    LooksLikeChannelName = False
End Function

Private Function LooksLikeAngleName(ByVal u As String) As Boolean
    On Error GoTo EH

    If InStr(1, u, "ANGLE", vbTextCompare) > 0 Then
        LooksLikeAngleName = True
        Exit Function
    End If

    Dim toks As Variant
    toks = Split(TokenizedHintText(u), " ")

    Dim i As Long
    For i = LBound(toks) To UBound(toks)
        Dim t As String
        t = Trim$(SafeStr(toks(i)))
        If Len(t) >= 2 Then
            If Left$(t, 1) = "L" And IsDigitChar(Mid$(t, 2, 1)) Then
                LooksLikeAngleName = True
                Exit Function
            End If
        End If
    Next i

    Exit Function

EH:
    LooksLikeAngleName = False
End Function

Private Function LooksLikePlateName(ByVal u As String) As Boolean
    On Error GoTo EH

    If InStr(1, u, "PLATE", vbTextCompare) > 0 Then
        LooksLikePlateName = True
        Exit Function
    End If

    Dim toks As Variant
    toks = Split(TokenizedHintText(u), " ")

    Dim i As Long
    For i = LBound(toks) To UBound(toks)
        Dim t As String
        t = Trim$(SafeStr(toks(i)))
        If t = "PL" Then
            LooksLikePlateName = True
            Exit Function
        End If
        If Len(t) >= 3 Then
            If Left$(t, 2) = "PL" And IsDigitChar(Mid$(t, 3, 1)) Then
                LooksLikePlateName = True
                Exit Function
            End If
        End If
    Next i

    Exit Function

EH:
    LooksLikePlateName = False
End Function

Private Function TokenizedHintText(ByVal s As String) As String
    s = UCase$(s)
    s = Replace(s, "|", " ")
    s = Replace(s, "<", " ")
    s = Replace(s, ">", " ")
    s = Replace(s, "(", " ")
    s = Replace(s, ")", " ")
    s = Replace(s, "[", " ")
    s = Replace(s, "]", " ")
    s = Replace(s, "-", " ")
    s = Replace(s, "_", " ")
    s = Replace(s, ",", " ")
    s = Replace(s, ";", " ")
    TokenizedHintText = s
End Function

Private Function IsDigitChar(ByVal ch As String) As Boolean
    If Len(ch) <> 1 Then
        IsDigitChar = False
    Else
        IsDigitChar = (ch >= "0" And ch <= "9")
    End If
End Function

Private Sub ResetDirectionFamilySlots(ByVal item As Object)
    On Error Resume Next

    item("Sig_PrimaryDirX") = 0#: item("Sig_PrimaryDirY") = 0#: item("Sig_PrimaryDirZ") = 0#: item("Sig_PrimaryDirLen") = 0#
    item("Sig_SecondaryDirX") = 0#: item("Sig_SecondaryDirY") = 0#: item("Sig_SecondaryDirZ") = 0#: item("Sig_SecondaryDirLen") = 0#
    item("Sig_TertiaryDirX") = 0#: item("Sig_TertiaryDirY") = 0#: item("Sig_TertiaryDirZ") = 0#: item("Sig_TertiaryDirLen") = 0#
End Sub

Private Sub ResetPlanarFaceFamilySlots(ByVal item As Object)
    On Error Resume Next

    Dim i As Long
    For i = 1 To 6
        item("Sig_FaceDir" & CStr(i) & "X") = 0#
        item("Sig_FaceDir" & CStr(i) & "Y") = 0#
        item("Sig_FaceDir" & CStr(i) & "Z") = 0#
        item("Sig_FaceDir" & CStr(i) & "Area") = 0#
        item("Sig_FaceDir" & CStr(i) & "Ct") = 0
    Next i
End Sub

Private Sub AnalyzeBodyEdgesAndDirections(ByVal swBody As SldWorks.Body2, _
                                          ByVal fams As Collection, _
                                          ByRef lineEdgeCt As Long, _
                                          ByRef circleEdgeCt As Long, _
                                          ByRef otherEdgeCt As Long)
    On Error GoTo EH

    lineEdgeCt = 0
    circleEdgeCt = 0
    otherEdgeCt = 0

    If swBody Is Nothing Then Exit Sub

    Dim vEdges As Variant
    vEdges = swBody.GetEdges
    If IsEmpty(vEdges) Then Exit Sub
    If Not IsArray(vEdges) Then Exit Sub

    Dim i As Long
    For i = LBound(vEdges) To UBound(vEdges)
        Dim swEdge As SldWorks.Edge
        Set swEdge = vEdges(i)
        If swEdge Is Nothing Then GoTo NextEdge

        Dim swCurve As SldWorks.Curve
        Set swCurve = Nothing
        On Error Resume Next
        Set swCurve = swEdge.GetCurve
        On Error GoTo EH

        If swCurve Is Nothing Then
            otherEdgeCt = otherEdgeCt + 1
            GoTo NextEdge
        End If

        Dim isLine As Boolean, isCircle As Boolean
        isLine = False: isCircle = False

        On Error Resume Next
        isLine = swCurve.isLine
        Err.Clear
        isCircle = swCurve.isCircle
        Err.Clear
        On Error GoTo EH

        If isLine Then
            lineEdgeCt = lineEdgeCt + 1

            Dim ux As Double, uy As Double, uz As Double, edgeLen As Double
            If TryGetModelLineEdgeDirection(swEdge, ux, uy, uz, edgeLen) Then
                If edgeLen >= CAT_MIN_LINE_LEN_MODEL Then
                    AddDirectionFamily fams, ux, uy, uz, edgeLen
                End If
            End If
        ElseIf isCircle Then
            circleEdgeCt = circleEdgeCt + 1
        Else
            otherEdgeCt = otherEdgeCt + 1
        End If

NextEdge:
    Next i

    Exit Sub

EH:
    LogWarn "AnalyzeBodyEdgesAndDirections exception | " & Err.Number & " | " & Err.Description
End Sub

Private Sub AnalyzeBodyFaces(ByVal swBody As SldWorks.Body2, _
                             ByRef planarFaceCt As Long, _
                             ByRef cylFaceCt As Long, _
                             ByRef otherFaceCt As Long)
    On Error GoTo EH

    planarFaceCt = 0
    cylFaceCt = 0
    otherFaceCt = 0

    If swBody Is Nothing Then Exit Sub

    Dim vFaces As Variant
    vFaces = swBody.GetFaces
    If IsEmpty(vFaces) Then Exit Sub
    If Not IsArray(vFaces) Then Exit Sub

    Dim i As Long
    For i = LBound(vFaces) To UBound(vFaces)
        Dim swFace As SldWorks.Face2
        Set swFace = vFaces(i)
        If swFace Is Nothing Then GoTo NextFace

        Dim swSurf As SldWorks.Surface
        Set swSurf = Nothing
        On Error Resume Next
        Set swSurf = swFace.GetSurface
        On Error GoTo EH

        If swSurf Is Nothing Then
            otherFaceCt = otherFaceCt + 1
            GoTo NextFace
        End If

        Dim isPlane As Boolean, isCyl As Boolean
        isPlane = False: isCyl = False

        On Error Resume Next
        isPlane = swSurf.isPlane
        Err.Clear
        isCyl = swSurf.IsCylinder
        Err.Clear
        On Error GoTo EH

        If isPlane Then
            planarFaceCt = planarFaceCt + 1
        ElseIf isCyl Then
            cylFaceCt = cylFaceCt + 1
        Else
            otherFaceCt = otherFaceCt + 1
        End If

NextFace:
    Next i

    Exit Sub

EH:
    LogWarn "AnalyzeBodyFaces exception | " & Err.Number & " | " & Err.Description
End Sub

Private Sub AnalyzeBodyPlanarFaceFamilies(ByVal swBody As SldWorks.Body2, ByVal item As Object)
    On Error GoTo EH

    If swBody Is Nothing Then Exit Sub
    If item Is Nothing Then Exit Sub

    Dim fams As Collection
    Set fams = New Collection

    Dim vFaces As Variant
    vFaces = swBody.GetFaces
    If IsEmpty(vFaces) Then Exit Sub
    If Not IsArray(vFaces) Then Exit Sub

    Dim totalFaces As Long
    Dim acceptedPlanarFaces As Long
    Dim noSurfaceCt As Long
    Dim notPlaneCt As Long
    Dim normalFailCt As Long
    Dim areaFailCt As Long
    Dim otherRejectCt As Long
    Dim normalFacePropCt As Long
    Dim normalPlaneParamCt As Long
    Dim areaNativeCt As Long
    Dim areaProxyCt As Long

    Dim i As Long
    For i = LBound(vFaces) To UBound(vFaces)
        Dim swFace As SldWorks.Face2
        Set swFace = vFaces(i)
        If swFace Is Nothing Then GoTo NextFace
        totalFaces = totalFaces + 1

        Dim nx As Double, ny As Double, nz As Double, faceArea As Double
        Dim rejectReason As String
        Dim normalSource As String
        Dim areaSource As String
        If TryGetPlanarFaceNormalAndArea(swFace, nx, ny, nz, faceArea, rejectReason, normalSource, areaSource) Then
            acceptedPlanarFaces = acceptedPlanarFaces + 1
            If UCase$(normalSource) = "FACE.NORMAL" Then
                normalFacePropCt = normalFacePropCt + 1
            ElseIf Left$(UCase$(normalSource), 11) = "PLANEPARAMS" Then
                normalPlaneParamCt = normalPlaneParamCt + 1
            End If
            If UCase$(areaSource) = "GETAREA" Then
                areaNativeCt = areaNativeCt + 1
            ElseIf UCase$(areaSource) = "FACEBOX_PROXY" Then
                areaProxyCt = areaProxyCt + 1
            End If
            If faceArea > 0# Then
                AddPlanarFaceFamily fams, nx, ny, nz, faceArea
            End If
        Else
            Select Case UCase$(rejectReason)
                Case "NO_SURFACE"
                    noSurfaceCt = noSurfaceCt + 1
                Case "NOT_PLANE"
                    notPlaneCt = notPlaneCt + 1
                Case "NORMAL_FAIL"
                    normalFailCt = normalFailCt + 1
                Case "AREA_FAIL"
                    areaFailCt = areaFailCt + 1
                Case Else
                    otherRejectCt = otherRejectCt + 1
            End Select
        End If

NextFace:
    Next i

    CaptureTopPlanarFaceFamiliesToItem item, fams

    If CAT_LOG_ANALYSIS Then
        LogInfo "FaceFamilies | itemNo=" & SafeStr(item("ItemNo")) & _
                " | pn=" & SafeStr(item("PartNo")) & _
                " | count=" & CStr(fams.Count) & _
                " | f1Area=" & Fmt(SafeCDbl(item("Sig_FaceDir1Area"))) & _
                " | f2Area=" & Fmt(SafeCDbl(item("Sig_FaceDir2Area"))) & _
                " | f3Area=" & Fmt(SafeCDbl(item("Sig_FaceDir3Area")))
        LogInfo "FaceFamiliesDetail | itemNo=" & SafeStr(item("ItemNo")) & _
                " | pn=" & SafeStr(item("PartNo")) & _
                " | totalFaces=" & CStr(totalFaces) & _
                " | acceptedPlanarFaces=" & CStr(acceptedPlanarFaces) & _
                " | families=" & CStr(fams.Count) & _
                " | noSurface=" & CStr(noSurfaceCt) & _
                " | notPlane=" & CStr(notPlaneCt) & _
                " | normalFail=" & CStr(normalFailCt) & _
                " | areaFail=" & CStr(areaFailCt) & _
                " | otherReject=" & CStr(otherRejectCt) & _
                " | normalFaceProperty=" & CStr(normalFacePropCt) & _
                " | normalPlaneParams=" & CStr(normalPlaneParamCt) & _
                " | areaGetArea=" & CStr(areaNativeCt) & _
                " | areaFaceBoxProxy=" & CStr(areaProxyCt) & _
                " | f1=(" & Fmt(SafeCDbl(item("Sig_FaceDir1X"))) & "," & Fmt(SafeCDbl(item("Sig_FaceDir1Y"))) & "," & Fmt(SafeCDbl(item("Sig_FaceDir1Z"))) & ")" & _
                " | f2=(" & Fmt(SafeCDbl(item("Sig_FaceDir2X"))) & "," & Fmt(SafeCDbl(item("Sig_FaceDir2Y"))) & "," & Fmt(SafeCDbl(item("Sig_FaceDir2Z"))) & ")" & _
                " | f3=(" & Fmt(SafeCDbl(item("Sig_FaceDir3X"))) & "," & Fmt(SafeCDbl(item("Sig_FaceDir3Y"))) & "," & Fmt(SafeCDbl(item("Sig_FaceDir3Z"))) & ")"
    End If
    Exit Sub

EH:
    LogWarn "AnalyzeBodyPlanarFaceFamilies exception | " & Err.Number & " | " & Err.Description & _
            " | itemNo=" & SafeStr(item("ItemNo"))
End Sub

Private Function TryGetPlanarFaceNormalAndArea(ByVal swFace As SldWorks.Face2, _
                                               ByRef nx As Double, _
                                               ByRef ny As Double, _
                                               ByRef nz As Double, _
                                               ByRef faceArea As Double, _
                                               ByRef rejectReason As String, _
                                               ByRef normalSource As String, _
                                               ByRef areaSource As String) As Boolean
    On Error GoTo EH

    TryGetPlanarFaceNormalAndArea = False
    nx = 0#: ny = 0#: nz = 0#: faceArea = 0#
    rejectReason = ""
    normalSource = ""
    areaSource = ""
    If swFace Is Nothing Then
        rejectReason = "NO_FACE"
        Exit Function
    End If

    Dim swSurf As SldWorks.Surface
    Set swSurf = swFace.GetSurface
    If swSurf Is Nothing Then
        rejectReason = "NO_SURFACE"
        Exit Function
    End If

    Dim isPlane As Boolean
    isPlane = False
    On Error Resume Next
    isPlane = swSurf.isPlane
    Err.Clear
    On Error GoTo EH

    If Not TryGetPlanarFaceNormalVector(swFace, swSurf, nx, ny, nz, normalSource) Then
        If Not isPlane Then
            rejectReason = "NOT_PLANE"
        Else
            rejectReason = "NORMAL_FAIL"
        End If
        Exit Function
    End If

    ' Some SOLIDWORKS versions/wrappers report IsPlane=False on faces that still
    ' expose valid plane parameters. Treat PlaneParams as the stronger evidence.
    If Not isPlane Then
        If Left$(UCase$(normalSource), 11) <> "PLANEPARAMS" Then
            rejectReason = "NOT_PLANE"
            Exit Function
        End If
        normalSource = normalSource & "+IsPlaneFalse"
    End If

    If Not NormalizeVector3(nx, ny, nz) Then
        rejectReason = "NORMAL_FAIL"
        Exit Function
    End If
    CanonicalizeVectorPositive nx, ny, nz

    faceArea = GetPlanarFaceAreaOrProxy(swFace, areaSource)
    If faceArea <= 0# Then
        rejectReason = "AREA_FAIL"
        Exit Function
    End If

    TryGetPlanarFaceNormalAndArea = True
    Exit Function

EH:
    rejectReason = "EXCEPTION"
    LogWarn "TryGetPlanarFaceNormalAndArea exception | " & Err.Number & " | " & Err.Description
    TryGetPlanarFaceNormalAndArea = False
End Function

Private Function TryGetPlanarFaceNormalVector(ByVal swFace As SldWorks.Face2, _
                                              ByVal swSurf As SldWorks.Surface, _
                                              ByRef nx As Double, _
                                              ByRef ny As Double, _
                                              ByRef nz As Double, _
                                              ByRef normalSource As String) As Boolean
    On Error GoTo EH

    TryGetPlanarFaceNormalVector = False
    normalSource = ""
    nx = 0#: ny = 0#: nz = 0#

    Dim vPlane As Variant
    vPlane = Empty
    On Error Resume Next
    vPlane = swSurf.PlaneParams
    If Err.Number <> 0 Then
        Err.Clear
        vPlane = Empty
    End If
    On Error GoTo EH

    ' PlaneParams is the most reliable proof that the surface is actually planar.
    ' It is normally root point XYZ followed by normal XYZ.
    If TryReadVectorFromVariantArray(vPlane, 3, nx, ny, nz) Then
        normalSource = "PlaneParams[3:5]"
        TryGetPlanarFaceNormalVector = True
        Exit Function
    End If

    ' Some API wrappers expose plane normal first; keep this as a defensive fallback.
    If TryReadVectorFromVariantArray(vPlane, 0, nx, ny, nz) Then
        normalSource = "PlaneParams[0:2]"
        TryGetPlanarFaceNormalVector = True
        Exit Function
    End If

    Dim vNorm As Variant
    vNorm = Empty
    On Error Resume Next
    vNorm = swFace.Normal
    If Err.Number <> 0 Then
        Err.Clear
        vNorm = Empty
    End If
    On Error GoTo EH

    If TryReadVectorFromVariantArray(vNorm, 0, nx, ny, nz) Then
        normalSource = "Face.Normal"
        TryGetPlanarFaceNormalVector = True
        Exit Function
    End If

    Exit Function

EH:
    TryGetPlanarFaceNormalVector = False
End Function

Private Function TryReadVectorFromVariantArray(ByVal vArr As Variant, _
                                               ByVal startOffset As Long, _
                                               ByRef x As Double, _
                                               ByRef y As Double, _
                                               ByRef z As Double) As Boolean
    On Error GoTo EH

    TryReadVectorFromVariantArray = False
    x = 0#: y = 0#: z = 0#
    If Not IsArray(vArr) Then Exit Function

    Dim lb As Long, ub As Long
    lb = LBound(vArr)
    ub = UBound(vArr)
    If ub < lb + startOffset + 2 Then Exit Function

    x = CDbl(vArr(lb + startOffset))
    y = CDbl(vArr(lb + startOffset + 1))
    z = CDbl(vArr(lb + startOffset + 2))

    TryReadVectorFromVariantArray = NormalizeVector3(x, y, z)
    Exit Function

EH:
    TryReadVectorFromVariantArray = False
End Function

Private Function GetPlanarFaceAreaOrProxy(ByVal swFace As SldWorks.Face2, ByRef areaSource As String) As Double
    On Error GoTo EH

    GetPlanarFaceAreaOrProxy = 0#
    areaSource = ""
    If swFace Is Nothing Then Exit Function

    On Error Resume Next
    GetPlanarFaceAreaOrProxy = CDbl(swFace.GetArea)
    If Err.Number <> 0 Then
        Err.Clear
        GetPlanarFaceAreaOrProxy = 0#
    End If
    On Error GoTo EH

    If GetPlanarFaceAreaOrProxy > 0# Then
        areaSource = "GetArea"
        Exit Function
    End If

    Dim vBox As Variant
    vBox = Empty
    On Error Resume Next
    vBox = swFace.GetBox
    If Err.Number <> 0 Then
        Err.Clear
        vBox = Empty
    End If
    On Error GoTo EH

    If IsArray(vBox) Then
        Dim lb As Long, ub As Long
        lb = LBound(vBox)
        ub = UBound(vBox)
        If ub >= lb + 5 Then
            Dim dx As Double, dy As Double, dz As Double
            Dim sMin As Double, sMid As Double, sMax As Double
            dx = Abs(CDbl(vBox(lb + 3)) - CDbl(vBox(lb)))
            dy = Abs(CDbl(vBox(lb + 4)) - CDbl(vBox(lb + 1)))
            dz = Abs(CDbl(vBox(lb + 5)) - CDbl(vBox(lb + 2)))
            Sort3Doubles dx, dy, dz, sMin, sMid, sMax
            GetPlanarFaceAreaOrProxy = sMid * sMax
            If GetPlanarFaceAreaOrProxy > 0# Then areaSource = "FaceBox_Proxy"
        End If
    End If

    Exit Function

EH:
    GetPlanarFaceAreaOrProxy = 0#
End Function

Private Sub AddPlanarFaceFamily(ByVal fams As Collection, _
                                ByVal nx As Double, _
                                ByVal ny As Double, _
                                ByVal nz As Double, _
                                ByVal addArea As Double)
    On Error GoTo EH

    If fams Is Nothing Then Exit Sub
    If addArea <= 0# Then Exit Sub
    If Not NormalizeVector3(nx, ny, nz) Then Exit Sub
    CanonicalizeVectorPositive nx, ny, nz

    Dim cosTol As Double
    cosTol = Cos(DegToRad(CAT_FAMILY_ANGLE_TOL_DEG))

    Dim i As Long
    For i = 1 To fams.Count
        Dim fd As Object
        Set fd = fams(i)

        Dim dotAbs As Double
        dotAbs = Abs((SafeCDbl(fd("x")) * nx) + (SafeCDbl(fd("y")) * ny) + (SafeCDbl(fd("z")) * nz))

        If dotAbs >= cosTol Then
            Dim oldArea As Double
            oldArea = SafeCDbl(fd("area"))

            Dim vx As Double, vy As Double, vz As Double
            vx = (SafeCDbl(fd("x")) * oldArea) + (nx * addArea)
            vy = (SafeCDbl(fd("y")) * oldArea) + (ny * addArea)
            vz = (SafeCDbl(fd("z")) * oldArea) + (nz * addArea)

            If NormalizeVector3(vx, vy, vz) Then
                CanonicalizeVectorPositive vx, vy, vz
                fd("x") = vx
                fd("y") = vy
                fd("z") = vz
            End If

            fd("area") = oldArea + addArea
            fd("ct") = CLng(SafeCDbl(fd("ct"))) + 1
            Exit Sub
        End If
    Next i

    Dim fdNew As Object
    Set fdNew = CreateObject("Scripting.Dictionary")
    fdNew("x") = nx
    fdNew("y") = ny
    fdNew("z") = nz
    fdNew("area") = addArea
    fdNew("ct") = 1
    fams.Add fdNew
    Exit Sub

EH:
    LogWarn "AddPlanarFaceFamily exception | " & Err.Number & " | " & Err.Description
End Sub

Private Sub CaptureTopPlanarFaceFamiliesToItem(ByVal item As Object, ByVal fams As Collection)
    On Error GoTo EH

    Call ResetPlanarFaceFamilySlots(item)
    If item Is Nothing Then Exit Sub
    If fams Is Nothing Then Exit Sub
    If fams.Count = 0 Then Exit Sub

    Dim picked(1 To 6) As Long
    Dim pickedArea(1 To 6) As Double

    Dim i As Long, slot As Long
    For i = 1 To fams.Count
        Dim fd As Object
        Set fd = fams(i)

        Dim area As Double
        area = SafeCDbl(fd("area"))

        For slot = 1 To 6
            If area > pickedArea(slot) Then
                Dim moveSlot As Long
                For moveSlot = 6 To slot + 1 Step -1
                    pickedArea(moveSlot) = pickedArea(moveSlot - 1)
                    picked(moveSlot) = picked(moveSlot - 1)
                Next moveSlot

                pickedArea(slot) = area
                picked(slot) = i
                Exit For
            End If
        Next slot
    Next i

    For slot = 1 To 6
        If picked(slot) > 0 Then
            WritePlanarFaceFamilyToItem item, slot, fams(picked(slot))
        End If
    Next slot
    Exit Sub

EH:
    LogWarn "CaptureTopPlanarFaceFamiliesToItem exception | " & Err.Number & " | " & Err.Description
End Sub

Private Sub WritePlanarFaceFamilyToItem(ByVal item As Object, ByVal slot As Long, ByVal fd As Object)
    On Error Resume Next
    If slot < 1 Or slot > 6 Then Exit Sub

    item("Sig_FaceDir" & CStr(slot) & "X") = SafeCDbl(fd("x"))
    item("Sig_FaceDir" & CStr(slot) & "Y") = SafeCDbl(fd("y"))
    item("Sig_FaceDir" & CStr(slot) & "Z") = SafeCDbl(fd("z"))
    item("Sig_FaceDir" & CStr(slot) & "Area") = SafeCDbl(fd("area"))
    item("Sig_FaceDir" & CStr(slot) & "Ct") = CLng(SafeCDbl(fd("ct")))
End Sub

Private Sub CaptureTopDirectionFamiliesToItem(ByVal item As Object, ByVal fams As Collection)
    On Error GoTo EH

    Call ResetDirectionFamilySlots(item)

    If fams Is Nothing Then Exit Sub
    If fams.Count = 0 Then Exit Sub

    Dim idx1 As Long, idx2 As Long, idx3 As Long
    Dim len1 As Double, len2 As Double, len3 As Double
    idx1 = 0: idx2 = 0: idx3 = 0
    len1 = -1#: len2 = -1#: len3 = -1#

    Dim i As Long
    For i = 1 To fams.Count
        Dim fd As Object
        Set fd = fams(i)

        Dim curLen As Double
        curLen = SafeCDbl(fd("len"))

        If curLen > len1 Then
            len3 = len2: idx3 = idx2
            len2 = len1: idx2 = idx1
            len1 = curLen: idx1 = i
        ElseIf curLen > len2 Then
            len3 = len2: idx3 = idx2
            len2 = curLen: idx2 = i
        ElseIf curLen > len3 Then
            len3 = curLen: idx3 = i
        End If
    Next i

    If idx1 > 0 Then WriteDirectionFamilyToItem item, "Sig_PrimaryDir", fams(idx1)
    If idx2 > 0 Then WriteDirectionFamilyToItem item, "Sig_SecondaryDir", fams(idx2)
    If idx3 > 0 Then WriteDirectionFamilyToItem item, "Sig_TertiaryDir", fams(idx3)

    Exit Sub

EH:
    LogWarn "CaptureTopDirectionFamiliesToItem exception | " & Err.Number & " | " & Err.Description
End Sub

Private Sub WriteDirectionFamilyToItem(ByVal item As Object, ByVal keyPrefix As String, ByVal fd As Object)
    On Error Resume Next

    item(keyPrefix & "X") = SafeCDbl(fd("x"))
    item(keyPrefix & "Y") = SafeCDbl(fd("y"))
    item(keyPrefix & "Z") = SafeCDbl(fd("z"))
    item(keyPrefix & "Len") = SafeCDbl(fd("len"))
End Sub

Private Sub AddDirectionFamily(ByVal fams As Collection, _
                               ByVal ux As Double, _
                               ByVal uy As Double, _
                               ByVal uz As Double, _
                               ByVal addLen As Double)
    On Error GoTo EH

    If addLen <= 0# Then Exit Sub
    If Not NormalizeVector3(ux, uy, uz) Then Exit Sub
    CanonicalizeVectorPositive ux, uy, uz

    Dim cosTol As Double
    cosTol = Cos(DegToRad(CAT_FAMILY_ANGLE_TOL_DEG))

    Dim i As Long
    For i = 1 To fams.Count
        Dim fd As Object
        Set fd = fams(i)

        Dim dotAbs As Double
        dotAbs = Abs((SafeCDbl(fd("x")) * ux) + (SafeCDbl(fd("y")) * uy) + (SafeCDbl(fd("z")) * uz))

        If dotAbs >= cosTol Then
            Dim oldLen As Double
            oldLen = SafeCDbl(fd("len"))

            Dim nx As Double, ny As Double, nz As Double
            nx = (SafeCDbl(fd("x")) * oldLen) + (ux * addLen)
            ny = (SafeCDbl(fd("y")) * oldLen) + (uy * addLen)
            nz = (SafeCDbl(fd("z")) * oldLen) + (uz * addLen)

            If NormalizeVector3(nx, ny, nz) Then
                CanonicalizeVectorPositive nx, ny, nz
                fd("x") = nx
                fd("y") = ny
                fd("z") = nz
            End If

            fd("len") = oldLen + addLen
            fd("ct") = CLng(SafeCDbl(fd("ct"))) + 1
            Exit Sub
        End If
    Next i

    Dim fdNew As Object
    Set fdNew = CreateObject("Scripting.Dictionary")
    fdNew("x") = ux
    fdNew("y") = uy
    fdNew("z") = uz
    fdNew("len") = addLen
    fdNew("ct") = 1

    fams.Add fdNew
    Exit Sub

EH:
    LogWarn "AddDirectionFamily exception | " & Err.Number & " | " & Err.Description
End Sub

Private Function TryGetModelLineEdgeDirection(ByVal swEdge As SldWorks.Edge, _
                                              ByRef ux As Double, _
                                              ByRef uy As Double, _
                                              ByRef uz As Double, _
                                              ByRef edgeLen As Double) As Boolean
    On Error GoTo EH

    TryGetModelLineEdgeDirection = False
    ux = 0#: uy = 0#: uz = 0#: edgeLen = 0#

    If swEdge Is Nothing Then Exit Function

    Dim swStartVtx As SldWorks.Vertex
    Dim swEndVtx As SldWorks.Vertex
    Set swStartVtx = swEdge.GetStartVertex
    Set swEndVtx = swEdge.GetEndVertex

    If swStartVtx Is Nothing Or swEndVtx Is Nothing Then Exit Function

    Dim vStart As Variant
    Dim vEnd As Variant
    vStart = swStartVtx.GetPoint
    vEnd = swEndVtx.GetPoint

    If Not IsArray(vStart) Or Not IsArray(vEnd) Then Exit Function

    ux = CDbl(vEnd(0)) - CDbl(vStart(0))
    uy = CDbl(vEnd(1)) - CDbl(vStart(1))
    uz = CDbl(vEnd(2)) - CDbl(vStart(2))

    edgeLen = Sqr((ux * ux) + (uy * uy) + (uz * uz))
    If edgeLen <= 0# Then Exit Function

    ux = ux / edgeLen
    uy = uy / edgeLen
    uz = uz / edgeLen

    CanonicalizeVectorPositive ux, uy, uz
    TryGetModelLineEdgeDirection = True
    Exit Function

EH:
    LogWarn "TryGetModelLineEdgeDirection exception | " & Err.Number & " | " & Err.Description
    TryGetModelLineEdgeDirection = False
End Function

Private Function NormalizeVector3(ByRef x As Double, ByRef y As Double, ByRef z As Double) As Boolean
    Dim m As Double
    m = Sqr((x * x) + (y * y) + (z * z))
    NormalizeVector3 = False

    If m <= 0# Then Exit Function

    x = x / m
    y = y / m
    z = z / m
    NormalizeVector3 = True
End Function

Private Function Dot3(ByVal ax As Double, ByVal ay As Double, ByVal az As Double, _
                      ByVal bx As Double, ByVal by As Double, ByVal bz As Double) As Double
    Dot3 = (ax * bx) + (ay * by) + (az * bz)
End Function

Private Sub Cross3(ByVal ax As Double, ByVal ay As Double, ByVal az As Double, _
                   ByVal bx As Double, ByVal by As Double, ByVal bz As Double, _
                   ByRef ox As Double, ByRef oy As Double, ByRef oz As Double)
    ox = (ay * bz) - (az * by)
    oy = (az * bx) - (ax * bz)
    oz = (ax * by) - (ay * bx)
End Sub

Private Sub CanonicalizeVectorPositive(ByRef x As Double, ByRef y As Double, ByRef z As Double)
    Const EPS As Double = 0.000000001

    If x < -EPS Then
        x = -x: y = -y: z = -z
    ElseIf Abs(x) <= EPS And y < -EPS Then
        x = -x: y = -y: z = -z
    ElseIf Abs(x) <= EPS And Abs(y) <= EPS And z < -EPS Then
        x = -x: y = -y: z = -z
    End If
End Sub

Private Sub Sort3Doubles(ByVal a As Double, ByVal b As Double, ByVal c As Double, _
                         ByRef sMin As Double, ByRef sMid As Double, ByRef sMax As Double)
    sMin = a: sMid = b: sMax = c

    If sMin > sMid Then SwapD sMin, sMid
    If sMid > sMax Then SwapD sMid, sMax
    If sMin > sMid Then SwapD sMin, sMid
End Sub

Private Sub SwapD(ByRef a As Double, ByRef b As Double)
    Dim t As Double
    t = a
    a = b
    b = t
End Sub

Private Sub GetProjectedDominantDirectionMetrics(ByVal swView As SldWorks.View, _
                                                 ByVal item As Object, _
                                                 ByRef bestWeightedLen As Double, _
                                                 ByRef bestAngRad As Double, _
                                                 ByRef secondWeightedLen As Double, _
                                                 ByRef detail As String)
    On Error GoTo EH

    bestWeightedLen = 0#
    bestAngRad = 0#
    secondWeightedLen = 0#
    detail = ""

    If swView Is Nothing Then Exit Sub
    If item Is Nothing Then Exit Sub

    Dim names As Variant
    names = Array("Sig_PrimaryDir", "Sig_SecondaryDir", "Sig_TertiaryDir")

    Dim i As Long
    For i = LBound(names) To UBound(names)
        Dim pfx As String
        pfx = CStr(names(i))

        Dim ux As Double, uy As Double, uz As Double, famLen As Double
        ux = SafeCDbl(item(pfx & "X"))
        uy = SafeCDbl(item(pfx & "Y"))
        uz = SafeCDbl(item(pfx & "Z"))
        famLen = SafeCDbl(item(pfx & "Len"))

        If famLen > 0# Then
            Dim projMag As Double, projAngRad As Double
            If TryProjectModelDirectionToView(swView, ux, uy, uz, projMag, projAngRad) Then
                Dim weightedLen As Double
                weightedLen = famLen * projMag

                If weightedLen > bestWeightedLen Then
                    secondWeightedLen = bestWeightedLen
                    bestWeightedLen = weightedLen
                    bestAngRad = projAngRad
                ElseIf weightedLen > secondWeightedLen Then
                    secondWeightedLen = weightedLen
                End If

                If Len(detail) > 0 Then detail = detail & " ; "
                detail = detail & pfx & "=" & Fmt(weightedLen) & "@ang=" & Format$(RadToDeg(projAngRad), "0.0")
            End If
        End If
    Next i

    Exit Sub

EH:
    LogWarn "GetProjectedDominantDirectionMetrics exception | " & Err.Number & " | " & Err.Description
End Sub

Private Function TryProjectModelDirectionToView(ByVal swView As SldWorks.View, _
                                                ByVal ux As Double, _
                                                ByVal uy As Double, _
                                                ByVal uz As Double, _
                                                ByRef projMag As Double, _
                                                ByRef angRad As Double) As Boolean
    On Error GoTo EH

    TryProjectModelDirectionToView = False
    projMag = 0#
    angRad = 0#

    Dim dx As Double, dy As Double
    If Not TryProjectModelDirectionToViewXY(swView, ux, uy, uz, dx, dy, projMag, angRad) Then Exit Function

    TryProjectModelDirectionToView = True
    Exit Function

EH:
    LogWarn "TryProjectModelDirectionToView exception | " & Err.Number & " | " & Err.Description
    TryProjectModelDirectionToView = False
End Function

Private Function TryProjectModelDirectionToViewXY(ByVal swView As SldWorks.View, _
                                                  ByVal ux As Double, _
                                                  ByVal uy As Double, _
                                                  ByVal uz As Double, _
                                                  ByRef dx As Double, _
                                                  ByRef dy As Double, _
                                                  ByRef projMag As Double, _
                                                  ByRef angRad As Double) As Boolean
    On Error GoTo EH

    TryProjectModelDirectionToViewXY = False
    dx = 0#: dy = 0#
    projMag = 0#
    angRad = 0#

    If swView Is Nothing Then Exit Function

    Dim vx As Double, vy As Double, vz As Double
    vx = ux: vy = uy: vz = uz
    If Not NormalizeVector3(vx, vy, vz) Then Exit Function

    Dim o As Variant
    Dim p As Variant
    o = Array(0#, 0#, 0#)
    p = Array(vx, vy, vz)

    Dim x0 As Double, y0 As Double
    Dim x1 As Double, y1 As Double

    Dim swXf As SldWorks.MathTransform
    Set swXf = swView.ModelToViewTransform
    If swXf Is Nothing Then Exit Function

    If Not TryTransformModelPointToViewXY(o, swXf, x0, y0) Then Exit Function
    If Not TryTransformModelPointToViewXY(p, swXf, x1, y1) Then Exit Function

    dx = x1 - x0
    dy = y1 - y0

    projMag = Sqr((dx * dx) + (dy * dy))
    If projMag <= 0# Then Exit Function

    angRad = Atn2Safe(dy, dx)
    TryProjectModelDirectionToViewXY = True
    Exit Function

EH:
    LogWarn "TryProjectModelDirectionToViewXY exception | " & Err.Number & " | " & Err.Description
    TryProjectModelDirectionToViewXY = False
End Function

Private Sub GetBodyProjectedPointCloudMetrics(ByVal swView As SldWorks.View, _
                                              ByVal item As Object, _
                                              ByRef ptCt As Long, _
                                              ByRef spanLen As Double, _
                                              ByRef spanAngRad As Double, _
                                              ByRef boxW As Double, _
                                              ByRef boxH As Double, _
                                              ByRef boxArea As Double, _
                                              ByRef detail As String)
    On Error GoTo EH

    ptCt = 0
    spanLen = 0#
    spanAngRad = 0#
    boxW = 0#
    boxH = 0#
    boxArea = 0#
    detail = ""

    If swView Is Nothing Then Exit Sub
    If item Is Nothing Then Exit Sub

    Dim pts As Collection
    Set pts = New Collection

    If Not CollectProjectedBodySamplePoints(swView, item, pts) Then Exit Sub

    ptCt = pts.Count
    If ptCt = 0 Then Exit Sub

    Dim minX As Double, minY As Double, maxX As Double, maxY As Double
    Dim firstPt As Variant
    firstPt = pts(1)
    minX = CDbl(firstPt(0)): maxX = CDbl(firstPt(0))
    minY = CDbl(firstPt(1)): maxY = CDbl(firstPt(1))

    Dim i As Long
    For i = 1 To pts.Count
        Dim pv As Variant
        pv = pts(i)

        If CDbl(pv(0)) < minX Then minX = CDbl(pv(0))
        If CDbl(pv(0)) > maxX Then maxX = CDbl(pv(0))
        If CDbl(pv(1)) < minY Then minY = CDbl(pv(1))
        If CDbl(pv(1)) > maxY Then maxY = CDbl(pv(1))
    Next i

    boxW = maxX - minX
    boxH = maxY - minY
    boxArea = boxW * boxH

    Dim j As Long
    For i = 1 To pts.Count - 1
        Dim p1 As Variant
        p1 = pts(i)

        For j = i + 1 To pts.Count
            Dim p2 As Variant
            p2 = pts(j)

            Dim dx As Double, dy As Double, d As Double
            dx = CDbl(p2(0)) - CDbl(p1(0))
            dy = CDbl(p2(1)) - CDbl(p1(1))
            d = Sqr((dx * dx) + (dy * dy))

            If d > spanLen Then
                spanLen = d
                spanAngRad = Atn2Safe(dy, dx)
            End If
        Next j
    Next i

    detail = "span=" & Fmt(spanLen) & "@ang=" & Format$(RadToDeg(spanAngRad), "0.0") & _
             " | box=" & Fmt(boxW) & "x" & Fmt(boxH) & _
             " | area=" & Fmt(boxArea) & _
             " | pts=" & CStr(ptCt)
    Exit Sub

EH:
    LogWarn "GetBodyProjectedPointCloudMetrics exception | " & Err.Number & " | " & Err.Description
End Sub

Private Function CollectProjectedBodySamplePoints(ByVal swView As SldWorks.View, _
                                                  ByVal item As Object, _
                                                  ByRef pts As Collection) As Boolean
    On Error GoTo EH

    CollectProjectedBodySamplePoints = False
    If swView Is Nothing Then Exit Function
    If item Is Nothing Then Exit Function

    Dim swBody As SldWorks.Body2
    Set swBody = Nothing
    On Error Resume Next
    Set swBody = item("RepBody_Model")
    On Error GoTo EH
    If swBody Is Nothing Then Exit Function

    Dim swXf As SldWorks.MathTransform
    Set swXf = swView.ModelToViewTransform
    If swXf Is Nothing Then Exit Function

    Dim seen As Object
    Set seen = CreateObject("Scripting.Dictionary")

    Dim vEdges As Variant
    vEdges = swBody.GetEdges

    If IsArray(vEdges) Then
        Dim i As Long
        For i = LBound(vEdges) To UBound(vEdges)
            Dim swEdge As SldWorks.Edge
            Set swEdge = vEdges(i)
            If swEdge Is Nothing Then GoTo NextEdge

            Dim swV1 As SldWorks.Vertex, swV2 As SldWorks.Vertex
            Set swV1 = swEdge.GetStartVertex
            Set swV2 = swEdge.GetEndVertex

            Dim vP1 As Variant, vP2 As Variant
            vP1 = Empty: vP2 = Empty

            If Not swV1 Is Nothing Then
                vP1 = swV1.GetPoint
                AddProjectedVertexPoint swV1, swXf, pts, seen
            End If

            If Not swV2 Is Nothing Then
                vP2 = swV2.GetPoint
                AddProjectedVertexPoint swV2, swXf, pts, seen
            End If

            If IsArray(vP1) And IsArray(vP2) Then
                Dim swCurve As SldWorks.Curve
                Set swCurve = Nothing
                On Error Resume Next
                Set swCurve = swEdge.GetCurve
                On Error GoTo EH

                Dim isLine As Boolean
                isLine = False
                If Not swCurve Is Nothing Then
                    On Error Resume Next
                    isLine = swCurve.isLine
                    Err.Clear
                    On Error GoTo EH
                End If

                If isLine Then
                    Dim vQ1(0 To 2) As Double
                    Dim vMid(0 To 2) As Double
                    Dim vQ3(0 To 2) As Double

                    vQ1(0) = (CDbl(vP1(0)) * 0.75) + (CDbl(vP2(0)) * 0.25)
                    vQ1(1) = (CDbl(vP1(1)) * 0.75) + (CDbl(vP2(1)) * 0.25)
                    vQ1(2) = (CDbl(vP1(2)) * 0.75) + (CDbl(vP2(2)) * 0.25)

                    vMid(0) = (CDbl(vP1(0)) + CDbl(vP2(0))) / 2#
                    vMid(1) = (CDbl(vP1(1)) + CDbl(vP2(1))) / 2#
                    vMid(2) = (CDbl(vP1(2)) + CDbl(vP2(2))) / 2#

                    vQ3(0) = (CDbl(vP1(0)) * 0.25) + (CDbl(vP2(0)) * 0.75)
                    vQ3(1) = (CDbl(vP1(1)) * 0.25) + (CDbl(vP2(1)) * 0.75)
                    vQ3(2) = (CDbl(vP1(2)) * 0.25) + (CDbl(vP2(2)) * 0.75)

                    AddProjectedXYZPoint vQ1, swXf, pts, seen
                    AddProjectedXYZPoint vMid, swXf, pts, seen
                    AddProjectedXYZPoint vQ3, swXf, pts, seen
                End If
            End If

NextEdge:
        Next i
    End If

    If pts.Count < 2 Then
        Dim beforeCt As Long
        beforeCt = pts.Count
        AddProjectedBodyBoxCornerPoints swBody, swXf, pts, seen
        If pts.Count > beforeCt Then
            LogInfo "CollectProjectedBodySamplePoints | bbox-corner fallback added points | itemNo=" & SafeStr(item("ItemNo")) & _
                    " | pn=" & SafeStr(item("PartNo")) & _
                    " | added=" & CStr(pts.Count - beforeCt) & _
                    " | total=" & CStr(pts.Count) & _
                    " | cat=" & SafeStr(item("BodyCategory"))
        End If
    End If

    CollectProjectedBodySamplePoints = (pts.Count > 0)
    Exit Function

EH:
    LogWarn "CollectProjectedBodySamplePoints exception | " & Err.Number & " | " & Err.Description
    CollectProjectedBodySamplePoints = False
End Function


Private Sub AddProjectedXYZPoint(ByVal vPt As Variant, _
                                 ByVal swXf As SldWorks.MathTransform, _
                                 ByVal pts As Collection, _
                                 ByVal seen As Object)
    On Error GoTo EH

    If swXf Is Nothing Then Exit Sub
    If IsEmpty(vPt) Then Exit Sub
    If Not IsArray(vPt) Then Exit Sub

    Dim key As String
    key = MakePointKey3D(vPt)
    If Len(key) = 0 Then Exit Sub
    If seen.Exists(key) Then Exit Sub

    Dim x As Double, y As Double
    If Not TryTransformModelPointToViewXY(vPt, swXf, x, y) Then Exit Sub

    pts.Add Array(x, y)
    seen.Add key, True
    Exit Sub

EH:
    LogWarn "AddProjectedXYZPoint exception | " & Err.Number & " | " & Err.Description
End Sub

Private Sub GetBodyProjectedPrincipalMetrics(ByVal swView As SldWorks.View, _
                                             ByVal item As Object, _
                                             ByRef ptCt As Long, _
                                             ByRef majorLen As Double, _
                                             ByRef majorAngRad As Double, _
                                             ByRef minorLen As Double, _
                                             ByRef minorAngRad As Double, _
                                             ByRef minorRatio As Double, _
                                             ByRef detail As String)
    On Error GoTo EH

    ptCt = 0
    majorLen = 0#
    majorAngRad = 0#
    minorLen = 0#
    minorAngRad = 0#
    minorRatio = 0#
    detail = ""

    Dim pts As Collection
    Set pts = New Collection

    If Not CollectProjectedBodySamplePoints(swView, item, pts) Then Exit Sub

    ptCt = pts.Count
    If ptCt < 2 Then Exit Sub

    If ComputeProjectedPointCloudPrincipalMetrics(pts, majorLen, majorAngRad, minorLen, minorAngRad, minorRatio) Then
        detail = "major=" & Fmt(majorLen) & "@ang=" & Format$(RadToDeg(majorAngRad), "0.0") & _
                 " | minor=" & Fmt(minorLen) & "@ang=" & Format$(RadToDeg(minorAngRad), "0.0") & _
                 " | ratio=" & Fmt(minorRatio) & _
                 " | pts=" & CStr(ptCt)
    End If
    Exit Sub

EH:
    LogWarn "GetBodyProjectedPrincipalMetrics exception | " & Err.Number & " | " & Err.Description
End Sub

Private Function ComputeProjectedPointCloudPrincipalMetrics(ByVal pts As Collection, _
                                                            ByRef majorLen As Double, _
                                                            ByRef majorAngRad As Double, _
                                                            ByRef minorLen As Double, _
                                                            ByRef minorAngRad As Double, _
                                                            ByRef minorRatio As Double) As Boolean
    On Error GoTo EH

    ComputeProjectedPointCloudPrincipalMetrics = False
    majorLen = 0#
    majorAngRad = 0#
    minorLen = 0#
    minorAngRad = 0#
    minorRatio = 0#

    If pts Is Nothing Then Exit Function
    If pts.Count < 2 Then Exit Function

    Dim i As Long
    Dim meanX As Double, meanY As Double
    For i = 1 To pts.Count
        Dim p As Variant
        p = pts(i)
        meanX = meanX + CDbl(p(0))
        meanY = meanY + CDbl(p(1))
    Next i

    meanX = meanX / CDbl(pts.Count)
    meanY = meanY / CDbl(pts.Count)

    Dim sxx As Double, syy As Double, sxy As Double
    For i = 1 To pts.Count
        Dim q As Variant
        q = pts(i)

        Dim dx As Double, dy As Double
        dx = CDbl(q(0)) - meanX
        dy = CDbl(q(1)) - meanY

        sxx = sxx + (dx * dx)
        syy = syy + (dy * dy)
        sxy = sxy + (dx * dy)
    Next i

    majorAngRad = 0.5 * Atn2Safe(2# * sxy, sxx - syy)
    minorAngRad = NormalizeAngleRad(majorAngRad + (PI / 2#))

    Dim ux As Double, uy As Double
    Dim vx As Double, vy As Double
    ux = Cos(majorAngRad): uy = Sin(majorAngRad)
    vx = Cos(minorAngRad): vy = Sin(minorAngRad)

    Dim minU As Double, maxU As Double, minV As Double, maxV As Double
    Dim first As Variant
    first = pts(1)

    minU = (CDbl(first(0)) * ux) + (CDbl(first(1)) * uy)
    maxU = minU
    minV = (CDbl(first(0)) * vx) + (CDbl(first(1)) * vy)
    maxV = minV

    For i = 2 To pts.Count
        Dim r As Variant
        r = pts(i)

        Dim pu As Double, pv As Double
        pu = (CDbl(r(0)) * ux) + (CDbl(r(1)) * uy)
        pv = (CDbl(r(0)) * vx) + (CDbl(r(1)) * vy)

        If pu < minU Then minU = pu
        If pu > maxU Then maxU = pu
        If pv < minV Then minV = pv
        If pv > maxV Then maxV = pv
    Next i

    majorLen = maxU - minU
    minorLen = maxV - minV

    If minorLen > majorLen Then
        SwapD majorLen, minorLen
        SwapD majorAngRad, minorAngRad
    End If

    majorAngRad = NormalizeAngleRad(majorAngRad)
    minorAngRad = NormalizeAngleRad(minorAngRad)

    If majorLen > 0# Then minorRatio = minorLen / majorLen

    ComputeProjectedPointCloudPrincipalMetrics = (majorLen > 0#)
    Exit Function

EH:
    LogWarn "ComputeProjectedPointCloudPrincipalMetrics exception | " & Err.Number & " | " & Err.Description
    ComputeProjectedPointCloudPrincipalMetrics = False
End Function


Private Sub AddProjectedBodyBoxCornerPoints(ByVal swBody As SldWorks.Body2, _
                                            ByVal swXf As SldWorks.MathTransform, _
                                            ByVal pts As Collection, _
                                            ByVal seen As Object)
    On Error GoTo EH

    If swBody Is Nothing Then Exit Sub
    If swXf Is Nothing Then Exit Sub
    If pts Is Nothing Then Exit Sub
    If seen Is Nothing Then Exit Sub

    Dim vBox As Variant
    vBox = swBody.GetBodyBox
    If Not IsArray(vBox) Then Exit Sub
    If UBound(vBox) - LBound(vBox) < 5 Then Exit Sub

    Dim x0 As Double, y0 As Double, z0 As Double
    Dim x1 As Double, y1 As Double, z1 As Double

    x0 = CDbl(vBox(0)): y0 = CDbl(vBox(1)): z0 = CDbl(vBox(2))
    x1 = CDbl(vBox(3)): y1 = CDbl(vBox(4)): z1 = CDbl(vBox(5))

    If x1 < x0 Then SwapD x0, x1
    If y1 < y0 Then SwapD y0, y1
    If z1 < z0 Then SwapD z0, z1

    Dim p(0 To 2) As Double
    Dim ix As Long, iy As Long, iz As Long

    For ix = 0 To 1
        For iy = 0 To 1
            For iz = 0 To 1
                If ix = 0 Then
                    p(0) = x0
                Else
                    p(0) = x1
                End If

                If iy = 0 Then
                    p(1) = y0
                Else
                    p(1) = y1
                End If

                If iz = 0 Then
                    p(2) = z0
                Else
                    p(2) = z1
                End If

                AddProjectedXYZPoint p, swXf, pts, seen
            Next iz
        Next iy
    Next ix

    Exit Sub

EH:
    LogWarn "AddProjectedBodyBoxCornerPoints exception | " & Err.Number & " | " & Err.Description
End Sub

Private Sub AddProjectedVertexPoint(ByVal swVtx As SldWorks.Vertex, _
                                    ByVal swXf As SldWorks.MathTransform, _
                                    ByVal pts As Collection, _
                                    ByVal seen As Object)
    On Error GoTo EH

    If swVtx Is Nothing Then Exit Sub
    If swXf Is Nothing Then Exit Sub

    Dim vPt As Variant
    vPt = swVtx.GetPoint
    If Not IsArray(vPt) Then Exit Sub

    Dim key As String
    key = MakePointKey3D(vPt)
    If seen.Exists(key) Then Exit Sub

    Dim x As Double, y As Double
    If Not TryTransformModelPointToViewXY(vPt, swXf, x, y) Then Exit Sub

    pts.Add Array(x, y)
    seen.Add key, True
    Exit Sub

EH:
    LogWarn "AddProjectedVertexPoint exception | " & Err.Number & " | " & Err.Description
End Sub

Private Function MakePointKey3D(ByVal vPt As Variant) As String
    On Error GoTo EH

    MakePointKey3D = Format$(CDbl(vPt(0)), "0." & String$(CAT_POINT_KEY_DECIMALS, "0")) & "|" & _
                     Format$(CDbl(vPt(1)), "0." & String$(CAT_POINT_KEY_DECIMALS, "0")) & "|" & _
                     Format$(CDbl(vPt(2)), "0." & String$(CAT_POINT_KEY_DECIMALS, "0"))
    Exit Function

EH:
    MakePointKey3D = ""
End Function

Private Function GetCategoryOrientationReference(ByVal swView As SldWorks.View, _
                                                 ByVal item As Object, _
                                                 ByRef refMetric As Double, _
                                                 ByRef refAngRad As Double, _
                                                 ByRef qualityScore As Double, _
                                                 ByRef refDesc As String) As Boolean
    On Error GoTo EH

    GetCategoryOrientationReference = False
    refMetric = 0#
    refAngRad = 0#
    qualityScore = -1E+30
    refDesc = ""

    If swView Is Nothing Then Exit Function
    If item Is Nothing Then Exit Function

    Call EnsureItemCategoryAnalysis(item)

    Dim catName As String
    catName = SafeStr(item("BodyCategory"))

    Dim subtypeName As String
    subtypeName = UCase$(SafeStr(item("BodySubtype")))

    Dim famBest As Double, famAng As Double, famSecond As Double, famDetail As String
    Dim spanLen As Double, spanAng As Double, cloudW As Double, cloudH As Double, cloudArea As Double, cloudDetail As String
    Dim ptCt As Long
    Dim visLen As Double, visAng As Double, visDesc As String
    Dim edgeCt As Long, lineCt As Long

    Dim pcPtCt As Long
    Dim pcMajor As Double, pcMajorAng As Double
    Dim pcMinor As Double, pcMinorAng As Double
    Dim pcRatio As Double, pcDetail As String
    Dim faceMetric As Double
    Dim profileMetric As Double
    Dim penalty As Double

    Call GetProjectedDominantDirectionMetrics(swView, item, famBest, famAng, famSecond, famDetail)
    Call GetBodyProjectedPointCloudMetrics(swView, item, ptCt, spanLen, spanAng, cloudW, cloudH, cloudArea, cloudDetail)
    Call GetBodyProjectedPrincipalMetrics(swView, item, pcPtCt, pcMajor, pcMajorAng, pcMinor, pcMinorAng, pcRatio, pcDetail)
    Call GetBestVisibleLinearEdgeInView(swView, visLen, visAng, visDesc, edgeCt, lineCt)

    faceMetric = pcMajor * pcMinor
    profileMetric = MaxD(pcMinor, famSecond)
    penalty = 0#

    Select Case UCase$(catName)
        Case UCase$(CAT_NAME_LINEAR_STRUCTURAL)
            If famBest > 0# Then
                Dim familyAgreesWithPrincipal As Boolean
                Dim familyPrincipalDeltaDeg As Double

                familyAgreesWithPrincipal = True
                familyPrincipalDeltaDeg = 0#
                If pcMajor > 0# Then
                    familyPrincipalDeltaDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(famAng - pcMajorAng)))
                    familyAgreesWithPrincipal = (familyPrincipalDeltaDeg <= 12#)
                End If

                If familyAgreesWithPrincipal Then
                    refMetric = famBest
                    refAngRad = famAng
                    qualityScore = (famBest * 1200000#) + (famSecond * 450000#) + (pcMajor * 150000#) + (cloudArea * 50000#)
                    refDesc = "Linear 3D direction-family axis | famBest=" & Fmt(famBest) & _
                              " | famSecond=" & Fmt(famSecond) & _
                              " | familyPrincipalDeltaDeg=" & Format$(familyPrincipalDeltaDeg, "0.000") & _
                              " | " & famDetail & " | pcDiag={" & pcDetail & "}"
                ElseIf pcMajor > 0# Then
                    refMetric = pcMajor
                    refAngRad = pcMajorAng
                    qualityScore = (pcMajor * 900000#) + (profileMetric * 600000#) + (famBest * 100000#) + (cloudArea * 100000#)
                    refDesc = "Linear principal-axis fallback after 3D family mismatch | pcMajor=" & Fmt(pcMajor) & _
                              " | profile=" & Fmt(profileMetric) & _
                              " | pcRatio=" & Fmt(pcRatio) & _
                              " | familyPrincipalDeltaDeg=" & Format$(familyPrincipalDeltaDeg, "0.000") & _
                              " | " & famDetail & " | " & pcDetail
                    If profileMetric < MaxD(CAT_LINEAR_PROFILE_ABS_MIN, pcMajor * CAT_LINEAR_PROFILE_RATIO_MIN) Then
                        penalty = CAT_REJECT_PENALTY
                        refDesc = refDesc & " | rejectHint=linear end-on/readability penalty"
                    End If
                End If
            ElseIf pcMajor > 0# Then
                refMetric = pcMajor
                refAngRad = pcMajorAng
                qualityScore = (pcMajor * 900000#) + (profileMetric * 600000#) + (cloudArea * 100000#)
                refDesc = "Linear principal-axis fallback; no 3D direction family projected | pcMajor=" & Fmt(pcMajor) & _
                          " | profile=" & Fmt(profileMetric) & _
                          " | pcRatio=" & Fmt(pcRatio) & " | " & pcDetail
                If profileMetric < MaxD(CAT_LINEAR_PROFILE_ABS_MIN, pcMajor * CAT_LINEAR_PROFILE_RATIO_MIN) Then
                    penalty = CAT_REJECT_PENALTY
                    refDesc = refDesc & " | rejectHint=linear end-on/readability penalty"
                End If
            ElseIf famBest > 0# Then
                refMetric = famBest
                refAngRad = famAng
                qualityScore = (famBest * 700000#) + (famSecond * 300000#) + (cloudArea * 50000#)
                refDesc = "Linear family fallback | famBest=" & Fmt(famBest) & " | famSecond=" & Fmt(famSecond) & " | " & famDetail
            ElseIf spanLen > 0# Then
                refMetric = spanLen
                refAngRad = spanAng
                qualityScore = (spanLen * 500000#) + (cloudArea * 50000#)
                refDesc = "Linear span fallback | " & cloudDetail
            ElseIf visLen > 0# Then
                refMetric = visLen
                refAngRad = visAng
                qualityScore = (visLen * 300000#) + (cloudArea * 50000#)
                refDesc = "Linear visible-edge fallback | len=" & Fmt(visLen) & " | " & visDesc
            End If

        Case UCase$(CAT_NAME_SHEET_PLATE)
            If pcMajor > 0# Then
                refMetric = pcMajor
                refAngRad = pcMajorAng
                qualityScore = (faceMetric * 2500000#) + (pcMinor * 1200000#) + (pcMajor * 500000#) + (cloudArea * 200000#)
                refDesc = "Sheet broad-face principal axis | faceMetric=" & Fmt(faceMetric) & " | pcMajor=" & Fmt(pcMajor) & _
                          " | pcMinor=" & Fmt(pcMinor) & " | pcRatio=" & Fmt(pcRatio) & " | " & pcDetail
                If pcMinor < MaxD(CAT_PLATE_FACE_ABS_MIN, pcMajor * CAT_PLATE_FACE_RATIO_MIN) Then
                    penalty = CAT_REJECT_PENALTY
                    refDesc = refDesc & " | rejectHint=sheet edge-on penalty"
                End If
            ElseIf spanLen > 0# Then
                refMetric = spanLen
                refAngRad = spanAng
                qualityScore = (spanLen * 500000#) + (cloudArea * 150000#)
                refDesc = "Sheet span fallback | " & cloudDetail
            ElseIf famBest > 0# Then
                refMetric = famBest
                refAngRad = famAng
                qualityScore = (famBest * 300000#) + (cloudArea * 50000#)
                refDesc = "Sheet family fallback | " & famDetail
            End If

        Case UCase$(CAT_NAME_CURVED_STRUCTURAL)
            If pcMajor > 0# Then
                refMetric = pcMajor
                refAngRad = pcMajorAng
                qualityScore = (pcMajor * 800000#) + (pcMinor * 300000#) + (cloudArea * 200000#) + (spanLen * 150000#)
                refDesc = "Curved principal chord/span | pcMajor=" & Fmt(pcMajor) & " | pcMinor=" & Fmt(pcMinor) & " | " & pcDetail
            ElseIf spanLen > 0# Then
                refMetric = spanLen
                refAngRad = spanAng
                qualityScore = (spanLen * 700000#) + (cloudArea * 150000#)
                refDesc = "Curved span fallback | " & cloudDetail
            ElseIf visLen > 0# Then
                refMetric = visLen
                refAngRad = visAng
                qualityScore = (visLen * 500000#) + (cloudArea * 50000#)
                refDesc = "Curved visible-edge fallback | " & visDesc
            End If

        Case UCase$(CAT_NAME_ROUND_STOCK)
            If famBest > 0# Then
                refMetric = famBest
                refAngRad = famAng
                qualityScore = (famBest * 800000#) + (pcMajor * 200000#) + (cloudArea * 50000#)
                refDesc = "Round/rod dominant axis | famBest=" & Fmt(famBest) & " | " & famDetail
            ElseIf pcMajor > 0# Then
                refMetric = pcMajor
                refAngRad = pcMajorAng
                qualityScore = (pcMajor * 500000#) + (pcMinor * 100000#) + (cloudArea * 50000#)
                refDesc = "Round/rod principal fallback | " & pcDetail
            ElseIf spanLen > 0# Then
                refMetric = spanLen
                refAngRad = spanAng
                qualityScore = (spanLen * 300000#) + (cloudArea * 50000#)
                refDesc = "Round/rod span fallback | " & cloudDetail
            End If

        Case Else
            If pcMajor > 0# Then
                refMetric = pcMajor
                refAngRad = pcMajorAng
                qualityScore = (faceMetric * 500000#) + (pcMajor * 300000#) + (pcMinor * 200000#) + (cloudArea * 100000#)
                refDesc = "Irregular principal axis | " & pcDetail
            ElseIf spanLen > 0# Then
                refMetric = spanLen
                refAngRad = spanAng
                qualityScore = (spanLen * 400000#) + (cloudArea * 150000#)
                refDesc = "Irregular span axis | " & cloudDetail
            ElseIf famBest > 0# Then
                refMetric = famBest
                refAngRad = famAng
                qualityScore = (famBest * 250000#) + (cloudArea * 50000#)
                refDesc = "Irregular family fallback | " & famDetail
            ElseIf visLen > 0# Then
                refMetric = visLen
                refAngRad = visAng
                qualityScore = (visLen * 200000#) + (cloudArea * 50000#)
                refDesc = "Irregular visible-edge fallback | " & visDesc
            End If
    End Select

    If penalty > 0# Then qualityScore = qualityScore - penalty

    If refMetric > 0# Then
        GetCategoryOrientationReference = True
    End If

    Exit Function

EH:
    LogWarn "GetCategoryOrientationReference exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
    GetCategoryOrientationReference = False
End Function



Private Function AppendRejectNote(ByVal baseText As String, ByVal addText As String) As String
    If Len(baseText) = 0 Then
        AppendRejectNote = addText
    Else
        AppendRejectNote = baseText & "; " & addText
    End If
End Function

Private Function ValidateOrientationResultByCategory(ByVal swView As SldWorks.View, _
                                                     ByVal item As Object, _
                                                     ByRef validationScore As Double, _
                                                     ByRef validationDetail As String) As Boolean
    On Error GoTo EH

    ValidateOrientationResultByCategory = False
    validationScore = -1E+30
    validationDetail = ""

    If swView Is Nothing Then Exit Function
    If item Is Nothing Then Exit Function

    Call EnsureItemCategoryAnalysis(item)

    Dim catName As String
    catName = SafeStr(item("BodyCategory"))

    Dim subtypeName As String
    subtypeName = UCase$(SafeStr(item("BodySubtype")))

    Dim famBest As Double, famAng As Double, famSecond As Double, famDetail As String
    Dim spanLen As Double, spanAng As Double, cloudW As Double, cloudH As Double, cloudArea As Double, cloudDetail As String
    Dim ptCt As Long
    Dim pcPtCt As Long
    Dim pcMajor As Double, pcMajorAng As Double
    Dim pcMinor As Double, pcMinorAng As Double
    Dim pcRatio As Double, pcDetail As String
    Dim angleEdgeOk As Boolean
    Dim angleEdgeMetric As Double
    Dim angleEdgeAng As Double
    Dim angleEdgeRemainDeg As Double
    Dim angleEdgeDetail As String
    Dim angleEdgeTopDetail As String
    Dim angleEdgeCt As Long
    Dim angleLineCt As Long
    Dim angleFamilyCt As Long
    Dim outlineW As Double, outlineH As Double
    Dim majorRemainDeg As Double
    Dim outlineRatio As Double
    Dim faceMetric As Double
    Dim profileMetric As Double
    Dim valid As Boolean
    Dim minReadable As Double
    Dim angleHorizontalRemainDeg As Double

    Call GetProjectedDominantDirectionMetrics(swView, item, famBest, famAng, famSecond, famDetail)
    Call GetBodyProjectedPointCloudMetrics(swView, item, ptCt, spanLen, spanAng, cloudW, cloudH, cloudArea, cloudDetail)
    Call GetBodyProjectedPrincipalMetrics(swView, item, pcPtCt, pcMajor, pcMajorAng, pcMinor, pcMinorAng, pcRatio, pcDetail)

    Dim roundFallbackDetail As String
    roundFallbackDetail = ""

    Dim visEdgeCt As Long, visVertexCt As Long, visFaceCt As Long
    visEdgeCt = 0: visVertexCt = 0: visFaceCt = 0
    GetVisibleEntityCountsInView swView, visEdgeCt, visVertexCt, visFaceCt

    If UCase$(catName) = UCase$(CAT_NAME_ROUND_STOCK) Then
        If (pcMajor <= 0# Or spanLen <= 0#) And (visEdgeCt > 0 Or visFaceCt > 0) Then
            If ApplyRoundStockProjectedOutlineFallback(swView, item, ptCt, spanLen, cloudW, cloudH, cloudArea, _
                                                       pcPtCt, pcMajor, pcMajorAng, pcMinor, pcMinorAng, pcRatio, _
                                                       roundFallbackDetail) Then
                LogInfo "RoundStockFallback | final orientation metrics restored from outline | view=" & _
                        ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
                        " | detail={" & roundFallbackDetail & "}"
            End If
        End If
    End If

    majorRemainDeg = 9999#
    If pcMajor > 0# Then majorRemainDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(pcMajorAng)))

    angleEdgeRemainDeg = 9999#
    If subtypeName = CAT_SUBTYPE_ANGLE Then
        angleEdgeOk = TryGetBestProjectedModelEdgeFamily(swView, item, angleEdgeMetric, angleEdgeAng, angleEdgeDetail, _
                                                         angleEdgeCt, angleLineCt, angleFamilyCt, angleEdgeTopDetail)
        If angleEdgeOk Then angleEdgeRemainDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(angleEdgeAng)))
    End If

    outlineW = 0#: outlineH = 0#
    If TryGetViewOutlineWH(swView, outlineW, outlineH) Then
        If outlineH > 0# Then outlineRatio = outlineW / outlineH
    End If

    faceMetric = pcMajor * pcMinor
    profileMetric = MaxD(pcMinor, famSecond)
    valid = False

    Select Case subtypeName
        Case CAT_SUBTYPE_CHANNEL
            minReadable = MaxD(CAT_SECTION_PROFILE_ABS_MIN, pcMajor * CAT_CHANNEL_PROFILE_RATIO_MIN)
            If pcMajor > 0# Then
                valid = (majorRemainDeg <= CAT_MAJOR_HORIZONTAL_TOL_DEG) And _
                        (profileMetric >= minReadable) And _
                        (pcMajor >= (profileMetric * 1.1))
                validationScore = (pcMajor * 1200000#) + (profileMetric * 1700000#) + _
                                  (famSecond * 1000000#) - (majorRemainDeg * 1000#)
                If outlineRatio >= CAT_OUTLINE_HORIZONTAL_RATIO_MIN Then validationScore = validationScore + 500#
            End If

        Case CAT_SUBTYPE_ANGLE
            minReadable = MaxD(CAT_SECTION_PROFILE_ABS_MIN, pcMajor * CAT_ANGLE_PROFILE_RATIO_MIN)
            If pcMajor > 0# Then
                angleHorizontalRemainDeg = majorRemainDeg
                If angleEdgeOk Then angleHorizontalRemainDeg = angleEdgeRemainDeg

                valid = (angleHorizontalRemainDeg <= CAT_MAJOR_HORIZONTAL_TOL_DEG) And _
                        (profileMetric >= minReadable) And _
                        (pcMajor >= (profileMetric * 1.05))
                validationScore = (pcMajor * 1250000#) + (profileMetric * 1600000#) + _
                                  (famSecond * 1200000#) - (angleHorizontalRemainDeg * 1000#)
                If outlineRatio >= CAT_OUTLINE_HORIZONTAL_RATIO_MIN Then validationScore = validationScore + 500#
            End If

        Case Else
            Select Case UCase$(catName)
                Case UCase$(CAT_NAME_LINEAR_STRUCTURAL)
                    If pcMajor > 0# Then
                        minReadable = MaxD(CAT_LINEAR_PROFILE_ABS_MIN, pcMajor * CAT_LINEAR_PROFILE_RATIO_MIN)
                        valid = (majorRemainDeg <= CAT_MAJOR_HORIZONTAL_TOL_DEG) And _
                                (profileMetric >= minReadable)
                        validationScore = (pcMajor * 1000000#) + (profileMetric * 700000#) - (majorRemainDeg * 1000#)
                        If outlineRatio >= CAT_OUTLINE_HORIZONTAL_RATIO_MIN Then validationScore = validationScore + 500#
                    End If

                Case UCase$(CAT_NAME_SHEET_PLATE)
                    If pcMajor > 0# Then
                        minReadable = MaxD(CAT_PLATE_FACE_ABS_MIN, pcMajor * CAT_PLATE_FACE_RATIO_MIN)
                        valid = (majorRemainDeg <= CAT_MAJOR_HORIZONTAL_TOL_DEG) And _
                                (pcMinor >= minReadable)
                        validationScore = (faceMetric * 2500000#) + (pcMinor * 1200000#) - (majorRemainDeg * 1000#)
                        If outlineRatio >= CAT_OUTLINE_HORIZONTAL_RATIO_MIN Then validationScore = validationScore + 500#
                    End If

                Case UCase$(CAT_NAME_CURVED_STRUCTURAL)
                    If pcMajor > 0# Then
                        valid = (majorRemainDeg <= CAT_MAJOR_HORIZONTAL_TOL_DEG)
                        validationScore = (pcMajor * 800000#) + (pcMinor * 200000#) - (majorRemainDeg * 1000#)
                    End If

                Case UCase$(CAT_NAME_ROUND_STOCK)
                    If famBest > 0# Then
                        valid = (Abs(RadToDeg(NormalizeAngleToHorizontal(famAng))) <= CAT_MAJOR_HORIZONTAL_TOL_DEG)
                        validationScore = (famBest * 700000#) - (Abs(RadToDeg(NormalizeAngleToHorizontal(famAng))) * 1000#)
                    ElseIf pcMajor > 0# Then
                        valid = (majorRemainDeg <= CAT_MAJOR_HORIZONTAL_TOL_DEG)
                        validationScore = (pcMajor * 400000#) - (majorRemainDeg * 1000#)
                    End If

                Case Else
                    If pcMajor > 0# Then
                        valid = (majorRemainDeg <= CAT_MAJOR_HORIZONTAL_TOL_DEG)
                        validationScore = (faceMetric * 500000#) + (pcMajor * 300000#) - (majorRemainDeg * 1000#)
                    End If
            End Select
    End Select

    validationDetail = "cat=" & catName & _
                       " | subtype=" & SafeStr(item("BodySubtype")) & _
                       " | pcMajor=" & Fmt(pcMajor) & "@ang=" & Format$(RadToDeg(pcMajorAng), "0.000") & _
                       " | pcMinor=" & Fmt(pcMinor) & _
                       " | pcRatio=" & Fmt(pcRatio) & _
                       " | profileMetric=" & Fmt(profileMetric) & _
                       " | minReadable=" & Fmt(minReadable) & _
                       " | famBest=" & Fmt(famBest) & _
                       " | famSecond=" & Fmt(famSecond) & _
                       " | majorRemainDeg=" & Format$(majorRemainDeg, "0.000") & _
                       " | angleEdgeOk=" & BoolWord(angleEdgeOk) & _
                       " | angleEdgeRemainDeg=" & Format$(angleEdgeRemainDeg, "0.000") & _
                       " | angleEdgeMetric=" & Fmt(angleEdgeMetric) & _
                       " | angleEdge={" & angleEdgeDetail & "}" & _
                       " | outline=" & Fmt(outlineW) & "x" & Fmt(outlineH) & _
                       " | outlineRatio=" & Fmt(outlineRatio) & _
                       " | valid=" & BoolWord(valid)

    If Len(roundFallbackDetail) > 0 Then
        validationDetail = validationDetail & " | fallback={" & roundFallbackDetail & "}"
    End If

    ValidateOrientationResultByCategory = valid
    Exit Function

EH:
    LogWarn "ValidateOrientationResultByCategory exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
    ValidateOrientationResultByCategory = False
End Function

Private Function IsAngleProjectedEdgeFamilyHorizontal(ByVal swView As SldWorks.View, _
                                                      ByVal item As Object, _
                                                      ByRef edgeDetail As String) As Boolean
    On Error GoTo EH

    IsAngleProjectedEdgeFamilyHorizontal = False
    edgeDetail = ""

    Dim bestMetric As Double
    Dim bestAngRad As Double
    Dim famDetail As String
    Dim edgeCt As Long
    Dim lineCt As Long
    Dim familyCt As Long
    Dim topDetail As String

    If Not TryGetBestProjectedModelEdgeFamily(swView, item, bestMetric, bestAngRad, famDetail, _
                                              edgeCt, lineCt, familyCt, topDetail) Then
        edgeDetail = "edgeFamilyAvailable=False | edgeCt=" & CStr(edgeCt) & _
                     " | lineCt=" & CStr(lineCt) & _
                     " | familyCt=" & CStr(familyCt)
        Exit Function
    End If

    Dim remainDeg As Double
    remainDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(bestAngRad)))

    edgeDetail = "edgeFamilyAvailable=True | remainDeg=" & Format$(remainDeg, "0.000") & _
                 " | tolDeg=" & Format$(ANGLE_EDGE_FAMILY_FINAL_TOL_DEG, "0.000") & _
                 " | selected={" & famDetail & "}" & _
                 " | top={" & topDetail & "}"

    IsAngleProjectedEdgeFamilyHorizontal = (remainDeg <= ANGLE_EDGE_FAMILY_FINAL_TOL_DEG)
    Exit Function

EH:
    edgeDetail = "IsAngleProjectedEdgeFamilyHorizontal exception | " & Err.Number & " | " & Err.Description
    LogWarn edgeDetail & " | view=" & ViewGetNameSafe(swView)
    IsAngleProjectedEdgeFamilyHorizontal = False
End Function

Private Function TryRotateAngleViewByProjectedEdgeFamily(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                                         ByVal swView As SldWorks.View, _
                                                         ByVal item As Object, _
                                                         ByVal tag As String, _
                                                         ByRef edgeRotateDetail As String, _
                                                         ByRef edgeFamilyAvailable As Boolean) As Boolean
    On Error GoTo EH

    TryRotateAngleViewByProjectedEdgeFamily = False
    edgeRotateDetail = ""
    edgeFamilyAvailable = False

    If swDrawModel Is Nothing Then
        edgeRotateDetail = "method=model-edge-family | drawing model is Nothing"
        Exit Function
    End If
    If swView Is Nothing Then
        edgeRotateDetail = "method=model-edge-family | view is Nothing"
        Exit Function
    End If
    If item Is Nothing Then
        edgeRotateDetail = "method=model-edge-family | item is Nothing"
        Exit Function
    End If
    If UCase$(SafeStr(item("BodySubtype"))) <> CAT_SUBTYPE_ANGLE Then
        edgeRotateDetail = "method=model-edge-family | skipped; subtype=" & SafeStr(item("BodySubtype"))
        Exit Function
    End If

    Dim basisDetail As String
    basisDetail = DescribeViewProjectionBasis(swView)
    LogInfo "ViewProjectionBasis | view=" & ViewGetNameSafe(swView) & _
            " | itemNo=" & SafeStr(item("ItemNo")) & _
            " | subtype=ANGLE" & _
            " | basis={" & basisDetail & "}"

    Dim bestMetric As Double
    Dim bestAngRad As Double
    Dim famDetail As String
    Dim edgeCt As Long
    Dim lineCt As Long
    Dim familyCt As Long
    Dim topDetail As String

    If Not TryGetBestProjectedModelEdgeFamily(swView, item, bestMetric, bestAngRad, famDetail, _
                                              edgeCt, lineCt, familyCt, topDetail) Then
        edgeRotateDetail = "method=model-edge-family | edgeFamilyAvailable=False" & _
                           " | edgeCt=" & CStr(edgeCt) & _
                           " | lineCt=" & CStr(lineCt) & _
                           " | familyCt=" & CStr(familyCt) & _
                           " | basis={" & basisDetail & "}"
        LogWarn "RotationFallback | no usable projected model edge family for angle | view=" & _
                ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
                " | detail={" & edgeRotateDetail & "}"
        Exit Function
    End If

    edgeFamilyAvailable = True
    LogInfo "RotationEdgeFamily | view=" & ViewGetNameSafe(swView) & _
            " | itemNo=" & SafeStr(item("ItemNo")) & _
            " | source=MODEL_EDGE_FAMILY" & _
            " | selected={" & famDetail & "}" & _
            " | edgeCt=" & CStr(edgeCt) & _
            " | lineCt=" & CStr(lineCt) & _
            " | familyCt=" & CStr(familyCt) & _
            " | top={" & topDetail & "}"

    Dim baseAngle As Double
    Dim thetaA As Double
    Dim thetaB As Double
    Dim selectedTheta As Double
    Dim choiceReason As String

    baseAngle = swView.Angle
    thetaA = NormalizeAngleRad(baseAngle - NormalizeAngleToHorizontal(bestAngRad))
    thetaB = NormalizeAngleRad(thetaA + PI)
    selectedTheta = thetaA
    choiceReason = "thetaA selected; no decisive polarity evidence"

    Dim primaryX As Double, primaryY As Double, primaryZ As Double
    Dim secondaryX As Double, secondaryY As Double, secondaryZ As Double
    Dim hasSecondary As Boolean
    Dim frameReason As String
    Dim secondaryDetail As String

    secondaryDetail = "secondary/profile polarity unavailable"
    If TryGetBodyFabricationFrame(item, primaryX, primaryY, primaryZ, _
                                  secondaryX, secondaryY, secondaryZ, _
                                  hasSecondary, frameReason) Then
        If hasSecondary Then
            Dim secondaryDx As Double, secondaryDy As Double
            Dim secondaryMag As Double, secondaryAng As Double
            If TryProjectModelDirectionToViewXY(swView, secondaryX, secondaryY, secondaryZ, _
                                                secondaryDx, secondaryDy, secondaryMag, secondaryAng) Then
                Dim secA_X As Double, secA_Y As Double
                Dim secB_X As Double, secB_Y As Double
                Dim scoreA As Double, scoreB As Double

                Rotate2DVector secondaryDx, secondaryDy, NormalizeAngleRad(thetaA - baseAngle), secA_X, secA_Y
                Rotate2DVector secondaryDx, secondaryDy, NormalizeAngleRad(thetaB - baseAngle), secB_X, secB_Y

                scoreA = ScoreRotationPolarityChoice(item, secA_X, secA_Y)
                scoreB = ScoreRotationPolarityChoice(item, secB_X, secB_Y)

                secondaryDetail = "secondaryProj=(" & Fmt(secondaryDx) & "," & Fmt(secondaryDy) & ")" & _
                                  " | secondaryAngDeg=" & Format$(RadToDeg(secondaryAng), "0.000") & _
                                  " | afterA=(" & Fmt(secA_X) & "," & Fmt(secA_Y) & ")" & _
                                  " | afterB=(" & Fmt(secB_X) & "," & Fmt(secB_Y) & ")" & _
                                  " | scoreA=" & Fmt(scoreA) & _
                                  " | scoreB=" & Fmt(scoreB) & _
                                  " | frame={" & frameReason & "}"

                If scoreB > (scoreA + 0.000001) Then
                    selectedTheta = thetaB
                    choiceReason = "thetaB selected by angle secondary/profile bottom-side polarity"
                ElseIf scoreA > (scoreB + 0.000001) Then
                    selectedTheta = thetaA
                    choiceReason = "thetaA selected by angle secondary/profile bottom-side polarity"
                Else
                    selectedTheta = thetaA
                    choiceReason = "thetaA kept; angle secondary/profile polarity ambiguous"
                End If
            Else
                secondaryDetail = "secondary/profile vector projected edge-on or transform failed | frame={" & frameReason & "}"
            End If
        Else
            secondaryDetail = "fabrication frame found but no secondary/profile vector | frame={" & frameReason & "}"
        End If
    Else
        secondaryDetail = "fabrication frame unavailable for polarity | frame={" & frameReason & "}"
    End If

    LogInfo "RotationChoice | view=" & ViewGetNameSafe(swView) & _
            " | itemNo=" & SafeStr(item("ItemNo")) & _
            " | subtype=ANGLE" & _
            " | source=MODEL_EDGE_FAMILY" & _
            " | refMetric=" & Fmt(bestMetric) & _
            " | refAngDeg=" & Format$(RadToDeg(bestAngRad), "0.000") & _
            " | thetaA=" & Format$(RadToDeg(thetaA), "0.000") & _
            " | thetaB=" & Format$(RadToDeg(thetaB), "0.000") & _
            " | selectedTheta=" & Format$(RadToDeg(selectedTheta), "0.000") & _
            " | choiceReason=" & choiceReason & _
            " | selectedFamily={" & famDetail & "}" & _
            " | " & secondaryDetail & _
            " | basis={" & basisDetail & "}"

    SetViewAngleAndRefresh swDrawModel, swView, selectedTheta, tag & " | exact projected model edge-family align"

    Dim confMetric As Double
    Dim confAngRad As Double
    Dim confDetail As String
    Dim confEdgeCt As Long
    Dim confLineCt As Long
    Dim confFamilyCt As Long
    Dim confTopDetail As String
    Dim remainDeg As Double
    Dim validNow As Boolean
    Dim validationScore As Double
    Dim validationDetail As String

    remainDeg = 9999#
    If TryGetBestProjectedModelEdgeFamily(swView, item, confMetric, confAngRad, confDetail, _
                                          confEdgeCt, confLineCt, confFamilyCt, confTopDetail) Then
        remainDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(confAngRad)))
    Else
        confDetail = "confirm edge-family unavailable | edgeCt=" & CStr(confEdgeCt) & _
                     " | lineCt=" & CStr(confLineCt) & _
                     " | familyCt=" & CStr(confFamilyCt)
    End If

    validNow = ValidateOrientationResultByCategory(swView, item, validationScore, validationDetail)

    LogInfo "RotationEdgeValidation | view=" & ViewGetNameSafe(swView) & _
            " | itemNo=" & SafeStr(item("ItemNo")) & _
            " | source=MODEL_EDGE_FAMILY" & _
            " | edgeRemainDeg=" & Format$(remainDeg, "0.000") & _
            " | tolDeg=" & Format$(ANGLE_EDGE_FAMILY_FINAL_TOL_DEG, "0.000") & _
            " | validCategory=" & BoolWord(validNow) & _
            " | confirm={" & confDetail & "}" & _
            " | validation={" & validationDetail & "}"

    edgeRotateDetail = "method=model-edge-family" & _
                       " | edgeFamilyAvailable=True" & _
                       " | valid=" & BoolWord(validNow And (remainDeg <= ANGLE_EDGE_FAMILY_FINAL_TOL_DEG)) & _
                       " | edgeRemainDeg=" & Format$(remainDeg, "0.000") & _
                       " | refAngDeg=" & Format$(RadToDeg(bestAngRad), "0.000") & _
                       " | thetaA=" & Format$(RadToDeg(thetaA), "0.000") & _
                       " | thetaB=" & Format$(RadToDeg(thetaB), "0.000") & _
                       " | selectedDeg=" & Format$(RadToDeg(selectedTheta), "0.000") & _
                       " | choice=" & choiceReason & _
                       " | selected={" & famDetail & "}" & _
                       " | confirm={" & confDetail & "}" & _
                       " | validation={" & validationDetail & "}"

    If validNow And remainDeg <= ANGLE_EDGE_FAMILY_FINAL_TOL_DEG Then
        TryRotateAngleViewByProjectedEdgeFamily = True
        Exit Function
    End If

    SetViewAngleAndRefresh swDrawModel, swView, baseAngle, tag & " | restore after failed projected edge-family rotation"
    edgeRotateDetail = edgeRotateDetail & " | restoredBeforeFallback=True"
    LogWarn "RotationEdgeValidation | projected model edge-family rotation failed validation; restored candidate | view=" & _
            ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
            " | detail={" & edgeRotateDetail & "}"
    Exit Function

EH:
    edgeRotateDetail = "TryRotateAngleViewByProjectedEdgeFamily exception | " & Err.Number & " | " & Err.Description
    LogWarn edgeRotateDetail & " | view=" & ViewGetNameSafe(swView)
    TryRotateAngleViewByProjectedEdgeFamily = False
End Function

Private Function TryGetBestProjectedModelEdgeFamily(ByVal swView As SldWorks.View, _
                                                    ByVal item As Object, _
                                                    ByRef bestMetric As Double, _
                                                    ByRef bestAngRad As Double, _
                                                    ByRef bestDetail As String, _
                                                    ByRef edgeCt As Long, _
                                                    ByRef lineCt As Long, _
                                                    ByRef familyCt As Long, _
                                                    ByRef topFamiliesDetail As String) As Boolean
    On Error GoTo EH

    TryGetBestProjectedModelEdgeFamily = False
    bestMetric = 0#
    bestAngRad = 0#
    bestDetail = ""
    edgeCt = 0
    lineCt = 0
    familyCt = 0
    topFamiliesDetail = ""

    If swView Is Nothing Then Exit Function
    If item Is Nothing Then Exit Function

    Dim swBody As SldWorks.Body2
    Set swBody = Nothing
    On Error Resume Next
    Set swBody = item("RepBody_Model")
    On Error GoTo EH
    If swBody Is Nothing Then Exit Function

    Dim swXf As SldWorks.MathTransform
    Set swXf = swView.ModelToViewTransform
    If swXf Is Nothing Then Exit Function

    Dim vEdges As Variant
    vEdges = swBody.GetEdges
    If IsEmpty(vEdges) Then Exit Function
    If Not IsArray(vEdges) Then Exit Function

    Dim fams As Collection
    Set fams = New Collection

    Dim i As Long
    For i = LBound(vEdges) To UBound(vEdges)
        Dim swEdge As SldWorks.Edge
        Set swEdge = Nothing
        On Error Resume Next
        Set swEdge = vEdges(i)
        On Error GoTo EH

        If Not swEdge Is Nothing Then
            edgeCt = edgeCt + 1

            Dim projectedLen As Double
            Dim edgeAngRad As Double
            Dim modelLen As Double
            Dim edgeDesc As String

            If TryGetProjectedModelLineEdgeData(swEdge, swXf, projectedLen, edgeAngRad, modelLen, edgeDesc) Then
                lineCt = lineCt + 1
                If projectedLen >= ANGLE_EDGE_MIN_PROJECTED_LEN Then
                    AddProjectedEdgeFamily fams, edgeAngRad, projectedLen, modelLen, edgeDesc
                End If
            End If
        End If
    Next i

    familyCt = fams.Count
    If familyCt = 0 Then Exit Function

    Dim bestIdx As Long
    Dim bestScore As Double
    bestScore = -1E+30

    For i = 1 To fams.Count
        Dim fd As Object
        Set fd = fams(i)

        Dim score As Double
        score = ProjectedEdgeFamilyScore(fd)
        If score > bestScore Then
            bestScore = score
            bestIdx = i
        End If
    Next i

    If bestIdx <= 0 Then Exit Function

    Dim bestFd As Object
    Set bestFd = fams(bestIdx)

    bestMetric = SafeCDbl(bestFd("totalLen"))
    bestAngRad = SafeCDbl(bestFd("ang"))
    bestDetail = FormatProjectedEdgeFamily(bestFd)
    topFamiliesDetail = BuildTopProjectedEdgeFamiliesDetail(fams, ANGLE_EDGE_FAMILY_TOP_LOG_COUNT)

    If bestMetric <= 0# Then Exit Function

    TryGetBestProjectedModelEdgeFamily = True
    Exit Function

EH:
    LogWarn "TryGetBestProjectedModelEdgeFamily exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
    TryGetBestProjectedModelEdgeFamily = False
End Function

Private Function TryGetProjectedModelLineEdgeData(ByVal swEdge As SldWorks.Edge, _
                                                  ByVal swXf As SldWorks.MathTransform, _
                                                  ByRef projectedLen As Double, _
                                                  ByRef angRad As Double, _
                                                  ByRef modelLen As Double, _
                                                  ByRef edgeDesc As String) As Boolean
    On Error GoTo EH

    TryGetProjectedModelLineEdgeData = False
    projectedLen = 0#
    angRad = 0#
    modelLen = 0#
    edgeDesc = ""

    If swEdge Is Nothing Then Exit Function
    If swXf Is Nothing Then Exit Function

    Dim swCurve As SldWorks.Curve
    Set swCurve = swEdge.GetCurve
    If swCurve Is Nothing Then Exit Function

    Dim isLine As Boolean
    isLine = False
    On Error Resume Next
    isLine = swCurve.isLine
    Err.Clear
    On Error GoTo EH
    If Not isLine Then Exit Function

    Dim swStartVtx As SldWorks.Vertex
    Dim swEndVtx As SldWorks.Vertex
    Set swStartVtx = swEdge.GetStartVertex
    Set swEndVtx = swEdge.GetEndVertex
    If swStartVtx Is Nothing Or swEndVtx Is Nothing Then Exit Function

    Dim vStart As Variant
    Dim vEnd As Variant
    vStart = swStartVtx.GetPoint
    vEnd = swEndVtx.GetPoint
    If Not IsArray(vStart) Or Not IsArray(vEnd) Then Exit Function

    Dim modelDx As Double, modelDy As Double, modelDz As Double
    modelDx = CDbl(vEnd(0)) - CDbl(vStart(0))
    modelDy = CDbl(vEnd(1)) - CDbl(vStart(1))
    modelDz = CDbl(vEnd(2)) - CDbl(vStart(2))
    modelLen = Sqr((modelDx * modelDx) + (modelDy * modelDy) + (modelDz * modelDz))
    If modelLen <= CAT_MIN_LINE_LEN_MODEL Then Exit Function

    Dim x1 As Double, y1 As Double
    Dim x2 As Double, y2 As Double
    If Not TryTransformModelPointToViewXY(vStart, swXf, x1, y1) Then Exit Function
    If Not TryTransformModelPointToViewXY(vEnd, swXf, x2, y2) Then Exit Function

    Dim dx As Double, dy As Double
    dx = x2 - x1
    dy = y2 - y1

    projectedLen = Sqr((dx * dx) + (dy * dy))
    If projectedLen <= 0# Then Exit Function

    angRad = NormalizeAngleToHorizontal(Atn2Safe(dy, dx))
    edgeDesc = "modelLen=" & Fmt(modelLen) & _
               " | projectedLen=" & Fmt(projectedLen) & _
               " | angDeg=" & Format$(RadToDeg(angRad), "0.000") & _
               " | p1=(" & Fmt(x1) & "," & Fmt(y1) & ")" & _
               " | p2=(" & Fmt(x2) & "," & Fmt(y2) & ")"

    TryGetProjectedModelLineEdgeData = True
    Exit Function

EH:
    LogWarn "TryGetProjectedModelLineEdgeData exception | " & Err.Number & " | " & Err.Description & _
            " | viewEdgePtr=" & CStr(ObjPtr(swEdge))
    TryGetProjectedModelLineEdgeData = False
End Function

Private Sub AddProjectedEdgeFamily(ByVal fams As Collection, _
                                   ByVal edgeAngRad As Double, _
                                   ByVal projectedLen As Double, _
                                   ByVal modelLen As Double, _
                                   ByVal edgeDesc As String)
    On Error GoTo EH

    If fams Is Nothing Then Exit Sub
    If projectedLen <= 0# Then Exit Sub

    edgeAngRad = NormalizeAngleToHorizontal(edgeAngRad)

    Dim i As Long
    For i = 1 To fams.Count
        Dim fd As Object
        Set fd = fams(i)

        Dim famAng As Double
        famAng = SafeCDbl(fd("ang"))

        If Abs(RadToDeg(NormalizeAngleToHorizontal(edgeAngRad - famAng))) <= ANGLE_EDGE_FAMILY_TOL_DEG Then
            fd("totalLen") = SafeCDbl(fd("totalLen")) + projectedLen
            fd("modelLenTotal") = SafeCDbl(fd("modelLenTotal")) + modelLen
            fd("ct") = CLng(SafeCDbl(fd("ct"))) + 1
            fd("sumCos2") = SafeCDbl(fd("sumCos2")) + (Cos(2# * edgeAngRad) * projectedLen)
            fd("sumSin2") = SafeCDbl(fd("sumSin2")) + (Sin(2# * edgeAngRad) * projectedLen)
            fd("ang") = NormalizeAngleToHorizontal(0.5 * Atn2Safe(SafeCDbl(fd("sumSin2")), SafeCDbl(fd("sumCos2"))))

            If projectedLen > SafeCDbl(fd("maxLen")) Then
                fd("maxLen") = projectedLen
                fd("sample") = edgeDesc
            End If
            Exit Sub
        End If
    Next i

    Dim fdNew As Object
    Set fdNew = CreateObject("Scripting.Dictionary")
    fdNew("ang") = edgeAngRad
    fdNew("totalLen") = projectedLen
    fdNew("modelLenTotal") = modelLen
    fdNew("maxLen") = projectedLen
    fdNew("ct") = 1
    fdNew("sumCos2") = Cos(2# * edgeAngRad) * projectedLen
    fdNew("sumSin2") = Sin(2# * edgeAngRad) * projectedLen
    fdNew("sample") = edgeDesc
    fams.Add fdNew
    Exit Sub

EH:
    LogWarn "AddProjectedEdgeFamily exception | " & Err.Number & " | " & Err.Description
End Sub

Private Function ProjectedEdgeFamilyScore(ByVal fd As Object) As Double
    On Error GoTo EH

    If fd Is Nothing Then Exit Function

    ProjectedEdgeFamilyScore = (SafeCDbl(fd("totalLen")) * 1000000#) + _
                               (SafeCDbl(fd("maxLen")) * 500000#) + _
                               (SafeCDbl(fd("modelLenTotal")) * 1000#) + _
                               (SafeCDbl(fd("ct")) * 100#)
    Exit Function

EH:
    ProjectedEdgeFamilyScore = -1E+30
End Function

Private Function FormatProjectedEdgeFamily(ByVal fd As Object) As String
    On Error GoTo EH

    If fd Is Nothing Then
        FormatProjectedEdgeFamily = "None"
        Exit Function
    End If

    FormatProjectedEdgeFamily = "angDeg=" & Format$(RadToDeg(SafeCDbl(fd("ang"))), "0.000") & _
                                " | totalLen=" & Fmt(SafeCDbl(fd("totalLen"))) & _
                                " | maxLen=" & Fmt(SafeCDbl(fd("maxLen"))) & _
                                " | modelLenTotal=" & Fmt(SafeCDbl(fd("modelLenTotal"))) & _
                                " | count=" & CStr(CLng(SafeCDbl(fd("ct")))) & _
                                " | score=" & Format$(ProjectedEdgeFamilyScore(fd), "0.000") & _
                                " | sample={" & SafeStr(fd("sample")) & "}"
    Exit Function

EH:
    FormatProjectedEdgeFamily = "FormatProjectedEdgeFamily exception | " & Err.Number & " | " & Err.Description
End Function

Private Function BuildTopProjectedEdgeFamiliesDetail(ByVal fams As Collection, _
                                                     ByVal topN As Long) As String
    On Error GoTo EH

    BuildTopProjectedEdgeFamiliesDetail = ""
    If fams Is Nothing Then Exit Function
    If fams.Count = 0 Then Exit Function
    If topN <= 0 Then topN = 1

    Dim used As Object
    Set used = CreateObject("Scripting.Dictionary")

    Dim rank As Long
    For rank = 1 To topN
        Dim bestIdx As Long
        Dim bestScore As Double
        bestIdx = 0
        bestScore = -1E+30

        Dim i As Long
        For i = 1 To fams.Count
            If Not used.Exists(CStr(i)) Then
                Dim score As Double
                score = ProjectedEdgeFamilyScore(fams(i))
                If score > bestScore Then
                    bestScore = score
                    bestIdx = i
                End If
            End If
        Next i

        If bestIdx <= 0 Then Exit For
        used.Add CStr(bestIdx), True

        If Len(BuildTopProjectedEdgeFamiliesDetail) > 0 Then BuildTopProjectedEdgeFamiliesDetail = BuildTopProjectedEdgeFamiliesDetail & " ; "
        BuildTopProjectedEdgeFamiliesDetail = BuildTopProjectedEdgeFamiliesDetail & _
                                             "#" & CStr(rank) & "={" & FormatProjectedEdgeFamily(fams(bestIdx)) & "}"
    Next rank
    Exit Function

EH:
    BuildTopProjectedEdgeFamiliesDetail = "BuildTopProjectedEdgeFamiliesDetail exception | " & Err.Number & " | " & Err.Description
End Function

Private Function DescribeViewProjectionBasis(ByVal swView As SldWorks.View) As String
    On Error GoTo EH

    DescribeViewProjectionBasis = "viewAngleDeg=" & Format$(RadToDeg(swView.Angle), "0.000")

    Dim dx As Double, dy As Double, mag As Double, ang As Double
    If TryProjectModelDirectionToViewXY(swView, 1#, 0#, 0#, dx, dy, mag, ang) Then
        DescribeViewProjectionBasis = DescribeViewProjectionBasis & _
                                      " | modelXProj=(" & Fmt(dx) & "," & Fmt(dy) & ")" & _
                                      "@" & Format$(RadToDeg(ang), "0.000") & "deg"
    Else
        DescribeViewProjectionBasis = DescribeViewProjectionBasis & " | modelXProj=edge-on"
    End If

    If TryProjectModelDirectionToViewXY(swView, 0#, 1#, 0#, dx, dy, mag, ang) Then
        DescribeViewProjectionBasis = DescribeViewProjectionBasis & _
                                      " | modelYProj=(" & Fmt(dx) & "," & Fmt(dy) & ")" & _
                                      "@" & Format$(RadToDeg(ang), "0.000") & "deg"
    Else
        DescribeViewProjectionBasis = DescribeViewProjectionBasis & " | modelYProj=edge-on"
    End If

    If TryProjectModelDirectionToViewXY(swView, 0#, 0#, 1#, dx, dy, mag, ang) Then
        DescribeViewProjectionBasis = DescribeViewProjectionBasis & _
                                      " | modelZProj=(" & Fmt(dx) & "," & Fmt(dy) & ")" & _
                                      "@" & Format$(RadToDeg(ang), "0.000") & "deg"
    Else
        DescribeViewProjectionBasis = DescribeViewProjectionBasis & " | modelZProj=edge-on"
    End If
    Exit Function

EH:
    DescribeViewProjectionBasis = "DescribeViewProjectionBasis exception | " & Err.Number & " | " & Err.Description
End Function

Private Function TryRotateViewByFabricationFrame(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                                 ByVal swView As SldWorks.View, _
                                                 ByVal item As Object, _
                                                 ByVal tag As String, _
                                                 ByRef frameRotateDetail As String) As Boolean
    On Error GoTo EH

    TryRotateViewByFabricationFrame = False
    frameRotateDetail = ""

    If swDrawModel Is Nothing Then
        frameRotateDetail = "drawing model is Nothing"
        Exit Function
    End If
    If swView Is Nothing Then
        frameRotateDetail = "view is Nothing"
        Exit Function
    End If
    If item Is Nothing Then
        frameRotateDetail = "item is Nothing"
        Exit Function
    End If

    Dim primaryX As Double, primaryY As Double, primaryZ As Double
    Dim secondaryX As Double, secondaryY As Double, secondaryZ As Double
    Dim hasSecondary As Boolean
    Dim frameReason As String
    Dim usedPlateFrame As Boolean
    Dim forcePrimaryPolarity As Boolean

    usedPlateFrame = False
    forcePrimaryPolarity = False
    hasSecondary = False
    frameReason = ""

    If IsPlateLikeOrientationItem(item) Then
        If TryGetPlateFabricationFrameFromBody(item, primaryX, primaryY, primaryZ, _
                                               secondaryX, secondaryY, secondaryZ, _
                                               hasSecondary, frameReason) Then
            usedPlateFrame = True
            forcePrimaryPolarity = True
            LogInfo "RotationPlateFrame | view=" & ViewGetNameSafe(swView) & _
                    " | itemNo=" & SafeStr(item("ItemNo")) & _
                    " | pn=" & SafeStr(item("PartNo")) & _
                    " | subtype=" & SafeStr(item("BodySubtype")) & _
                    " | reason={" & frameReason & "}"
        Else
            LogWarn "RotationPlateFrame | unavailable; falling back to generic fabrication frame | view=" & _
                    ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
                    " | pn=" & SafeStr(item("PartNo")) & _
                    " | subtype=" & SafeStr(item("BodySubtype")) & _
                    " | reason={" & frameReason & "}"
        End If
    End If

    If Not usedPlateFrame Then
        If Not TryGetBodyFabricationFrame(item, primaryX, primaryY, primaryZ, _
                                          secondaryX, secondaryY, secondaryZ, _
                                          hasSecondary, frameReason) Then
            frameRotateDetail = "fabrication frame unavailable | " & frameReason
            LogWarn "RotationBasis3D | unavailable | view=" & ViewGetNameSafe(swView) & _
                    " | itemNo=" & SafeStr(item("ItemNo")) & " | reason=" & frameRotateDetail
            Exit Function
        End If
    End If

    Dim primaryDx As Double, primaryDy As Double, primaryMag As Double, primaryAng As Double
    If Not TryProjectModelDirectionToViewXY(swView, primaryX, primaryY, primaryZ, _
                                            primaryDx, primaryDy, primaryMag, primaryAng) Then
        frameRotateDetail = "primary 3D axis projects edge-on or transform failed | " & frameReason
        LogWarn "RotationBasis3D | projection failed | view=" & ViewGetNameSafe(swView) & _
                " | itemNo=" & SafeStr(item("ItemNo")) & " | reason=" & frameRotateDetail
        Exit Function
    End If

    Dim refAng As Double
    Dim refMetric As Double
    Dim refSource As String
    Dim refDetail As String
    refAng = primaryAng
    refMetric = primaryMag
    If usedPlateFrame Then
        refSource = "PLATE_FACE_CORNER_XDIR"
        refDetail = "deterministic plate/sheet face-corner x direction"
    Else
        refSource = "PRIMARY_3D_AXIS"
        refDetail = "projected primary vector"
    End If

    Dim edgeLen As Double, edgeAng As Double, edgeDesc As String
    Dim edgeCt As Long, lineCt As Long, matchedCt As Long
    If (Not usedPlateFrame) Then
        If GetBestVisibleLinearEdgeMatchingModelAxis(swView, primaryX, primaryY, primaryZ, _
                                                     edgeLen, edgeAng, edgeDesc, edgeCt, lineCt, matchedCt) Then
            Dim primaryEdgeDeltaDeg As Double
            primaryEdgeDeltaDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(primaryAng - edgeAng)))

            refAng = edgeAng
            refMetric = edgeLen
            refSource = "VISIBLE_EDGE_MATCH_3D_PRIMARY"
            refDetail = "matched visible linear edge | primaryEdgeDeltaDeg=" & Format$(primaryEdgeDeltaDeg, "0.000") & _
                        " | edgeCt=" & CStr(edgeCt) & _
                        " | lineCt=" & CStr(lineCt) & _
                        " | matchedCt=" & CStr(matchedCt) & _
                        " | edge={" & edgeDesc & "}"
        Else
            refDetail = refDetail & " | no visible edge matched primary axis" & _
                        " | edgeCt=" & CStr(edgeCt) & _
                        " | lineCt=" & CStr(lineCt) & _
                        " | matchedCt=" & CStr(matchedCt)
        End If
    End If

    Dim baseAngle As Double
    Dim thetaA As Double, thetaB As Double
    Dim selectedTheta As Double
    Dim choiceReason As String

    baseAngle = swView.Angle
    thetaA = NormalizeAngleRad(baseAngle - NormalizeAngleToHorizontal(refAng))
    thetaB = NormalizeAngleRad(thetaA + PI)
    selectedTheta = thetaA
    choiceReason = "default thetaA"

    Dim secondaryDx As Double, secondaryDy As Double
    Dim secondaryMag As Double, secondaryAng As Double
    Dim secA_X As Double, secA_Y As Double
    Dim secB_X As Double, secB_Y As Double
    Dim scoreA As Double, scoreB As Double
    Dim secondaryDetail As String

    secondaryDx = 0#: secondaryDy = 0#
    secondaryMag = 0#: secondaryAng = 0#
    secondaryDetail = "secondary unavailable"

    If forcePrimaryPolarity Then
        selectedTheta = thetaA
        choiceReason = "thetaA forced by deterministic plate face/corner polarity"
        secondaryDetail = "secondary bypassed because plate x-direction sign is already deterministic"
    ElseIf hasSecondary Then
        If TryProjectModelDirectionToViewXY(swView, secondaryX, secondaryY, secondaryZ, _
                                            secondaryDx, secondaryDy, secondaryMag, secondaryAng) Then
            Rotate2DVector secondaryDx, secondaryDy, NormalizeAngleRad(thetaA - baseAngle), secA_X, secA_Y
            Rotate2DVector secondaryDx, secondaryDy, NormalizeAngleRad(thetaB - baseAngle), secB_X, secB_Y

            scoreA = ScoreRotationPolarityChoice(item, secA_X, secA_Y)
            scoreB = ScoreRotationPolarityChoice(item, secB_X, secB_Y)

            secondaryDetail = "secondaryProj=(" & Fmt(secondaryDx) & "," & Fmt(secondaryDy) & ")" & _
                              " | secondaryAngDeg=" & Format$(RadToDeg(secondaryAng), "0.000") & _
                              " | afterA=(" & Fmt(secA_X) & "," & Fmt(secA_Y) & ")" & _
                              " | afterB=(" & Fmt(secB_X) & "," & Fmt(secB_Y) & ")" & _
                              " | scoreA=" & Fmt(scoreA) & _
                              " | scoreB=" & Fmt(scoreB)

            If scoreB > (scoreA + 0.000001) Then
                selectedTheta = thetaB
                choiceReason = "thetaB selected by secondary/profile polarity"
            Else
                selectedTheta = thetaA
                choiceReason = "thetaA selected by secondary/profile polarity"
            End If
        Else
            secondaryDetail = "secondary vector projected edge-on or transform failed"
        End If
    End If

    LogInfo "RotationChoice | view=" & ViewGetNameSafe(swView) & _
            " | itemNo=" & SafeStr(item("ItemNo")) & _
            " | subtype=" & SafeStr(item("BodySubtype")) & _
            " | source=" & refSource & _
            " | refMetric=" & Fmt(refMetric) & _
            " | refAngDeg=" & Format$(RadToDeg(refAng), "0.000") & _
            " | thetaA=" & Format$(RadToDeg(thetaA), "0.000") & _
            " | thetaB=" & Format$(RadToDeg(thetaB), "0.000") & _
            " | selectedTheta=" & Format$(RadToDeg(selectedTheta), "0.000") & _
            " | primaryProj=(" & Fmt(primaryDx) & "," & Fmt(primaryDy) & ")" & _
            " | primaryProjMag=" & Fmt(primaryMag) & _
            " | usedPlateFrame=" & BoolWord(usedPlateFrame) & _
            " | " & secondaryDetail & _
            " | choiceReason=" & choiceReason & _
            " | refDetail={" & refDetail & "}" & _
            " | frame={" & frameReason & "}"

    SetViewAngleAndRefresh swDrawModel, swView, selectedTheta, tag & " | deterministic fabrication-frame rotation"

    Dim validationScore As Double
    Dim validationDetail As String
    Dim validNow As Boolean
    validNow = ValidateOrientationResultByCategory(swView, item, validationScore, validationDetail)

    frameRotateDetail = "method=" & IIf(usedPlateFrame, "plate-face-corner", "fabrication-frame") & _
                        " | valid=" & BoolWord(validNow) & _
                        " | source=" & refSource & _
                        " | refAngDeg=" & Format$(RadToDeg(refAng), "0.000") & _
                        " | thetaA=" & Format$(RadToDeg(thetaA), "0.000") & _
                        " | thetaB=" & Format$(RadToDeg(thetaB), "0.000") & _
                        " | selectedDeg=" & Format$(RadToDeg(selectedTheta), "0.000") & _
                        " | choice=" & choiceReason & _
                        " | validation={" & validationDetail & "}"

    If validNow Then
        TryRotateViewByFabricationFrame = True
        Exit Function
    End If

    SetViewAngleAndRefresh swDrawModel, swView, baseAngle, tag & " | restore after failed fabrication-frame rotation"
    frameRotateDetail = frameRotateDetail & " | restoredBeforeFallback=True"
    LogWarn "RotationChoice | deterministic fabrication-frame rotation failed validation; restored candidate | view=" & _
            ViewGetNameSafe(swView) & " | itemNo=" & SafeStr(item("ItemNo")) & _
            " | detail={" & frameRotateDetail & "}"
    Exit Function

EH:
    frameRotateDetail = "TryRotateViewByFabricationFrame exception | " & Err.Number & " | " & Err.Description
    LogWarn frameRotateDetail & " | view=" & ViewGetNameSafe(swView)
    TryRotateViewByFabricationFrame = False
End Function
Private Sub Rotate2DVector(ByVal x As Double, _
                           ByVal y As Double, _
                           ByVal angRad As Double, _
                           ByRef outX As Double, _
                           ByRef outY As Double)
    Dim c As Double, s As Double
    c = Cos(angRad)
    s = Sin(angRad)
    outX = (x * c) - (y * s)
    outY = (x * s) + (y * c)
End Sub

Private Function ScoreRotationPolarityChoice(ByVal item As Object, _
                                             ByVal secondaryX2D As Double, _
                                             ByVal secondaryY2D As Double) As Double
    On Error GoTo EH

    Dim subtypeName As String
    subtypeName = UCase$(SafeStr(item("BodySubtype")))

    Select Case subtypeName
        Case CAT_SUBTYPE_ANGLE
            ' Production convention confirmed by testing: an angle detail should read like
            ' an L/profile, with the projecting leg preferred on the bottom side of the view.
            ScoreRotationPolarityChoice = (-secondaryY2D * 1000000#) + (Abs(secondaryX2D) * 1000#)

        Case CAT_SUBTYPE_CHANNEL
            ' Channel candidates already passed profile readability. Use secondary polarity
            ' only to choose the equivalent 180-degree presentation, not to rescue a side view.
            ScoreRotationPolarityChoice = (-secondaryY2D * 250000#) + (Abs(secondaryX2D) * 500#)

        Case Else
            ScoreRotationPolarityChoice = Abs(secondaryX2D)
    End Select

    Exit Function

EH:
    ScoreRotationPolarityChoice = 0#
End Function

Private Function TryRotateViewByCategoryReferenceAlignment(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                                           ByVal swView As SldWorks.View, _
                                                           ByVal item As Object, _
                                                           ByVal tag As String) As Boolean
    On Error GoTo EH

    TryRotateViewByCategoryReferenceAlignment = False
    If swView Is Nothing Then Exit Function

    Dim refMetric As Double
    Dim refAngRad As Double
    Dim qualityScore As Double
    Dim refDesc As String

    If Not GetSnapReferenceForView(swView, item, refMetric, refAngRad, qualityScore, refDesc) Then
        LogWarn "TryRotateViewByCategoryReferenceAlignment | no usable snap/category reference | view=" & ViewGetNameSafe(swView)
        Exit Function
    End If

    Dim currentViewAngle As Double
    Dim deltaToHorizontal As Double
    Dim targetAngle As Double

    currentViewAngle = swView.Angle
    deltaToHorizontal = NormalizeAngleToHorizontal(refAngRad)
    targetAngle = NormalizeAngleRad(currentViewAngle - deltaToHorizontal)

    LogInfo "RotCategoryPick | view=" & ViewGetNameSafe(swView) & _
            " | cat=" & SafeStr(item("BodyCategory")) & _
            " | metric=" & Fmt(refMetric) & _
            " | refAngDeg=" & Format$(RadToDeg(refAngRad), "0.000") & _
            " | deltaDeg=" & Format$(RadToDeg(deltaToHorizontal), "0.000") & _
            " | currentViewDeg=" & Format$(RadToDeg(currentViewAngle), "0.000") & _
            " | targetViewDeg=" & Format$(RadToDeg(targetAngle), "0.000") & _
            " | ref=" & refDesc

    SetViewAngleAndRefresh swDrawModel, swView, targetAngle, tag & " | category/snap direct align"

    Dim confMetric As Double
    Dim confAngRad As Double
    Dim confQuality As Double
    Dim confDesc As String
    If GetSnapReferenceForView(swView, item, confMetric, confAngRad, confQuality, confDesc) Then
        Dim remainDeg As Double
        Dim validNow As Boolean
        Dim validScore As Double
        Dim validDetail As String

        remainDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(confAngRad)))
        validNow = ValidateOrientationResultByCategory(swView, item, validScore, validDetail)

        LogInfo "RotCategoryConfirm | view=" & ViewGetNameSafe(swView) & _
                " | cat=" & SafeStr(item("BodyCategory")) & _
                " | remainDeg=" & Format$(remainDeg, "0.000") & _
                " | metric=" & Fmt(confMetric) & _
                " | valid=" & BoolWord(validNow) & _
                " | ref=" & confDesc & _
                " | verify=" & validDetail

        If remainDeg <= CAT_MAJOR_HORIZONTAL_TOL_DEG And validNow Then
            TryRotateViewByCategoryReferenceAlignment = True
            Exit Function
        End If
    End If

    Exit Function

EH:
    LogWarn "TryRotateViewByCategoryReferenceAlignment exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
    TryRotateViewByCategoryReferenceAlignment = False
End Function

Private Function BuildDiscreteAngleList(ByVal baseAngleRad As Double, ByVal stepDeg As Double) As Collection
    On Error GoTo EH

    Dim outCol As New Collection
    Dim seen As Object
    Set seen = CreateObject("Scripting.Dictionary")

    AddAngleIfMissing outCol, seen, baseAngleRad
    AddAngleIfMissing outCol, seen, NormalizeAngleRad(baseAngleRad + DegToRad(stepDeg))
    AddAngleIfMissing outCol, seen, NormalizeAngleRad(baseAngleRad - DegToRad(stepDeg))
    AddAngleIfMissing outCol, seen, NormalizeAngleRad(baseAngleRad + DegToRad(stepDeg * 2#))
    AddAngleIfMissing outCol, seen, NormalizeAngleRad(baseAngleRad - DegToRad(stepDeg * 2#))
    AddAngleIfMissing outCol, seen, NormalizeAngleRad(baseAngleRad + DegToRad(stepDeg * 3#))
    AddAngleIfMissing outCol, seen, NormalizeAngleRad(baseAngleRad - DegToRad(stepDeg * 3#))
    AddAngleIfMissing outCol, seen, NormalizeAngleRad(baseAngleRad + DegToRad(stepDeg * 4#))
    AddAngleIfMissing outCol, seen, NormalizeAngleRad(baseAngleRad - DegToRad(stepDeg * 4#))

    Set BuildDiscreteAngleList = outCol
    Exit Function

EH:
    LogWarn "BuildDiscreteAngleList exception | " & Err.Number & " | " & Err.Description
    Set BuildDiscreteAngleList = New Collection
End Function

Private Sub AddAngleIfMissing(ByVal outCol As Collection, ByVal seen As Object, ByVal angRad As Double)
    Dim key As String
    key = Format$(NormalizeDeg360(RadToDeg(angRad)), "0.000")
    If Not seen.Exists(key) Then
        seen.Add key, True
        outCol.Add angRad
    End If
End Sub

Private Function SafeCDbl(ByVal v As Variant) As Double
    On Error Resume Next
    SafeCDbl = CDbl(v)
    If Err.Number <> 0 Then
        SafeCDbl = 0#
        Err.Clear
    End If
End Function


Private Sub ForceSheetContext()
    On Error Resume Next
    g_swDrwModel.ActivateView ""
    g_swDrwModel.ClearSelection2 True
    On Error GoTo 0
End Sub

Private Function ForceViewPosition(ByVal v As SldWorks.View, _
                                   ByVal x As Double, ByVal y As Double, _
                                   ByVal tol As Double, _
                                   ByVal retries As Long) As Boolean
    On Error GoTo EH

    Dim k As Long
    For k = 1 To retries
        ForceSheetContext

        If Not SetViewPositionTyped(v, x, y) Then
            LogWarn "   FVP[" & k & "] SetViewPositionTyped=False | " & ViewGetNameSafe(v)
        End If

        On Error Resume Next
        v.Update
        On Error GoTo 0
        g_swDrwModel.EditRebuild3

        Dim ax As Double, ay As Double, gotPos As Boolean
        gotPos = GetViewPositionXY(v, ax, ay)

        Dim cx As Double, cy As Double, gotCtr As Boolean
        gotCtr = GetViewOutlineCenter(v, cx, cy)

        Dim vx As Double, vy As Double
        If gotPos Then
            vx = ax: vy = ay
        ElseIf gotCtr Then
            vx = cx: vy = cy
        Else
            LogWarn "   FVP[" & k & "] no readable pos/outline | " & ViewGetNameSafe(v)
            GoTo NextTry
        End If

        Dim dx As Double, dy As Double
        dx = Abs(vx - x): dy = Abs(vy - y)

        LogInfo "   FVP[" & k & "] " & ViewGetNameSafe(v) & _
                " | target=(" & Fmt(x) & "," & Fmt(y) & ")" & _
                " | pos=(" & FmtIf(gotPos, ax) & "," & FmtIf(gotPos, ay) & ")" & _
                " | ctr=(" & FmtIf(gotCtr, cx) & "," & FmtIf(gotCtr, cy) & ")" & _
                " | d=(" & Fmt(dx) & "," & Fmt(dy) & ")"

        If dx <= tol And dy <= tol Then
            ForceViewPosition = True
            Exit Function
        End If

NextTry:
        DoEvents
    Next k

    ForceViewPosition = False
    Exit Function

EH:
    LogError "ForceViewPosition exception | " & Err.Number & " | " & Err.Description
    ForceViewPosition = False
End Function

Private Function SetViewPositionTyped(ByVal v As SldWorks.View, _
                                      ByVal x As Double, ByVal y As Double) As Boolean
    On Error GoTo EH

    Dim pos(0 To 2) As Double
    pos(0) = x
    pos(1) = y
    pos(2) = 0#

    On Error Resume Next
    v.PositionLocked = False
    v.Position = pos
    If Err.Number <> 0 Then
        LogWarn "SetViewPositionTyped | setter error | " & Err.Number & " | " & Err.Description
        Err.Clear
    End If
    On Error GoTo 0

    SetViewPositionTyped = True
    Exit Function

EH:
    LogError "SetViewPositionTyped exception | " & Err.Number & " | " & Err.Description
    SetViewPositionTyped = False
End Function

Private Function GetViewPositionXY(ByVal v As SldWorks.View, _
                                   ByRef x As Double, ByRef y As Double) As Boolean
    On Error GoTo EH

    Dim p As Variant
    p = v.Position
    If Not IsArray(p) Then GoTo EH
    x = CDbl(p(0))
    y = CDbl(p(1))
    GetViewPositionXY = True
    Exit Function

EH:
    x = 0#: y = 0#
    GetViewPositionXY = False
End Function

Private Function GetViewOutlineCenter(ByVal v As SldWorks.View, _
                                      ByRef cx As Double, ByRef cy As Double) As Boolean
    On Error GoTo EH

    Dim o As Variant
    o = v.GetOutline
    If Not IsArray(o) Then GoTo EH

    cx = (CDbl(o(0)) + CDbl(o(2))) / 2#
    cy = (CDbl(o(1)) + CDbl(o(3))) / 2#
    GetViewOutlineCenter = True
    Exit Function

EH:
    cx = 0#: cy = 0#
    GetViewOutlineCenter = False
End Function

'====================================================================================
' SCALE
'====================================================================================
Private Function ApplyViewScale(ByVal v As SldWorks.View, ByVal scaleDec As Double) As Boolean
    On Error GoTo EH
    If scaleDec <= 0# Then GoTo EH

    On Error Resume Next
    v.ScaleDecimal = scaleDec
    v.Update
    On Error GoTo 0

    ApplyViewScale = True
    Exit Function

EH:
    LogError "ApplyViewScale exception | " & Err.Number & " | " & Err.Description
    ApplyViewScale = False
End Function

'====================================================================================
' VIEW GEOMETRY
'====================================================================================
Private Function GetViewWH(ByVal swV As SldWorks.View, _
                           ByRef w As Double, ByRef h As Double) As Boolean
    On Error GoTo EH

    Dim vOut As Variant
    vOut = swV.GetOutline
    If Not IsArray(vOut) Then GoTo EH

    w = Abs(CDbl(vOut(2)) - CDbl(vOut(0)))
    h = Abs(CDbl(vOut(3)) - CDbl(vOut(1)))

    GetViewWH = (w > 0# And h > 0#)
    Exit Function

EH:
    w = 0#: h = 0#
    GetViewWH = False
End Function

Private Function GetViewRect(ByVal swV As SldWorks.View, _
                             ByRef x0 As Double, ByRef y0 As Double, _
                             ByRef x1 As Double, ByRef y1 As Double) As Boolean
    On Error GoTo EH

    Dim o As Variant
    o = swV.GetOutline
    If Not IsArray(o) Then GoTo EH

    x0 = CDbl(o(0))
    y0 = CDbl(o(1))
    x1 = CDbl(o(2))
    y1 = CDbl(o(3))

    If x1 < x0 Then SwapD x0, x1
    If y1 < y0 Then SwapD y0, y1

    GetViewRect = (x1 > x0 And y1 > y0)
    Exit Function

EH:
    x0 = 0#: y0 = 0#: x1 = 0#: y1 = 0#
    GetViewRect = False
End Function

'====================================================================================
' EXACT SHEET BOUNDS
'====================================================================================
Private Function GetSheetBoundsExact(ByVal drw As SldWorks.DrawingDoc, _
                                     ByRef xmin As Double, ByRef ymin As Double, _
                                     ByRef xmax As Double, ByRef ymax As Double) As Boolean
    On Error GoTo EH

    Dim sheetView As SldWorks.View
    Set sheetView = drw.GetFirstView
    If sheetView Is Nothing Then GoTo EH

    Dim o As Variant
    o = sheetView.GetOutline
    If Not IsArray(o) Then GoTo EH

    xmin = CDbl(o(0))
    ymin = CDbl(o(1))
    xmax = CDbl(o(2))
    ymax = CDbl(o(3))

    GetSheetBoundsExact = (xmax > xmin And ymax > ymin)
    Exit Function

EH:
    xmin = 0#: ymin = 0#: xmax = 0#: ymax = 0#
    GetSheetBoundsExact = False
End Function

'====================================================================================
' CUT-LIST COLLECTION
'====================================================================================
Private Function GetCutListItems(ByVal swModel As SldWorks.ModelDoc2) As Collection
    On Error GoTo EH

    Dim items As New Collection
    Dim swFeat As SldWorks.Feature
    Set swFeat = swModel.FirstFeature

    CollectCutListItemsFromFeatureTree swModel, swFeat, items

    If items.Count = 0 Then
        LogWarn "GetCutListItems | no weldment cut-list folders found; trying solid-body fallback"
        AddFallbackSolidBodyItems swModel, items
    End If

    Set GetCutListItems = items
    Exit Function

EH:
    LogError "GetCutListItems exception | " & Err.Number & " | " & Err.Description
    Set GetCutListItems = Nothing
End Function

Private Sub CollectCutListItemsFromFeatureTree(ByVal swModel As SldWorks.ModelDoc2, _
                                               ByVal swFeat As SldWorks.Feature, _
                                               ByVal items As Collection)
    On Error GoTo EH

    Dim seenFeatures As Object
    Dim seenBodies As Object
    Dim seenTopFeatures As Object
    Set seenFeatures = CreateObject("Scripting.Dictionary")
    Set seenBodies = CreateObject("Scripting.Dictionary")
    Set seenTopFeatures = CreateObject("Scripting.Dictionary")

    Dim scanCt As Long
    Dim topCt As Long

    Do While Not swFeat Is Nothing
        topCt = topCt + 1

        Dim topKey As String
        topKey = FeatureVisitKey(swFeat)
        If Len(topKey) > 0 Then
            If seenTopFeatures.Exists(topKey) Then
                LogWarn "CollectCutListItemsFromFeatureTree | top-level feature cycle detected; stopping | feat=" & FeatureNameSafe(swFeat)
                Exit Do
            End If
            seenTopFeatures.Add topKey, True
        End If

        CollectCutListItemsFromFeatureBranch swModel, swFeat, items, seenFeatures, seenBodies, 0, scanCt

        If scanCt >= CUTLIST_MAX_FEATURE_SCAN Then
            LogWarn "CollectCutListItemsFromFeatureTree | scan cap reached; stopping feature traversal | scanCt=" & CStr(scanCt) & _
                    " | itemCount=" & CStr(items.Count)
            Exit Do
        End If

        On Error Resume Next
        Set swFeat = swFeat.GetNextFeature
        If Err.Number <> 0 Then
            LogWarn "CollectCutListItemsFromFeatureTree | GetNextFeature failed | " & Err.Number & " | " & Err.Description
            Err.Clear
            Set swFeat = Nothing
        End If
        On Error GoTo EH
    Loop

    LogInfo "CollectCutListItemsFromFeatureTree | done | topFeatures=" & CStr(topCt) & _
            " | scanned=" & CStr(scanCt) & _
            " | cutListItems=" & CStr(items.Count) & _
            " | uniqueFeatures=" & CStr(seenFeatures.Count) & _
            " | uniqueBodies=" & CStr(seenBodies.Count)
    Exit Sub

EH:
    LogWarn "CollectCutListItemsFromFeatureTree exception | " & Err.Number & " | " & Err.Description
End Sub

Private Sub CollectCutListItemsFromFeatureBranch(ByVal swModel As SldWorks.ModelDoc2, _
                                                 ByVal startFeat As SldWorks.Feature, _
                                                 ByVal items As Collection, _
                                                 ByVal seenFeatures As Object, _
                                                 ByVal seenBodies As Object, _
                                                 ByVal depth As Long, _
                                                 ByRef scanCt As Long)
    On Error GoTo EH

    If startFeat Is Nothing Then Exit Sub
    If seenFeatures Is Nothing Then Exit Sub
    If seenBodies Is Nothing Then Exit Sub
    If scanCt >= CUTLIST_MAX_FEATURE_SCAN Then Exit Sub

    If depth > CUTLIST_MAX_FEATURE_DEPTH Then
        LogWarn "CollectCutListItemsFromFeatureBranch | depth cap reached | depth=" & CStr(depth) & _
                " | feat=" & FeatureNameSafe(startFeat)
        Exit Sub
    End If

    Dim swFeat As SldWorks.Feature
    Set swFeat = startFeat

    Do While Not swFeat Is Nothing
        Dim featKey As String
        featKey = FeatureVisitKey(swFeat)

        If Len(featKey) > 0 Then
            If seenFeatures.Exists(featKey) Then
                Dim swSeenNext As SldWorks.Feature
                Set swSeenNext = GetNextSubFeatureSafe(swFeat)
                If swSeenNext Is Nothing Then Exit Do
                If FeatureVisitKey(swSeenNext) = featKey Then Exit Do
                Set swFeat = swSeenNext
                GoTo ContinueLoop
            End If
            seenFeatures.Add featKey, True
        End If

        scanCt = scanCt + 1
        If scanCt >= CUTLIST_MAX_FEATURE_SCAN Then Exit Do

        If StrComp(SafeStr(swFeat.GetTypeName2), "CutListFolder", vbTextCompare) = 0 Then
            AddCutListItemFromFeature swModel, swFeat, items, seenBodies
        End If

        Dim swChild As SldWorks.Feature
        Set swChild = Nothing
        On Error Resume Next
        Set swChild = swFeat.GetFirstSubFeature
        If Err.Number <> 0 Then
            Err.Clear
            Set swChild = Nothing
        End If
        On Error GoTo EH

        If Not swChild Is Nothing Then
            CollectCutListItemsFromFeatureBranch swModel, swChild, items, seenFeatures, seenBodies, depth + 1, scanCt
            If scanCt >= CUTLIST_MAX_FEATURE_SCAN Then Exit Do
        End If

        Dim swNext As SldWorks.Feature
        Set swNext = GetNextSubFeatureSafe(swFeat)
        If swNext Is Nothing Then Exit Do
        If FeatureVisitKey(swNext) = featKey Then Exit Do
        Set swFeat = swNext

ContinueLoop:
    Loop
    Exit Sub

EH:
    LogWarn "CollectCutListItemsFromFeatureBranch exception | " & Err.Number & " | " & Err.Description & _
            " | depth=" & CStr(depth) & _
            " | scanCt=" & CStr(scanCt)
End Sub

Private Sub AddCutListItemFromFeature(ByVal swModel As SldWorks.ModelDoc2, _
                                      ByVal swFeat As SldWorks.Feature, _
                                      ByVal items As Collection, _
                                      ByVal seenBodies As Object)
    On Error GoTo EH

    If swFeat Is Nothing Then Exit Sub
    If items Is Nothing Then Exit Sub

    Dim bf As SldWorks.BodyFolder
    Set bf = swFeat.GetSpecificFeature2

    If bf Is Nothing Then Exit Sub

    On Error Resume Next
    bf.UpdateCutList
    On Error GoTo EH

    Dim vBodies As Variant
    vBodies = bf.GetBodies

    If IsArray(vBodies) And SafeArrayCount(vBodies) > 0 Then
        Dim repB As SldWorks.Body2
        Set repB = vBodies(LBound(vBodies))

        If repB Is Nothing Then Exit Sub

        Dim bodyKey As String
        bodyKey = BodyVisitKey(repB)
        If Len(bodyKey) > 0 Then
            If Not seenBodies Is Nothing Then
                If seenBodies.Exists(bodyKey) Then
                    LogInfo "CutListSkipDuplicate | feat=" & FeatureNameSafe(swFeat) & _
                            " | bodyKey=" & bodyKey
                    Exit Sub
                End If
                seenBodies.Add bodyKey, True
            End If
        End If

        Dim dx As Double, dy As Double, dz As Double
        GetBodyBBoxDims repB, dx, dy, dz

        Dim d As Object
        Set d = CreateObject("Scripting.Dictionary")

        Set d("RepBody_Model") = repB
        d("dx") = dx
        d("dy") = dy
        d("dz") = dz
        d("CutListFeatureName") = FeatureNameSafe(swFeat)
        d("BodyName") = GetBodyNameSafe(repB)

        d("ItemNo") = GetCLProp(swModel, swFeat, _
                      Array(PROP_ITEMNO_1, PROP_ITEMNO_2, PROP_ITEMNO_3, PROP_ITEMNO_4))
        d("PartNo") = GetCLProp(swModel, swFeat, _
                      Array(PROP_PN_1, PROP_PN_2, PROP_PN_3, PROP_PN_4))
        d("Qty") = GetCLProp(swModel, swFeat, _
                      Array(PROP_QTY_1, PROP_QTY_2, PROP_QTY_3))

        If Len(SafeStr(d("ItemNo"))) = 0 Then d("ItemNo") = ParseItemFromName(swFeat.name)
        If Len(SafeStr(d("PartNo"))) = 0 Then d("PartNo") = swFeat.name
        If Len(SafeStr(d("Qty"))) = 0 Then d("Qty") = "?"

        items.Add d

        LogInfo "CutList | item=" & SafeStr(d("ItemNo")) & _
                " | pn=" & SafeStr(d("PartNo")) & _
                " | qty=" & SafeStr(d("Qty")) & _
                " | bbox=" & Fmt(dx) & "x" & Fmt(dy) & "x" & Fmt(dz)
    End If
    Exit Sub

EH:
    LogWarn "AddCutListItemFromFeature exception | " & Err.Number & " | " & Err.Description & _
            " | feat=" & FeatureNameSafe(swFeat)
End Sub

Private Function GetNextSubFeatureSafe(ByVal swFeat As SldWorks.Feature) As SldWorks.Feature
    On Error GoTo EH

    Set GetNextSubFeatureSafe = Nothing
    If swFeat Is Nothing Then Exit Function

    Err.Clear
    On Error Resume Next
    Set GetNextSubFeatureSafe = CallByName(swFeat, "GetNextSubFeature", VbMethod)
    If Err.Number <> 0 Then
        Err.Clear
        Set GetNextSubFeatureSafe = Nothing
    End If
    On Error GoTo EH
    Exit Function

EH:
    Set GetNextSubFeatureSafe = Nothing
End Function

Private Function FeatureVisitKey(ByVal swFeat As SldWorks.Feature) As String
    On Error GoTo EH
    If swFeat Is Nothing Then Exit Function
    Dim nm As String
    Dim typ As String
    nm = UCase$(Trim$(FeatureNameSafe(swFeat)))
    typ = UCase$(Trim$(SafeStr(swFeat.GetTypeName2)))

    If Len(nm) > 0 Or Len(typ) > 0 Then
        FeatureVisitKey = typ & "|" & nm
    Else
        FeatureVisitKey = CStr(ObjPtr(swFeat))
    End If
    Exit Function
EH:
    FeatureVisitKey = ""
End Function

Private Function BodyVisitKey(ByVal swBody As SldWorks.Body2) As String
    On Error GoTo EH
    If swBody Is Nothing Then Exit Function

    Dim nm As String
    nm = UCase$(Trim$(GetBodyNameSafe(swBody)))

    If Len(nm) > 0 Then
        Dim dx As Double, dy As Double, dz As Double
        GetBodyBBoxDims swBody, dx, dy, dz
        BodyVisitKey = nm & "|" & Format$(dx, "0.000000") & "|" & Format$(dy, "0.000000") & "|" & Format$(dz, "0.000000")
    Else
        BodyVisitKey = CStr(ObjPtr(swBody))
    End If
    Exit Function
EH:
    BodyVisitKey = ""
End Function

Private Function FeatureNameSafe(ByVal swFeat As SldWorks.Feature) As String
    On Error Resume Next
    FeatureNameSafe = ""
    If swFeat Is Nothing Then Exit Function
    FeatureNameSafe = SafeStr(swFeat.name)
    If Err.Number <> 0 Then
        Err.Clear
        FeatureNameSafe = ""
    End If
    On Error GoTo 0
End Function

Private Sub AddFallbackSolidBodyItems(ByVal swModel As SldWorks.ModelDoc2, ByVal items As Collection)
    On Error GoTo EH

    If swModel Is Nothing Then Exit Sub
    If items Is Nothing Then Exit Sub
    If swModel.GetType <> swDocumentTypes_e.swDocPART Then Exit Sub

    Dim swPart As SldWorks.PartDoc
    Set swPart = swModel
    If swPart Is Nothing Then Exit Sub

    Dim vBodies As Variant
    vBodies = swPart.GetBodies2(swBodyType_e.swSolidBody, True)
    If Not IsArray(vBodies) Then Exit Sub

    Dim i As Long
    For i = LBound(vBodies) To UBound(vBodies)
        Dim repB As SldWorks.Body2
        Set repB = vBodies(i)
        If repB Is Nothing Then GoTo NextBody

        Dim dx As Double, dy As Double, dz As Double
        GetBodyBBoxDims repB, dx, dy, dz

        If dx <= 0# Or dy <= 0# Or dz <= 0# Then GoTo NextBody

        Dim d As Object
        Set d = CreateObject("Scripting.Dictionary")
        Set d("RepBody_Model") = repB
        d("dx") = dx
        d("dy") = dy
        d("dz") = dz
        d("ItemNo") = CStr(items.Count + 1)
        d("PartNo") = GetBodyNameSafe(repB)
        If Len(SafeStr(d("PartNo"))) = 0 Then d("PartNo") = swModel.GetTitle & "_BODY_" & CStr(items.Count + 1)
        d("Qty") = "1"
        d("CutListFeatureName") = "SOLID_BODY_FALLBACK"
        d("BodyName") = GetBodyNameSafe(repB)

        items.Add d

        LogInfo "SolidBodyFallback | item=" & SafeStr(d("ItemNo")) & _
                " | pn=" & SafeStr(d("PartNo")) & _
                " | bbox=" & Fmt(dx) & "x" & Fmt(dy) & "x" & Fmt(dz)

NextBody:
    Next i
    Exit Sub

EH:
    LogWarn "AddFallbackSolidBodyItems exception | " & Err.Number & " | " & Err.Description
End Sub

Private Function GetBodyNameSafe(ByVal swBody As SldWorks.Body2) As String
    On Error Resume Next
    GetBodyNameSafe = ""
    If swBody Is Nothing Then Exit Function
    GetBodyNameSafe = SafeStr(swBody.name)
    If Err.Number <> 0 Then
        Err.Clear
        GetBodyNameSafe = ""
    End If
    On Error GoTo 0
End Function

Private Sub GetBodyBBoxDims(ByVal b As SldWorks.Body2, _
                            ByRef dx As Double, ByRef dy As Double, ByRef dz As Double)
    dx = 0#: dy = 0#: dz = 0#
    On Error Resume Next
    Dim bb As Variant
    bb = b.GetBodyBox
    On Error GoTo 0

    If IsArray(bb) Then
        dx = Abs(CDbl(bb(3)) - CDbl(bb(0)))
        dy = Abs(CDbl(bb(4)) - CDbl(bb(1)))
        dz = Abs(CDbl(bb(5)) - CDbl(bb(2)))
    End If
End Sub

Private Function GetCLProp(ByVal swModel As SldWorks.ModelDoc2, _
                           ByVal feat As SldWorks.Feature, _
                           ByVal names As Variant) As String
    On Error GoTo EH

    Dim cpm As SldWorks.CustomPropertyManager
    On Error Resume Next
    Set cpm = feat.CustomPropertyManager
    On Error GoTo 0

    If cpm Is Nothing Then
        On Error Resume Next
        Set cpm = swModel.Extension.CustomPropertyManager(feat.name)
        On Error GoTo 0
    End If
    If cpm Is Nothing Then Exit Function

    Dim i As Long
    For i = LBound(names) To UBound(names)
        Dim vOut As String, vRes As String
        vOut = "": vRes = ""

        On Error Resume Next
        cpm.Get4 CStr(names(i)), False, vOut, vRes
        On Error GoTo 0

        Dim s As String
        s = Trim$(SafeStr(vRes))
        If Len(s) = 0 Then s = Trim$(SafeStr(vOut))

        If Len(s) > 0 Then
            GetCLProp = s
            Exit Function
        End If
    Next i
    Exit Function

EH:
    GetCLProp = ""
End Function

Private Function ParseItemFromName(ByVal s As String) As String
    Dim p1 As Long, p2 As Long
    p1 = InStr(1, s, "<", vbTextCompare)
    p2 = InStr(1, s, ">", vbTextCompare)

    If p1 > 0 And p2 > p1 Then
        ParseItemFromName = Mid$(s, p1 + 1, p2 - p1 - 1)
    Else
        ParseItemFromName = "?"
    End If
End Function

'====================================================================================
' CONFIGURATION RESOLUTION / VALIDATION
'====================================================================================
Private Function ResolvePreferredReferencedConfiguration(ByVal swModel As SldWorks.ModelDoc2, _
                                                         ByVal rawViewCfg As String) As String
    On Error GoTo EH

    Dim rawCfg As String
    Dim baseCfg As String
    Dim activeCfg As String
    Dim derivedCfg As String
    Dim firstCfg As String

    rawCfg = Trim$(rawViewCfg)
    baseCfg = Trim$(StripDisplayState(rawCfg))
    activeCfg = GetActiveConfigurationNameSafe(swModel)
    firstCfg = GetFirstConfigurationNameSafe(swModel)

    LogInfo "ResolvePreferredReferencedConfiguration | raw=" & rawCfg & _
            " | base=" & baseCfg & _
            " | active=" & activeCfg & _
            " | first=" & firstCfg

    ' 1) Exact raw match
    If Len(rawCfg) > 0 Then
        If ModelHasConfiguration(swModel, rawCfg) Then
            ResolvePreferredReferencedConfiguration = GetActualConfigurationCase(swModel, rawCfg)
            LogInfo "ResolvePreferredReferencedConfiguration | using exact raw match=" & ResolvePreferredReferencedConfiguration
            Exit Function
        End If
    End If

    ' 2) If raw/base missing but a derived config exists, prefer derived match
    If Len(baseCfg) > 0 Then
        derivedCfg = FindBestDerivedConfigurationByBaseName(swModel, baseCfg)
        If Len(derivedCfg) > 0 Then
            ResolvePreferredReferencedConfiguration = derivedCfg
            LogInfo "ResolvePreferredReferencedConfiguration | using derived-from-base match=" & ResolvePreferredReferencedConfiguration
            Exit Function
        End If
    End If

    ' 3) Plain base match
    If Len(baseCfg) > 0 Then
        If ModelHasConfiguration(swModel, baseCfg) Then
            ResolvePreferredReferencedConfiguration = GetActualConfigurationCase(swModel, baseCfg)
            LogInfo "ResolvePreferredReferencedConfiguration | using plain base match=" & ResolvePreferredReferencedConfiguration
            Exit Function
        End If
    End If

    ' 4) If active config shares the base stem, prefer it
    If Len(activeCfg) > 0 And Len(baseCfg) > 0 Then
        If StrComp(Left$(UCase$(activeCfg), Len(baseCfg)), UCase$(baseCfg), vbTextCompare) = 0 Then
            ResolvePreferredReferencedConfiguration = activeCfg
            LogInfo "ResolvePreferredReferencedConfiguration | using active config matching base=" & ResolvePreferredReferencedConfiguration
            Exit Function
        End If
    End If

    ' 5) Use active config
    If Len(activeCfg) > 0 Then
        ResolvePreferredReferencedConfiguration = activeCfg
        LogInfo "ResolvePreferredReferencedConfiguration | using active config fallback=" & ResolvePreferredReferencedConfiguration
        Exit Function
    End If

    ' 6) Use first configuration
    If Len(firstCfg) > 0 Then
        ResolvePreferredReferencedConfiguration = firstCfg
        LogInfo "ResolvePreferredReferencedConfiguration | using first config fallback=" & ResolvePreferredReferencedConfiguration
        Exit Function
    End If

    ResolvePreferredReferencedConfiguration = ""
    Exit Function

EH:
    LogError "ResolvePreferredReferencedConfiguration exception | " & Err.Number & " | " & Err.Description
    ResolvePreferredReferencedConfiguration = ""
End Function

Private Function ModelHasConfiguration(ByVal swModel As SldWorks.ModelDoc2, ByVal cfgName As String) As Boolean
    On Error GoTo EH

    Dim vNames As Variant
    Dim i As Long

    If swModel Is Nothing Then Exit Function
    If Len(Trim$(cfgName)) = 0 Then Exit Function

    vNames = swModel.GetConfigurationNames
    If Not IsArray(vNames) Then Exit Function

    For i = LBound(vNames) To UBound(vNames)
        If StrComp(Trim$(SafeStr(vNames(i))), Trim$(cfgName), vbTextCompare) = 0 Then
            ModelHasConfiguration = True
            Exit Function
        End If
    Next i
    Exit Function

EH:
    ModelHasConfiguration = False
End Function

Private Function GetActualConfigurationCase(ByVal swModel As SldWorks.ModelDoc2, ByVal cfgName As String) As String
    On Error GoTo EH

    Dim vNames As Variant
    Dim i As Long

    If swModel Is Nothing Then Exit Function
    If Len(Trim$(cfgName)) = 0 Then Exit Function

    vNames = swModel.GetConfigurationNames
    If Not IsArray(vNames) Then Exit Function

    For i = LBound(vNames) To UBound(vNames)
        If StrComp(Trim$(SafeStr(vNames(i))), Trim$(cfgName), vbTextCompare) = 0 Then
            GetActualConfigurationCase = SafeStr(vNames(i))
            Exit Function
        End If
    Next i
    Exit Function

EH:
    GetActualConfigurationCase = cfgName
End Function

Private Function FindBestDerivedConfigurationByBaseName(ByVal swModel As SldWorks.ModelDoc2, _
                                                        ByVal baseCfg As String) As String
    On Error GoTo EH

    Dim vNames As Variant
    Dim i As Long
    Dim nm As String
    Dim best As String
    Dim prefix As String

    If swModel Is Nothing Then Exit Function
    If Len(Trim$(baseCfg)) = 0 Then Exit Function

    prefix = UCase$(Trim$(baseCfg)) & "<"

    vNames = swModel.GetConfigurationNames
    If Not IsArray(vNames) Then Exit Function

    ' Prefer As Machined if present
    For i = LBound(vNames) To UBound(vNames)
        nm = SafeStr(vNames(i))
        If Left$(UCase$(nm), Len(prefix)) = prefix Then
            If InStr(1, nm, "<As Machined>", vbTextCompare) > 0 Then
                FindBestDerivedConfigurationByBaseName = nm
                Exit Function
            End If
        End If
    Next i

    ' Then prefer As Welded if present
    For i = LBound(vNames) To UBound(vNames)
        nm = SafeStr(vNames(i))
        If Left$(UCase$(nm), Len(prefix)) = prefix Then
            If InStr(1, nm, "<As Welded>", vbTextCompare) > 0 Then
                FindBestDerivedConfigurationByBaseName = nm
                Exit Function
            End If
        End If
    Next i

    ' Else first derived match
    For i = LBound(vNames) To UBound(vNames)
        nm = SafeStr(vNames(i))
        If Left$(UCase$(nm), Len(prefix)) = prefix Then
            best = nm
            Exit For
        End If
    Next i

    FindBestDerivedConfigurationByBaseName = best
    Exit Function

EH:
    LogError "FindBestDerivedConfigurationByBaseName exception | " & Err.Number & " | " & Err.Description
    FindBestDerivedConfigurationByBaseName = ""
End Function

Private Function GetActiveConfigurationNameSafe(ByVal swModel As SldWorks.ModelDoc2) As String
    On Error GoTo EH

    Dim cfg As SldWorks.Configuration
    Set cfg = Nothing

    If swModel Is Nothing Then Exit Function

    On Error Resume Next
    Set cfg = swModel.ConfigurationManager.ActiveConfiguration
    On Error GoTo EH

    If Not cfg Is Nothing Then
        GetActiveConfigurationNameSafe = SafeStr(cfg.name)
    End If
    Exit Function

EH:
    GetActiveConfigurationNameSafe = ""
End Function

Private Function GetFirstConfigurationNameSafe(ByVal swModel As SldWorks.ModelDoc2) As String
    On Error GoTo EH

    Dim vNames As Variant
    If swModel Is Nothing Then Exit Function

    vNames = swModel.GetConfigurationNames
    If Not IsArray(vNames) Then Exit Function
    If UBound(vNames) >= LBound(vNames) Then
        GetFirstConfigurationNameSafe = SafeStr(vNames(LBound(vNames)))
    End If
    Exit Function

EH:
    GetFirstConfigurationNameSafe = ""
End Function

Private Function ActivateModelConfigurationSafe(ByVal swModel As SldWorks.ModelDoc2, _
                                                ByVal cfgName As String) As Boolean
    On Error GoTo EH

    ActivateModelConfigurationSafe = False
    If swModel Is Nothing Then Exit Function
    If Len(Trim$(cfgName)) = 0 Then Exit Function

    If Not ModelHasConfiguration(swModel, cfgName) Then
        LogWarn "ActivateModelConfigurationSafe | requested config not found | cfg=" & cfgName
        Exit Function
    End If

    On Error Resume Next
    ActivateModelConfigurationSafe = swModel.ShowConfiguration2(cfgName)
    If Err.Number <> 0 Then
        LogWarn "ActivateModelConfigurationSafe | ShowConfiguration2 error | " & Err.Number & " | " & Err.Description & _
                " | cfg=" & cfgName
        Err.Clear
    End If
    On Error GoTo EH

    swModel.EditRebuild3

    Dim activeNow As String
    activeNow = GetActiveConfigurationNameSafe(swModel)
    If StrComp(activeNow, cfgName, vbTextCompare) = 0 Then
        ActivateModelConfigurationSafe = True
    End If

    LogInfo "ActivateModelConfigurationSafe | requested=" & cfgName & _
            " | activeNow=" & activeNow & _
            " | ok=" & CStr(ActivateModelConfigurationSafe)
    Exit Function

EH:
    LogError "ActivateModelConfigurationSafe exception | " & Err.Number & " | " & Err.Description & _
             " | cfg=" & cfgName
    ActivateModelConfigurationSafe = False
End Function

Private Function EnsureViewUsesConfiguration(ByVal swV As SldWorks.View, _
                                             ByVal cfgName As String, _
                                             ByVal stageTag As String) As Boolean
    On Error GoTo EH

    Dim k As Long
    Dim curCfg As String
    Dim vName As String

    EnsureViewUsesConfiguration = False
    If swV Is Nothing Then Exit Function
    If Len(Trim$(cfgName)) = 0 Then Exit Function

    vName = ViewGetNameSafe(swV)

    For k = 1 To VIEW_CFG_RETRY_COUNT
        ForceSheetContext

        ' Keep model active config aligned as well.
        Call ActivateModelConfigurationSafe(g_refModel, cfgName)

        On Error Resume Next
        swV.ReferencedConfiguration = cfgName
        If Err.Number <> 0 Then
            LogWarn "EnsureViewUsesConfiguration | setter error | try=" & k & _
                    " | stage=" & stageTag & " | view=" & vName & _
                    " | " & Err.Number & " | " & Err.Description
            Err.Clear
        End If
        On Error GoTo EH

        On Error Resume Next
        swV.Update
        On Error GoTo EH

        g_swDrwModel.EditRebuild3
        DoEvents

        curCfg = Trim$(SafeStr(swV.ReferencedConfiguration))

        LogInfo "EnsureViewUsesConfiguration | try=" & k & _
                " | stage=" & stageTag & _
                " | view=" & vName & _
                " | target=" & cfgName & _
                " | current=" & curCfg

        If StrComp(curCfg, cfgName, vbTextCompare) = 0 Then
            EnsureViewUsesConfiguration = True
            Exit Function
        End If
    Next k

    Exit Function

EH:
    LogError "EnsureViewUsesConfiguration exception | " & Err.Number & " | " & Err.Description & _
             " | stage=" & stageTag & " | view=" & ViewGetNameSafe(swV) & " | cfg=" & cfgName
    EnsureViewUsesConfiguration = False
End Function

Private Sub NormalizePlacedViewConfigurations(ByVal placedViews As Collection, ByVal cfgName As String)
    On Error GoTo EH

    Dim i As Long
    If placedViews Is Nothing Then Exit Sub
    If placedViews.Count = 0 Then Exit Sub
    If Len(Trim$(cfgName)) = 0 Then Exit Sub

    LogInfo "NormalizePlacedViewConfigurations | count=" & CStr(placedViews.Count) & " | cfg=" & cfgName

    For i = 1 To placedViews.Count
        Dim swV As SldWorks.View
        Set swV = placedViews(i)

        If Not swV Is Nothing Then
            If Not EnsureViewUsesConfiguration(swV, cfgName, "normalize pass idx=" & CStr(i)) Then
                LogWarn "NormalizePlacedViewConfigurations | failed | idx=" & i & _
                        " | view=" & ViewGetNameSafe(swV)
            End If
        End If
    Next i

    Exit Sub

EH:
    LogError "NormalizePlacedViewConfigurations exception | " & Err.Number & " | " & Err.Description
End Sub

'====================================================================================
' BOM BALLOONS
'====================================================================================
Private Function AddBOMBalloonsForPlacedViews(ByVal placedViews As Collection, _
                                              ByVal placedItems As Collection) As Long
    On Error GoTo EH

    AddBOMBalloonsForPlacedViews = 0
    If Not ADD_BOM_BALLOON_AFTER_PLACE Then Exit Function
    If placedViews Is Nothing Then Exit Function
    If placedItems Is Nothing Then Exit Function
    If placedViews.Count = 0 Then Exit Function

    Dim i As Long
    For i = 1 To placedViews.Count
        Dim v As SldWorks.View
        Set v = placedViews(i)

        Dim item As Object
        Set item = placedItems(i)

        If Not v Is Nothing Then
            ' Make sure configuration is still correct before ballooning.
            If Not EnsureViewUsesConfiguration(v, g_refCfg, "pre-balloon idx=" & CStr(i)) Then
                LogWarn "AddBOMBalloonsForPlacedViews | view cfg not fully validated before balloon | idx=" & i & _
                        " | view=" & ViewGetNameSafe(v)
            End If

            If InsertOneBOMBalloonForView(v, item) Then
                AddBOMBalloonsForPlacedViews = AddBOMBalloonsForPlacedViews + 1
            Else
                LogWarn "BOM balloon failed | view=" & ViewGetNameSafe(v) & _
                        " | pn=" & SafeStr(item("PartNo")) & " | itemNo=" & SafeStr(item("ItemNo"))
            End If
        End If
    Next i

    Exit Function

EH:
    LogError "AddBOMBalloonsForPlacedViews exception | " & Err.Number & " | " & Err.Description
End Function

Private Function InsertOneBOMBalloonForView(ByVal v As SldWorks.View, _
                                            ByVal item As Object) As Boolean
    On Error GoTo EH

    InsertOneBOMBalloonForView = False
    If v Is Nothing Then Exit Function

    Dim mdExt As SldWorks.ModelDocExtension
    Set mdExt = g_swDrwModel.Extension
    If mdExt Is Nothing Then
        LogWarn "InsertOneBOMBalloonForView | ModelDocExtension is Nothing"
        Exit Function
    End If

    Dim vName As String
    vName = ViewGetNameSafe(v)

    Dim tryN As Long
    For tryN = 1 To BALLOON_RETRY_COUNT
        ForceSheetContext
        g_swDrwModel.ClearSelection2 True

        ' Anchor selection: entity in view (best), else fallback to selecting view
        Dim anchorOk As Boolean
        anchorOk = TrySelectBalloonAnchorEntityInView(v)

        If Not anchorOk Then
            LogWarn "InsertOneBOMBalloonForView | no visible entity anchor found, fallback=view selection | try=" & tryN & " | view=" & vName
            anchorOk = g_swDrwModel.Extension.SelectByID2(vName, "DRAWINGVIEW", 0#, 0#, 0#, False, 0, Nothing, 0)
        End If

        If Not anchorOk Then
            LogWarn "InsertOneBOMBalloonForView | anchor selection failed | try=" & tryN & " | view=" & vName
            GoTo NextTry
        End If

        Dim bo As Object
        Set bo = mdExt.CreateBalloonOptions
        If bo Is Nothing Then
            LogWarn "InsertOneBOMBalloonForView | CreateBalloonOptions returned Nothing"
            GoTo NextTry
        End If

        ConfigureBalloonOptionsSafe bo

        Dim n As Object
        Set n = mdExt.InsertBOMBalloon2(bo)
        If n Is Nothing Then
            LogWarn "InsertOneBOMBalloonForView | InsertBOMBalloon2 returned Nothing | try=" & tryN & _
                    " | view=" & vName & " | pn=" & SafeStr(item("PartNo")) & " | itemNo=" & SafeStr(item("ItemNo"))
            GoTo NextTry
        End If

        If PositionBalloonNearView(n, v) Then
            LogInfo "BOM balloon OK | view=" & vName & _
                    " | pn=" & SafeStr(item("PartNo")) & " | itemNo=" & SafeStr(item("ItemNo"))
        Else
            LogWarn "BOM balloon inserted but reposition failed | view=" & vName
        End If

        g_swDrwModel.EditRebuild3
        ForceSheetContext
        InsertOneBOMBalloonForView = True
        Exit Function

NextTry:
        g_swDrwModel.ClearSelection2 True
        DoEvents
    Next tryN

    ForceSheetContext
    Exit Function

EH:
    LogError "InsertOneBOMBalloonForView exception | " & Err.Number & " | " & Err.Description & _
             " | view=" & ViewGetNameSafe(v)
    ForceSheetContext
    InsertOneBOMBalloonForView = False
End Function

Private Sub ConfigureBalloonOptionsSafe(ByVal bo As Object)
    On Error Resume Next
    CallByName bo, "ShowQuantity", VbLet, False
    On Error GoTo 0
End Sub

Private Function TrySelectBalloonAnchorEntityInView(ByVal v As SldWorks.View) As Boolean
    On Error GoTo EH

    TrySelectBalloonAnchorEntityInView = False
    If v Is Nothing Then Exit Function

    Dim vVisComps As Variant
    On Error Resume Next
    vVisComps = v.GetVisibleComponents
    On Error GoTo EH

    If IsArray(vVisComps) Then
        Dim i As Long

        ' Prefer edges
        For i = LBound(vVisComps) To UBound(vVisComps)
            If TrySelectFirstVisibleEntityFromComp(v, vVisComps(i), swViewEntityType_e.swViewEntityType_Edge, "edge") Then
                TrySelectBalloonAnchorEntityInView = True
                Exit Function
            End If
        Next i

        ' Then vertices
        For i = LBound(vVisComps) To UBound(vVisComps)
            If TrySelectFirstVisibleEntityFromComp(v, vVisComps(i), swViewEntityType_e.swViewEntityType_Vertex, "vertex") Then
                TrySelectBalloonAnchorEntityInView = True
                Exit Function
            End If
        Next i

        ' Then faces
        For i = LBound(vVisComps) To UBound(vVisComps)
            If TrySelectFirstVisibleEntityFromComp(v, vVisComps(i), swViewEntityType_e.swViewEntityType_Face, "face") Then
                TrySelectBalloonAnchorEntityInView = True
                Exit Function
            End If
        Next i
    End If

    If TrySelectFirstVisibleEntityFromComp(v, Nothing, swViewEntityType_e.swViewEntityType_Edge, "edge(nil)") Then
        TrySelectBalloonAnchorEntityInView = True
        Exit Function
    End If
    If TrySelectFirstVisibleEntityFromComp(v, Nothing, swViewEntityType_e.swViewEntityType_Vertex, "vertex(nil)") Then
        TrySelectBalloonAnchorEntityInView = True
        Exit Function
    End If
    If TrySelectFirstVisibleEntityFromComp(v, Nothing, swViewEntityType_e.swViewEntityType_Face, "face(nil)") Then
        TrySelectBalloonAnchorEntityInView = True
        Exit Function
    End If

    Exit Function

EH:
    LogWarn "TrySelectBalloonAnchorEntityInView exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(v)
    TrySelectBalloonAnchorEntityInView = False
End Function

Private Function TrySelectFirstVisibleEntityFromComp(ByVal v As SldWorks.View, _
                                                     ByVal visComp As Variant, _
                                                     ByVal entType As Long, _
                                                     ByVal entTypeName As String) As Boolean
    On Error GoTo EH

    TrySelectFirstVisibleEntityFromComp = False
    If v Is Nothing Then Exit Function

    Dim vVisEnts As Variant
    On Error Resume Next
    vVisEnts = v.GetVisibleEntities2(visComp, entType)
    If Err.Number <> 0 Then
        Err.Clear
        On Error GoTo EH
        Exit Function
    End If
    On Error GoTo EH

    If Not IsArray(vVisEnts) Then Exit Function

    Dim i As Long
    For i = LBound(vVisEnts) To UBound(vVisEnts)
        Dim swEnt As SldWorks.Entity
        Set swEnt = Nothing

        On Error Resume Next
        Set swEnt = vVisEnts(i)
        On Error GoTo EH

        If Not swEnt Is Nothing Then
            g_swDrwModel.ClearSelection2 True

            If swEnt.Select4(False, Nothing) Then
                LogInfo "   Balloon anchor select OK | mode=Select4 | type=" & entTypeName & " | view=" & ViewGetNameSafe(v)
                TrySelectFirstVisibleEntityFromComp = True
                Exit Function
            End If

            g_swDrwModel.ClearSelection2 True
            If v.SelectEntity(swEnt, False) Then
                LogInfo "   Balloon anchor select OK | mode=View.SelectEntity | type=" & entTypeName & " | view=" & ViewGetNameSafe(v)
                TrySelectFirstVisibleEntityFromComp = True
                Exit Function
            End If
        End If
    Next i

    Exit Function

EH:
    LogWarn "TrySelectFirstVisibleEntityFromComp exception | " & Err.Number & " | " & Err.Description & _
            " | type=" & entTypeName & " | view=" & ViewGetNameSafe(v)
    TrySelectFirstVisibleEntityFromComp = False
End Function

Private Function PositionBalloonNearView(ByVal noteObj As Object, ByVal v As SldWorks.View) As Boolean
    On Error GoTo EH

    PositionBalloonNearView = False

    If noteObj Is Nothing Then
        LogWarn "PositionBalloonNearView | noteObj is Nothing"
        Exit Function
    End If

    If v Is Nothing Then
        LogWarn "PositionBalloonNearView | view is Nothing"
        Exit Function
    End If

    Dim x0 As Double, y0 As Double, x1 As Double, y1 As Double
    If Not GetViewRect(v, x0, y0, x1, y1) Then
        LogWarn "PositionBalloonNearView | GetViewRect failed | view=" & ViewGetNameSafe(v)
        Exit Function
    End If

    Dim ann As SldWorks.Annotation
    Set ann = Nothing

    If Not TryGetBalloonAnnotation(noteObj, ann) Then
        LogWarn "PositionBalloonNearView | could not resolve annotation from balloon object | view=" & ViewGetNameSafe(v)
        Exit Function
    End If

    Dim bx As Double, by As Double
    bx = x1 + BALLOON_OFFSET_X
    by = y1 + BALLOON_OFFSET_Y

    On Error Resume Next
    ann.SetPosition2 bx, by, 0#
    If Err.Number <> 0 Then
        LogWarn "PositionBalloonNearView | SetPosition2 failed | " & Err.Number & " | " & Err.Description & _
                " | target=(" & Fmt(bx) & "," & Fmt(by) & ")"
        Err.Clear
        On Error GoTo EH
        Exit Function
    End If
    On Error GoTo EH

    LogInfo "PositionBalloonNearView | moved balloon | view=" & ViewGetNameSafe(v) & _
            " | pos=(" & Fmt(bx) & "," & Fmt(by) & ")"

    PositionBalloonNearView = True
    Exit Function

EH:
    LogWarn "PositionBalloonNearView exception | " & Err.Number & " | " & Err.Description
    PositionBalloonNearView = False
End Function

Private Function TryGetBalloonAnnotation(ByVal noteObj As Object, _
                                         ByRef ann As SldWorks.Annotation) As Boolean
    On Error GoTo EH

    TryGetBalloonAnnotation = False
    Set ann = Nothing

    If noteObj Is Nothing Then Exit Function

    Err.Clear
    On Error Resume Next
    Set ann = noteObj.GetAnnotation
    If Err.Number <> 0 Then
        LogWarn "TryGetBalloonAnnotation | GetAnnotation failed | " & Err.Number & " | " & Err.Description
        Err.Clear
    End If
    On Error GoTo EH

    If ann Is Nothing Then
        LogWarn "TryGetBalloonAnnotation | GetAnnotation returned Nothing"
        Exit Function
    End If

    TryGetBalloonAnnotation = True
    Exit Function

EH:
    LogWarn "TryGetBalloonAnnotation exception | " & Err.Number & " | " & Err.Description
    TryGetBalloonAnnotation = False
End Function

Private Function DegToRad(ByVal degVal As Double) As Double
    DegToRad = degVal * PI / 180#
End Function

Private Function RadToDeg(ByVal radVal As Double) As Double
    RadToDeg = radVal * 180# / PI
End Function

Private Function NormalizeAngleRad(ByVal angRad As Double) As Double
    Do While angRad > PI
        angRad = angRad - (2# * PI)
    Loop
    Do While angRad <= -PI
        angRad = angRad + (2# * PI)
    Loop
    NormalizeAngleRad = angRad
End Function

Private Function NormalizeAngleToHorizontal(ByVal angRad As Double) As Double
    angRad = NormalizeAngleRad(angRad)

    Do While angRad > (PI / 2#)
        angRad = angRad - PI
    Loop

    Do While angRad <= (-PI / 2#)
        angRad = angRad + PI
    Loop

    NormalizeAngleToHorizontal = angRad
End Function

Private Function GetSnapReferenceForView(ByVal swView As SldWorks.View, _
                                         ByVal item As Object, _
                                         ByRef refMetric As Double, _
                                         ByRef refAngRad As Double, _
                                         ByRef refQuality As Double, _
                                         ByRef refDesc As String) As Boolean
    On Error GoTo EH

    GetSnapReferenceForView = False
    refMetric = 0#
    refAngRad = 0#
    refQuality = -1E+30
    refDesc = ""

    If swView Is Nothing Then Exit Function
    If item Is Nothing Then Exit Function

    If SNAP_REF_USE_VISIBLE_EDGE_FIRST Then
        Dim visLen As Double
        Dim visAng As Double
        Dim visDesc As String
        Dim edgeCt As Long
        Dim lineCt As Long

        visLen = 0#: visAng = 0#: visDesc = ""
        edgeCt = 0: lineCt = 0

        Call GetBestVisibleLinearEdgeInView(swView, visLen, visAng, visDesc, edgeCt, lineCt)

        If visLen >= ROT_MIN_VISIBLE_EDGE_LEN Then
            refMetric = visLen
            refAngRad = visAng
            refQuality = (visLen * 1000000#) + (CDbl(lineCt) * 100#) + (CDbl(edgeCt) * 10#)
            refDesc = "Visible longest edge | len=" & Fmt(visLen) & " | " & visDesc
            GetSnapReferenceForView = True
            Exit Function
        End If
    End If

    If GetCategoryOrientationReference(swView, item, refMetric, refAngRad, refQuality, refDesc) Then
        refDesc = "CatRefFallback | " & refDesc
        GetSnapReferenceForView = True
    End If

    Exit Function

EH:
    LogWarn "GetSnapReferenceForView exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
    GetSnapReferenceForView = False
End Function

Private Function GetOrientationSnapState(ByVal refAngRad As Double, _
                                         ByVal tolDeg As Double, _
                                         ByRef deltaHDeg As Double, _
                                         ByRef deltaVDeg As Double, _
                                         ByRef stateName As String) As Long
    Dim absHorizDeg As Double

    absHorizDeg = Abs(RadToDeg(NormalizeAngleToHorizontal(refAngRad)))
    deltaHDeg = absHorizDeg
    deltaVDeg = Abs(90# - absHorizDeg)

    If deltaHDeg <= tolDeg Then
        stateName = "HORIZONTAL_READY"
        GetOrientationSnapState = 2
        Exit Function
    End If

    If deltaVDeg <= tolDeg Then
        stateName = "VERTICAL_READY_NEEDS_90"
        GetOrientationSnapState = 1
        Exit Function
    End If

    stateName = "UNSNAPPED"
    GetOrientationSnapState = 0
End Function

Private Function TryApplySnapFirstOrientation(ByVal swDrawModel As SldWorks.ModelDoc2, _
                                              ByVal swView As SldWorks.View, _
                                              ByVal item As Object, _
                                              ByVal tag As String) As Boolean
    On Error GoTo EH

    TryApplySnapFirstOrientation = False
    If swDrawModel Is Nothing Then Exit Function
    If swView Is Nothing Then Exit Function
    If item Is Nothing Then Exit Function

    Dim baseAngleRad As Double
    baseAngleRad = swView.Angle

    Dim refMetric As Double
    Dim refAngRad As Double
    Dim refQuality As Double
    Dim refDesc As String
    Dim deltaHDeg As Double
    Dim deltaVDeg As Double
    Dim snapState As String
    Dim snapRank As Long

    If Not GetSnapReferenceForView(swView, item, refMetric, refAngRad, refQuality, refDesc) Then Exit Function

    snapRank = GetOrientationSnapState(refAngRad, ROT_SNAP_TOL_DEG, deltaHDeg, deltaVDeg, snapState)

    LogInfo "SnapCheck | view=" & ViewGetNameSafe(swView) & _
            " | cat=" & SafeStr(item("BodyCategory")) & _
            " | state=" & snapState & _
            " | refMetric=" & Fmt(refMetric) & _
            " | refAngDeg=" & Format$(RadToDeg(refAngRad), "0.000") & _
            " | deltaH=" & Format$(deltaHDeg, "0.000") & _
            " | deltaV=" & Format$(deltaVDeg, "0.000") & _
            " | ref=" & refDesc

    If snapRank <= 0 Then Exit Function

    Dim validNow As Boolean
    Dim validScore As Double
    Dim validDetail As String

    If snapRank = 2 Then
        validNow = ValidateOrientationResultByCategory(swView, item, validScore, validDetail)

        LogInfo "SnapAction | chosen=keep | view=" & ViewGetNameSafe(swView) & _
                " | deltaH=" & Format$(deltaHDeg, "0.000") & _
                " | valid=" & BoolWord(validNow) & _
                " | verify=" & validDetail

        If validNow Then
            TryApplySnapFirstOrientation = True
            Exit Function
        End If

        Exit Function
    End If

    Dim targetAngle As Double
    targetAngle = NormalizeAngleRad(baseAngleRad - NormalizeAngleToHorizontal(refAngRad))

    SetViewAngleAndRefresh swDrawModel, swView, targetAngle, tag & " | snap-first rotate90"

    Dim confMetric As Double
    Dim confAngRad As Double
    Dim confQuality As Double
    Dim confDesc As String
    Dim confDeltaHDeg As Double
    Dim confDeltaVDeg As Double
    Dim confState As String
    Dim confRank As Long

    confRank = 0
    confDeltaHDeg = 9999#
    confDeltaVDeg = 9999#

    If GetSnapReferenceForView(swView, item, confMetric, confAngRad, confQuality, confDesc) Then
        confRank = GetOrientationSnapState(confAngRad, ROT_SNAP_TOL_DEG, confDeltaHDeg, confDeltaVDeg, confState)
    End If

    validNow = ValidateOrientationResultByCategory(swView, item, validScore, validDetail)

    LogInfo "SnapVerify | view=" & ViewGetNameSafe(swView) & _
            " | state=" & confState & _
            " | refMetric=" & Fmt(confMetric) & _
            " | refAngDeg=" & Format$(RadToDeg(confAngRad), "0.000") & _
            " | deltaH=" & Format$(confDeltaHDeg, "0.000") & _
            " | deltaV=" & Format$(confDeltaVDeg, "0.000") & _
            " | valid=" & BoolWord(validNow) & _
            " | ref=" & confDesc & _
            " | verify=" & validDetail

    If validNow And confRank = 2 Then
        TryApplySnapFirstOrientation = True
        Exit Function
    End If

    SetViewAngleAndRefresh swDrawModel, swView, baseAngleRad, tag & " | snap-first restore"

    Exit Function

EH:
    LogWarn "TryApplySnapFirstOrientation exception | " & Err.Number & " | " & Err.Description & _
            " | view=" & ViewGetNameSafe(swView)
    On Error Resume Next
    If Not swDrawModel Is Nothing Then
        SetViewAngleAndRefresh swDrawModel, swView, swView.Angle, tag & " | snap-first exception"
    End If
    On Error GoTo 0
    TryApplySnapFirstOrientation = False
End Function


Private Function Atn2Safe(ByVal y As Double, ByVal x As Double) As Double
    If x > 0# Then
        Atn2Safe = Atn(y / x)
    ElseIf x < 0# Then
        If y >= 0# Then
            Atn2Safe = Atn(y / x) + PI
        Else
            Atn2Safe = Atn(y / x) - PI
        End If
    Else
        If y > 0# Then
            Atn2Safe = PI / 2#
        ElseIf y < 0# Then
            Atn2Safe = -PI / 2#
        Else
            Atn2Safe = 0#
        End If
    End If
End Function

Private Function MaxD(ByVal a As Double, ByVal b As Double) As Double
    If a >= b Then MaxD = a Else MaxD = b
End Function

Private Function MinD(ByVal a As Double, ByVal b As Double) As Double
    If a <= b Then MinD = a Else MinD = b
End Function

'====================================================================================
' SHEET HELPERS
'====================================================================================
Private Sub EnsureSheetExistsCopyProps(ByVal swDraw As SldWorks.DrawingDoc, _
                                       ByVal src As SldWorks.Sheet, _
                                       ByVal name As String)
    If SheetExists(swDraw, name) Then Exit Sub

    Dim p As Variant
    p = src.GetProperties2

    Dim ok As Boolean
    ok = (False <> swDraw.NewSheet3(name, CLng(p(0)), CLng(p(1)), _
                                    CDbl(p(2)), CDbl(p(3)), CBool(p(4)), _
                                    src.GetTemplateName, CDbl(p(5)), CDbl(p(6)), "Default"))

    If ok Then
        LogInfo "Sheet created: " & name
    Else
        LogError "Sheet creation failed: " & name
    End If
End Sub

Private Function SheetExists(ByVal swDraw As SldWorks.DrawingDoc, ByVal name As String) As Boolean
    Dim vNames As Variant
    vNames = swDraw.GetSheetNames
    If Not IsArray(vNames) Then Exit Function

    Dim i As Long
    For i = LBound(vNames) To UBound(vNames)
        If StrComp(SafeStr(vNames(i)), name, vbTextCompare) = 0 Then
            SheetExists = True
            Exit Function
        End If
    Next i
End Function

Private Sub SafeDeleteView(ByVal swModel As SldWorks.ModelDoc2, ByVal swV As SldWorks.View)
    On Error Resume Next

    Dim nm As String
    nm = ViewGetNameSafe(swV)

    swModel.ClearSelection2 True
    swModel.Extension.SelectByID2 nm, "DRAWINGVIEW", 0#, 0#, 0#, False, 0, Nothing, 0
    swModel.EditDelete
    swModel.ClearSelection2 True

    On Error GoTo 0
End Sub

'====================================================================================
' MODEL OPEN / REFERENCE VIEW
'====================================================================================
Private Function EnsureModelOpen(ByVal refView As SldWorks.View, _
                                 ByVal modelPath As String, _
                                 ByVal cfg As String, _
                                 ByRef outModel As SldWorks.ModelDoc2, _
                                 ByRef outOpened As Boolean, _
                                 ByRef outTitle As String) As Boolean
    On Error GoTo EH

    outOpened = False
    outTitle = ""

    On Error Resume Next
    Set outModel = refView.ReferencedDocument
    On Error GoTo 0

    If Not outModel Is Nothing Then
        outTitle = outModel.GetTitle
        LogInfo "Model already open | " & outTitle
        EnsureModelOpen = True
        Exit Function
    End If

    Dim errs As Long, warns As Long
    Dim opts As Long
    opts = swOpenDocOptions_e.swOpenDocOptions_Silent Or _
           swOpenDocOptions_e.swOpenDocOptions_ReadOnly

    ' First attempt with requested cfg if supplied
    If Len(Trim$(cfg)) > 0 Then
        LogInfo "Opening model | preferred cfg=" & cfg & " | path=" & modelPath
        Set outModel = g_swApp.OpenDoc6(modelPath, swDocumentTypes_e.swDocPART, opts, cfg, errs, warns)
        LogInfo "OpenDoc6 attempt-1 | errs=" & errs & " warns=" & warns & " ok=" & CStr(Not outModel Is Nothing)
    End If

    ' Second attempt without forcing cfg
    If outModel Is Nothing Then
        errs = 0: warns = 0
        LogInfo "Opening model | fallback blank cfg | path=" & modelPath
        Set outModel = g_swApp.OpenDoc6(modelPath, swDocumentTypes_e.swDocPART, opts, "", errs, warns)
        LogInfo "OpenDoc6 attempt-2 | errs=" & errs & " warns=" & warns & " ok=" & CStr(Not outModel Is Nothing)
    End If

    outOpened = Not (outModel Is Nothing)

    If outModel Is Nothing Then
        EnsureModelOpen = False
        Exit Function
    End If

    outTitle = outModel.GetTitle
    EnsureModelOpen = True
    Exit Function

EH:
    LogError "EnsureModelOpen exception | " & Err.Number & " | " & Err.Description
    EnsureModelOpen = False
End Function

Private Function GetReferenceDrawingView(ByVal drw As SldWorks.DrawingDoc, _
                                         ByVal swDrwModel As SldWorks.ModelDoc2) As SldWorks.View
    On Error GoTo EH

    ' Priority 1: user-selected drawing view
    Dim mgr As SldWorks.SelectionMgr
    Set mgr = swDrwModel.SelectionManager

    If Not mgr Is Nothing Then
        Dim ct As Long
        ct = mgr.GetSelectedObjectCount2(-1)

        Dim i As Long
        For i = 1 To ct
            If mgr.GetSelectedObjectType3(i, -1) = swSelectType_e.swSelDRAWINGVIEWS Then
                Dim vSel As Object
                Set vSel = mgr.GetSelectedObject6(i, -1)
                If Not vSel Is Nothing Then
                    Set GetReferenceDrawingView = vSel
                    Exit Function
                End If
            End If
        Next i
    End If

    ' Priority 2: first model view on active sheet
    Dim sv As SldWorks.View
    Set sv = drw.GetFirstView
    If sv Is Nothing Then Exit Function

    Set sv = sv.GetNextView
    Do While Not sv Is Nothing
        If Len(SafeStr(sv.GetReferencedModelName)) > 0 Then
            Set GetReferenceDrawingView = sv
            Exit Function
        End If
        Set sv = sv.GetNextView
    Loop
    Exit Function

EH:
    Set GetReferenceDrawingView = Nothing
End Function

Private Function ViewGetNameSafe(ByVal v As SldWorks.View) As String
    Dim s As String
    On Error Resume Next
    s = v.GetName2
    On Error GoTo 0

    If Len(Trim$(s)) = 0 Then
        On Error Resume Next
        s = v.name
        On Error GoTo 0
    End If

    ViewGetNameSafe = s
End Function

Private Function StripDisplayState(ByVal cfg As String) As String
    Dim p As Long
    p = InStr(1, cfg, "<", vbTextCompare)
    If p > 0 Then
        StripDisplayState = Trim$(Left$(cfg, p - 1))
    Else
        StripDisplayState = Trim$(cfg)
    End If
End Function

'====================================================================================
' UTILITIES
'====================================================================================
Private Function SafeArrayCount(ByVal vArr As Variant) As Long
    On Error GoTo EH
    SafeArrayCount = UBound(vArr) - LBound(vArr) + 1
    Exit Function
EH:
    SafeArrayCount = 0
End Function

Private Function SafeStr(ByVal v As Variant) As String
    On Error Resume Next
    SafeStr = CStr(v)
    If Err.Number <> 0 Then
        SafeStr = ""
        Err.Clear
    End If
End Function


Private Function BoolWord(ByVal v As Boolean) As String
    If v Then
        BoolWord = "True"
    Else
        BoolWord = "False"
    End If
End Function

Private Function Fmt(ByVal d As Double) As String
    Fmt = Format$(d, "0.0000")
End Function

Private Function FmtIf(ByVal cond As Boolean, ByVal d As Double) As String
    If cond Then
        FmtIf = Fmt(d)
    Else
        FmtIf = "n/a"
    End If
End Function

'====================================================================================
' LOGGING
'====================================================================================
Private Sub InitDebugLog()
    On Error GoTo EH

    If g_debugLogInitDone Then Exit Sub
    g_debugLogInitDone = True
    g_debugLogEnabled = False
    g_debugLogFailed = False
    g_debugLogPath = ""
    g_debugLogFileNo = 0

    Dim logFolder As String
    logFolder = DEBUG_LOG_FOLDER

    If Not EnsureFolderExistsSafe(logFolder) Then
        Dim tempFolder As String
        tempFolder = Environ$("TEMP")
        If Len(Trim$(tempFolder)) > 0 Then
            logFolder = tempFolder
        End If
    End If

    If Len(Trim$(logFolder)) = 0 Then
        Debug.Print TimeStamp & " WARN  | FILE_LOGGER | no usable log folder; file logging disabled"
        g_debugLogFailed = True
        Exit Sub
    End If

    g_debugLogPath = CombinePathSafe(logFolder, DEBUG_LOG_PREFIX & FileTimestamp() & ".log")
    g_debugLogFileNo = FreeFile

    Open g_debugLogPath For Append As #g_debugLogFileNo
    g_debugLogEnabled = True

    LogDivider "LOG HEADER"
    LogKeyValue "HEADER", "macroName", "AUTO_COPE_DETAIL_GENERATO"
    LogKeyValue "HEADER", "macroRevision", MACRO_REVISION
    LogKeyValue "HEADER", "macroTitle", MACRO_TITLE
    LogKeyValue "HEADER", "runDateTime", TimeStamp()
    LogKeyValue "HEADER", "solidWorksVersion", GetSolidWorksVersionSafe()
    LogKeyValue "HEADER", "activeDocumentTitle", ModelTitleSafe(g_swDrwModel)
    LogKeyValue "HEADER", "activeDocumentFullPath", ModelPathSafe(g_swDrwModel)
    If Not g_swDrwModel Is Nothing Then
        LogKeyValue "HEADER", "activeDocumentType", DocumentTypeNameSafe(g_swDrwModel.GetType)
    Else
        LogKeyValue "HEADER", "activeDocumentType", "None"
    End If
    LogKeyValue "HEADER", "activeConfiguration", ActiveConfigurationNameForLog(g_swDrwModel)
    LogKeyValue "HEADER", "requiredLogFolder", DEBUG_LOG_FOLDER
    LogKeyValue "HEADER", "actualLogFilePath", g_debugLogPath
    LogDivider "LOG HEADER END"
    Exit Sub

EH:
    Debug.Print TimeStamp & " WARN  | FILE_LOGGER | InitDebugLog failed | " & Err.Number & " | " & Err.Description
    On Error Resume Next
    If g_debugLogFileNo <> 0 Then Close #g_debugLogFileNo
    On Error GoTo 0
    g_debugLogEnabled = False
    g_debugLogFailed = True
End Sub

Private Sub CloseDebugLog()
    On Error Resume Next

    If g_debugLogEnabled Then
        WriteDebugLogLine "INFO", "FILE_LOGGER | closing log | path=" & g_debugLogPath
        Close #g_debugLogFileNo
    End If

    g_debugLogEnabled = False
    g_debugLogFileNo = 0
    On Error GoTo 0
End Sub

Private Sub LogLine(ByVal s As String)
    WriteDebugLogLine "INFO", s
End Sub

Private Sub LogInfo(ByVal msg As String)
    WriteDebugLogLine "INFO", msg
End Sub

Private Sub LogWarn(ByVal msg As String)
    WriteDebugLogLine "WARN", msg
End Sub

Private Sub LogError(ByVal msg As String)
    WriteDebugLogLine "ERROR", msg
End Sub

Private Sub LogStage(ByVal stageName As String, ByVal msg As String)
    WriteDebugLogLine "STAGE", SafeLogText(stageName) & " | " & SafeLogText(msg)
End Sub

Private Sub LogKeyValue(ByVal scopeName As String, ByVal keyName As String, ByVal valueText As String)
    WriteDebugLogLine "INFO", "KV | " & SafeLogText(scopeName) & " | " & SafeLogText(keyName) & "=" & SafeLogText(valueText)
End Sub

Private Sub LogDivider(Optional ByVal title As String = "")
    Dim msg As String
    If Len(Trim$(title)) > 0 Then
        msg = String$(24, "=") & " " & SafeLogText(title) & " " & String$(24, "=")
    Else
        msg = String$(72, "=")
    End If
    WriteDebugLogLine "INFO", msg
End Sub

Private Sub WriteDebugLogLine(ByVal levelName As String, ByVal msg As String)
    On Error Resume Next

    Dim lineText As String
    lineText = TimeStamp() & " " & PadLevelName(levelName) & " | " & SafeLogText(msg)

    Debug.Print lineText

    If g_debugLogEnabled Then
        Print #g_debugLogFileNo, lineText
        If Err.Number <> 0 Then
            Debug.Print TimeStamp & " WARN  | FILE_LOGGER | write failed; disabling file logging | " & Err.Number & " | " & Err.Description
            Err.Clear
            Close #g_debugLogFileNo
            g_debugLogEnabled = False
            g_debugLogFailed = True
        End If
    End If

    On Error GoTo 0
End Sub

Private Function EnsureFolderExistsSafe(ByVal folderPath As String) As Boolean
    On Error GoTo EH

    EnsureFolderExistsSafe = False
    folderPath = Trim$(folderPath)
    If Len(folderPath) = 0 Then Exit Function

    If Dir$(folderPath, vbDirectory) <> "" Then
        EnsureFolderExistsSafe = True
        Exit Function
    End If

    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    If fso Is Nothing Then Exit Function

    If Not fso.FolderExists(folderPath) Then
        EnsureFolderExistsFso fso, folderPath
    End If

    EnsureFolderExistsSafe = fso.FolderExists(folderPath)
    Exit Function

EH:
    Debug.Print TimeStamp & " WARN  | FILE_LOGGER | failed to create log folder | folder=" & folderPath & " | " & Err.Number & " | " & Err.Description
    EnsureFolderExistsSafe = False
End Function

Private Sub EnsureFolderExistsFso(ByVal fso As Object, ByVal folderPath As String)
    On Error GoTo EH

    If fso Is Nothing Then Exit Sub
    If Len(Trim$(folderPath)) = 0 Then Exit Sub
    If fso.FolderExists(folderPath) Then Exit Sub

    Dim parentPath As String
    parentPath = fso.GetParentFolderName(folderPath)
    If Len(parentPath) > 0 Then
        If Not fso.FolderExists(parentPath) Then
            EnsureFolderExistsFso fso, parentPath
        End If
    End If

    If Not fso.FolderExists(folderPath) Then
        fso.CreateFolder folderPath
    End If
    Exit Sub

EH:
    Debug.Print TimeStamp & " WARN  | FILE_LOGGER | EnsureFolderExistsFso failed | folder=" & folderPath & " | " & Err.Number & " | " & Err.Description
End Sub

Private Function CombinePathSafe(ByVal folderPath As String, ByVal fileName As String) As String
    folderPath = Trim$(folderPath)
    fileName = SanitizeFileName(fileName)

    If Right$(folderPath, 1) = "\" Then
        CombinePathSafe = folderPath & fileName
    Else
        CombinePathSafe = folderPath & "\" & fileName
    End If
End Function

Private Function SanitizeFileName(ByVal fileName As String) As String
    Dim badChars As Variant
    badChars = Array("<", ">", ":", """", "/", "\", "|", "?", "*")

    Dim i As Long
    For i = LBound(badChars) To UBound(badChars)
        fileName = Replace$(fileName, CStr(badChars(i)), "_")
    Next i

    SanitizeFileName = fileName
End Function

Private Function SafeLogText(ByVal msg As String) As String
    msg = Replace$(msg, vbCrLf, " ")
    msg = Replace$(msg, vbCr, " ")
    msg = Replace$(msg, vbLf, " ")
    SafeLogText = msg
End Function

Private Function PadLevelName(ByVal levelName As String) As String
    levelName = UCase$(Trim$(levelName))
    If Len(levelName) = 0 Then levelName = "INFO"

    Do While Len(levelName) < 5
        levelName = levelName & " "
    Loop

    PadLevelName = Left$(levelName, 5)
End Function

Private Function FileTimestamp() As String
    FileTimestamp = Format$(Now, "yyyymmdd_hhnnss")
End Function

Private Function GetSolidWorksVersionSafe() As String
    On Error Resume Next
    GetSolidWorksVersionSafe = ""
    If Not g_swApp Is Nothing Then GetSolidWorksVersionSafe = SafeStr(g_swApp.RevisionNumber)
    If Len(GetSolidWorksVersionSafe) = 0 Then GetSolidWorksVersionSafe = "Unavailable"
    If Err.Number <> 0 Then
        Err.Clear
        GetSolidWorksVersionSafe = "Unavailable"
    End If
    On Error GoTo 0
End Function

Private Function ModelTitleSafe(ByVal swModel As SldWorks.ModelDoc2) As String
    On Error Resume Next
    ModelTitleSafe = ""
    If swModel Is Nothing Then
        ModelTitleSafe = "None"
    Else
        ModelTitleSafe = SafeStr(swModel.GetTitle)
    End If
    If Len(ModelTitleSafe) = 0 Then ModelTitleSafe = "Untitled"
    If Err.Number <> 0 Then
        Err.Clear
        ModelTitleSafe = "Unavailable"
    End If
    On Error GoTo 0
End Function

Private Function ModelPathSafe(ByVal swModel As SldWorks.ModelDoc2) As String
    On Error Resume Next
    ModelPathSafe = ""
    If Not swModel Is Nothing Then ModelPathSafe = SafeStr(swModel.GetPathName)
    If Err.Number <> 0 Then
        Err.Clear
        ModelPathSafe = ""
    End If
    If Len(ModelPathSafe) = 0 Then ModelPathSafe = "(unsaved)"
    On Error GoTo 0
End Function

Private Function IsModelSavedSafe(ByVal swModel As SldWorks.ModelDoc2) As Boolean
    On Error Resume Next
    IsModelSavedSafe = False
    If swModel Is Nothing Then Exit Function
    IsModelSavedSafe = (Len(Trim$(SafeStr(swModel.GetPathName))) > 0)
    If Err.Number <> 0 Then
        Err.Clear
        IsModelSavedSafe = False
    End If
    On Error GoTo 0
End Function

Private Function ActiveConfigurationNameForLog(ByVal swModel As SldWorks.ModelDoc2) As String
    On Error Resume Next
    ActiveConfigurationNameForLog = ""
    If swModel Is Nothing Then
        ActiveConfigurationNameForLog = "None"
        Exit Function
    End If

    If swModel.GetType = swDocumentTypes_e.swDocDRAWING Then
        ActiveConfigurationNameForLog = "(drawing document)"
    Else
        ActiveConfigurationNameForLog = GetActiveConfigurationNameSafe(swModel)
    End If

    If Len(ActiveConfigurationNameForLog) = 0 Then ActiveConfigurationNameForLog = "Unavailable"
    If Err.Number <> 0 Then
        Err.Clear
        ActiveConfigurationNameForLog = "Unavailable"
    End If
    On Error GoTo 0
End Function

Private Function DocumentTypeNameSafe(ByVal docType As Long) As String
    Select Case docType
        Case swDocumentTypes_e.swDocPART
            DocumentTypeNameSafe = "PART"
        Case swDocumentTypes_e.swDocASSEMBLY
            DocumentTypeNameSafe = "ASSEMBLY"
        Case swDocumentTypes_e.swDocDRAWING
            DocumentTypeNameSafe = "DRAWING"
        Case Else
            DocumentTypeNameSafe = "UNKNOWN(" & CStr(docType) & ")"
    End Select
End Function



'====================================================================================
' PLATE / SHEET FACE-CORNER ORIENTATION HELPERS (V8.11)
'====================================================================================

Private Function HybridCandidateTrustAdjustment(ByVal sourceName As String, _
                                                ByVal item As Object, _
                                                ByVal rotateDetail As String, _
                                                ByVal finalValidationDetail As String) As Double
    On Error GoTo EH

    HybridCandidateTrustAdjustment = 0#
    If UCase$(Trim$(sourceName)) <> "CUSTOM_VIEW" Then Exit Function
    If item Is Nothing Then Exit Function

    If UCase$(SafeStr(item("BodySubtype"))) = CAT_SUBTYPE_ANGLE Then
        If InStr(1, rotateDetail, "method=model-edge-family", vbTextCompare) > 0 And _
           InStr(1, rotateDetail, "usedFallback=True", vbTextCompare) = 0 Then
            HybridCandidateTrustAdjustment = 0#
        Else
            HybridCandidateTrustAdjustment = -PLATE_CUSTOM_VIEW_STRICT_PENALTY
        End If
        Exit Function
    End If

    If IsPlateLikeOrientationItem(item) Then
        If InStr(1, rotateDetail, "method=plate-face-corner", vbTextCompare) > 0 Then
            HybridCandidateTrustAdjustment = 0#
        Else
            HybridCandidateTrustAdjustment = -PLATE_CUSTOM_VIEW_STRICT_PENALTY
        End If
        Exit Function
    End If

    If InStr(1, finalValidationDetail, "method=projected-category", vbTextCompare) > 0 Then
        HybridCandidateTrustAdjustment = -PLATE_CUSTOM_VIEW_PENALTY
    End If
    Exit Function

EH:
    HybridCandidateTrustAdjustment = 0#
End Function

Private Function IsPlateLikeOrientationItem(ByVal item As Object) As Boolean
    On Error GoTo EH

    IsPlateLikeOrientationItem = False
    If item Is Nothing Then Exit Function

    Dim catName As String
    Dim subtypeName As String
    Dim thinRatio As Double
    Dim dx As Double, dy As Double, dz As Double
    Dim thick As Double
    Dim spanA As Double, spanB As Double

    catName = UCase$(SafeStr(item("BodyCategory")))
    subtypeName = UCase$(SafeStr(item("BodySubtype")))

    If catName = UCase$(CAT_NAME_SHEET_PLATE) Then
        IsPlateLikeOrientationItem = True
        Exit Function
    End If

    If subtypeName = CAT_SUBTYPE_PLATE Or subtypeName = CAT_SUBTYPE_GUSSET Then
        IsPlateLikeOrientationItem = True
        Exit Function
    End If

    dx = SafeCDbl(item("dx"))
    dy = SafeCDbl(item("dy"))
    dz = SafeCDbl(item("dz"))

    thick = dx
    If dy < thick Then thick = dy
    If dz < thick Then thick = dz

    spanA = dx
    spanB = dy
    If spanA < spanB Then
        spanA = dy
        spanB = dx
    End If
    If dz > spanA Then
        spanB = spanA
        spanA = dz
    ElseIf dz > spanB Then
        spanB = dz
    End If

    If spanA > 0# Then
        thinRatio = thick / spanA
        If thinRatio <= CAT_PLATE_THIN_RATIO_MAX Then
            IsPlateLikeOrientationItem = True
            Exit Function
        End If
    End If

    Exit Function

EH:
    IsPlateLikeOrientationItem = False
End Function

Private Function TryGetPlateFabricationFrameFromBody(ByVal item As Object, _
                                                     ByRef primaryX As Double, _
                                                     ByRef primaryY As Double, _
                                                     ByRef primaryZ As Double, _
                                                     ByRef secondaryX As Double, _
                                                     ByRef secondaryY As Double, _
                                                     ByRef secondaryZ As Double, _
                                                     ByRef hasSecondary As Boolean, _
                                                     ByRef frameReason As String) As Boolean
    On Error GoTo EH

    TryGetPlateFabricationFrameFromBody = False
    primaryX = 0#: primaryY = 0#: primaryZ = 0#
    secondaryX = 0#: secondaryY = 0#: secondaryZ = 0#
    hasSecondary = False
    frameReason = ""

    If item Is Nothing Then
        frameReason = "item is Nothing"
        Exit Function
    End If

    Dim swBody As SldWorks.Body2
    Set swBody = Nothing
    On Error Resume Next
    Set swBody = item("RepBody_Model")
    On Error GoTo EH

    If swBody Is Nothing Then
        frameReason = "RepBody_Model is Nothing"
        Exit Function
    End If

    Dim swFace As SldWorks.Face2
    Set swFace = PlateGetLargestPlanarFaceOnBody(swBody)

    If swFace Is Nothing Then
        frameReason = "largest planar face not found"
        Exit Function
    End If

    Dim faceNormal(0 To 2) As Double
    If Not PlateTryGetStableFaceNormal(swFace, faceNormal) Then
        frameReason = "stable face normal unavailable"
        Exit Function
    End If

    If Not PlateFindHorizontalDirectionFromFace(swFace, faceNormal, primaryX, primaryY, primaryZ, frameReason) Then
        frameReason = "horizontal direction unavailable | " & frameReason
        Exit Function
    End If

    secondaryX = faceNormal(0)
    secondaryY = faceNormal(1)
    secondaryZ = faceNormal(2)

    Dim inPlaneYx As Double, inPlaneYy As Double, inPlaneYz As Double
    Cross3 secondaryX, secondaryY, secondaryZ, primaryX, primaryY, primaryZ, inPlaneYx, inPlaneYy, inPlaneYz
    If Not NormalizeVector3(inPlaneYx, inPlaneYy, inPlaneYz) Then
        Cross3 primaryX, primaryY, primaryZ, secondaryX, secondaryY, secondaryZ, inPlaneYx, inPlaneYy, inPlaneYz
        If Not NormalizeVector3(inPlaneYx, inPlaneYy, inPlaneYz) Then
            frameReason = "failed to derive in-plane secondary axis | " & frameReason
            Exit Function
        End If
    End If

    secondaryX = inPlaneYx
    secondaryY = inPlaneYy
    secondaryZ = inPlaneYz
    hasSecondary = True

    frameReason = frameReason & _
                  " | primary=(" & Fmt(primaryX) & "," & Fmt(primaryY) & "," & Fmt(primaryZ) & ")" & _
                  " | secondary=(" & Fmt(secondaryX) & "," & Fmt(secondaryY) & "," & Fmt(secondaryZ) & ")" & _
                  " | faceNormal=(" & Fmt(faceNormal(0)) & "," & Fmt(faceNormal(1)) & "," & Fmt(faceNormal(2)) & ")"

    TryGetPlateFabricationFrameFromBody = True
    Exit Function

EH:
    frameReason = "TryGetPlateFabricationFrameFromBody exception | " & Err.Number & " | " & Err.Description
    LogWarn frameReason & " | itemNo=" & SafeStr(item("ItemNo"))
    TryGetPlateFabricationFrameFromBody = False
End Function

Private Function PlateGetLargestPlanarFaceOnBody(ByVal swBody As SldWorks.Body2) As SldWorks.Face2
    On Error GoTo EH

    Set PlateGetLargestPlanarFaceOnBody = Nothing
    If swBody Is Nothing Then Exit Function

    Dim vFaces As Variant
    vFaces = swBody.GetFaces
    If IsEmpty(vFaces) Then Exit Function
    If Not IsArray(vFaces) Then Exit Function

    Dim bestArea As Double
    bestArea = -1#

    Dim i As Long
    For i = LBound(vFaces) To UBound(vFaces)
        Dim swFace As SldWorks.Face2
        Set swFace = Nothing
        On Error Resume Next
        Set swFace = vFaces(i)
        On Error GoTo EH

        If Not swFace Is Nothing Then
            Dim swSurf As SldWorks.Surface
            Set swSurf = swFace.GetSurface
            If Not swSurf Is Nothing Then
                If swSurf.isPlane Then
                    Dim areaVal As Double
                    areaVal = 0#
                    On Error Resume Next
                    areaVal = swFace.GetArea
                    On Error GoTo EH

                    If areaVal > bestArea Then
                        bestArea = areaVal
                        Set PlateGetLargestPlanarFaceOnBody = swFace
                    End If
                End If
            End If
        End If
    Next i

    Exit Function

EH:
    Set PlateGetLargestPlanarFaceOnBody = Nothing
End Function

Private Function PlateTryGetStableFaceNormal(ByVal swFace As SldWorks.Face2, ByRef n() As Double) As Boolean
    On Error GoTo EH

    PlateTryGetStableFaceNormal = False
    If swFace Is Nothing Then Exit Function

    If PlateTryGetNormalFromFaceVertices(swFace, n) Then
        If NormalizeVector3(n(0), n(1), n(2)) Then
            CanonicalizeVectorPositive n(0), n(1), n(2)
            PlateTryGetStableFaceNormal = True
            Exit Function
        End If
    End If

    Dim vNorm As Variant
    On Error Resume Next
    vNorm = swFace.Normal
    On Error GoTo EH

    If PlateVariantHas3Numbers(vNorm) Then
        n(0) = CDbl(vNorm(LBound(vNorm) + 0))
        n(1) = CDbl(vNorm(LBound(vNorm) + 1))
        n(2) = CDbl(vNorm(LBound(vNorm) + 2))
        If NormalizeVector3(n(0), n(1), n(2)) Then
            CanonicalizeVectorPositive n(0), n(1), n(2)
            PlateTryGetStableFaceNormal = True
            Exit Function
        End If
    End If

    Dim swSurf As SldWorks.Surface
    Set swSurf = swFace.GetSurface
    If Not swSurf Is Nothing Then
        Dim planeProps As Variant
        On Error Resume Next
        planeProps = swSurf.PlaneParams
        On Error GoTo EH

        If PlateVariantHasAtLeast6Numbers(planeProps) Then
            n(0) = CDbl(planeProps(LBound(planeProps) + 3))
            n(1) = CDbl(planeProps(LBound(planeProps) + 4))
            n(2) = CDbl(planeProps(LBound(planeProps) + 5))
            If NormalizeVector3(n(0), n(1), n(2)) Then
                CanonicalizeVectorPositive n(0), n(1), n(2)
                PlateTryGetStableFaceNormal = True
                Exit Function
            End If
        End If
    End If

    Exit Function

EH:
    PlateTryGetStableFaceNormal = False
End Function

Private Function PlateTryGetNormalFromFaceVertices(ByVal swFace As SldWorks.Face2, ByRef n() As Double) As Boolean
    On Error GoTo EH

    PlateTryGetNormalFromFaceVertices = False
    If swFace Is Nothing Then Exit Function

    Dim vEdges As Variant
    vEdges = swFace.GetEdges
    If IsEmpty(vEdges) Then Exit Function
    If Not IsArray(vEdges) Then Exit Function

    Dim pts() As Double
    Dim ptCount As Long
    ptCount = 0

    Dim i As Long
    For i = LBound(vEdges) To UBound(vEdges)
        Dim swEdge As SldWorks.Edge
        Set swEdge = Nothing
        On Error Resume Next
        Set swEdge = vEdges(i)
        On Error GoTo EH

        If Not swEdge Is Nothing Then
            Dim p0(0 To 2) As Double
            Dim p1(0 To 2) As Double
            If PlateGetEdgeEndPoints(swEdge, p0, p1) Then
                PlateAddUniquePoint3 pts, ptCount, p0
                PlateAddUniquePoint3 pts, ptCount, p1
            End If
        End If
    Next i

    If ptCount < 3 Then Exit Function

    Dim i0 As Long, i1 As Long, i2 As Long
    For i0 = 0 To ptCount - 3
        For i1 = i0 + 1 To ptCount - 2
            For i2 = i1 + 1 To ptCount - 1
                Dim ax As Double, ay As Double, az As Double
                Dim bx As Double, by As Double, bz As Double
                ax = pts(0, i1) - pts(0, i0)
                ay = pts(1, i1) - pts(1, i0)
                az = pts(2, i1) - pts(2, i0)

                bx = pts(0, i2) - pts(0, i0)
                by = pts(1, i2) - pts(1, i0)
                bz = pts(2, i2) - pts(2, i0)

                Cross3 ax, ay, az, bx, by, bz, n(0), n(1), n(2)
                If Sqr((n(0) * n(0)) + (n(1) * n(1)) + (n(2) * n(2))) > PLATE_FACE_NORMAL_EPS Then
                    PlateTryGetNormalFromFaceVertices = True
                    Exit Function
                End If
            Next i2
        Next i1
    Next i0

    Exit Function

EH:
    PlateTryGetNormalFromFaceVertices = False
End Function

Private Sub PlateAddUniquePoint3(ByRef pts() As Double, ByRef ptCount As Long, ByRef p() As Double)
    Dim i As Long

    For i = 0 To ptCount - 1
        If Abs(pts(0, i) - p(0)) <= PLATE_POINT_TOL And _
           Abs(pts(1, i) - p(1)) <= PLATE_POINT_TOL And _
           Abs(pts(2, i) - p(2)) <= PLATE_POINT_TOL Then
            Exit Sub
        End If
    Next i

    If ptCount = 0 Then
        ReDim pts(0 To 2, 0 To 0)
    Else
        ReDim Preserve pts(0 To 2, 0 To ptCount)
    End If

    pts(0, ptCount) = p(0)
    pts(1, ptCount) = p(1)
    pts(2, ptCount) = p(2)
    ptCount = ptCount + 1
End Sub

Private Function PlateVariantHas3Numbers(ByVal v As Variant) As Boolean
    On Error GoTo EH

    PlateVariantHas3Numbers = False
    If IsEmpty(v) Then Exit Function
    If Not IsArray(v) Then Exit Function

    Dim lb As Long, ub As Long
    lb = LBound(v)
    ub = UBound(v)
    If (ub - lb + 1) < 3 Then Exit Function

    Dim tmp As Double
    tmp = CDbl(v(lb + 0))
    tmp = CDbl(v(lb + 1))
    tmp = CDbl(v(lb + 2))
    PlateVariantHas3Numbers = True
    Exit Function

EH:
    PlateVariantHas3Numbers = False
End Function

Private Function PlateVariantHasAtLeast6Numbers(ByVal v As Variant) As Boolean
    On Error GoTo EH

    PlateVariantHasAtLeast6Numbers = False
    If IsEmpty(v) Then Exit Function
    If Not IsArray(v) Then Exit Function

    Dim lb As Long, ub As Long
    lb = LBound(v)
    ub = UBound(v)
    If (ub - lb + 1) < 6 Then Exit Function

    Dim i As Long
    Dim tmp As Double
    For i = 0 To 5
        tmp = CDbl(v(lb + i))
    Next i

    PlateVariantHasAtLeast6Numbers = True
    Exit Function

EH:
    PlateVariantHasAtLeast6Numbers = False
End Function

Private Function PlateFindHorizontalDirectionFromFace(ByVal swFace As SldWorks.Face2, _
                                                      ByRef faceNormal() As Double, _
                                                      ByRef outX As Double, _
                                                      ByRef outY As Double, _
                                                      ByRef outZ As Double, _
                                                      ByRef detailText As String) As Boolean
    On Error GoTo EH

    PlateFindHorizontalDirectionFromFace = False
    outX = 0#: outY = 0#: outZ = 0#
    detailText = ""

    If swFace Is Nothing Then
        detailText = "face is Nothing"
        Exit Function
    End If

    Dim sharedFound As Boolean
    Dim virtualFound As Boolean
    Dim sharedPrimaryLen As Double, sharedSecondaryLen As Double
    Dim sharedX(0 To 2) As Double, sharedY(0 To 2) As Double, sharedCorner(0 To 2) As Double
    Dim virtualPrimaryLen As Double, virtualSecondaryLen As Double
    Dim virtualX(0 To 2) As Double, virtualY(0 To 2) As Double, virtualCorner(0 To 2) As Double

    sharedFound = PlateGetBestSharedCornerOrientationCandidate(swFace, faceNormal, sharedPrimaryLen, sharedSecondaryLen, sharedX, sharedY, sharedCorner)
    virtualFound = PlateGetBestVirtualCornerOrientationCandidate(swFace, faceNormal, virtualPrimaryLen, virtualSecondaryLen, virtualX, virtualY, virtualCorner)

    If sharedFound Then
        If virtualFound Then
            If PlateIsPairLengthBetter(virtualPrimaryLen, virtualSecondaryLen, sharedPrimaryLen, sharedSecondaryLen) Then
                outX = virtualX(0): outY = virtualX(1): outZ = virtualX(2)
                detailText = "mode=virtual-corner override | primaryLen=" & Fmt(virtualPrimaryLen) & _
                             " | secondaryLen=" & Fmt(virtualSecondaryLen)
            Else
                outX = sharedX(0): outY = sharedX(1): outZ = sharedX(2)
                detailText = "mode=shared-corner | primaryLen=" & Fmt(sharedPrimaryLen) & _
                             " | secondaryLen=" & Fmt(sharedSecondaryLen)
            End If
            CanonicalizeVectorPositive outX, outY, outZ
            PlateFindHorizontalDirectionFromFace = NormalizeVector3(outX, outY, outZ)
            Exit Function
        Else
            outX = sharedX(0): outY = sharedX(1): outZ = sharedX(2)
            CanonicalizeVectorPositive outX, outY, outZ
            detailText = "mode=shared-corner | primaryLen=" & Fmt(sharedPrimaryLen) & _
                         " | secondaryLen=" & Fmt(sharedSecondaryLen)
            PlateFindHorizontalDirectionFromFace = NormalizeVector3(outX, outY, outZ)
            Exit Function
        End If
    End If

    If virtualFound Then
        outX = virtualX(0): outY = virtualX(1): outZ = virtualX(2)
        CanonicalizeVectorPositive outX, outY, outZ
        detailText = "mode=virtual-corner fallback | primaryLen=" & Fmt(virtualPrimaryLen) & _
                     " | secondaryLen=" & Fmt(virtualSecondaryLen)
        PlateFindHorizontalDirectionFromFace = NormalizeVector3(outX, outY, outZ)
        Exit Function
    End If

    Dim vEdges As Variant
    vEdges = swFace.GetEdges

    Dim bestLen As Double
    bestLen = -1#

    Dim bestVec(0 To 2) As Double
    Dim foundLine As Boolean
    foundLine = False

    If Not IsEmpty(vEdges) Then
        If IsArray(vEdges) Then
            Dim i As Long
            For i = LBound(vEdges) To UBound(vEdges)
                Dim swEdge As SldWorks.Edge
                Set swEdge = Nothing
                On Error Resume Next
                Set swEdge = vEdges(i)
                On Error GoTo EH

                If Not swEdge Is Nothing Then
                    If PlateEdgeIsLinear(swEdge) Then
                        Dim p0(0 To 2) As Double, p1(0 To 2) As Double
                        If PlateGetEdgeEndPoints(swEdge, p0, p1) Then
                            Dim vx As Double, vy As Double, vz As Double
                            vx = p1(0) - p0(0)
                            vy = p1(1) - p0(1)
                            vz = p1(2) - p0(2)

                            PlateProjectVectorOntoPlane vx, vy, vz, faceNormal(0), faceNormal(1), faceNormal(2)
                            Dim L As Double
                            L = Sqr((vx * vx) + (vy * vy) + (vz * vz))

                            If L > PLATE_FACE_NORMAL_EPS Then
                                vx = vx / L: vy = vy / L: vz = vz / L
                                CanonicalizeVectorPositive vx, vy, vz

                                Dim trueLen As Double
                                trueLen = PlateDistance3(p0, p1)

                                If (trueLen > bestLen + PLATE_FACE_NORMAL_EPS) Or _
                                   (Abs(trueLen - bestLen) <= PLATE_FACE_NORMAL_EPS And PlateCompareVectorLex(vx, vy, vz, bestVec(0), bestVec(1), bestVec(2)) > 0) Then
                                    bestLen = trueLen
                                    bestVec(0) = vx: bestVec(1) = vy: bestVec(2) = vz
                                    foundLine = True
                                End If
                            End If
                        End If
                    End If
                End If
            Next i
        End If
    End If

    If foundLine Then
        outX = bestVec(0): outY = bestVec(1): outZ = bestVec(2)
        detailText = "mode=longest-linear-edge fallback | length=" & Fmt(bestLen)
        PlateFindHorizontalDirectionFromFace = True
        Exit Function
    End If

    PlateChooseFallbackXAxis faceNormal(0), faceNormal(1), faceNormal(2), outX, outY, outZ
    detailText = "mode=world-axis fallback"
    PlateFindHorizontalDirectionFromFace = NormalizeVector3(outX, outY, outZ)
    Exit Function

EH:
    detailText = "PlateFindHorizontalDirectionFromFace exception | " & Err.Number & " | " & Err.Description
    PlateFindHorizontalDirectionFromFace = False
End Function

Private Function PlateGetBestSharedCornerOrientationCandidate(ByVal swFace As SldWorks.Face2, _
                                                              ByRef faceNormal() As Double, _
                                                              ByRef bestPrimaryLen As Double, _
                                                              ByRef bestSecondaryLen As Double, _
                                                              ByRef bestX() As Double, _
                                                              ByRef bestY() As Double, _
                                                              ByRef bestCorner() As Double) As Boolean
    On Error GoTo EH

    PlateGetBestSharedCornerOrientationCandidate = False
    If swFace Is Nothing Then Exit Function

    Dim vEdges As Variant
    vEdges = swFace.GetEdges
    If IsEmpty(vEdges) Then Exit Function
    If Not IsArray(vEdges) Then Exit Function

    bestPrimaryLen = -1#
    bestSecondaryLen = -1#

    Dim foundPair As Boolean
    foundPair = False

    Dim i As Long, j As Long
    For i = LBound(vEdges) To UBound(vEdges)
        Dim ed1 As SldWorks.Edge
        Set ed1 = Nothing
        On Error Resume Next
        Set ed1 = vEdges(i)
        On Error GoTo EH

        If Not ed1 Is Nothing Then
            If PlateEdgeIsLinear(ed1) Then
                Dim e1p0(0 To 2) As Double, e1p1(0 To 2) As Double
                If PlateGetEdgeEndPoints(ed1, e1p0, e1p1) Then
                    For j = i + 1 To UBound(vEdges)
                        Dim ed2 As SldWorks.Edge
                        Set ed2 = Nothing
                        On Error Resume Next
                        Set ed2 = vEdges(j)
                        On Error GoTo EH

                        If Not ed2 Is Nothing Then
                            If PlateEdgeIsLinear(ed2) Then
                                Dim e2p0(0 To 2) As Double, e2p1(0 To 2) As Double
                                If PlateGetEdgeEndPoints(ed2, e2p0, e2p1) Then
                                    Dim corner(0 To 2) As Double
                                    Dim far1(0 To 2) As Double
                                    Dim far2(0 To 2) As Double

                                    If PlateTryGetSharedCornerFromEdgeEndpoints(e1p0, e1p1, e2p0, e2p1, corner, far1, far2) Then
                                        Dim vx1 As Double, vy1 As Double, vz1 As Double
                                        Dim vx2 As Double, vy2 As Double, vz2 As Double
                                        vx1 = far1(0) - corner(0): vy1 = far1(1) - corner(1): vz1 = far1(2) - corner(2)
                                        vx2 = far2(0) - corner(0): vy2 = far2(1) - corner(1): vz2 = far2(2) - corner(2)

                                        PlateProjectVectorOntoPlane vx1, vy1, vz1, faceNormal(0), faceNormal(1), faceNormal(2)
                                        PlateProjectVectorOntoPlane vx2, vy2, vz2, faceNormal(0), faceNormal(1), faceNormal(2)

                                        Dim len1 As Double, len2 As Double
                                        len1 = Sqr((vx1 * vx1) + (vy1 * vy1) + (vz1 * vz1))
                                        len2 = Sqr((vx2 * vx2) + (vy2 * vy2) + (vz2 * vz2))

                                        If len1 > PLATE_FACE_NORMAL_EPS And len2 > PLATE_FACE_NORMAL_EPS Then
                                            vx1 = vx1 / len1: vy1 = vy1 / len1: vz1 = vz1 / len1
                                            vx2 = vx2 / len2: vy2 = vy2 / len2: vz2 = vz2 / len2

                                            Dim dotVal As Double
                                            dotVal = Abs(Dot3(vx1, vy1, vz1, vx2, vy2, vz2))
                                            If dotVal <= PLATE_PERP_DOT_TOL Then
                                                Dim candPrimaryLen As Double, candSecondaryLen As Double
                                                Dim candX(0 To 2) As Double, candY(0 To 2) As Double

                                                If len1 >= len2 Then
                                                    candPrimaryLen = len1
                                                    candSecondaryLen = len2
                                                    candX(0) = vx1: candX(1) = vy1: candX(2) = vz1
                                                    candY(0) = vx2: candY(1) = vy2: candY(2) = vz2
                                                Else
                                                    candPrimaryLen = len2
                                                    candSecondaryLen = len1
                                                    candX(0) = vx2: candX(1) = vy2: candX(2) = vz2
                                                    candY(0) = vx1: candY(1) = vy1: candY(2) = vz1
                                                End If

                                                If (Not foundPair) Or PlateIsPairLengthBetter(candPrimaryLen, candSecondaryLen, bestPrimaryLen, bestSecondaryLen) Then
                                                    bestPrimaryLen = candPrimaryLen
                                                    bestSecondaryLen = candSecondaryLen
                                                    bestX(0) = candX(0): bestX(1) = candX(1): bestX(2) = candX(2)
                                                    bestY(0) = candY(0): bestY(1) = candY(1): bestY(2) = candY(2)
                                                    bestCorner(0) = corner(0): bestCorner(1) = corner(1): bestCorner(2) = corner(2)
                                                    foundPair = True
                                                End If
                                            End If
                                        End If
                                    End If
                                End If
                            End If
                        End If
                    Next j
                End If
            End If
        End If
    Next i

    PlateGetBestSharedCornerOrientationCandidate = foundPair
    Exit Function

EH:
    PlateGetBestSharedCornerOrientationCandidate = False
End Function

Private Function PlateGetBestVirtualCornerOrientationCandidate(ByVal swFace As SldWorks.Face2, _
                                                               ByRef faceNormal() As Double, _
                                                               ByRef bestPrimaryLen As Double, _
                                                               ByRef bestSecondaryLen As Double, _
                                                               ByRef bestX() As Double, _
                                                               ByRef bestY() As Double, _
                                                               ByRef bestCorner() As Double) As Boolean
    On Error GoTo EH

    PlateGetBestVirtualCornerOrientationCandidate = False
    If swFace Is Nothing Then Exit Function

    Dim vEdges As Variant
    vEdges = swFace.GetEdges
    If IsEmpty(vEdges) Then Exit Function
    If Not IsArray(vEdges) Then Exit Function

    bestPrimaryLen = -1#
    bestSecondaryLen = -1#

    Dim foundPair As Boolean
    foundPair = False

    Dim i As Long, j As Long
    For i = LBound(vEdges) To UBound(vEdges)
        Dim ed1 As SldWorks.Edge
        Set ed1 = Nothing
        On Error Resume Next
        Set ed1 = vEdges(i)
        On Error GoTo EH

        If Not ed1 Is Nothing Then
            If PlateEdgeIsLinear(ed1) Then
                Dim e1p0(0 To 2) As Double, e1p1(0 To 2) As Double
                If PlateGetEdgeEndPoints(ed1, e1p0, e1p1) Then
                    For j = i + 1 To UBound(vEdges)
                        Dim ed2 As SldWorks.Edge
                        Set ed2 = Nothing
                        On Error Resume Next
                        Set ed2 = vEdges(j)
                        On Error GoTo EH

                        If Not ed2 Is Nothing Then
                            If PlateEdgeIsLinear(ed2) Then
                                Dim e2p0(0 To 2) As Double, e2p1(0 To 2) As Double
                                If PlateGetEdgeEndPoints(ed2, e2p0, e2p1) Then
                                    Dim sharedCorner(0 To 2) As Double
                                    Dim sharedFar1(0 To 2) As Double
                                    Dim sharedFar2(0 To 2) As Double

                                    If Not PlateTryGetSharedCornerFromEdgeEndpoints(e1p0, e1p1, e2p0, e2p1, sharedCorner, sharedFar1, sharedFar2) Then
                                        Dim corner(0 To 2) As Double
                                        Dim far1(0 To 2) As Double
                                        Dim far2(0 To 2) As Double

                                        If PlateBuildVirtualCornerFromEdges(e1p0, e1p1, e2p0, e2p1, corner, far1, far2) Then
                                            Dim vx1 As Double, vy1 As Double, vz1 As Double
                                            Dim vx2 As Double, vy2 As Double, vz2 As Double
                                            vx1 = far1(0) - corner(0): vy1 = far1(1) - corner(1): vz1 = far1(2) - corner(2)
                                            vx2 = far2(0) - corner(0): vy2 = far2(1) - corner(1): vz2 = far2(2) - corner(2)

                                            PlateProjectVectorOntoPlane vx1, vy1, vz1, faceNormal(0), faceNormal(1), faceNormal(2)
                                            PlateProjectVectorOntoPlane vx2, vy2, vz2, faceNormal(0), faceNormal(1), faceNormal(2)

                                            Dim len1 As Double, len2 As Double
                                            len1 = Sqr((vx1 * vx1) + (vy1 * vy1) + (vz1 * vz1))
                                            len2 = Sqr((vx2 * vx2) + (vy2 * vy2) + (vz2 * vz2))

                                            If len1 > PLATE_FACE_NORMAL_EPS And len2 > PLATE_FACE_NORMAL_EPS Then
                                                vx1 = vx1 / len1: vy1 = vy1 / len1: vz1 = vz1 / len1
                                                vx2 = vx2 / len2: vy2 = vy2 / len2: vz2 = vz2 / len2

                                                Dim dotVal As Double
                                                dotVal = Abs(Dot3(vx1, vy1, vz1, vx2, vy2, vz2))
                                                If dotVal <= PLATE_PERP_DOT_TOL Then
                                                    Dim candPrimaryLen As Double, candSecondaryLen As Double
                                                    Dim candX(0 To 2) As Double, candY(0 To 2) As Double

                                                    If len1 >= len2 Then
                                                        candPrimaryLen = len1
                                                        candSecondaryLen = len2
                                                        candX(0) = vx1: candX(1) = vy1: candX(2) = vz1
                                                        candY(0) = vx2: candY(1) = vy2: candY(2) = vz2
                                                    Else
                                                        candPrimaryLen = len2
                                                        candSecondaryLen = len1
                                                        candX(0) = vx2: candX(1) = vy2: candX(2) = vz2
                                                        candY(0) = vx1: candY(1) = vy1: candY(2) = vz1
                                                    End If

                                                    If (Not foundPair) Or PlateIsPairLengthBetter(candPrimaryLen, candSecondaryLen, bestPrimaryLen, bestSecondaryLen) Then
                                                        bestPrimaryLen = candPrimaryLen
                                                        bestSecondaryLen = candSecondaryLen
                                                        bestX(0) = candX(0): bestX(1) = candX(1): bestX(2) = candX(2)
                                                        bestY(0) = candY(0): bestY(1) = candY(1): bestY(2) = candY(2)
                                                        bestCorner(0) = corner(0): bestCorner(1) = corner(1): bestCorner(2) = corner(2)
                                                        foundPair = True
                                                    End If
                                                End If
                                            End If
                                        End If
                                    End If
                                End If
                            End If
                        End If
                    Next j
                End If
            End If
        End If
    Next i

    PlateGetBestVirtualCornerOrientationCandidate = foundPair
    Exit Function

EH:
    PlateGetBestVirtualCornerOrientationCandidate = False
End Function

Private Function PlateBuildVirtualCornerFromEdges(ByRef e1p0() As Double, _
                                                  ByRef e1p1() As Double, _
                                                  ByRef e2p0() As Double, _
                                                  ByRef e2p1() As Double, _
                                                  ByRef corner() As Double, _
                                                  ByRef far1() As Double, _
                                                  ByRef far2() As Double) As Boolean
    On Error GoTo EH

    PlateBuildVirtualCornerFromEdges = False

    Dim bestD As Double
    Dim choice As Long
    bestD = 1E+100
    choice = 0

    Dim d As Double
    d = PlateDistance3(e1p0, e2p0)
    If d < bestD Then bestD = d: choice = 1
    d = PlateDistance3(e1p0, e2p1)
    If d < bestD Then bestD = d: choice = 2
    d = PlateDistance3(e1p1, e2p0)
    If d < bestD Then bestD = d: choice = 3
    d = PlateDistance3(e1p1, e2p1)
    If d < bestD Then bestD = d: choice = 4

    If choice = 0 Then Exit Function

    Select Case choice
        Case 1
            corner(0) = (e1p0(0) + e2p0(0)) / 2#
            corner(1) = (e1p0(1) + e2p0(1)) / 2#
            corner(2) = (e1p0(2) + e2p0(2)) / 2#
            far1(0) = e1p1(0): far1(1) = e1p1(1): far1(2) = e1p1(2)
            far2(0) = e2p1(0): far2(1) = e2p1(1): far2(2) = e2p1(2)
        Case 2
            corner(0) = (e1p0(0) + e2p1(0)) / 2#
            corner(1) = (e1p0(1) + e2p1(1)) / 2#
            corner(2) = (e1p0(2) + e2p1(2)) / 2#
            far1(0) = e1p1(0): far1(1) = e1p1(1): far1(2) = e1p1(2)
            far2(0) = e2p0(0): far2(1) = e2p0(1): far2(2) = e2p0(2)
        Case 3
            corner(0) = (e1p1(0) + e2p0(0)) / 2#
            corner(1) = (e1p1(1) + e2p0(1)) / 2#
            corner(2) = (e1p1(2) + e2p0(2)) / 2#
            far1(0) = e1p0(0): far1(1) = e1p0(1): far1(2) = e1p0(2)
            far2(0) = e2p1(0): far2(1) = e2p1(1): far2(2) = e2p1(2)
        Case 4
            corner(0) = (e1p1(0) + e2p1(0)) / 2#
            corner(1) = (e1p1(1) + e2p1(1)) / 2#
            corner(2) = (e1p1(2) + e2p1(2)) / 2#
            far1(0) = e1p0(0): far1(1) = e1p0(1): far1(2) = e1p0(2)
            far2(0) = e2p0(0): far2(1) = e2p0(1): far2(2) = e2p0(2)
    End Select

    PlateBuildVirtualCornerFromEdges = True
    Exit Function

EH:
    PlateBuildVirtualCornerFromEdges = False
End Function

Private Function PlateTryGetSharedCornerFromEdgeEndpoints(ByRef e1p0() As Double, _
                                                          ByRef e1p1() As Double, _
                                                          ByRef e2p0() As Double, _
                                                          ByRef e2p1() As Double, _
                                                          ByRef sharedCorner() As Double, _
                                                          ByRef far1() As Double, _
                                                          ByRef far2() As Double) As Boolean
    On Error GoTo EH

    PlateTryGetSharedCornerFromEdgeEndpoints = False

    If PlatePointsEqual3(e1p0, e2p0) Then
        sharedCorner(0) = e1p0(0): sharedCorner(1) = e1p0(1): sharedCorner(2) = e1p0(2)
        far1(0) = e1p1(0): far1(1) = e1p1(1): far1(2) = e1p1(2)
        far2(0) = e2p1(0): far2(1) = e2p1(1): far2(2) = e2p1(2)
        PlateTryGetSharedCornerFromEdgeEndpoints = True
        Exit Function
    End If

    If PlatePointsEqual3(e1p0, e2p1) Then
        sharedCorner(0) = e1p0(0): sharedCorner(1) = e1p0(1): sharedCorner(2) = e1p0(2)
        far1(0) = e1p1(0): far1(1) = e1p1(1): far1(2) = e1p1(2)
        far2(0) = e2p0(0): far2(1) = e2p0(1): far2(2) = e2p0(2)
        PlateTryGetSharedCornerFromEdgeEndpoints = True
        Exit Function
    End If

    If PlatePointsEqual3(e1p1, e2p0) Then
        sharedCorner(0) = e1p1(0): sharedCorner(1) = e1p1(1): sharedCorner(2) = e1p1(2)
        far1(0) = e1p0(0): far1(1) = e1p0(1): far1(2) = e1p0(2)
        far2(0) = e2p1(0): far2(1) = e2p1(1): far2(2) = e2p1(2)
        PlateTryGetSharedCornerFromEdgeEndpoints = True
        Exit Function
    End If

    If PlatePointsEqual3(e1p1, e2p1) Then
        sharedCorner(0) = e1p1(0): sharedCorner(1) = e1p1(1): sharedCorner(2) = e1p1(2)
        far1(0) = e1p0(0): far1(1) = e1p0(1): far1(2) = e1p0(2)
        far2(0) = e2p0(0): far2(1) = e2p0(1): far2(2) = e2p0(2)
        PlateTryGetSharedCornerFromEdgeEndpoints = True
        Exit Function
    End If

    Exit Function

EH:
    PlateTryGetSharedCornerFromEdgeEndpoints = False
End Function

Private Function PlatePointsEqual3(ByRef p1() As Double, ByRef p2() As Double) As Boolean
    PlatePointsEqual3 = (Abs(p1(0) - p2(0)) <= PLATE_POINT_TOL) And _
                        (Abs(p1(1) - p2(1)) <= PLATE_POINT_TOL) And _
                        (Abs(p1(2) - p2(2)) <= PLATE_POINT_TOL)
End Function

Private Function PlateIsPairLengthBetter(ByVal primaryA As Double, _
                                         ByVal secondaryA As Double, _
                                         ByVal primaryB As Double, _
                                         ByVal secondaryB As Double) As Boolean
    If primaryA > (primaryB + PLATE_FACE_NORMAL_EPS) Then
        PlateIsPairLengthBetter = True
    ElseIf Abs(primaryA - primaryB) <= PLATE_FACE_NORMAL_EPS Then
        PlateIsPairLengthBetter = (secondaryA > (secondaryB + PLATE_FACE_NORMAL_EPS))
    Else
        PlateIsPairLengthBetter = False
    End If
End Function

Private Function PlateEdgeIsLinear(ByVal swEdge As SldWorks.Edge) As Boolean
    On Error GoTo EH

    PlateEdgeIsLinear = False
    If swEdge Is Nothing Then Exit Function

    Dim swCurve As SldWorks.Curve
    Set swCurve = swEdge.GetCurve
    If swCurve Is Nothing Then Exit Function

    On Error Resume Next
    PlateEdgeIsLinear = swCurve.isLine
    On Error GoTo EH
    Exit Function

EH:
    PlateEdgeIsLinear = False
End Function

Private Function PlateGetEdgeEndPoints(ByVal swEdge As SldWorks.Edge, _
                                       ByRef p0() As Double, _
                                       ByRef p1() As Double) As Boolean
    On Error GoTo EH

    PlateGetEdgeEndPoints = False
    If swEdge Is Nothing Then Exit Function

    Dim swStartVtx As SldWorks.Vertex
    Dim swEndVtx As SldWorks.Vertex
    Set swStartVtx = swEdge.GetStartVertex
    Set swEndVtx = swEdge.GetEndVertex
    If swStartVtx Is Nothing Or swEndVtx Is Nothing Then Exit Function

    Dim v0 As Variant
    Dim v1 As Variant
    v0 = swStartVtx.GetPoint
    v1 = swEndVtx.GetPoint
    If Not IsArray(v0) Or Not IsArray(v1) Then Exit Function

    p0(0) = CDbl(v0(0)): p0(1) = CDbl(v0(1)): p0(2) = CDbl(v0(2))
    p1(0) = CDbl(v1(0)): p1(1) = CDbl(v1(1)): p1(2) = CDbl(v1(2))
    PlateGetEdgeEndPoints = True
    Exit Function

EH:
    PlateGetEdgeEndPoints = False
End Function

Private Sub PlateProjectVectorOntoPlane(ByRef vx As Double, _
                                        ByRef vy As Double, _
                                        ByRef vz As Double, _
                                        ByVal nx As Double, _
                                        ByVal ny As Double, _
                                        ByVal nz As Double)
    Dim d As Double
    d = Dot3(vx, vy, vz, nx, ny, nz)
    vx = vx - (d * nx)
    vy = vy - (d * ny)
    vz = vz - (d * nz)
End Sub

Private Sub PlateChooseFallbackXAxis(ByVal nx As Double, ByVal ny As Double, ByVal nz As Double, _
                                     ByRef outX As Double, ByRef outY As Double, ByRef outZ As Double)
    outX = 1#: outY = 0#: outZ = 0#
    PlateProjectVectorOntoPlane outX, outY, outZ, nx, ny, nz
    If Not NormalizeVector3(outX, outY, outZ) Then
        outX = 0#: outY = 1#: outZ = 0#
        PlateProjectVectorOntoPlane outX, outY, outZ, nx, ny, nz
        If Not NormalizeVector3(outX, outY, outZ) Then
            outX = 0#: outY = 0#: outZ = 1#
            PlateProjectVectorOntoPlane outX, outY, outZ, nx, ny, nz
            Call NormalizeVector3(outX, outY, outZ)
        End If
    End If
    CanonicalizeVectorPositive outX, outY, outZ
End Sub

Private Function PlateDistance3(ByRef p0() As Double, ByRef p1() As Double) As Double
    Dim dx As Double, dy As Double, dz As Double
    dx = p1(0) - p0(0)
    dy = p1(1) - p0(1)
    dz = p1(2) - p0(2)
    PlateDistance3 = Sqr((dx * dx) + (dy * dy) + (dz * dz))
End Function

Private Function PlateCompareVectorLex(ByVal ax As Double, ByVal ay As Double, ByVal az As Double, _
                                       ByVal bx As Double, ByVal by As Double, ByVal bz As Double) As Long
    If ax > (bx + PLATE_FACE_NORMAL_EPS) Then
        PlateCompareVectorLex = 1
    ElseIf ax < (bx - PLATE_FACE_NORMAL_EPS) Then
        PlateCompareVectorLex = -1
    ElseIf ay > (by + PLATE_FACE_NORMAL_EPS) Then
        PlateCompareVectorLex = 1
    ElseIf ay < (by - PLATE_FACE_NORMAL_EPS) Then
        PlateCompareVectorLex = -1
    ElseIf az > (bz + PLATE_FACE_NORMAL_EPS) Then
        PlateCompareVectorLex = 1
    ElseIf az < (bz - PLATE_FACE_NORMAL_EPS) Then
        PlateCompareVectorLex = -1
    Else
        PlateCompareVectorLex = 0
    End If
End Function

Private Function TimeStamp() As String
    TimeStamp = Format$(Now, "yyyy-mm-dd hh:nn:ss")
End Function
Procedure index · 253 declarations
File checksum

SHA-256: 719cabad617ccb2d6a3829f66f490aa0509fbbb50721506c4210256d1604414d