# ONEBOT 直线气缸目录【源数据生成器】(统一路线, 2026-08-22):
#   系列定义 meta JSON + 参数表 CSV + 简化几何 STEP → 目录源目录 onebot-data\catalog\
#   .opc 打包改由 C# 统一内核完成: 维护窗口【重建全量目录】或 CLI:
#     OnebotCatalog\bin\OnebotCatalog.exe --buildfull onebot-data\catalog sample\OnebotCatalog_<版本>.opc
# 数据来源: 《ONEBOT - 直线气缸 - 中文.pdf》订购码与规格参数表 (2026-08 提取)
# 说明:
#   - STEP 为简化圆柱组合占位模型 (尺寸随缸径/行程缩放), 待 NX 母模批量导出后替换同名文件即可
#   - 相同几何的型号组合共用同一 STEP (磁石/固定形式/杆材/后盖不改变简化几何)
#   - 行程取标准档位 10/20/30/50/75/100; 手册标注"可调行程 25~1000mm"属参数化二期
#   - 本脚本只负责【生成/刷新源数据】(几何+CSV+系列定义); 目录打包/发布走 C# 统一路线
# 用法: powershell -NoProfile -ExecutionPolicy Bypass -File onebot-data\tools\gen-onebot-source.ps1
param(
  [string]$OutDir = (Join-Path $PSScriptRoot '..\catalog')
)
$ProgressPreference = 'SilentlyContinue'
$inv = [System.Globalization.CultureInfo]::InvariantCulture

$stepDir  = Join-Path $OutDir 'step'
$csvDir   = Join-Path $OutDir 'csv'
$seriesDir = Join-Path $OutDir 'series'
$manDir   = Join-Path $OutDir 'manual'
New-Item -ItemType Directory -Force -Path $stepDir | Out-Null
New-Item -ItemType Directory -Force -Path $csvDir | Out-Null
New-Item -ItemType Directory -Force -Path $seriesDir | Out-Null

# ==================== 系列与分类定义 ====================
$SERIES = New-Object System.Collections.Generic.List[object]
$SERIES.Add(@{ code='KC';   cat='AUT'; zh='KC 系列标准气缸 (ISO15552)';        en='KC Series Standard Cylinder (ISO15552)';    kw='kc iso15552 biaozhun qigang' })
$SERIES.Add(@{ code='KCB';  cat='AUT'; zh='KCB 系列倍力气缸';                  en='KCB Series Tandem Cylinder';               kw='kcb beili qigang tandem' })
$SERIES.Add(@{ code='KSB';  cat='AUT'; zh='KSB 系列多位置气缸';                en='KSB Series Multi-position Cylinder';       kw='ksb duoweizhi multi-position' })
$SERIES.Add(@{ code='KCS';  cat='AUT'; zh='KCS 系列超经济型标准气缸';          en='KCS Series Economy Standard Cylinder';     kw='kcs chaojingji economy' })
$SERIES.Add(@{ code='KS';   cat='AUT'; zh='KS 系列拉杆式标准气缸';             en='KS Series Tie-rod Standard Cylinder';      kw='ks lagan tie-rod' })
$SERIES.Add(@{ code='KSU';  cat='AUT'; zh='KSU 系列拉杆内藏式标准气缸';        en='KSU Series Concealed Tie-rod Cylinder';    kw='ksu lagan neicang concealed' })
$SERIES.Add(@{ code='M';    cat='MIN'; zh='M 系列不锈钢迷你缸 (ISO6432)';      en='M Series Stainless Mini Cylinder (ISO6432)'; kw='m iso6432 minigang stainless mini' })
$SERIES.Add(@{ code='MS';   cat='MIN'; zh='MS 系列不锈钢迷你缸 (ISO6432)';     en='MS Series Stainless Mini Cylinder (ISO6432)'; kw='ms iso6432 minigang stainless mini' })
$SERIES.Add(@{ code='MSC';  cat='MIN'; zh='MSC 系列不锈钢迷你缸 (ISO6432)';    en='MSC Series Stainless Mini Cylinder (ISO6432)'; kw='msc iso6432 minigang stainless mini' })
$SERIES.Add(@{ code='MAL';  cat='ALU'; zh='MAL 系列铝合金迷你缸';              en='MAL Series Aluminium Mini Cylinder';       kw='mal lvhejin minigang aluminium mini' })
$SERIES.Add(@{ code='MALC'; cat='ALU'; zh='MALC 系列铝合金迷你缸';             en='MALC Series Aluminium Mini Cylinder';      kw='malc lvhejin minigang aluminium mini' })
$CATS = New-Object System.Collections.Generic.List[object]
$CATS.Add(@{ code='AUT'; zh='汽车行业气缸 Automotive'; en='Automotive Cylinders' })
$CATS.Add(@{ code='MIN'; zh='不锈钢迷你缸 (ISO6432)';  en='Stainless Mini Cylinders (ISO6432)' })
$CATS.Add(@{ code='ALU'; zh='铝合金迷你缸';             en='Aluminium Mini Cylinders' })
$MOUNT_DISP  = @{ ''='基本型'; FA='前盖固定型 FA'; FB='后盖固定型 FB'; CA='后盖单耳固定型 CA'; CB='后盖双耳固定型 CB'; LB='前后固定型 LB'; YB='后盖支座固定型 YB'; TC='中间摇摆式 TC'; 'TC-M'='摇摆式附脚座 TC-M'; SDB='后盖摇摆式 SDB' }
$MAGNET_DISP = @{ ''='不附磁石'; M='附磁石 M' }
$ROD_DISP    = @{ ''='碳钢'; E='不锈钢 E' }
$CAP_DISP    = @{ ''='标准摆尾型'; U='标准平尾型 U'; CM='标准圆尾型 CM' }
$STROKES     = @(10, 20, 30, 50, 75, 100)
$STROKES_KSB = @(0, 10, 20, 30, 50, 75, 100)
$BORE_STD    = @(32, 40, 50, 63, 80, 100, 125)
$BORE_KC     = @(32, 40, 50, 63, 80, 100, 125, 160, 200, 250, 320)
$BORE_KCS    = @(32, 40, 50, 63, 80, 100)
$BORE_M      = @(8, 10, 12, 16, 20, 25, 32, 40)
$BORE_MINI   = @(16, 20, 25, 32, 40)
$MOUNT_STD   = @('', 'FA', 'FB', 'CA', 'CB', 'LB', 'YB')
$MOUNT_TIE   = @('', 'FA', 'FB', 'CA', 'CB', 'LB', 'TC', 'TC-M')
$MOUNT_MINI  = @('', 'LB', 'FA', 'FB', 'SDB')

