[C# Avalonia] 3D Model Explorer #02 — Windows用 WPFアプリをLinuxに移植

[C# Avalonia] 3D Model Explorer #02 — Windows用 WPFアプリをLinuxに移植

なぜLinux版を作ったのか

1年以上 lain-lab.com でブラウザツールを量産してきて、VRM/PMX/GLB周りのブラウザでできることは大体やり尽くし、

ずっとWeb系のツール開発を続けてきて流石に飽きてきたので、昨日は、以前から何となくほしいと思っていたWindows用の複数のモデルを管理できるアプリを開発。

WPF版の3D Model Explorerが完成した翌日、知的好奇心でLinuxでも動かせるんじゃないかと思い立ち開発をして完成までメモです。

[C# Avalonia] 3D Model Explorer #02 — Windows用 WPFアプリをLinuxに移植

技術スタック

  • 言語: C# / Avalonia UI 11.1.0(.NET 8.0)
  • ターゲット: Linux x64(Debian 12 bookworm)
  • 外部ライブラリ: Avalonia関連のみ(バイナリパースは標準ライブラリのみ)
  • 開発環境: Windows VS Code + Remote-SSH → Debian 12(GUI確認は xrdp)
  • 配布: self-contained SingleFile + tar.gz(約85MB)

WPF → Avalonia で変わったこと

Avalonia UIはWPFに極めて近い設計思想を持つクロスプラットフォームUIフレームワーク。移植で書き換えが必要だった箇所は限定的。

XAML の名前空間

<!-- WPF -->
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" ...>

<!-- Avalonia -->
<Window xmlns="https://github.com/avaloniaui" ...>

GridStackPanelDockPanelScrollViewerWrapPanel — レイアウト系はすべてそのまま使える。

ToolTip

<!-- WPF -->
<Button ToolTip="フォルダを登録"/>

<!-- Avalonia -->
<Button ToolTip.Tip="フォルダを登録"/>

StatusBar

Avalonia には StatusBar コントロールがない。Border + DockPanel で代替。

<!-- WPF -->
<StatusBar Grid.Row="2">
    <StatusBarItem><TextBlock x:Name="StatusText"/></StatusBarItem>
</StatusBar>

<!-- Avalonia -->
<Border Grid.Row="2" Background="#F0F0F0" Padding="4,2">
    <DockPanel>
        <TextBlock x:Name="StatusText" Text="準備完了"/>
        <TextBlock x:Name="FileCountText" DockPanel.Dock="Right" HorizontalAlignment="Right"/>
    </DockPanel>
</Border>

BitmapImage → Bitmap

WPFとAvaloniaで画像の扱いが大きく異なる。ここが移植の最大の変更点。

// WPF — 初期化・凍結が必要
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = new MemoryStream(imageData);
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();

// Avalonia — ストリームを渡すだけ
using var ms = new MemoryStream(imageData);
var bitmap = new Avalonia.Media.Imaging.Bitmap(ms);

WPFの BeginInit / EndInit / Freeze が不要になり、コードがシンプルになった。

イベントハンドラ

// WPF
border.MouseLeftButtonUp += OnThumbnailClick;
border.MouseEnter += (s, e) => ...;

// Avalonia
border.PointerPressed += (s, e) => ...;

MouseLeftButtonUpPointerPressedMouseButtonEventArgsPointerPressedEventArgs

ファイルダイアログ

WPFの Microsoft.Win32.OpenFileDialog / OpenFolderDialog は使えない。Avalonia では StorageProvider API を使う。

// WPF
var dialog = new OpenFolderDialog { Title = "フォルダを選択" };
if (dialog.ShowDialog() == true) { ... }

// Avalonia
var folders = await StorageProvider.OpenFolderPickerAsync(
    new FolderPickerOpenOptions { Title = "フォルダを選択", AllowMultiple = false });
if (folders.Count > 0) { var path = folders[0].Path.LocalPath; ... }

非同期APIになっているため、イベントハンドラに async が必要。Linux上ではGTKのネイティブダイアログが表示される。

設定ファイルの保存先

// WPF (Windows)
// %APPDATA%\3DModelExplorer\settings.json

// Avalonia (Linux)
// ~/.config/3DModelExplorer/settings.json

Environment.SpecialFolder.ApplicationData がLinuxでは ~/.config を返す。XDG Base Directory準拠。

変わらなかったこと(全部)

以下のロジックは 1行も書き直さずに そのまま動いた。

  • VRMサムネイル抽出(GLBバイナリパース、VRM 0.x/1.0両対応)
  • GLBサムネイル抽出(asset.extras / EXT_thumbnail / 名前フォールバック)
  • ZIPサムネイル抽出(thumbnail.png/jpg/webp探索)
  • ZIP内モデル形式判定(PMX/FBX/GLB/VRM)
  • VRMメタデータ抽出(タイトル・作者・バージョン・ライセンス)
  • GLBメタデータ抽出(タイトル・ジェネレータ・バージョン・著作権)
  • PMXメタデータ抽出(モデル名・コメント・頂点数、UTF-16LE/UTF-8自動判定)
  • FBXメタデータ抽出(Creator・ボーン数・メッシュ数、ASCII/バイナリ両対応)
  • 設定の読み書き(System.Text.Json によるJSON永続化)
  • 検索ロジック(現フォルダ/全フォルダ切替)
  • お気に入り管理
  • タグ管理・フィルタリング

BinaryReaderSystem.Text.JsonSystem.IO.Compression は .NET 標準ライブラリなので、プラットフォームに依存しない。WPF固有のAPIを使っていない純C#コードは、そのままAvaloniaに持ってこれる。

これが「WPFで書いた資産をAvaloniaで再利用できる」最大のメリット。

環境構築の罠

Debian 12 + .NET 8 + Avalonia UI の環境構築では6つのトラップを踏み抜いた。詳細は別記事にまとめている。

[Linux] Debian 12 + .NET 8 環境で Avalonia UI の空ウィンドウを出すまでに踏み抜いた全ハマりポイント

要点だけ挙げると:

  1. Debian 12の標準リポジトリに dotnet-sdk-8.0 がない → Microsoft公式リポジトリ追加
  2. Avaloniaテンプレートがデフォルトで .NET 10 をターゲット → -f net8.0 明示指定
  3. 最新Avalonia(12.x)と .NET 8のRoslynバージョン不整合 → Avalonia 11.1.0 に固定
  4. Source Generatorのサイレントスキップで InitializeComponent 消失

空ウィンドウを出すまでに環境構築の摩擦係数90%を使い果たした。

開発環境と開発時間

  • コード編集: Windows VS Code + Remote-SSH で Debian に接続
  • ビルド・実行: VS Code内ターミナルから dotnet run
  • GUI確認: xrdp でリモートデスクトップ接続
  • ソース管理: Git + GitHub(Private)
  • 開発時間: 実質約2時間

WPF版(約5時間)の半分以下の時間で同等の機能を移植できた。バイナリパースのロジックを書き直す必要がなかったのが最大の理由。

ビルドと配布

self-contained SingleFileビルドで、.NETランタイム不要の実行ファイルを生成。

dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true

実行ファイル + Skia/HarfBuzzの .so ファイル(Avaloniaの描画エンジン)を tar.gz で配布。合計約85MB。

cd bin/Release/net8.0/linux-x64/publish/
tar czf ~/3D_Model_Explorer_Linux_v1.0.0.tar.gz model-explorer-linux *.so

利用者は解凍して実行するだけ。

tar xzf 3D_Model_Explorer_Linux_v1.0.0.tar.gz
chmod +x model-explorer-linux
./model-explorer-linux

まとめ

  • WPF → Avalonia の移植は、UIの名前空間とダイアログAPIの変更がメイン
  • バイナリパース・JSON処理・検索/タグ/お気に入りのロジックは完全に再利用できた
  • 「触っておけば次に活きる」を3段階で実証(Web → WPF → Avalonia)
  • Linux版の3Dモデル管理ツールは検索しても出てこない。ニッチの王を狙う

リポジトリ

GitHub: fixtan/3D_Model_Explorer_Linux(現在Private)