poker-mcp 1.7.2 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,6 +11,38 @@ YAML-based input file management tool for radiation-shielding calculation code P
11
11
  - **核種データ**: `${POKER_INSTALL_PATH}/LIB/ICRP-07.NDX` を直接参照
12
12
  - **実行方式**: STDIO通信(MCPプロトコル標準)
13
13
 
14
+ ## 🆕 バージョン1.8.0〜1.8.1の新機能
15
+
16
+ ### 📐 CAD連携が MCP だけで完結
17
+ 従来、CAD からの経路抽出は MCP を経由せず、FreeCAD を手で起動する必要が
18
+ ありました。`spec.json` を手書きし、`poker_cui -p` を叩き、`freecadcmd` に
19
+ スクリプトを渡す。3 手順すべてが手作業でした。
20
+
21
+ ```javascript
22
+ poker_generatePaths({ fcstd: "C:/path/to/model.FCStd" })
23
+ poker_executeCalculation({ yaml_file: "poker.yaml", path_input: "poker.paths" })
24
+ ```
25
+
26
+ CAD ファイルを指定するだけになりました。分割点の取得、グリッド検出器の展開、
27
+ ビルドアップ等価材料の解決(`Source_Dry` → `Tungsten` など)は自動です。
28
+
29
+ ### 🔍 FREECAD_PATH
30
+ FreeCAD の場所を、環境変数 → PATH → 既定のインストール先の順で解決します。
31
+ `POKER_INSTALL_PATH` のように固定の既定値を持てないのは、インストール先が
32
+ バージョン番号を含むためです(FreeCAD 1.1, 1.0 …)。
33
+
34
+ ```json
35
+ "env": {
36
+ "POKER_INSTALL_PATH": "C:\\Poker",
37
+ "FREECAD_PATH": "C:\\Program Files\\FreeCAD 1.1"
38
+ }
39
+ ```
40
+
41
+ ### 🐛 相対パスの解決を修正
42
+ `tasks/` 配下のファイル名指定が、プロセスのカレントディレクトリを基準に
43
+ 解決されていました。MCP サーバの起動場所によっては別の場所を指します。
44
+
45
+
14
46
  ## 🆕 バージョン1.7.0〜1.7.2の新機能
15
47
 
16
48
  ### 📉 サマリー出力量の制御(ThinnedIndices 操作系 3メソッド)
@@ -72,28 +104,61 @@ poker_getThinnedIndices()
72
104
 
73
105
  ### 📡 CAD連携レイトレース
74
106
  FreeCAD のソリッドモデルを、STEP のような中間フォーマットを介さず、CSG で
75
- 表現し直す作業も無しに遮蔽体系として扱えます。線源点→検出器の直線を FreeCAD 側で
76
- 追跡し、通過した材質と厚さを `.paths` ファイルで POKER に渡します。
107
+ 表現し直す作業も無しに遮蔽体系として扱えます。**MCP の 2 呼び出しで完結**します。
77
108
 