# 每系列: 参数定义 (code/zh/en/unit/type/values) + 变体组合规则
function Get-SeriesSpec([string]$code) {
  switch ($code) {
    'KC' {
      $params = @(
        @('type',   '型号',   'Type',    'enum',   @('00', '02', '03')),
        @('bore',   '缸径',   'Bore',    'number', $BORE_KC),
        @('stroke', '行程',   'Stroke',  'number', $STROKES),
        @('magnet', '磁石',   'Magnet',  'enum',   @('', 'M')),
        @('mount',  '固定形式', 'Mounting', 'enum', $MOUNT_STD)
      )
      $rules = @()
      $rules += @{ if=@{param='mount'; op='eq'; value='YB'}; then=@{param='bore'; op='in'; values=@(32,40,50,63,80,100,125)} }
      return @{ params=$params; rules=$rules; twin=$false }
    }
    'KCB' {
      $params = @(
        @('bore', '缸径', 'Bore', 'number', $BORE_STD),
        @('stroke', '行程', 'Stroke', 'number', $STROKES),
        @('magnet', '磁石', 'Magnet', 'enum', @('', 'M')),
        @('mount', '固定形式', 'Mounting', 'enum', $MOUNT_STD)
      )
      return @{ params=$params; rules=@(); twin=$false }
    }
    'KSB' {
      $params = @(
        @('bore', '缸径', 'Bore', 'number', $BORE_STD),
        @('stroke1', '行程1', 'Stroke 1', 'number', $STROKES_KSB),
        @('stroke2', '行程2', 'Stroke 2', 'number', $STROKES_KSB),
        @('magnet', '磁石', 'Magnet', 'enum', @('', 'M')),
        @('mount', '固定形式', 'Mounting', 'enum', $MOUNT_STD)
      )
      return @{ params=$params; rules=@(); twin=$false }
    }
    'KCS' {
      $params = @(
        @('type', '型号', 'Type', 'enum', @('00', '02', '03')),
        @('bore', '缸径', 'Bore', 'number', $BORE_KCS),
        @('stroke', '行程', 'Stroke', 'number', $STROKES),
        @('magnet', '磁石', 'Magnet', 'enum', @('', 'M')),
        @('mount', '固定形式', 'Mounting', 'enum', $MOUNT_STD)
      )
      return @{ params=$params; rules=@(); twin=$false }
    }
    'KS' {
      $params = @(
        @('type', '型号', 'Type', 'enum', @('00', '02', '03')),
        @('bore', '缸径', 'Bore', 'number', $BORE_STD),
        @('stroke', '行程', 'Stroke', 'number', $STROKES),
        @('magnet', '磁石', 'Magnet', 'enum', @('', 'M')),
        @('mount', '固定形式', 'Mounting', 'enum', $MOUNT_TIE)
      )
      $rules = @()
      $rules += @{ if=@{param='type'; op='in'; values=@('02','03')}; then=@{param='mount'; op='in'; values=@('FA','FB','LB','TC','TC-M')} }
      return @{ params=$params; rules=$rules; twin=$false }
    }
    'KSU' {
      $params = @(
        @('type', '型号', 'Type', 'enum', @('00', '02', '03')),
        @('bore', '缸径', 'Bore', 'number', $BORE_STD),
        @('stroke', '行程', 'Stroke', 'number', $STROKES),
        @('magnet', '磁石', 'Magnet', 'enum', @('', 'M')),
        @('mount', '固定形式', 'Mounting', 'enum', $MOUNT_TIE)
      )
      $rules = @()
      $rules += @{ if=@{param='type'; op='in'; values=@('02','03')}; then=@{param='mount'; op='in'; values=@('FA','FB','LB','TC','TC-M')} }
      return @{ params=$params; rules=$rules; twin=$false }
    }
    'M' {
      $params = @(
        @('action', '动作形式', 'Action', 'enum', @('N', 'S', 'T', 'ND', 'NJ')),
        @('bore', '缸径', 'Bore', 'number', $BORE_M),
        @('stroke', '行程', 'Stroke', 'number', $STROKES),
        @('magnet', '磁石', 'Magnet', 'enum', @('', 'M')),
        @('rod', '活塞杆材质', 'Rod Material', 'enum', @('E', '')),
        @('mount', '固定形式', 'Mounting', 'enum', $MOUNT_MINI),
        @('cap', '后盖形式', 'Cap Type', 'enum', @('', 'U', 'CM'))
      )
      return @{ params=$params; rules=@(); twin=$false }
    }
    'MS' {
      $params = @(
        @('action', '动作形式', 'Action', 'enum', @('N', 'D', 'J')),
        @('bore', '缸径', 'Bore', 'number', $BORE_MINI),
        @('stroke', '行程', 'Stroke', 'number', $STROKES),
        @('magnet', '磁石', 'Magnet', 'enum', @('', 'M')),
        @('rod', '活塞杆材质', 'Rod Material', 'enum', @('E', '')),
        @('mount', '固定形式', 'Mounting', 'enum', $MOUNT_MINI),
        @('cap', '后盖形式', 'Cap Type', 'enum', @('', 'U', 'CM'))
      )
      return @{ params=$params; rules=@(); twin=$false }
    }
    'MSC' {
      $params = @(
        @('action', '动作形式', 'Action', 'enum', @('C', 'D', 'J')),
        @('bore', '缸径', 'Bore', 'number', $BORE_MINI),
        @('stroke', '行程', 'Stroke', 'number', $STROKES),
        @('magnet', '磁石', 'Magnet', 'enum', @('', 'M')),
        @('rod', '活塞杆材质', 'Rod Material', 'enum', @('E', '')),
        @('mount', '固定形式', 'Mounting', 'enum', $MOUNT_MINI),
        @('cap', '后盖形式', 'Cap Type', 'enum', @('', 'U', 'CM'))
      )
      return @{ params=$params; rules=@(); twin=$false }
    }
    'MAL' {
      $params = @(
        @('type', '型号', 'Type', 'enum', @('MAL', 'MSAL', 'MTAL', 'MALD', 'MALJ')),
        @('bore', '缸径', 'Bore', 'number', $BORE_MINI),
        @('stroke', '行程', 'Stroke', 'number', $STROKES),
        @('magnet', '磁石', 'Magnet', 'enum', @('', 'M')),
        @('rod', '活塞杆材质', 'Rod Material', 'enum', @('E', '')),
        @('mount', '固定形式', 'Mounting', 'enum', $MOUNT_MINI),
        @('cap', '后盖形式', 'Cap Type', 'enum', @('', 'U', 'CM'))
      )
      return @{ params=$params; rules=@(); twin=$false }
    }
    'MALC' {
      $params = @(
        @('type', '型号', 'Type', 'enum', @('MALC', 'MALCD', 'MALCJ')),
        @('bore', '缸径', 'Bore', 'number', $BORE_MINI),
        @('stroke', '行程', 'Stroke', 'number', $STROKES),
        @('magnet', '磁石', 'Magnet', 'enum', @('', 'M')),
        @('rod', '活塞杆材质', 'Rod Material', 'enum', @('E', '')),
        @('mount', '固定形式', 'Mounting', 'enum', $MOUNT_MINI),
        @('cap', '后盖形式', 'Cap Type', 'enum', @('', 'U', 'CM'))
      )
      return @{ params=$params; rules=@(); twin=$false }
    }
    default { return @{ params=@(); rules=@(); twin=$false } }
  }
}

