refactor(sourceengine): extract Source Engine into a plugin + schema-driven exporter contract#28
Merged
Conversation
…ema-driven exporter contract
Source Engine support now lives in a standalone GMConverter.SourceEngine plugin
assembly. Core no longer contains MDL importer/exporter code, the Source/ directory,
the MdlCrowbar package reference, or the CoACD MSBuild target. The SDK exporter
contract also moves to a schema-driven shape that the generic UI panel (follow-up
PR) will render directly.
Plugin extraction
- New GMConverter.SourceEngine project (net10.0, library) referencing only
GMConverter.SDK + MdlCrowbar + ImageSharp. 12 files moved from Core:
MDLImporter, MDLExporter, all of Source/{GameInfo,MaterialOptimizationOptions,
PhysicsOptions,SourceMaterialCompiler,SourceMaterialSurfaceProps,
SourcePhongSettings,SourceToolPaths}, plus the UE-side-but-Source-specific
helpers ProcessRunner, CoacdNative, CoacdDecompositionOptions.
- The plugin's CoACD MSBuild download target moved with it; lib_coacd is
resolved at runtime via PluginLoadContext's sibling-folder fallback
(introduced in #26 for CUE4Parse-Natives).
- SourceEnginePlugin : IPlugin registers MDLImporter and MDLExporter via DI;
both receive ITextureFactory via constructor injection.
- plugin.json id is "gmconverter.sourceengine".
- Source-specific texture transforms (ToSourcePhongExponent, WithMaskInAlpha)
reimplemented in the plugin against the SDK's new Texture.GetRgbaPixels()
surface + ITextureFactory.FromRgba(). Core's TextureExtensions keeps the
general-purpose transforms (ToGltfMetallicRoughness, WithOpenGlNormalMap,
ToSpecularFactorMask).
Schema-driven exporter contract
- New SDK types: OptionType (enum), OptionDescriptor (data, with deferred-default
factory hook), OptionGroup, ExporterOptionSchema, ExportOptions (typed-accessor
bag). IExporter<TOptions> and IExporterDescriptor collapsed into a single
non-generic IExporter exposing OptionSchema + Export(Model, string, string,
ExportOptions). Plugins read what they need from the bag and assemble their
own internal typed options.
- MDLExporter declares the full ~12-option schema across Tools / Materials /
Physics groups. GLTFExporter declares a minimal {binary, bakeUvTransforms}
schema; OBJExporter declares ExporterOptionSchema.Empty.
Host bundling
- GMConverter.CLI.csproj and GMConverter.UI.csproj both add a <BundledPlugin>
entry for SourceEngine (same shape as the existing UnrealEngine entry). The
CopyBundledPlugins target stages the plugin's bin into
bin/.../plugins/sourceengine/, where PluginLoader picks it up at startup.
- The host's MdlCrowbar package ref is removed from GMConverter/GMConverter.csproj.
Host-side surface cleanup
- CLI Program.cs and UI ConversionService.cs no longer reference plugin-private
Source types (PhysicsOptions, PhysicsMode, CoacdOptions, SourceToolPaths,
MDLExporter, MDLImporter). Both build ExportOptions bags from primitive
inputs and look up the exporter via PluginHost.Registry.GetExporter("mdl").
Failing that, they surface a clear error pointing at the missing plugin.
- ConvertViewModel's auto-fill of StudioMDL / VTFCmd paths uses a small
local copy of the path-discovery convention (./tools/<tool>/...) instead of
calling into the plugin. The plugin keeps its own copy for the resolve
path used at export time.
- UI's CoACD physics preview removed; preview falls back to a bounds
visualisation for both modes. Actual export still produces CoACD physics
when the user picks that mode. Surfacing a plugin-contributed preview hook
is a follow-up.
Empirically verified
- CLI smoke: runs against an empty test.psk, plugin log line
"Loaded plugin gmconverter.sourceengine v0.1.0" appears, importer error
surfaces as expected.
- Plugin deploy: bin/Release/net10.0/plugins/sourceengine/ contains
GMConverter.SourceEngine.dll, MdlCrowbar.dll, lib_coacd.dll, plugin.json
and the rest of the runtime deps.
- dotnet build GMConverter.slnx -c Release → 0 warnings, 0 errors
- dotnet format --verify-no-changes → clean
Deferred to follow-up
- Generic schema-driven UI panel (ExporterOptionsPanel.axaml + DataTemplates
+ ExporterOptionsViewModel). The existing SourceEngineSettings.axaml panel
still binds ConvertViewModel's typed Source properties; ConversionService
pipes those into the ExportOptions bag at runtime. The schema is the source
of truth for keys and defaults but rendering remains bespoke for Source
until the generic panel work lands.
- CLI dynamic argument construction from the schema (the schema declares the
options but the CLI still has hand-rolled --studiomdl-path etc. arguments).
- Manual UE4/UE5/Source runtime verification against real archives — same
gap as #25.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Source Engine support now lives in a standalone
GMConverter.SourceEngineplugin assembly. Core no longer contains MDL import/export code, theSource/directory, theMdlCrowbarpackage reference, or the CoACD MSBuild target. The SDK exporter contract also moves to a schema-driven shape that a future generic UI panel can render directly.Plugin extraction
GMConverter.SourceEngineproject (net10.0 library) referencing onlyGMConverter.SDK+MdlCrowbar+ImageSharp.MDLImporter,MDLExporter, all ofSource/*(GameInfo,MaterialOptimizationOptions,PhysicsOptions,SourceMaterialCompiler,SourceMaterialSurfaceProps,SourcePhongSettings,SourceToolPaths), plus the Source-only helpersProcessRunner,CoacdNative,CoacdDecompositionOptions.lib_coacdis resolved at runtime viaPluginLoadContext's sibling-folder fallback (introduced in fix(unrealengine): copy NuGet runtime DLLs into plugin bin so deploy ships them #26 for CUE4Parse-Natives).SourceEnginePlugin : IPluginregistersMDLImporterandMDLExportervia DI; both receiveITextureFactoryvia constructor injection.plugin.jsonid:gmconverter.sourceengine.ToSourcePhongExponent,WithMaskInAlpha) reimplemented in the plugin against the SDK's newTexture.GetRgbaPixels()surface +ITextureFactory.FromRgba(). Core'sTextureExtensionskeeps the general-purpose transforms (ToGltfMetallicRoughness,WithOpenGlNormalMap,ToSpecularFactorMask).Schema-driven exporter contract
OptionType(enum),OptionDescriptor(data, with deferred-default factory hook),OptionGroup,ExporterOptionSchema,ExportOptions(typed-accessor bag).IExporter<TOptions>andIExporterDescriptorcollapsed into a single non-genericIExporterexposingOptionSchema+Export(Model, string, string, ExportOptions). Plugins read what they need from the bag and assemble their own internal typed options.MDLExporterdeclares the full ~12-option schema across Tools / Materials / Physics groups.GLTFExporterdeclares a minimal{binary, bakeUvTransforms}schema;OBJExporterdeclaresExporterOptionSchema.Empty.Host bundling
GMConverter.CLI.csprojandGMConverter.UI.csprojboth add a<BundledPlugin>entry for SourceEngine (same shape as the existing UnrealEngine entry).CopyBundledPluginsstages the plugin'sbin/intobin/.../plugins/sourceengine/.MdlCrowbarpackage ref is removed from Core's csproj.Host-side surface cleanup
Program.csand UIConversionService.csno longer reference plugin-private Source types (PhysicsOptions,PhysicsMode,CoacdOptions,SourceToolPaths,MDLExporter,MDLImporter). Both buildExportOptionsbags from primitive inputs and look up the exporter viaPluginHost.Registry.GetExporter(\"mdl\"). Failing that, they surface a clear error pointing at the missing plugin.ConvertViewModel's auto-fill of StudioMDL / VTFCmd paths uses a small local copy of the path-discovery convention (./tools/<tool>/...) instead of calling into the plugin. The plugin keeps its own copy for the resolve path used at export time.Empirically verified
test.psk, plugin log lineLoaded plugin gmconverter.sourceengine v0.1.0appears alongsideLoaded plugin gmconverter.unrealengine v0.1.0, importer error surfaces as expected.bin/Release/net10.0/plugins/sourceengine/containsGMConverter.SourceEngine.dll,MdlCrowbar.dll,lib_coacd.dll,plugin.jsonand the rest of the runtime deps.dotnet build GMConverter.slnx -c Release→ 0 warnings, 0 errorsdotnet format --verify-no-changes→ cleanDeferred to follow-up (explicit non-goals)
ExporterOptionsPanel.axaml+DataTemplates +ExporterOptionsViewModel). The existingSourceEngineSettings.axamlpanel still bindsConvertViewModel's typed Source properties;ConversionServicepipes those into theExportOptionsbag at runtime. The schema is the source of truth for keys and defaults but rendering remains bespoke for Source until the generic panel lands.--studiomdl-pathetc. arguments.🤖 Generated with Claude Code