Merge remote-tracking branch 'origin/feat/inport-props' into feat/craft-discovery

# Conflicts:
#	Assets/External/Animated PBR Chest Demo/Materials/WoodChest.mat
#	Packages/com.distantlands.cozy.core/Content/Integration/Import for BiRP.unitypackage.meta
#	Packages/com.distantlands.cozy.core/Content/Integration/Import for HDRP.unitypackage.meta
#	Packages/com.distantlands.cozy.core/Content/Integration/Import for URP.unitypackage.meta
This commit is contained in:
2026-07-25 19:42:26 +02:00
954 changed files with 999772 additions and 26193 deletions
@@ -181,8 +181,8 @@ namespace Ashwild.EditorTools
/// <summary>
/// Target card: a Prefab/Mesh kind selector that filters the object picker to only those assets
/// (so materials, scripts and the like never clutter it), the object field itself, plus quick
/// Frame and auto-rotate controls that act on the loaded subject.
/// (so materials, scripts and the like never clutter it), the object field itself, per-axis
/// rotation sliders that pose the subject, plus quick Frame and auto-rotate controls.
/// </summary>
private VisualElement BuildTargetCard()
{
@@ -206,6 +206,13 @@ namespace Ashwild.EditorTools
card.Add(kind);
card.Add(field);
card.Add(SliderRow("Rotate X", 0f, 360f, stage.SubjectEuler.x,
v => stage.SetSubjectRotation(new Vector3(v, stage.SubjectEuler.y, stage.SubjectEuler.z))));
card.Add(SliderRow("Rotate Y", 0f, 360f, stage.SubjectEuler.y,
v => stage.SetSubjectRotation(new Vector3(stage.SubjectEuler.x, v, stage.SubjectEuler.z))));
card.Add(SliderRow("Rotate Z", 0f, 360f, stage.SubjectEuler.z,
v => stage.SetSubjectRotation(new Vector3(stage.SubjectEuler.x, stage.SubjectEuler.y, v))));
VisualElement buttons = new VisualElement();
buttons.AddToClassList("ss-buttons");
@@ -213,6 +220,10 @@ namespace Ashwild.EditorTools
frame.AddToClassList("ss-btn");
buttons.Add(frame);
Button resetRotation = new Button(() => { stage.SetSubjectRotation(Vector3.zero); RebuildControls(); RenderPreview(); }) { text = "Reset Rotation" };
resetRotation.AddToClassList("ss-btn");
buttons.Add(resetRotation);
Toggle rotate = new Toggle("Auto-rotate") { value = autoRotate };
rotate.RegisterValueChangedCallback(evt => SetAutoRotate(evt.newValue));
buttons.Add(rotate);
@@ -355,7 +366,10 @@ namespace Ashwild.EditorTools
/// <summary>
/// Builds the output-folder row: a read-only path label and a Browse button that opens a folder
/// picker and remembers the choice in EditorPrefs.
/// picker and remembers the choice in EditorPrefs. The picker starts from the resolved export
/// folder (normalised to native separators) so it opens where the label points — the native
/// Windows dialog ignores forward-slash paths like Application.dataPath and would otherwise fall
/// back to the shell's last-used location (often another project entirely).
/// </summary>
private VisualElement BuildFolderRow()
{
@@ -364,11 +378,12 @@ namespace Ashwild.EditorTools
Label path = new Label(ShortFolder());
path.AddToClassList("ss-path");
path.tooltip = exportFolder;
path.tooltip = ResolveFolder();
Button browse = new Button(() =>
{
string chosen = EditorUtility.OpenFolderPanel("Export folder", exportFolder, string.Empty);
string start = ResolveFolder().Replace('/', Path.DirectorySeparatorChar);
string chosen = EditorUtility.OpenFolderPanel("Export folder", start, string.Empty);
if (string.IsNullOrEmpty(chosen)) return;
exportFolder = chosen;
EditorPrefs.SetString(FolderPrefKey, exportFolder);
@@ -627,8 +642,8 @@ namespace Ashwild.EditorTools
/// <summary>
/// Captures the current view at the export resolution and writes it as a PNG to the chosen folder,
/// avoiding overwrites by auto-numbering, refreshing the AssetDatabase when the path is in-project,
/// and revealing the file. Guards a missing target and failed writes with clear logs.
/// avoiding overwrites by auto-numbering, importing it as a single-mode Sprite when the path is
/// in-project, and revealing the file. Guards a missing target and failed writes with clear logs.
/// </summary>
private void ExportPng()
{
@@ -656,7 +671,7 @@ namespace Ashwild.EditorTools
}
EditorPrefs.SetString(FilePrefKey, exportFileName);
RefreshIfInProject(path);
ImportAsSprite(path);
Debug.Log($"[ScreenshotStudio] Exported {exportWidth}×{exportHeight} PNG → {path}");
ShowNotification(new GUIContent($"Exported {Path.GetFileName(path)}"));
}
@@ -676,6 +691,7 @@ namespace Ashwild.EditorTools
string folder = ResolveFolder();
string baseName = Path.GetFileNameWithoutExtension(ResolveFileName());
float startYaw = stage.Yaw;
System.Collections.Generic.List<string> written = new System.Collections.Generic.List<string>();
try
{
@@ -691,7 +707,9 @@ namespace Ashwild.EditorTools
byte[] png = frame.EncodeToPNG();
DestroyImmediate(frame);
File.WriteAllBytes(Path.Combine(folder, $"{baseName}_{i:000}.png"), png);
string framePath = Path.Combine(folder, $"{baseName}_{i:000}.png");
File.WriteAllBytes(framePath, png);
written.Add(framePath);
}
}
catch (Exception e)
@@ -702,10 +720,10 @@ namespace Ashwild.EditorTools
{
EditorUtility.ClearProgressBar();
stage.Yaw = startYaw;
RefreshIfInProject(folder);
foreach (string framePath in written) ImportAsSprite(framePath);
RenderPreview();
Debug.Log($"[ScreenshotStudio] Exported {turntableFrames}-frame turntable → {folder}");
ShowNotification(new GUIContent($"Exported {turntableFrames} frames"));
Debug.Log($"[ScreenshotStudio] Exported {written.Count}-frame turntable → {folder}");
ShowNotification(new GUIContent($"Exported {written.Count} frames"));
}
}
@@ -752,15 +770,40 @@ namespace Ashwild.EditorTools
}
/// <summary>
/// Imports the written file so it appears in the Project window when it lives under Assets.
/// The project-relative "Assets/…" path for a written file, or null when it lives outside the
/// project — so external-folder exports skip the in-project import step entirely.
/// </summary>
private static void RefreshIfInProject(string path)
private static string ToAssetPath(string path)
{
string full = Path.GetFullPath(path).Replace('\\', '/');
string root = Path.GetFullPath(Application.dataPath).Replace('\\', '/');
if (!full.StartsWith(root, StringComparison.OrdinalIgnoreCase)) return;
if (!full.StartsWith(root, StringComparison.OrdinalIgnoreCase)) return null;
AssetDatabase.Refresh();
return "Assets" + full.Substring(root.Length);
}
/// <summary>
/// Imports the written file when it lives under Assets and retypes it as a single-mode Sprite, so
/// studio exports drop straight into UI/inventory work without a manual texture-type change in the
/// inspector. Files exported to an external folder are left untouched.
/// </summary>
private static void ImportAsSprite(string path)
{
string assetPath = ToAssetPath(path);
if (assetPath == null) return;
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceSynchronousImport);
TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
if (importer == null)
{
Debug.LogError($"[ScreenshotStudio] No TextureImporter for '{assetPath}' — cannot set Sprite type.");
return;
}
importer.textureType = TextureImporterType.Sprite;
importer.spriteImportMode = SpriteImportMode.Single;
importer.SaveAndReimport();
}
/// <summary>