mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-08-03 01:09:29 -04:00
254 lines
6.2 KiB
C#
254 lines
6.2 KiB
C#
using System.IO;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace Editor;
|
|
|
|
/// <summary>
|
|
/// A popup dialog for creating models from mesh files (.fbx, .obj, .dmx).
|
|
/// Lets you configure collision type and output path.
|
|
/// </summary>
|
|
public class CreateModelFromMeshDialog : Widget
|
|
{
|
|
public enum CollisionMode
|
|
{
|
|
[Icon( "change_history" ), Title( "Convex Hull" )]
|
|
Hull,
|
|
[Icon( "grid_on" ), Title( "Exact Mesh" )]
|
|
Mesh,
|
|
[Icon( "block" )]
|
|
None,
|
|
}
|
|
|
|
public enum ScaleUnit
|
|
{
|
|
Inches,
|
|
Feet,
|
|
Meters,
|
|
Centimeters,
|
|
Millimeters,
|
|
Custom,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import options, remembered between uses. Add a property here to add a setting to the dialog.
|
|
/// </summary>
|
|
public class ImportOptions
|
|
{
|
|
[Property, Title( "Collision" )]
|
|
public CollisionMode Collision { get; set; } = CollisionMode.Hull;
|
|
|
|
/// <summary>
|
|
/// Unit presets, derived from <see cref="ImportScale"/> - same conversions ModelDoc uses.
|
|
/// </summary>
|
|
[Property, Title( "Units" ), JsonIgnore]
|
|
public ScaleUnit Units
|
|
{
|
|
get => ImportScale switch
|
|
{
|
|
1.0f => ScaleUnit.Inches,
|
|
12.0f => ScaleUnit.Feet,
|
|
39.37f => ScaleUnit.Meters,
|
|
0.3937f => ScaleUnit.Centimeters,
|
|
0.03937f => ScaleUnit.Millimeters,
|
|
_ => ScaleUnit.Custom,
|
|
};
|
|
set => ImportScale = value switch
|
|
{
|
|
ScaleUnit.Inches => 1.0f,
|
|
ScaleUnit.Feet => 12.0f,
|
|
ScaleUnit.Meters => 39.37f,
|
|
ScaleUnit.Centimeters => 0.3937f,
|
|
ScaleUnit.Millimeters => 0.03937f,
|
|
_ => ImportScale,
|
|
};
|
|
}
|
|
|
|
[Property, Title( "Import Scale" ), Range( 0.001f, 1000.0f, slider: false )]
|
|
public float ImportScale { get; set; } = 1.0f;
|
|
}
|
|
|
|
const string OptionsCookie = "CreateModelFromMeshDialog.ImportOptions";
|
|
|
|
readonly List<Asset> _meshFiles;
|
|
readonly ImportOptions _options;
|
|
readonly LineEdit _fileEdit;
|
|
readonly FolderEdit _folderEdit;
|
|
readonly Widget _fileRow;
|
|
readonly Widget _folderRow;
|
|
|
|
public CreateModelFromMeshDialog( List<Asset> meshFiles ) : base( null )
|
|
{
|
|
_meshFiles = meshFiles;
|
|
|
|
WindowFlags = WindowFlags.Dialog | WindowFlags.Customized | WindowFlags.WindowTitle | WindowFlags.CloseButton | WindowFlags.WindowSystemMenuHint;
|
|
DeleteOnClose = true;
|
|
WindowTitle = meshFiles.Count == 1
|
|
? $"Create Model from {Path.GetFileName( meshFiles[0].AbsolutePath )}"
|
|
: $"Create {meshFiles.Count} Models from Mesh Files";
|
|
SetWindowIcon( "view_in_ar" );
|
|
|
|
Layout = Layout.Column();
|
|
Layout.Margin = 16;
|
|
Layout.Spacing = 8;
|
|
|
|
if ( meshFiles.Count <= 6 )
|
|
{
|
|
foreach ( var mesh in meshFiles )
|
|
{
|
|
var fileName = Path.GetFileName( mesh.AbsolutePath );
|
|
Layout.Add( new Label( $" 📄 {fileName}" ) { Color = Theme.TextControl.WithAlpha( 0.7f ) } );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Layout.Add( new Label( $"{meshFiles.Count} mesh files selected" ) { Color = Theme.TextControl.WithAlpha( 0.7f ) } );
|
|
}
|
|
|
|
Layout.AddSpacingCell( 4 );
|
|
|
|
_options = EditorCookie.Get( OptionsCookie, new ImportOptions() );
|
|
|
|
foreach ( var prop in _options.GetSerialized() )
|
|
{
|
|
AddRow( prop.DisplayName, ControlWidget.Create( prop ) );
|
|
}
|
|
|
|
var defaultDir = Path.GetDirectoryName( meshFiles[0].AbsolutePath );
|
|
var defaultFile = Path.ChangeExtension( meshFiles[0].AbsolutePath, ".vmdl" );
|
|
|
|
_fileEdit = new LineEdit( this );
|
|
_fileEdit.Text = defaultFile;
|
|
_fileEdit.AddOptionToEnd( new Option( "Browse", "folder", BrowseFile ) );
|
|
|
|
_folderEdit = new FolderEdit( this );
|
|
_folderEdit.Text = defaultDir;
|
|
|
|
_fileRow = AddRow( "Save To", _fileEdit );
|
|
_folderRow = AddRow( "Save To", _folderEdit );
|
|
|
|
_fileRow.Visible = meshFiles.Count == 1;
|
|
_folderRow.Visible = meshFiles.Count > 1;
|
|
|
|
var footer = Layout.AddRow();
|
|
footer.Margin = new Sandbox.UI.Margin( 0, 8, 0, 0 );
|
|
footer.AddStretchCell();
|
|
|
|
var cancelButton = new Button( "Cancel", "close" );
|
|
cancelButton.Clicked = Close;
|
|
footer.Add( cancelButton );
|
|
|
|
footer.AddSpacingCell( 8 );
|
|
|
|
var createButton = new Button.Primary( "Create", "add" );
|
|
createButton.Clicked = OnCreate;
|
|
footer.Add( createButton );
|
|
|
|
FixedWidth = 420;
|
|
AdjustSize();
|
|
FixedHeight = Height;
|
|
|
|
var geo = EditorCookie.GetString( "CreateModelFromMeshDialog.Geometry", null );
|
|
if ( geo is not null )
|
|
{
|
|
RestoreGeometry( geo );
|
|
}
|
|
else
|
|
{
|
|
Position = Application.CursorPosition - new Vector2( Width * 0.5f, 3 );
|
|
ConstrainToScreen();
|
|
}
|
|
|
|
Show();
|
|
Focus();
|
|
}
|
|
|
|
protected override void OnClosed()
|
|
{
|
|
EditorCookie.SetString( "CreateModelFromMeshDialog.Geometry", SaveGeometry() );
|
|
base.OnClosed();
|
|
}
|
|
|
|
Widget AddRow( string label, Widget control )
|
|
{
|
|
var row = new Widget( this );
|
|
row.Layout = Layout.Row();
|
|
row.Layout.Spacing = 8;
|
|
row.Layout.Add( new Label( label ) { MinimumWidth = 90 } );
|
|
row.Layout.Add( control, 1 );
|
|
Layout.Add( row );
|
|
return row;
|
|
}
|
|
|
|
void BrowseFile()
|
|
{
|
|
var result = EditorUtility.SaveFileDialog( "Save Model As..", "vmdl", _fileEdit.Text );
|
|
if ( result is not null )
|
|
_fileEdit.Text = result;
|
|
}
|
|
|
|
void OnCreate()
|
|
{
|
|
EditorCookie.Set( OptionsCookie, _options );
|
|
|
|
Close();
|
|
|
|
if ( _meshFiles.Count == 1 )
|
|
{
|
|
var outputPath = _fileEdit.Text;
|
|
if ( string.IsNullOrWhiteSpace( outputPath ) )
|
|
return;
|
|
|
|
CreateModel( _meshFiles[0], outputPath );
|
|
}
|
|
else
|
|
{
|
|
var outputFolder = _folderEdit.Text;
|
|
if ( string.IsNullOrWhiteSpace( outputFolder ) )
|
|
return;
|
|
|
|
foreach ( var mesh in _meshFiles )
|
|
{
|
|
var outputPath = Path.Combine( outputFolder, Path.ChangeExtension( Path.GetFileName( mesh.AbsolutePath ), ".vmdl" ) );
|
|
if ( File.Exists( outputPath ) )
|
|
{
|
|
Log.Warning( $"Skipping {Path.GetFileName( outputPath )} - already exists" );
|
|
continue;
|
|
}
|
|
|
|
CreateModel( mesh, outputPath );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CreateModel( Asset mesh, string outputPath )
|
|
{
|
|
if ( !g_pToolFramework2.InitEngineTool( "modeldoc_editor" ) )
|
|
return;
|
|
|
|
var document = CModelDoc.Create();
|
|
|
|
g_pModelDocUtils.InitFromMesh( document, mesh.Path );
|
|
|
|
if ( _options.ImportScale > 0.0f && !_options.ImportScale.AlmostEqual( 1.0f ) )
|
|
{
|
|
document.SetImportScale( _options.ImportScale );
|
|
}
|
|
|
|
switch ( _options.Collision )
|
|
{
|
|
case CollisionMode.Hull:
|
|
document.AddPhysicsHullFromRender();
|
|
break;
|
|
case CollisionMode.Mesh:
|
|
document.AddPhysicsMeshFromRender();
|
|
break;
|
|
}
|
|
|
|
document.SaveToFile( outputPath );
|
|
document.DeleteThis();
|
|
|
|
var asset = AssetSystem.RegisterFile( outputPath );
|
|
asset?.Compile( true );
|
|
}
|
|
}
|