78
- ```bash
79
- poker_cui model.yaml -p -t # 分割点を出力
80
- freecadcmd -c "... gen_paths.main('spec.json')" # CAD をトレース
81
- poker_cui model.yaml --path-input model.paths -t # 計算
109
+ ```javascript
110
+ poker_generatePaths({ fcstd: "C:/path/to/model.FCStd" })
111
+ poker_executeCalculation({ yaml_file: "poker.yaml", path_input: "poker.paths" })
82
112
  ```
83
113
 
84
- POKER 側の `--path-input` も実装され、CSG 経由と一致することを平板・円筒・球の
85
- 5 体系で確認済みです。フィレットや自由曲面のように CSG で表現できない形状も
86
- 扱えます。**複数線源・グリッド検出器・スラント補正に対応**。
114
+ 線源点→検出器の直線を FreeCAD 側で追跡し、通過した材質と厚さを `.paths` に
115
+ 記録して POKER に渡します。分割点の取得、グリッド検出器の展開、ビルドアップ
116
+ 等価材料の解決はすべて自動です。
117
+
118
+ CSG 経由と一致することを平板・円筒・球の 5 体系で確認済みです。フィレットや
119
+ 自由曲面のように CSG で表現できない形状も扱えます。**複数線源・グリッド検出器・
120
+ スラント補正に対応**。
87
121
 
88
122
  他の CAD で作ったモデルも、STEP で FreeCAD に読み込めば同じパイプラインに
89
123
  乗ります。副産物として、POKER の改造なしで使える**簡易化の監査ツール**
90
124
  (ある形状を省略してよいかを定量化する)もあります。
91
125
 
126
+ FreeCAD の場所は環境変数 `FREECAD_PATH` で指定します(未設定なら既定の
127
+ インストール先を探索)。
128
+
129
+ #### 必要なもの
130
+
131
+ | | 入手 | 環境変数 |
132
+ |---|---|---|
133
+ | poker-mcp | `npm install poker-mcp` | — |
134
+ | **POKER 本体** | 別途入手 | `POKER_INSTALL_PATH` |
135
+ | **FreeCAD** | [freecad.org](https://www.freecad.org/)(無償) | `FREECAD_PATH` |
136
+
137
+ numpy はレイトレーサが使いますが、FreeCAD に同梱されているので追加の
138
+ インストールは不要です。
139
+
140
+ ```json
141
+ "env": {
142
+ "POKER_MCP_HOME": "C:\\Users\\yoshi\\poker_mcp_workspace",
143
+ "POKER_INSTALL_PATH": "C:\\Poker",
144
+ "FREECAD_PATH": "C:\\Program Files\\FreeCAD 1.1"
145
+ }
146
+ ```
147
+
148
+ `FREECAD_PATH` は既定の場所にインストールされていれば省略できます。
149
+
150
+ #### モデル側の約束
151
+
152
+ **ソリッドに `PokerMaterial` プロパティで材質名を設定**しておく必要があります
153
+ (`Iron`、`Concrete` など、POKER の材料ライブラリの名前)。密度を上書きする場合は
154
+ `PokerDensity` も設定します。
155
+
156
+ 他の CAD で作ったモデルを STEP で読み込んだ場合、材質情報は失われるので
157
+ FreeCAD 上で設定してください。一度設定すれば `.FCStd` に保存されます。
158
+
92
159
  詳細は [CAD_RAYTRACE.md](./docs/manuals/CAD_RAYTRACE.md)、フォーマットは
93
160
  [PATHS_FORMAT.md](./docs/manuals/PATHS_FORMAT.md)。
94
161
 
95
- 詳細は [CHANGELOG.md](./CHANGELOG.md) を参照。
96
-
97
162
  ## 🆕 バージョン1.5.0の新機能
98
163
 
99
164
  ### 🖥️ POKER GUI を閉じずに表示を切り替え
@@ -336,7 +401,7 @@ Claude Desktopで以下のようにテストできます:
336
401
  ## 🏆 主要機能
337
402
 
338
403
  ### ✅ **MCP完全対応**
339
- - **33メソッド完全実装**: 全ての放射線遮蔽計算入力管理機能
404
+ - **34メソッド完全実装**: 全ての放射線遮蔽計算入力管理機能
340
405
  - **JSON-RPC 2.0準拠**: 標準プロトコル完全対応
341
406
  - **STDIO通信**: MCPクライアントとの標準通信方式
342
407
  - **自動バックアップ・ロールバック**: 企業品質のデータ保護
@@ -355,7 +420,7 @@ Claude Desktopで以下のようにテストできます:
355
420
 
356
421
  ## 🎯 API構成
357
422
 
358
- ### 🔧 **33メソッド完全実装**
423
+ ### 🔧 **34メソッド完全実装**
359
424
 
360
425
  | **カテゴリ** | **メソッド数** | **機能** | **主要操作** |
361
426
  |-------------|---------------|----------|-------------|
@@ -368,7 +433,7 @@ Claude Desktopで以下のようにテストできます:
368
433
  | **📏 Unit** | 5個 | 単位設定管理 | propose・get・update・validateIntegrity・analyzeConversion |
369
434
  | **⚙️ System** | 6個 | システム制御 | applyChanges・executeCalculation・resetYaml・confirmDaughterNuclides・openGui・各種検証 |
370
435
 
371
- ### 📋 **全33メソッド一覧**
436
+ ### 📋 **全34メソッド一覧**
372
437
  ```
373
438
  Body系 (3): poker_proposeBody, poker_updateBody, poker_deleteBody
374
439
  Zone系 (3): poker_proposeZone, poker_updateZone, poker_deleteZone
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poker-mcp",
3
- "version": "1.7.2",
3
+ "version": "1.8.2",
4
4
  "description": "YAML-based input file management tool for radiation shielding calculation code POKER with MCP support - Enhanced with collision detection, daughter nuclide auto-completion, and environment variable support",
5
5
  "main": "src/mcp_server_stdio_v4.js",
6
6
  "type": "module",
@@ -50,6 +50,11 @@
50
50
  },
51
51
  "files": [
52
52
  "src/",
53
+ "tools/gen_paths.py",
54
+ "tools/ray_trace_tri.py",
55
+ "tools/poker_lib.py",
56
+ "tools/audit_mfp.py",
57
+ "tools/compare_poker_trace.py",
53
58
  "README.md",
54
59
  ".mcp.json"
55
60
  ]