# ==================== STEP 几何生成 (简化圆柱组合) ====================
function Format-Num([double]$v) { return $v.ToString('0.###', $inv) }

function New-CylinderEntities([int]$id, [double]$x, [double]$y, [double]$z, [double]$r, [double]$len) {
  $X  = Format-Num $x; $Y = Format-Num $y; $Z = Format-Num $z
  $ZT = Format-Num ($z + $len); $R = Format-Num $r
  $VY = Format-Num ($y + $r)
  $e = @(
    "#$($id)=DIRECTION('',(0.,0.,1.));",
    "#$($id+1)=CARTESIAN_POINT('',($X,$Y,$Z));",
    "#$($id+2)=CARTESIAN_POINT('',($X,$Y,$ZT));",
    "#$($id+3)=DIRECTION('',(1.,0.,0.));",
    "#$($id+4)=AXIS2_PLACEMENT_3D('',#$($id+1),#$($id),#$($id+3));",
    "#$($id+5)=CARTESIAN_POINT('',($X,$VY,$Z));",
    "#$($id+6)=CARTESIAN_POINT('',($X,$VY,$ZT));",
    "#$($id+7)=VERTEX_POINT('',#$($id+5));",
    "#$($id+8)=VERTEX_POINT('',#$($id+6));",
    "#$($id+9)=CIRCLE('',#$($id+4),$R);",
    "#$($id+10)=EDGE_CURVE('',#$($id+7),#$($id+7),#$($id+9),.T.);",
    "#$($id+11)=CYLINDRICAL_SURFACE('',#$($id+4),$R);",
    "#$($id+12)=PLANE('',#$($id+4));",
    "#$($id+13)=DIRECTION('',(0.,0.,1.));",
    "#$($id+14)=AXIS2_PLACEMENT_3D('',#$($id+2),#$($id+13),#$($id+3));",
    "#$($id+15)=PLANE('',#$($id+14));",
    "#$($id+16)=CIRCLE('',#$($id+14),$R);",
    "#$($id+17)=EDGE_CURVE('',#$($id+8),#$($id+8),#$($id+16),.T.);",
    "#$($id+18)=DIRECTION('',(0.,0.,1.));",
    "#$($id+19)=VECTOR('',#$($id+18),1.);",
    "#$($id+20)=LINE('',#$($id+5),#$($id+19));",
    "#$($id+21)=EDGE_CURVE('',#$($id+7),#$($id+8),#$($id+20),.T.);",
    "#$($id+22)=ORIENTED_EDGE('',*,*,#$($id+10),.T.);",
    "#$($id+23)=ORIENTED_EDGE('',*,*,#$($id+17),.T.);",
    "#$($id+24)=ORIENTED_EDGE('',*,*,#$($id+21),.T.);",
    "#$($id+25)=ORIENTED_EDGE('',*,*,#$($id+17),.T.);",
    "#$($id+26)=ORIENTED_EDGE('',*,*,#$($id+21),.F.);",
    "#$($id+27)=ORIENTED_EDGE('',*,*,#$($id+10),.F.);",
    "#$($id+28)=EDGE_LOOP('',(#$($id+22)));",
    "#$($id+29)=EDGE_LOOP('',(#$($id+23)));",
    "#$($id+30)=EDGE_LOOP('',(#$($id+24),#$($id+25),#$($id+26),#$($id+27)));",
    "#$($id+31)=ADVANCED_FACE('',(#$($id+30)),#$($id+11),.T.);",
    "#$($id+32)=ADVANCED_FACE('',(#$($id+28)),#$($id+12),.F.);",
    "#$($id+33)=ADVANCED_FACE('',(#$($id+29)),#$($id+15),.T.);",
    "#$($id+34)=CLOSED_SHELL('',(#$($id+31),#$($id+32),#$($id+33)));",
    "#$($id+35)=MANIFOLD_SOLID_BREP('',#$($id+34));"
  )
  return $e
}

