Skip to content

Commit

Permalink
Merge pull request #26 from nasshu2916/feature/artnet-recorder-file-g…
Browse files Browse the repository at this point in the history
…enerate

DmxRecorder の保存先を wildcard で生成できるようにする
  • Loading branch information
nasshu2916 authored Nov 20, 2024
2 parents 6fc2ba5 + 7155a60 commit 5883757
Show file tree
Hide file tree
Showing 15 changed files with 408 additions and 38 deletions.
2 changes: 1 addition & 1 deletion Assets/ArtNet/Editor/DmxRecorder/ConvertAnimInspector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private static void ConvertAnim(ConvertAnim convertAnim)
.packet.Dmx));

TimelineConverter timelineConverter = new(universeData);
timelineConverter.SaveDmxTimelineClips(convertAnim.OutputDirectory);
timelineConverter.SaveDmxTimelineClips(convertAnim.OutputDirectory + "/ArtNetDmx.anim");

Debug.Log("Conversion complete");
}
Expand Down
198 changes: 198 additions & 0 deletions Assets/ArtNet/Editor/DmxRecorder/FileGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEngine;

namespace ArtNet.Editor.DmxRecorder
{
internal class Wildcard
{
private readonly Func<string> _resolver;

public string Pattern { get; }
public string Label { get; }

internal Wildcard(string pattern, Func<string> resolver)
{
Pattern = pattern;
Label = Pattern;
_resolver = resolver;
}

internal string Resolve()
{
return _resolver == null ? string.Empty : _resolver();
}
}

public static class DefaultWildcard
{
/// <summary>
/// The Recorder name.
/// </summary>
public static readonly string Recorder = GeneratePattern("Recorder");

/// <summary>
/// The date when the recording session started (in the yyyy-MM-dd format).
/// </summary>
public static readonly string Date = GeneratePattern("Date");

/// <summary>
/// The time the recording session started (in the 00h00m format).
/// </summary>
public static readonly string Time = GeneratePattern("Time");

/// <summary>
/// The take number (which is incremented every time a new session is started).
/// </summary>
public static readonly string Take = GeneratePattern("Take");

/// <summary>
/// The file extension of the output format.
/// </summary>
public static readonly string Extension = GeneratePattern("Extension");

private static string GeneratePattern(string tag)
{
return "<" + tag + ">";
}
}

/// <summary>
/// A class that provides a way to generate names of output files, with support for wildcards.
/// </summary>
[Serializable]
public class FileGenerator
{
[SerializeField] private string _directory = "Recordings";
[SerializeField] private string _fileName = DefaultWildcard.Recorder;

private readonly List<Wildcard> _wildcards;
internal IEnumerable<Wildcard> Wildcards => _wildcards;


public string Directory
{
get => _directory;
set => _directory = value;
}

public string FileName
{
get => _fileName;
set => _fileName = value;
}

internal RecorderSettings RecorderSettings { get; private set; }

internal FileGenerator(RecorderSettings recorderSettings)
{
RecorderSettings = recorderSettings;
_wildcards = new List<Wildcard>
{
new(DefaultWildcard.Recorder, RecorderResolver),
new(DefaultWildcard.Date, DateResolver),
new(DefaultWildcard.Time, TimeResolver),
new(DefaultWildcard.Take, TakeResolver),
new(DefaultWildcard.Extension, ExtensionResolver)
};
}

private string RecorderResolver()
{
return SanitizeInvalidName(RecorderSettings.name);
}

private static string DateResolver()
{
var date = DateTime.Now;
return date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
}

private static string TimeResolver()
{
var date = DateTime.Now;
return $"{date:HH}h{date:mm}m";
}

private string TakeResolver()
{
return RecorderSettings.Take.ToString("000");
}

private string ExtensionResolver()
{
return RecorderSettings.Extension;
}

public string AbsolutePath()
{
return OutputDirectory() + OutputFileName();
}

public string AssetsRelativePath()
{
var path = OutputDirectoryPath();
return "Assets/" + path + OutputFileName();
}

public string OutputDirectory()
{
var path = OutputDirectoryPath();
return Application.dataPath + Path.DirectorySeparatorChar + path;
}

private string OutputDirectoryPath()
{
var path = ApplyWildcards(Directory);
if (!string.IsNullOrEmpty(path) && !path.EndsWith("/"))
path += "/";
return path;
}

public string OutputFileName()
{
return ApplyWildcards(SanitizeFileName(FileName)) + "." + ExtensionResolver();
}

public void CreateDirectory()
{
var path = OutputDirectory();
if (!string.IsNullOrEmpty(path) && !System.IO.Directory.Exists(path))
System.IO.Directory.CreateDirectory(path);
}

/// <summary>
/// Replaces any invalid path character by "_".
/// </summary>
internal static string SanitizeInvalidName(string fileName)
{
var invalidChars = Path.GetInvalidFileNameChars();
return invalidChars.Aggregate(fileName, (current, c) => current.Replace(c, '_'));
}

/// <summary>
/// Replaces any occurrence of "/" or "\" in file name with "_".
/// </summary>
internal static string SanitizeFileName(string fileName)
{
return Regex.Replace(fileName, @"[\\|/]", "_");
}

private string ApplyWildcards(string str)
{
if (string.IsNullOrEmpty(str))
return string.Empty;

foreach (var w in Wildcards)
{
str = str.Replace(w.Pattern, w.Resolve());
}

return str;
}
}
}
3 changes: 3 additions & 0 deletions Assets/ArtNet/Editor/DmxRecorder/FileGenerator.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

