Files
2026-07-13 13:13:17 +08:00

288 lines
14 KiB
C#

using System.Text;
using T3.Core.Model;
using T3.Core.Operator;
using T3.Core.SystemUi;
using T3.Editor.Gui.UiHelpers;
using T3.Editor.SystemUi;
using T3.Editor.UiModel.Commands;
using T3.Editor.UiModel.Commands.Sections;
using T3.Editor.UiModel.Commands.Graph;
namespace T3.Editor.UiModel.Modification;
internal static class Combine
{
// todo - convert to proper c# style code generation
public static void CombineAsNewType(SymbolUi parentCompositionSymbolUi,
EditableSymbolProject project,
List<SymbolUi.Child> selectedChildUis,
List<Section> selectedSections,
string newSymbolName,
string nameSpace, string description, bool shouldBeTimeClip)
{
Dictionary<Guid, Guid> oldToNewIdMap = new Dictionary<Guid, Guid>();
Dictionary<Symbol.Connection, Guid> connectionToNewSlotIdMap = new Dictionary<Symbol.Connection, Guid>();
// get all the connections that go into the selection (selected ops as target)
var parentCompositionSymbol = parentCompositionSymbolUi.Symbol;
var potentialTargetIds = from child in selectedChildUis select child.Id;
var inputConnections = (from con in parentCompositionSymbol.Connections
from id in potentialTargetIds
where con.TargetParentOrChildId == id
where potentialTargetIds.All(potId => potId != con.SourceParentOrChildId)
select con).ToArray();
var inputsToGenerate = (from con in inputConnections
from child in parentCompositionSymbol.Children.Values
where child.Id == con.TargetParentOrChildId
from input in child.Symbol.InputDefinitions
where input.Id == con.TargetSlotId
select (child, input, con)).ToList().Distinct().ToArray();
var usingStringBuilder = new StringBuilder();
var inputStringBuilder = new StringBuilder();
var outputStringBuilder = new StringBuilder();
var connectionsFromNewInputs = new List<Symbol.Connection>(inputConnections.Length);
int inputNameCounter = 2;
var inputNameHashSet = new HashSet<string>();
foreach (var (child, input, origConnection) in inputsToGenerate)
{
var inputValueType = input.DefaultValue.ValueType;
if (TypeNameRegistry.Entries.TryGetValue(inputValueType, out var typeName))
{
var @namespace = input.DefaultValue.ValueType.Namespace;
usingStringBuilder.AppendLine("using " + @namespace + ";");
Guid newInputGuid = Guid.NewGuid();
connectionToNewSlotIdMap.Add(origConnection, newInputGuid);
var attributeString = " [Input(Guid = \"" + newInputGuid + "\")]";
inputStringBuilder.AppendLine(attributeString);
var newInputName = inputNameHashSet.Contains(input.Name) ? (input.Name + inputNameCounter++) : input.Name;
inputNameHashSet.Add(newInputName);
var slotString = (input.IsMultiInput ? "MultiInputSlot<" : "InputSlot<") + typeName + ">";
var inputString = " public readonly " + slotString + " " + newInputName + " = new " + slotString + "();";
inputStringBuilder.AppendLine(inputString);
inputStringBuilder.AppendLine("");
var newConnection = new Symbol.Connection(Guid.Empty, newInputGuid, child.Id, input.Id);
connectionsFromNewInputs.Add(newConnection);
}
else
{
Log.Error($"Error, no registered name found for typename: {input.DefaultValue.ValueType.Name}");
}
}
var outputConnections = (from con in parentCompositionSymbol.Connections
from id in potentialTargetIds
where con.SourceParentOrChildId == id
where potentialTargetIds.All(potId => potId != con.TargetParentOrChildId)
select con).ToArray();
var outputsToGenerate = (from con in outputConnections
from child in parentCompositionSymbol.Children.Values
where child.Id == con.SourceParentOrChildId
from output in child.Symbol.OutputDefinitions
where output.Id == con.SourceSlotId
select (child, output, con)).ToList().Distinct();
var connectionsToNewOutputs = new List<Symbol.Connection>(outputConnections.Length);
int outputNameCounter = 2;
var outputNameHashSet = new HashSet<string>();
foreach (var (child, output, origConnection) in outputsToGenerate)
{
var outputValueType = output.ValueType;
if (TypeNameRegistry.Entries.TryGetValue(outputValueType, out var typeName))
{
var @namespace = outputValueType.Namespace;
usingStringBuilder.AppendLine("using " + @namespace + ";");
Guid newOutputGuid = Guid.NewGuid();
var attributeString = " [Output(Guid = \"" + newOutputGuid + "\")]";
outputStringBuilder.AppendLine(attributeString);
var newOutputName = outputNameHashSet.Contains(output.Name) ? (output.Name + outputNameCounter++) : output.Name;
outputNameHashSet.Add(newOutputName);
var slotString = "Slot<" + typeName + ">";
var outputString = " public readonly " + slotString + " " + newOutputName + " = new " + slotString + "();";
outputStringBuilder.AppendLine(outputString);
outputStringBuilder.AppendLine("");
var newConnection = new Symbol.Connection(child.Id, output.Id, Guid.Empty, newOutputGuid);
connectionsToNewOutputs.Add(newConnection);
connectionToNewSlotIdMap.Add(origConnection, newOutputGuid);
}
else
{
Log.Error($"Error, no registered name found for typename: {output.ValueType.Name}");
}
}
usingStringBuilder.AppendLine("using T3.Core.Operator;");
usingStringBuilder.AppendLine("using T3.Core.Operator.Attributes;");
usingStringBuilder.AppendLine("using T3.Core.Operator.Slots;");
usingStringBuilder.AppendLine("using System.Runtime.InteropServices;");
Guid newSymbolId = Guid.NewGuid();
var classStringBuilder = new StringBuilder(usingStringBuilder.ToString());
classStringBuilder.AppendLine("");
classStringBuilder.Append("namespace ").Append(nameSpace);
classStringBuilder.AppendLine("{");
classStringBuilder.AppendLine(" [Guid(\"" + newSymbolId + "\")]");
classStringBuilder.AppendFormat(" internal sealed class {0} : Instance<{0}>\n", newSymbolName);
classStringBuilder.AppendLine(" {");
classStringBuilder.Append(outputStringBuilder);
classStringBuilder.AppendLine("");
classStringBuilder.Append(inputStringBuilder);
classStringBuilder.AppendLine(" }");
classStringBuilder.AppendLine("}");
classStringBuilder.AppendLine("");
var newSource = classStringBuilder.ToString();
Log.Debug(newSource);
// compile new instance type
var success = project.TryCompile(newSource, newSymbolName, newSymbolId, nameSpace, out var newSymbol, out var newSymbolUi, out var failureLog);
if (!success)
{
Log.Error($"Could not compile new symbol '{newSymbolName}': {failureLog}");
const string exit = "Exit";
var choice = BlockingWindow.Instance.ShowMessageBox("""
Sadly the compilation of the combined operator filed.
Potential reasons:
- An input name is using a known core type.
- Some other protected names or keywords are used
""" + failureLog,
"Can't compile",
exit,
"Try to continue");
if (choice != exit)
{
//reason = $"Failed to find soundTrack for [{symbol.Name}] - export cancelled, see log for details";
return;
}
EditorUi.Instance.ExitApplication();
return;
}
newSymbolUi.Description = description;
newSymbolUi.FlagAsModified();
// Apply content to new symbol
var copyCmd = new CopySymbolChildrenCommand(parentCompositionSymbolUi, selectedChildUis, selectedSections, newSymbolUi, Vector2.Zero);
copyCmd.Do();
var newChildrenArea = GetAreaFromChildren(newSymbolUi.ChildUis.Values);
// Initialize output positions
if (newSymbolUi.OutputUis.Count > 0)
{
var firstOutputPosition = new Vector2(newChildrenArea.Max.X + 300, (newChildrenArea.Min.Y + newChildrenArea.Max.Y) / 2);
foreach (var outputUi in newSymbolUi.OutputUis.Values)
{
outputUi.PosOnCanvas = firstOutputPosition;
firstOutputPosition += new Vector2(0, 100);
}
}
copyCmd.OldToNewChildIds.ToList().ForEach(x => oldToNewIdMap.Add(x.Key, x.Value));
var selectedChildrenIds = (from child in selectedChildUis select child.Id).ToList();
parentCompositionSymbol.Animator.RemoveAnimationsFromInstances(selectedChildrenIds);
foreach (var con in connectionsFromNewInputs)
{
var sourceId = con.SourceParentOrChildId;
var sourceSlotId = con.SourceSlotId;
var targetId = oldToNewIdMap[con.TargetParentOrChildId];
var targetSlotId = con.TargetSlotId;
var newConnection = new Symbol.Connection(sourceId, sourceSlotId, targetId, targetSlotId);
newSymbol.AddConnection(newConnection);
}
foreach (var con in connectionsToNewOutputs)
{
var sourceId = oldToNewIdMap[con.SourceParentOrChildId];
var sourceSlotId = con.SourceSlotId;
var targetId = con.TargetParentOrChildId;
var targetSlotId = con.TargetSlotId;
var newConnection = new Symbol.Connection(sourceId, sourceSlotId, targetId, targetSlotId);
newSymbol.AddConnection(newConnection);
}
// Insert instance of new symbol
var originalChildrenArea = GetAreaFromChildren(selectedChildUis);
var addCommand = new AddSymbolChildCommand(parentCompositionSymbolUi.Symbol, newSymbol.Id)
{ PosOnCanvas = originalChildrenArea.GetCenter() };
addCommand.Do();
var newSymbolChildId = addCommand.AddedChildId;
for (var i = inputConnections.Length - 1; i >= 0; i--) // reverse for multi input order preservation
{
var con = inputConnections[i];
var sourceId = con.SourceParentOrChildId;
var sourceSlotId = con.SourceSlotId;
var targetId = newSymbolChildId;
var targetSlotId = connectionToNewSlotIdMap[con];
var newConnection = new Symbol.Connection(sourceId, sourceSlotId, targetId, targetSlotId);
parentCompositionSymbol.AddConnection(newConnection);
}
for (var i = outputConnections.Length - 1; i >= 0; i--) // reverse for multi input order preservation
{
var con = outputConnections[i];
var sourceId = newSymbolChildId;
var sourceSlotId = connectionToNewSlotIdMap[con];
var targetId = con.TargetParentOrChildId;
var targetSlotId = con.TargetSlotId;
var newConnection = new Symbol.Connection(sourceId, sourceSlotId, targetId, targetSlotId);
parentCompositionSymbol.AddConnection(newConnection);
}
var deleteCmd = new DeleteSymbolChildrenCommand(parentCompositionSymbolUi, selectedChildUis);
deleteCmd.Do();
// Delete original sections
foreach (var section in selectedSections)
{
var deleteSectionCommand = new DeleteSectionCommand(parentCompositionSymbolUi, section);
deleteSectionCommand.Do();
}
// Creating a new symbol/assembly can't be cleanly undone (undoing the children delete would
// orphan the new operator), so drop the history rather than leave it inconsistent.
UndoRedoStack.Clear();
// Sadly saving in background does not save the source files.
// This needs to be fixed.
//T3Ui.SaveInBackground(false);
project.SaveModifiedSymbols();
}
private static ImRect GetAreaFromChildren(IEnumerable<SymbolUi.Child> childUis)
{
var min = new Vector2(float.PositiveInfinity, float.PositiveInfinity);
var max = new Vector2(float.NegativeInfinity, float.NegativeInfinity);
var hasAtLeastOne = false;
foreach (var childUi in childUis)
{
min = Vector2.Min(min, childUi.PosOnCanvas);
max = Vector2.Max(max, childUi.PosOnCanvas + childUi.Size);
hasAtLeastOne = true;
}
if (!hasAtLeastOne)
{
return new ImRect(new Vector2(-100, -100),
new Vector2(100, 100));
}
return new ImRect(min, max);
}
}