function Write-StepFile([string]$path, [double]$bore, [double]$stroke, [bool]$twin, [bool]$mini) {
  # 尺寸 (占位近似, 真实尺寸待 NX 母模导出替换)
  $wall = if ($mini) { 2.0 } else { 3.0 }
  $bodyR   = $bore / 2.0 + $wall
  $capR    = $bore / 2.0 + $(if ($mini) { 3.5 } else { 6.0 })
  $capLen  = if ($mini) { 6.0 } else { 8.0 }
  $fcapLen = if ($mini) { 8.0 } else { 10.0 }
  $bodyLen = $stroke + $bore * 1.1 + $(if ($mini) { 20.0 } else { 30.0 })
  $rodR    = [Math]::Max($bore / $(if ($mini) { 6.0 } else { 7.0 }), $(if ($mini) { 2.0 } else { 4.0 }))
  $rodBot  = $bodyLen - $(if ($mini) { 8.0 } else { 12.0 })
  $rodLen  = $stroke + $(if ($mini) { 20.0 } else { 26.0 })
  $rodOff  = if ($twin) { $rodR * 2.0 } else { 0.0 }

  $lines = New-Object System.Collections.Generic.List[string]
  $lines.Add('ISO-10303-21;')
  $lines.Add('HEADER;')
  $lines.Add("FILE_DESCRIPTION(('ONEBOT simplified sample geometry'),'2;1');")
  $lines.Add("FILE_NAME('$(Split-Path $path -Leaf)','2026-08-22T00:00:00',('ONEBOT'),('ONEBOT'),'','','');")
  $lines.Add("FILE_SCHEMA(('CONFIG_CONTROL_DESIGN'));")
  $lines.Add('ENDSEC;')
  $lines.Add('DATA;')

  $ids = New-Object System.Collections.Generic.List[int]
  $nextId = 1
  $cyls = New-Object System.Collections.Generic.List[object]
  $cyls.Add(@{ x = 0.0;        y = 0.0;       z = -$capLen; r = $capR;  len = $capLen  })
  $cyls.Add(@{ x = 0.0;        y = 0.0;       z = 0.0;      r = $bodyR; len = $bodyLen })
  $cyls.Add(@{ x = 0.0;        y = 0.0;       z = $bodyLen; r = $capR;  len = $fcapLen })
  $cyls.Add(@{ x = -$rodOff;   y = 0.0;       z = $rodBot;  r = $rodR;  len = $rodLen  })
  if ($twin) { $cyls.Add(@{ x = $rodOff; y = 0.0; z = $rodBot; r = $rodR; len = $rodLen }) }
  foreach ($cyl in $cyls) {
    [string[]]$entities = New-CylinderEntities -id $nextId -x $cyl['x'] -y $cyl['y'] -z $cyl['z'] -r $cyl['r'] -len $cyl['len']
    $lines.AddRange($entities)
    $ids.Add($nextId + 35)
    $nextId += 36
  }
  # 尾部共享实体: 注入 STYLED_ITEM 颜色 (occt-import-js 对无颜色模型会崩)
  $styledIds = New-Object System.Collections.Generic.List[string]
  $c = $nextId
  foreach ($s in $ids) {
    $lines.Add("#$c=COLOUR_RGB('',0.65,0.72,0.85);")
    $lines.Add("#$($c+1)=FILL_AREA_STYLE_COLOUR('',#$c);")
    $lines.Add("#$($c+2)=SURFACE_STYLE_FILL_AREA(#$($c+1));")
    $lines.Add("#$($c+3)=SURFACE_SIDE_STYLE('',(#$($c+2)));")
    $lines.Add("#$($c+4)=SURFACE_STYLE_USAGE(.BOTH.,#$($c+3));")
    $lines.Add("#$($c+5)=PRESENTATION_STYLE_ASSIGNMENT((#$($c+4)));")
    $lines.Add("#$($c+6)=STYLED_ITEM('',(#$($c+5)),#$s);")
    $styledIds.Add("#$($c+6)")
    $c += 7
  }
  $items = (($ids | ForEach-Object { "#$_" }) + $styledIds) -join ','
  $lines.Add("#$c=SHAPE_REPRESENTATION('',($items),#$($c+1));")
  $lines.Add("#$($c+1)=(GEOMETRIC_REPRESENTATION_CONTEXT(3)GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#$($c+2)))GLOBAL_UNIT_ASSIGNED_CONTEXT((#$($c+3),#$($c+4),#$($c+5)))REPRESENTATION_CONTEXT('',''));")
  $lines.Add("#$($c+2)=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-06),#$($c+3),'');")
  $lines.Add("#$($c+3)=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.));")
  $lines.Add("#$($c+4)=(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.));")
  $lines.Add("#$($c+5)=(NAMED_UNIT(*)SI_UNIT($,.STERADIAN.)SOLID_ANGLE_UNIT());")
  $lines.Add("#$($c+6)=PRODUCT('ONEBOT','ONEBOT','',(#$($c+7)));")
  $lines.Add("#$($c+7)=PRODUCT_CONTEXT('',#$($c+8),'mechanical');")
  $lines.Add("#$($c+8)=APPLICATION_CONTEXT('configuration controlled 3d designs of mechanical parts and assemblies');")
  $lines.Add("#$($c+9)=PRODUCT_DEFINITION_FORMATION('','',#$($c+6));")
  $lines.Add("#$($c+10)=PRODUCT_DEFINITION('design','',#$($c+9),#$($c+11));")
  $lines.Add("#$($c+11)=PRODUCT_DEFINITION_CONTEXT('part definition',#$($c+8),'design');")
  $lines.Add("#$($c+12)=PRODUCT_DEFINITION_SHAPE('','',#$($c+10));")
  $lines.Add("#$($c+13)=SHAPE_DEFINITION_REPRESENTATION(#$($c+12),#$c);")
  $lines.Add('ENDSEC;')
  $lines.Add('END-ISO-10303-21;')

  $enc = New-Object System.Text.UTF8Encoding($false)
  [System.IO.File]::WriteAllLines($path, $lines.ToArray(), $enc)
}