@@ -0,0 +1,187 @@
1
+ // mcp/handlers/cadHandlers.js
2
+ //
3
+ // CAD 連携レイトレース。従来は次の 3 手順を手で行う必要があった。
4
+ // 1. spec.json を手書き(絶対パスを 4 箇所)
5
+ // 2. poker_cui -p で分割点と評価点を出す
6
+ // 3. freecadcmd を起動して gen_paths.py を走らせる
7
+ // このハンドラがまとめて行う。
8
+
9
+ import { spawn } from 'child_process';
10
+ import fs from 'fs/promises';
11
+ import fssync from 'fs';
12
+ import path from 'path';
13
+ import { FREECAD_CMD, CAD_TOOLS_DIR, POKER_INSTALL_DIR, TASKS_DIR, YAML_FILE }
14
+ from '../../utils/paths.js';
15
+ import { logger } from '../../utils/logger.js';
16
+ import { MaterialCatalog } from '../../utils/MaterialCatalog.js';
17
+
18
+ function run(cmd, args, opts = {}) {
19
+ return new Promise((resolve) => {
20
+ const p = spawn(cmd, args, { ...opts, stdio: ['ignore', 'pipe', 'pipe'] });
21
+ let out = '', err = '';
22
+ p.stdout.on('data', d => out += d);
23
+ p.stderr.on('data', d => err += d);
24
+ p.on('error', e => resolve({ code: -1, out, err: e.message }));
25
+ p.on('close', code => resolve({ code, out, err }));
26
+ });
27
+ }
28
+
29
+ export function createCadHandlers(taskManager) {
30
+ return {
31
+ async generatePaths(args) {
32
+ const fail = (msg, hint) => ({
33
+ content: [{
34
+ type: 'text',
35
+ text: JSON.stringify({ success: false, error: msg, hint }, null, 2)
36
+ }],
37
+ isError: true
38
+ });
39
+
40
+ // --- FreeCAD の所在 ---
41
+ if (!FREECAD_CMD) {
42
+ return fail(
43
+ 'FreeCAD が見つかりません',
44
+ '環境変数 FREECAD_PATH に freecadcmd の場所を設定してください。' +
45
+ '実行ファイル(C:/Program Files/FreeCAD 1.1/bin/freecadcmd.exe)でも、' +
46
+ 'インストールフォルダ(C:/Program Files/FreeCAD 1.1)でも指定できます。' +
47
+ 'claude_desktop_config.json の env に追加します。');
48
+ }
49
+ if (!args || !args.fcstd) return fail('fcstd を指定してください');
50
+ if (!fssync.existsSync(args.fcstd))
51
+ return fail('CAD ファイルが見つかりません: ' + args.fcstd);
52
+
53
+ const yamlFile = YAML_FILE;
54
+ if (!fssync.existsSync(yamlFile))
55
+ return fail('入力 YAML がありません: ' + yamlFile,
56
+ 'applyChanges で入力を確定してから実行してください。');
57
+
58
+ // --- 1. poker_cui -p で分割点と評価点を出す ---
59
+ // .paths には全点が必要。間引かれていると gen_paths.py がエラーにする。
60
+ const exe = path.join(POKER_INSTALL_DIR, 'poker_cui.EXE');
61
+ if (!fssync.existsSync(exe))
62
+ return fail('poker_cui が見つかりません: ' + exe,
63
+ '環境変数 POKER_INSTALL_PATH を確認してください。');
64
+
65
+ const summary = yamlFile + '.summary';
66
+ const r1 = await run(exe, [yamlFile, '-p', '-t'], { cwd: TASKS_DIR });
67
+ if (r1.code !== 0 || !fssync.existsSync(summary)) {
68
+ return fail('poker_cui -p の実行に失敗しました(終了コード ' + r1.code + ')',
69
+ (r1.err || r1.out || '').slice(-400));
70
+ }
71
+
72
+ // --- 2. spec を組み立てる ---
73
+ const out = args.output || path.join(TASKS_DIR, 'poker.paths');
74
+ const spec = {
75
+ fcstd: args.fcstd.replace(/\\/g, '/'),
76
+ out: out.replace(/\\/g, '/'),
77
+ poker_summary: summary.replace(/\\/g, '/'),
78
+ detectors_from_summary: true,
79
+ deviation: args.deviation ?? 0.5,
80
+ unit_scale: args.unit_scale ?? 0.1,
81
+ poker_dir: POKER_INSTALL_DIR.replace(/\\/g, '/'),
82
+ mu_energy: args.mu_energy ?? 1.25,
83
+ };
84
+ if (args.source_name) spec.source_name = args.source_name;
85
+ if (args.buildup_exclude) spec.buildup_exclude = args.buildup_exclude;
86
+ if (args.chunk) spec.chunk = args.chunk;
87
+
88
+ // カスタム材料のビルドアップ等価材料を自動で解決する。
89
+ // gen_paths.py は層の縮約先を決める際、単層ビルドアップデータの有無を
90
+ // 材料名で照合する。Source_Dry のようなカスタム材料は標準材料に
91
+ // 読み替える必要があり、従来は spec.equivalent に手で書いていた。
92
+ // MaterialCatalog が lib_equivalent.dat と実データから解決できるので
93
+ // ここで埋める(POKER 側も同じ表を見るので食い違わない)。
94
+ try {
95
+ const data = taskManager.data || {};
96
+ const equiv = {};
97
+ for (const z of (data.zone || [])) {
98
+ const m = z.material;
99
+ if (!m || m === 'VOID' || equiv[m]) continue;
100
+ if (MaterialCatalog.isStandard && MaterialCatalog.isStandard(m)) continue;
101
+ const e = MaterialCatalog.nearestBuildupEquivalent(m);
102
+ const name = (e && typeof e === 'object') ? e.equivalent : e;
103
+ if (name) equiv[m] = name;
104
+ }
105
+ if (Object.keys(equiv).length) {
106
+ spec.equivalent = equiv;
107
+ logger.info('ビルドアップ等価材料を自動設定しました', { equivalent: equiv });
108
+ }
109
+ } catch (e) {
110
+ logger.warn('等価材料の自動解決に失敗(spec.equivalent なしで続行)',
111
+ { error: e.message });
112
+ }
113
+
114
+ const specFile = path.join(TASKS_DIR, '.generate_paths_spec.json');
115
+ await fs.writeFile(specFile, JSON.stringify(spec, null, 2), 'utf8');
116
+
117
+ // --- 3. FreeCAD をヘッドレスで起動してトレース ---
118
+ // GUI の MCP ブリッジ経由では、数万レイのトレースが GUI スレッドを
119
+ // 占有してブリッジが壊れる。freecadcmd を使う。
120
+ const script =
121
+ 'import sys, json, traceback\n' +
122
+ 'sys.path.insert(0, r"' + CAD_TOOLS_DIR + '")\n' +
123
+ 'try:\n' +
124
+ ' import gen_paths\n' +
125
+ ' gen_paths.main(r"' + specFile + '")\n' +
126
+ ' print("GEN_PATHS_OK")\n' +
127
+ 'except BaseException:\n' +
128
+ ' print("GEN_PATHS_ERROR")\n' +
129
+ ' traceback.print_exc()\n';
130
+
131
+ const scriptFile = path.join(TASKS_DIR, '.generate_paths.py');
132
+ await fs.writeFile(scriptFile, script, 'utf8');
133
+
134
+ const r2 = await run(FREECAD_CMD, [scriptFile], { cwd: TASKS_DIR });
135
+ const log = (r2.out || '') + (r2.err || '');
136
+
137
+ if (!log.includes('GEN_PATHS_OK') || !fssync.existsSync(out)) {
138
+ // Python のトレースバックから要点を抜く
139
+ const m = log.match(/(?:SystemExit|Error|Exception)[^\n]*(?:\n[^\n]*){0,3}/);
140
+ return fail('経路の生成に失敗しました',
141
+ m ? m[0].trim() : log.slice(-500));
142
+ }
143
+
144
+ // --- 4. 生成結果を要約する ---
145
+ const text = await fs.readFile(out, 'utf8');
146
+ const info = {};
147
+ for (const key of ['n_sources', 'n_source_points', 'n_detectors', 'n_materials',
148
+ 'format_version', 'deviation_mm']) {
149
+ const m = text.match(new RegExp('^\\s*' + key + ':\\s*(.+)$', 'm'));
150
+ if (m) info[key] = m[1].trim();
151
+ }
152
+ const nPaths = (text.split('paths: |')[1] || '').trim().split('\n').length;
153
+
154
+ logger.info('経路ファイルを生成しました', { out, ...info });
155
+
156
+ return {
157
+ content: [{
158
+ type: 'text',
159
+ text: JSON.stringify({
160
+ success: true,
161
+ message: '経路ファイルを生成しました',
162
+ output: out,
163
+ summary: {
164
+ 経路数: nPaths,
165
+ 線源数: info.n_sources,
166
+ 線源点数: info.n_source_points,
167
+ 検出器評価点数: info.n_detectors,
168
+ 材質数: info.n_materials,
169
+ テッセレーション偏差_mm: info.deviation_mm,
170
+ format_version: info.format_version
171
+ },
172
+ spec_used: {
173
+ deviation: spec.deviation,
174
+ unit_scale: spec.unit_scale,
175
+ mu_energy: spec.mu_energy,
176
+ buildup_exclude: spec.buildup_exclude ?? ['VOID', 'Air'],
177
+ equivalent: spec.equivalent ?? {},
178
+ freecad: FREECAD_CMD,
179
+ poker_dir: spec.poker_dir
180
+ },
181
+ next: 'poker_cui "' + yamlFile + '" --path-input "' + out + '" -t で計算できます'
182
+ }, null, 2)
183
+ }]
184
+ };
185
+ }
186
+ };
187
+ }
@@ -115,7 +115,8 @@ export function createCalculationHandlers(taskManager) {
115
115
  summaryOptions,
116
116
  resolvedOutputFiles,
117
117
  undefined,
118
- taskManager.dataManager
118
+ taskManager.dataManager,
119
+ args.path_input || null
119
120
  );