148 changes: 148 additions & 0 deletions Assets/ArtNet/Editor/DmxRecorder/FileGeneratorDrawer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using System.IO;
using UnityEditor;
using UnityEngine;

namespace ArtNet.Editor.DmxRecorder
{
[CustomPropertyDrawer(typeof(FileGenerator))]
internal class FileGeneratorDrawer : TargetedPropertyDrawer<FileGenerator>
{
private static class Styles
{
internal static readonly GUIContent FileNameLabel = new("File Name", "The name of the file to record to.");
internal static readonly GUIContent AddWildcardButton = new("+ Wildcards",
"Add a wildcard to the file name. Wildcards are replaced with values.");
internal static readonly GUIContent PathSelectButton = new("...", "Select the output location");
}

private SerializedProperty _fileName;
private SerializedProperty _directory;

private static readonly GUIStyle PathPreviewStyle = new(GUI.skin.label)
{
wordWrap = true,
stretchHeight = true,
stretchWidth = true,
padding = new RectOffset(20, 0, 0, 0),
clipping = TextClipping.Overflow
};

private static Texture2D _openPathIcon;

protected override void Initialize(SerializedProperty property)
{
if (_openPathIcon == null) _openPathIcon = IconHelper.FolderOpen as Texture2D;

base.Initialize(property);

_fileName = property.FindPropertyRelative("_fileName");
_directory = property.FindPropertyRelative("_directory");
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
Initialize(property);

EditorGUI.BeginProperty(position, label, property);

using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PropertyField(_fileName, Styles.FileNameLabel);

if (GUILayout.Button(Styles.AddWildcardButton, EditorStyles.popup, GUILayout.Width(90)))
{
GUI.FocusControl(null);
var menu = new GenericMenu();

foreach (var w in Target.Wildcards)
{
var pattern = w.Pattern;
menu.AddItem(new GUIContent(w.Label),
false,
() =>
{
_fileName.stringValue += pattern;
_fileName.serializedObject.ApplyModifiedProperties();
});
}

menu.ShowAsContext();
}
}


using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Assets Path", GUILayout.Width(EditorGUIUtility.labelWidth));

var restRect = GUILayoutUtility.GetRect(GUIContent.none, GUI.skin.textField);
var labelRect = new Rect(restRect.x, restRect.y, 50, restRect.height);
GUI.Label(labelRect, "Assets" + Path.DirectorySeparatorChar, EditorStyles.label);
var directoryInputRect = new Rect(
restRect.x + 55,
restRect.y,
restRect.width - 55,
restRect.height
);
_directory.stringValue = EditorGUI.TextField(directoryInputRect, _directory.stringValue);

if (GUILayout.Button(Styles.PathSelectButton, EditorStyles.miniButton, GUILayout.Width(30)))
{
var outputDirectory = Target.OutputDirectory();
var newDirectory = EditorUtility.OpenFolderPanel("Select Folder", outputDirectory, "");
GUI.FocusControl(null);

if (!string.IsNullOrEmpty(newDirectory))
{
if (!newDirectory.StartsWith(Application.dataPath))
{
EditorUtility.DisplayDialog("Invalid Path",
"Selected path " + newDirectory + " is not within the project's Assets folder.",
"Ok");
}
else
{
newDirectory = newDirectory[Application.dataPath.Length..];
if (newDirectory.StartsWith("/") || newDirectory.StartsWith("\\"))
{
newDirectory = newDirectory[1..];
}

_directory.stringValue = newDirectory;
_directory.serializedObject.ApplyModifiedProperties();
}
}

GUIUtility.ExitGUI();
}
}


using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PrefixLabel(" ");

var layoutOptions = new[]
{
GUILayout.ExpandWidth(true),
GUILayout.ExpandHeight(true)
};

var outputPath = Target.AbsolutePath();
var rect = GUILayoutUtility.GetRect(new GUIContent(outputPath), PathPreviewStyle, layoutOptions);
EditorGUI.SelectableLabel(rect, outputPath, PathPreviewStyle);

if (GUILayout.Button(_openPathIcon, EditorStyles.miniButton, GUILayout.Width(30)))
{
var outputDir = Target.OutputDirectory();
var dir = new DirectoryInfo(outputDir);
if (!dir.Exists) dir.Create();

EditorUtility.RevealInFinder(outputDir);
}
}

EditorGUI.EndProperty();
}
}
}
3 changes: 3 additions & 0 deletions Assets/ArtNet/Editor/DmxRecorder/FileGeneratorDrawer.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Assets/ArtNet/Editor/DmxRecorder/IconHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public static class IconHelper
public static Texture PlayButton => Icon("PlayButton@2x", true);
public static Texture PreMatQuad => Icon("PreMatQuad@2x", true);
public static Texture PauseButton => Icon("PauseButton@2x", true);
public static Texture FolderOpen => Icon("FolderOpened Icon");

internal static Texture Icon(string iconPath, bool provideDarkModel = false)
{
Expand Down
Loading

0 comments on commit 5883757

Please sign in to comment.