# 双轴型号判定 (两根活塞杆)
function Is-TwinType([string]$code, [string]$type) {
  if ($code -eq 'M') { return ($type -eq 'ND' -or $type -eq 'NJ') }
  if ($code -eq 'MS') { return ($type -eq 'D' -or $type -eq 'J') }
  if ($code -eq 'MSC') { return ($type -eq 'D' -or $type -eq 'J') }
  if ($code -eq 'MAL') { return ($type -eq 'MALD' -or $type -eq 'MALJ') }
  if ($code -eq 'MALC') { return ($type -eq 'MALCD' -or $type -eq 'MALCJ') }
  return ($type -eq '02' -or $type -eq '03')
}

# 规则求值 (与 C# Configurator 同语义: 违反规则的组合不生成)
function Cond-Matches($cond, $combo) {
  if (-not $combo.ContainsKey($cond['param'])) { return $false }
  $val = [string]$combo[$cond['param']]
  switch ($cond['op']) {
    'eq' { return ([string]$val -eq [string]$cond['value']) }
    'ne' { return ([string]$val -ne [string]$cond['value']) }
    'in' { foreach ($v in $cond['values']) { if ([string]$val -eq [string]$v) { return $true } } return $false }
    default { return $false }
  }
}
function Rule-Violated($rules, $combo) {
  foreach ($r in $rules) {
    if ((Cond-Matches $r['if'] $combo) -and -not (Cond-Matches $r['then'] $combo)) { return $true }
  }
  return $false
}
function Cond-ToJson($c) {
  if ($c['op'] -eq 'in') {
    $vals = ($c['values'] | ForEach-Object { '"' + $_ + '"' }) -join ','
    return '{"Param":"' + $c['param'] + '","Op":"in","Value":[' + $vals + ']}'
  }
  return '{"Param":"' + $c['param'] + '","Op":"' + $c['op'] + '","Value":"' + $c['value'] + '"}'
}
function Rule-ToJson($r) {
  return '{"If":' + (Cond-ToJson $r['if']) + ',"Then":' + (Cond-ToJson $r['then']) + '}'
}