120
121
 
121
122
  // 事前検証で重大エラーが検出された場合の処理
@@ -6,7 +6,8 @@ import { createBuildupFactorHandlers } from './buildupFactorHandlers.js';
6
6
  import { createSourceHandlers } from './sourceHandlers.js';
7
7
  import { createDetectorHandlers } from './detectorHandlers.js';
8
8
  import { createUnitHandlers } from './unitHandlers.js';
9
- import { createThinnedIndicesHandlers } from './thinnedIndicesHandlers.js';
9
+ import { createThinnedIndicesHandlers } from './thinnedIndicesHandlers.js';
10
+ import { createCadHandlers } from './cadHandlers.js';
10
11
  import { createCalculationHandlers } from './calculationHandlers.js';
11
12
  import { resetHandlers } from './resetHandlers.js';
12
13
  import { createDaughterNuclideHandler } from './daughterNuclideHandler.js';
@@ -34,7 +35,8 @@ export function createAllHandlers(taskManager) {
34
35
 
35
36
  // 単位操作
36
37
  ...createUnitHandlers(taskManager),
37
- ...createThinnedIndicesHandlers(taskManager),
38
+ ...createThinnedIndicesHandlers(taskManager),
39
+ ...createCadHandlers(taskManager),
38
40
 
39
41
  // 計算操作
40
42
  ...createCalculationHandlers(taskManager),
@@ -0,0 +1,65 @@
1
+ // cadTools.js
2
+ //
3
+ // CAD 連携レイトレース(.paths の生成)のツール定義。
4
+ //
5
+ // FreeCAD のソリッドモデルから線源点→検出器の経路を抽出し、POKER に渡す
6
+ // .paths ファイルを作る。従来は freecadcmd を手で叩き、spec.json を手書きする
7
+ // 必要があったが、このツールがその手順をまとめる。
8
+ //
9
+ // FreeCAD の場所は環境変数 FREECAD_PATH で指定する。未設定なら既定の
10
+ // インストール先を探す。
11
+
12
+ export const cadTools = [
13
+ {
14
+ name: 'poker_generatePaths',
15
+ description:
16
+ 'FreeCAD のモデルから経路ファイル(.paths)を生成します。線源分割点→検出器評価点の直線を追跡し、通過した材質と厚さを記録します。' +
17
+ 'CSG プリミティブで表現しにくい形状(フィレット、自由曲面、多数の貫通孔など)をそのまま遮蔽計算に使えます。' +
18
+ '内部で poker_cui -p を実行して分割点と評価点を取得し、FreeCAD をヘッドレスで起動してトレースします。' +
19
+ '生成後は executeCalculation の path_input に指定するか、poker_cui --path-input で計算します。',
20
+ inputSchema: {
21
+ type: 'object',
22
+ properties: {
23
+ fcstd: {
24
+ type: 'string',
25
+ description: 'FreeCAD モデルのパス(.FCStd)。ソリッドに PokerMaterial プロパティで材質名を設定しておくこと。'
26
+ },
27
+ output: {
28
+ type: 'string',
29
+ description: '出力する .paths のパス。省略時は作業ディレクトリの poker.paths。'
30
+ },
31
+ deviation: {
32
+ type: 'number',
33
+ minimum: 0.001,
34
+ description: 'テッセレーション偏差 [mm]。既定 0.5。曲面を三角形で近似する際の最大のずれ。球面や複曲面が支配的な体系では小さくする(0.1 で誤差が約 1/10 になる)。'
35
+ },
36
+ unit_scale: {
37
+ type: 'number',
38
+ description: 'CAD 単位 → POKER 単位の倍率。既定 0.1(FreeCAD の mm → POKER の cm)。'
39
+ },
40
+ mu_energy: {
41
+ type: 'number',
42
+ description: '層の縮約に使う参照エネルギー [MeV]。既定 1.25。ビルドアップ層の選定(参考情報)にのみ影響し、線量計算には使われない。'
43
+ },
44
+ source_name: {
45
+ type: 'string',
46
+ description: '線源名。省略時は全線源を対象にする。'
47
+ },
48
+ buildup_exclude: {
49
+ type: 'array',
50
+ items: { type: 'string' },
51
+ description: 'ビルドアップ層の候補から除外する材質。既定 ["VOID","Air"]。大気中の長距離伝播で Air を遮蔽として数えたい場合などに変更する。減衰計算には影響せず、層の縮約(参考情報)にのみ効く。'
52
+ },
53
+ chunk: {
54
+ type: 'integer',
55
+ minimum: 1024,
56
+ description: 'レイトレースのバッチサイズ。既定 32768。メモリが厳しい環境では下げる。結果には影響しない。'
57
+ }
58
+ },
59
+ required: ['fcstd'],
60
+ additionalProperties: false,
61
+ title: 'CAD から経路ファイルを生成',
62
+ description: '入力 YAML の線源・検出器の定義と、FreeCAD のモデルを突き合わせて .paths を作ります。'
63
+ }
64
+ }
65
+ ];
@@ -17,7 +17,7 @@ export const calculationTools = [
17
17
  },
18
18
  {
19
19
  name: 'poker_executeCalculation',
20
- description: '作成したYAMLファイルを使用してpoker_cuiで放射線遮蔽計算を実行します。応答には .summary(YAML) から抽出した構造化 result_total(検出器ごとの座標と E(AP)/DskinM(AP)/H*(10) の線量内訳)、dose_columns、警告フッタ calculation_warnings、注記 calculation_notes を含みます。',
20
+ description: '作成したYAMLファイルを使用してpoker_cuiで放射線遮蔽計算を実行します。path_input に .paths を指定すると、CAD から抽出した経路を幾何として使います。応答には .summary(YAML) から抽出した構造化 result_total(検出器ごとの座標と E(AP)/DskinM(AP)/H*(10) の線量内訳)、dose_columns、警告フッタ calculation_warnings、注記 calculation_notes を含みます。',
21
21
  inputSchema: {
22
22
  type: 'object',
23
23
  properties: {
@@ -26,6 +26,10 @@ export const calculationTools = [
26
26
  description: '計算に使用するYAMLファイル名(拡張子.yamlを含む)。ファイル名のみ指定(例: poker.yaml)で、POKER_MCP_HOME/tasks/ 配下のファイルが自動的に参照されます。絶対パスも指定可能です。',
27
27
  pattern: '^([a-zA-Z0-9_\\-\\.]+\\.(yaml|yml)|([a-zA-Z]:[\\\\\/]|\\/)[^\\\\]+\\.(yaml|yml))$'
28
28
  },
29
+ path_input: {
30
+ type: 'string',
31
+ description: 'CAD から抽出した経路ファイル(.paths)。指定すると、YAML の立体・ゾーン定義の代わりにこのファイルの経路を幾何として使います(poker_cui の --path-input)。poker_generatePaths で生成できます。線源・検出器・材料・ビルドアップ設定は従来どおり YAML から取得するので、入力の正本は YAML に保たれます。ファイル名のみの指定で POKER_MCP_HOME/tasks/ 配下が参照されます。'
32
+ },
29
33
  summary_options: {
30
34
  type: 'object',
31
35
  description: 'サマリー出力オプション(注意: show_source_dataとshow_total_doseの少なくとも一方は必須です)',
@@ -7,7 +7,8 @@ import { sourceTools } from './sourceTools.js';
7
7
  import { detectorTools } from './detectorTools.js';
8
8
  import { commonTools } from './commonTools.js';
9
9
  import { unitTools } from './unitTools.js';
10
- import { thinnedIndicesTools } from './thinnedIndicesTools.js';
10
+ import { thinnedIndicesTools } from './thinnedIndicesTools.js';
11
+ import { cadTools } from './cadTools.js';
11
12
  import { calculationTools } from './calculationTools.js';
12
13
  import { resetTools } from './resetTools.js';
13
14
  import { daughterNuclideTools } from './daughterNuclideTools.js';
@@ -22,7 +23,8 @@ export const allTools = [
22
23
  ...detectorTools,
23
24
  ...commonTools,
24
25
  ...unitTools,
25
- ...thinnedIndicesTools,
26
+ ...thinnedIndicesTools,
27
+ ...cadTools,
26
28
  ...calculationTools,
27
29
  ...resetTools,
28
30
  ...daughterNuclideTools,
@@ -38,7 +40,8 @@ export {
38
40
  detectorTools,
39
41
  commonTools,
40
42
  unitTools,
41
- thinnedIndicesTools,
43
+ thinnedIndicesTools,
44
+ cadTools,
42
45
  calculationTools,
43
46
  resetTools,
44
47
  daughterNuclideTools,
@@ -1,7 +1,8 @@
1
1
  // services/CalculationService.js
2
2
  import { spawn } from 'child_process';
3
3
  import { promises as fs } from 'fs';
4
- import path from 'path';
4
+ import path from 'path';
5
+ import { TASKS_DIR } from '../utils/paths.js';
5
6
  import { logger } from '../utils/logger.js';
6
7
  import { CalculationError } from '../utils/errors.js';
7
8
 
@@ -240,7 +241,7 @@ export class CalculationService {
240
241
  * @param {number} timeout タイムアウト時間(ミリ秒)
241
242
  * @returns {Promise<Object>} 計算結果
242
243
  */
243
- async executeCalculation(yamlFile, summaryOptions = {}, outputFiles = {}, timeout = this.defaultTimeout) {
244
+ async executeCalculation(yamlFile, summaryOptions = {}, outputFiles = {}, timeout = this.defaultTimeout, pathInput = null) {
244
245
  // 前処理: 各種検証
245
246
  logger.info('Starting calculation validation', { yamlFile, summaryOptions, outputFiles });
246
247
 
@@ -301,7 +302,7 @@ export class CalculationService {
301
302
  absoluteYamlPath = yamlFile;
302
303
  } else {
303
304
  // 相対パスの場合、tasks/ディレクトリからの相対パスとして解決
304
- absoluteYamlPath = path.resolve('tasks', yamlFile);
305
+ absoluteYamlPath = path.join(TASKS_DIR, yamlFile);
305
306
  }
306
307
  const args = [absoluteYamlPath];
307
308
 
@@ -318,6 +319,16 @@ export class CalculationService {
318
319
  args.push('-d', outputFiles.dose_file);
319
320
  }
320
321
 
322
+ // CAD から抽出した経路を幾何として使う。
323
+ // YAML の立体・ゾーン定義の代わりに .paths の経路を読む。線源・検出器・
324
+ // 材料・ビルドアップ設定は従来どおり YAML から取得するので、入力の
325
+ // 正本は YAML に保たれる。
326
+ if (pathInput) {
327
+ const absPathInput = path.isAbsolute(pathInput)
328
+ ? pathInput : path.join(TASKS_DIR, pathInput);
329
+ args.push('--path-input', absPathInput);
330
+ }
331
+
321
332
  logger.info('Executing poker_cui calculation', {
322
333
  command: this.pokercuiCommand,
323
334
  args,
@@ -540,7 +551,7 @@ export class CalculationService {
540
551
  * @param {Object} dataManager - データマネージャーインスタンス(オプション)
541
552
  * @returns {Promise<Object>} 実行結果
542
553
  */
543
- async executeWithValidation(yamlFile, summaryOptions = {}, outputFiles = {}, timeout = this.defaultTimeout, dataManager = null) {
554
+ async executeWithValidation(yamlFile, summaryOptions = {}, outputFiles = {}, timeout = this.defaultTimeout, dataManager = null, pathInput = null) {
544
555
  try {
545
556
  logger.info('統合検証付き計算実行を開始', { yamlFile });
546
557
 
@@ -615,7 +626,7 @@ export class CalculationService {
615
626
  }
616
627
 
617
628
  // 通常の計算実行
618
- const calculationResult = await this.executeCalculation(yamlFile, summaryOptions, outputFiles, timeout);
629
+ const calculationResult = await this.executeCalculation(yamlFile, summaryOptions, outputFiles, timeout, pathInput);
619
630
 
620
631
  // poker_cuiがエラーを返した場合、事後診断を実行
621
632
  if (!calculationResult.success && dataManager) {
@@ -10,6 +10,7 @@
10
10
  // "POKER_INSTALL_PATH": "C:\\Poker"
11
11
  // }
12
12
  //
13
+ import fs from 'fs';
13
14
  import path from 'path';
14
15
  import os from 'os';
15
16
 
@@ -24,6 +25,88 @@ export const BACKUPS_DIR = path.join(BASE_DIR, 'backups');
24
25
  export const POKER_INSTALL_DIR = process.env.POKER_INSTALL_PATH ?? 'C:/Poker';
25
26
  export const POKER_LIB_DIR = path.join(POKER_INSTALL_DIR, 'LIB');
26
27
 
28
+ // FreeCAD のコマンドライン版(freecadcmd)。CAD 連携レイトレースで使う。
29
+ //
30
+ // 環境変数 FREECAD_PATH で指定する。実行ファイルそのものでも、
31
+ // インストールフォルダ(bin の親)でも受け付ける。
32
+ //
33
+ // 未設定なら既定の場所を順に探す。見つからなければ null を返し、
34
+ // CAD 連携のツールを呼んだときに「設定してください」と案内する。
35
+ // POKER_INSTALL_PATH のように既定値を決め打ちしないのは、FreeCAD の
36
+ // インストール先がバージョン番号を含み(FreeCAD 1.1, FreeCAD 1.0 …)
37
+ // 決め打ちが当たらないため。
38
+ // 探索の順序:
39
+ // 1. 環境変数 FREECAD_PATH(実行ファイルでもインストールフォルダでも可)
40
+ // 2. PATH に freecadcmd があるか
41
+ // 3. 既定のインストール先を走査(バージョン番号を決め打ちしない)
42
+ //
43
+ // バージョン番号を含むフォルダ(FreeCAD 1.1, FreeCAD 1.0 …)に入るため、
44
+ // POKER_INSTALL_PATH のような固定の既定値は使えない。
45
+ const FREECAD_SEARCH_ROOTS = [
46
+ 'C:/Program Files',
47
+ 'C:/Program Files (x86)',
48
+ '/usr/lib',
49
+ '/opt',
50
+ process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'Programs') : null,
51
+ ].filter(Boolean);
52
+
53
+ const FREECAD_EXE = process.platform === 'win32' ? 'freecadcmd.exe' : 'freecadcmd';
54
+
55
+ function underDir(dir) {
56
+ for (const rel of [path.join('bin', FREECAD_EXE), FREECAD_EXE]) {
57
+ const c = path.join(dir, rel);
58
+ if (fs.existsSync(c)) return c;
59
+ }
60
+ return null;
61
+ }
62
+
63
+ function fromPathEnv() {
64
+ const sep = process.platform === 'win32' ? ';' : ':';
65
+ for (const d of (process.env.PATH || '').split(sep)) {
66
+ if (!d) continue;
67
+ const c = path.join(d, FREECAD_EXE);
68
+ try { if (fs.existsSync(c)) return c; } catch { /* 権限などは無視 */ }
69
+ }
70
+ return null;
71
+ }
72
+
73
+ function scanRoots() {
74
+ // FreeCAD* に一致するフォルダを探す。新しいバージョンから試すため降順。
75
+ for (const root of FREECAD_SEARCH_ROOTS) {
76
+ let entries;
77
+ try { entries = fs.readdirSync(root); } catch { continue; }
78
+ const cands = entries.filter(n => /^freecad/i.test(n)).sort().reverse();
79
+ for (const n of cands) {
80
+ const hit = underDir(path.join(root, n));
81
+ if (hit) return hit;
82
+ }
83
+ }
84
+ // Linux の標準的な配置
85
+ for (const c of ['/usr/bin/freecadcmd', '/usr/local/bin/freecadcmd']) {
86
+ if (fs.existsSync(c)) return c;
87
+ }
88
+ return null;
89
+ }
90
+
91
+ function resolveFreeCad() {
92
+ const env = process.env.FREECAD_PATH;
93
+ if (env) {
94
+ if (fs.existsSync(env) && fs.statSync(env).isFile()) return env;
95
+ const hit = underDir(env);
96
+ if (hit) return hit;
97
+ return null; // 指定されているが見つからない(呼び出し側で案内する)
98
+ }
99
+ return fromPathEnv() || scanRoots();
100
+ }
101
+
102
+ export const FREECAD_CMD = resolveFreeCad();
103
+
104
+ // CAD 連携ツール(tools/ 配下の Python)の場所。
105
+ // npm パッケージにも同梱するので、このファイルからの相対で解決する。
106
+ export const CAD_TOOLS_DIR = path.resolve(
107
+ path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')),
108
+ '..', '..', 'tools');
109
+
27
110
  export const YAML_FILE = path.join(TASKS_DIR, 'poker.yaml');
28
111
  export const PENDING_FILE = path.join(TASKS_DIR, 'pending_changes.json');
29
112
  export const USER_CONFIG = path.join(BASE_DIR, 'config.json');