# ==================== 主流程 ====================
$seriesJsonList = New-Object System.Collections.Generic.List[string]
$stepNames = New-Object System.Collections.Generic.HashSet[string]
$totalVariants = 0
$totalSteps = 0

foreach ($s in $SERIES) {
  $code = $s.code
  $spec = Get-SeriesSpec $code
  $mini = ($s.cat -eq 'MIN' -or $s.cat -eq 'ALU')

  # 变体组合: 参数值笛卡尔积
  $combos = New-Object System.Collections.Generic.List[object]
  $combos.Add(@{})
  foreach ($p in $spec.params) {
    $next = New-Object System.Collections.Generic.List[object]
    foreach ($c in $combos) {
      foreach ($v in $p[4]) {
        $nc = @{}
        foreach ($k in $c.Keys) { $nc[$k] = $c[$k] }
        $nc[$p[0]] = $v
        $next.Add($nc)
      }
    }
    $combos = $next
  }

  # CSV 行 + 变体 JSON + STEP (按 几何键: 系列+型号段+缸径+行程)
  $csvLines = New-Object System.Collections.Generic.List[string]
  $headerCols = @('model_code')
  foreach ($p in $spec.params) { $headerCols += $p[0] }
  $headerCols += 'step_file'
  $csvLines.Add(($headerCols -join ','))
  $varJson = New-Object System.Collections.Generic.List[string]
  $geoCodes = New-Object System.Collections.Generic.HashSet[string]

  foreach ($c in $combos) {
    # 规则过滤: 违反手册约束的组合不生成 (如 大缸径+YB、双轴型+受限固定形式)
    if (Rule-Violated $spec.rules $c) { continue }
    $typeSeg = ''
    if ($spec.params[0][0] -eq 'type' -or $spec.params[0][0] -eq 'action') { $typeSeg = [string]$c[$spec.params[0][0]] }
    $bore = [double]$c['bore']
    $stroke = if ($c.ContainsKey('stroke')) { [double]$c['stroke'] } else { [double]$c['stroke1'] }

    # 型号编码
    $model = ''
    if ($code -eq 'KSB') {
      $model = "KSB$($c['bore'])-$($c['stroke1'])x$($c['stroke2'])$($c['magnet'])$($c['mount'])"
    } elseif ($s.cat -eq 'MIN' -or $code -eq 'MAL' -or $code -eq 'MALC') {
      $model = "$typeSeg$($c['bore'])-$($c['stroke'])$($c['magnet'])$($c['rod'])$($c['mount'])$($c['cap'])"
    } else {
      $model = "$code$typeSeg$($c['bore'])-$($c['stroke'])$($c['magnet'])$($c['mount'])"
    }

    # 几何 STEP (共用)
    $geoKey = "$code-$typeSeg-${bore}x$stroke"
    $stepName = "$code-$typeSeg-${bore}x$stroke.step"
    if (-not $geoCodes.Contains($geoKey)) {
      [void]$geoCodes.Add($geoKey)
      if (-not $stepNames.Contains($stepName)) {
        Write-StepFile (Join-Path $stepDir $stepName) $bore $stroke (Is-TwinType $code $typeSeg) $mini
        [void]$stepNames.Add($stepName)
        $totalSteps++
      }
    }

    # 变体 JSON 与 CSV
    $paramsJson = New-Object System.Collections.Generic.List[string]
    foreach ($p in $spec.params) {
      $v = $c[$p[0]]
      if ($p[3] -eq 'number') { $paramsJson.Add('"' + $p[0] + '":' + [string]$v) }
      else { $paramsJson.Add('"' + $p[0] + '":"' + $v + '"') }
    }
    $varJson.Add('{"modelCode":"' + $model + '","params":{' + ($paramsJson -join ',') + '},"step":"step/' + $stepName + '"}')
    $rowVals = @($model)
    foreach ($p in $spec.params) { $rowVals += [string]$c[$p[0]] }
    $rowVals += $stepName
    $csvLines.Add(($rowVals -join ','))
    $totalVariants++
  }

  # 系列 meta JSON (series\<code>.meta.json, 与向导保存配置同构; 变体不存, 构建时从 CSV 读)
  $paramsJson = New-Object System.Collections.Generic.List[string]
  foreach ($p in $spec.params) {
    $display = ''
    if ($p[0] -eq 'mount') {
      $entries = New-Object System.Collections.Generic.List[string]
      foreach ($k in $MOUNT_DISP.Keys) { $entries.Add('"' + $k + '":"' + $MOUNT_DISP[$k] + '"') }
      $display = '"display":{' + ($entries -join ',') + '}'
    } elseif ($p[0] -eq 'magnet') {
      $display = '"display":{"":"不附磁石","M":"附磁石 M"}'
    } elseif ($p[0] -eq 'rod') {
      $display = '"display":{"":"碳钢","E":"不锈钢 E"}'
    } elseif ($p[0] -eq 'cap') {
      $display = '"display":{"":"标准摆尾型","U":"标准平尾型 U","CM":"标准圆尾型 CM"}'
    }
    $vals = New-Object System.Collections.Generic.List[string]
    foreach ($v in $p[4]) {
      if ($p[3] -eq 'number') { $vals.Add([string]$v) }
      else { $vals.Add('"' + $v + '"') }
    }
    $paramsJson.Add('{"code":"' + $p[0] + '","nameZh":"' + $p[1] + '","nameEn":"' + $p[2] + '","unit":"' + $(if ($p[3] -eq 'number') { 'mm' } else { '' }) + '","type":"' + $p[3] + '","keywords":"' + $p[0] + '","values":[' + ($vals -join ',') + ']' + $(if ($display) { ',' + $display } else { '' }) + '}')
  }
  $rulesJson = (($spec.rules | ForEach-Object { Rule-ToJson $_ }) -join ',')
  # 手册页附件 (尺寸图窗口对照用): catalog\manual\<系列>\pXX.png → source 相对路径
  $manAtt = New-Object System.Collections.Generic.List[string]
  if (Test-Path (Join-Path $manDir $code)) {
    foreach ($mf in (Get-ChildItem (Join-Path $manDir $code) -Filter '*.png' | Sort-Object Name)) {
      $manAtt.Add('{"kind":"manual","lang":"","source":"manual/' + $code + '/' + $mf.Name + '","model":null}')
    }
  }
  $sjson = '{"code":"' + $code + '","nameZh":"' + $s.zh + '","nameEn":"' + $s.en + '","keywords":"' + $s.kw + '",' +
           '"parameters":[' + ($paramsJson -join ',') + '],' +
           '"rules":[' + $rulesJson + '],' +
           '"naming":{"stepNameTemplate":"{model}.step"},' +
           '"attachments":[],"variants":[]}'
  $metaJson = '{"series":' + $sjson + ',"attachments":[' + ($manAtt -join ',') + ']}'
  [System.IO.File]::WriteAllText((Join-Path $seriesDir ($code + '.meta.json')), $metaJson, (New-Object System.Text.UTF8Encoding($false)))

  # 写 CSV
  [System.IO.File]::WriteAllLines((Join-Path $csvDir ($code + '.csv')), $csvLines.ToArray(), (New-Object System.Text.UTF8Encoding($true)))
  Write-Output ("系列 " + $code + ": " + $combos.Count + " 个变体, 几何 STEP " + $geoCodes.Count + " 个")
}

# catalog.json (源目录: 目录级定义 + 分类系列列表; series 定义在 series\*.meta.json)
$catJson = New-Object System.Collections.Generic.List[string]
foreach ($c in $CATS) {
  $codes = ($SERIES | Where-Object { $_.cat -eq $c.code } | ForEach-Object { '"' + $_.code + '"' }) -join ','
  $catJson.Add('{"code":"' + $c.code + '","nameZh":"' + $c.zh + '","nameEn":"' + $c.en + '","keywords":"","series":[' + $codes + ']}')
}
$catalogJson = '{"schemaVersion":"1.0","catalogName":"ONEBOT 气动元件目录","catalogNameEn":"ONEBOT Pneumatic Catalog",' +
  '"catalogVersion":"2026.08","defaultLang":"zh","langs":["zh","en"],' +
  '"categories":[' + ($catJson -join ',') + '],' +
  '"series":[]}'
[System.IO.File]::WriteAllText((Join-Path $OutDir 'catalog.json'), $catalogJson, (New-Object System.Text.UTF8Encoding($false)))

Write-Output ""
Write-Output ("完成: " + $SERIES.Count + " 个系列, " + $totalVariants + " 个变体, " + $totalSteps + " 个几何 STEP")
Write-Output ("源目录: " + $OutDir + "  (catalog.json + series\*.meta.json + csv\ + step\)")
Write-Output ("下一步打包 (C# 统一内核): OnebotCatalog\bin\OnebotCatalog.exe --buildfull <源目录> <输出.opc>")
