Răsfoiți Sursa

[animgraph] Bugfixes

- prevent animgraph Output from being removed
- Style tweaks
- Editor crash fixes
- Ui improvements
Clément Espeute 8 luni în urmă
părinte
comite
4f77e39fe3

+ 26 - 6
bin/style.css

@@ -4162,7 +4162,8 @@ button-2 {
   min-height: 16px;
   min-height: 16px;
   min-width: 24px;
   min-width: 24px;
   display: flex;
   display: flex;
-  justify-content: stretch;
+  justify-content: center;
+  align-items: center;
   border: var(--basic-border);
   border: var(--basic-border);
   border-radius: var(--basic-border-radius);
   border-radius: var(--basic-border-radius);
   box-sizing: border-box;
   box-sizing: border-box;
@@ -4172,9 +4173,6 @@ button-2 {
   padding-bottom: var(--basic-padding);
   padding-bottom: var(--basic-padding);
   background-color: var(--bg-2);
   background-color: var(--bg-2);
 }
 }
-button-2 value {
-  flex-grow: 1;
-}
 button-2:hover {
 button-2:hover {
   background-color: var(--hover);
   background-color: var(--hover);
 }
 }
@@ -4193,7 +4191,7 @@ graph-editor-root {
 graph-editor-root graph-container {
 graph-editor-root graph-container {
   display: flex;
   display: flex;
   flex: 1 1;
   flex: 1 1;
-  background-color: green;
+  background-color: var(--bg-1);
   position: relative;
   position: relative;
 }
 }
 graph-editor-root graph-container preview-container {
 graph-editor-root graph-container preview-container {
@@ -4296,6 +4294,9 @@ graph-editor-root properties-container graph-parameters > ul graph-parameter hea
   border-radius: var(--basic-border-radius);
   border-radius: var(--basic-border-radius);
   border: var(--basic-border);
   border: var(--basic-border);
 }
 }
+graph-editor-root properties-container graph-parameters > ul graph-parameter header .reorder {
+  padding: var(--basic-padding);
+}
 graph-editor-root properties-container graph-parameters > ul graph-parameter content {
 graph-editor-root properties-container graph-parameters > ul graph-parameter content {
   border-top: var(--basic-border);
   border-top: var(--basic-border);
   padding: var(--basic-padding);
   padding: var(--basic-padding);
@@ -4359,6 +4360,14 @@ graph-editor-root properties-container graph-parameters > ul graph-parameter:aft
   bottom: 0px;
   bottom: 0px;
   pointer-events: none;
   pointer-events: none;
 }
 }
+center-content {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
 /** Blendspace2dEditor **/
 /** Blendspace2dEditor **/
 blend-space-2d-root {
 blend-space-2d-root {
   width: 100%;
   width: 100%;
@@ -4377,13 +4386,19 @@ blend-space-2d-root main-panel {
   align-items: stretch;
   align-items: stretch;
 }
 }
 blend-space-2d-root main-panel preview-container {
 blend-space-2d-root main-panel preview-container {
-  background-color: blue;
+  position: relative;
+  background-color: var(--bg-1);
   flex: 1;
   flex: 1;
 }
 }
 blend-space-2d-root main-panel preview-container .scene-preview {
 blend-space-2d-root main-panel preview-container .scene-preview {
   width: 100%;
   width: 100%;
   height: 100%;
   height: 100%;
 }
 }
+blend-space-2d-root main-panel preview-container .hide-toolbar2 {
+  position: absolute;
+  top: 8px;
+  right: 8px;
+}
 blend-space-2d-root main-panel graph-container {
 blend-space-2d-root main-panel graph-container {
   height: 40%;
   height: 40%;
   background-color: var(--bg-1);
   background-color: var(--bg-1);
@@ -4436,6 +4451,11 @@ blend-space-2d-root properties-container {
 blend-space-2d-root properties-container parameters-container {
 blend-space-2d-root properties-container parameters-container {
   flex-grow: 1;
   flex-grow: 1;
 }
 }
+.basic-border {
+  padding: var(--basic-padding);
+  border: var(--basic-border);
+  border-radius: var(--basic-border-radius);
+}
 .anim-list {
 .anim-list {
   padding: var(--basic-padding);
   padding: var(--basic-padding);
   border: var(--basic-border);
   border: var(--basic-border);

+ 30 - 7
bin/style.less

@@ -4918,7 +4918,8 @@ button-2 {
 	min-height: 16px;
 	min-height: 16px;
 	min-width: 24px;
 	min-width: 24px;
 	display: flex;
 	display: flex;
-	justify-content: stretch;
+	justify-content: center;
+	align-items: center;
 	border: var(--basic-border);
 	border: var(--basic-border);
 	border-radius: var(--basic-border-radius);
 	border-radius: var(--basic-border-radius);
 	box-sizing: border-box;
 	box-sizing: border-box;
@@ -4929,10 +4930,6 @@ button-2 {
 
 
 	background-color: var(--bg-2);
 	background-color: var(--bg-2);
 
 
-	value {
-		flex-grow: 1;
-	}
-
 	&:hover {
 	&:hover {
 		background-color: var(--hover);
 		background-color: var(--hover);
 	}
 	}
@@ -4956,7 +4953,7 @@ graph-editor-root {
 	graph-container {
 	graph-container {
 		display: flex;
 		display: flex;
 		flex: 1 1;
 		flex: 1 1;
-		background-color: green;
+		background-color: var(--bg-1);
 		position: relative;
 		position: relative;
 
 
 		preview-container {
 		preview-container {
@@ -5081,6 +5078,10 @@ graph-editor-root {
 							border-radius: var(--basic-border-radius);
 							border-radius: var(--basic-border-radius);
 							border: var(--basic-border);
 							border: var(--basic-border);
 						}
 						}
+
+						.reorder {
+							padding: var(--basic-padding);
+						}
 					}
 					}
 
 
 					content {
 					content {
@@ -5163,6 +5164,15 @@ graph-editor-root {
 	}
 	}
 }
 }
 
 
+center-content {
+	width: 100%;
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+}
+
 /** Blendspace2dEditor **/
 /** Blendspace2dEditor **/
 
 
 blend-space-2d-root {
 blend-space-2d-root {
@@ -5183,13 +5193,20 @@ blend-space-2d-root {
 		align-items: stretch;
 		align-items: stretch;
 
 
 		preview-container {
 		preview-container {
-			background-color: blue;
+			position: relative;
+			background-color: var(--bg-1);
 			flex: 1;
 			flex: 1;
 
 
 			.scene-preview {
 			.scene-preview {
 				width: 100%;
 				width: 100%;
 				height: 100%;
 				height: 100%;
 			}
 			}
+
+			.hide-toolbar2 {
+				position: absolute;
+				top: 8px;
+				right: 8px;
+			}
 		}
 		}
 
 
 		graph-container {
 		graph-container {
@@ -5264,6 +5281,12 @@ blend-space-2d-root {
 
 
 }
 }
 
 
+.basic-border {
+	padding: var(--basic-padding);
+	border: var(--basic-border);
+	border-radius: var(--basic-border-radius);
+}
+
 .anim-list {
 .anim-list {
 	padding: var(--basic-padding);
 	padding: var(--basic-padding);
 	border: var(--basic-border);
 	border: var(--basic-border);

+ 3 - 1
hide/comp/Scene.hx

@@ -340,7 +340,7 @@ class Scene extends hide.comp.Component implements h3d.IDrawable {
 		cleanup.push(function() { ide.fileWatcher.unregister( path, onChange ); });
 		cleanup.push(function() { ide.fileWatcher.unregister( path, onChange ); });
 	}
 	}
 
 
-	public function listAnims( path : String ) {
+	public function listAnims( path : String, customFilter : (f:String) -> Bool = null ) {
 		var isDir = sys.FileSystem.isDirectory(ide.getPath(path));
 		var isDir = sys.FileSystem.isDirectory(ide.getPath(path));
 
 
 		var config = hide.Config.loadForFile(ide, path);
 		var config = hide.Config.loadForFile(ide, path);
@@ -371,6 +371,8 @@ class Scene extends hide.comp.Component implements h3d.IDrawable {
 				var file = f.toLowerCase();
 				var file = f.toLowerCase();
 				if( StringTools.startsWith(f,"Anim_") && (StringTools.endsWith(file,".hmd") || StringTools.endsWith(file,".fbx")) )
 				if( StringTools.startsWith(f,"Anim_") && (StringTools.endsWith(file,".hmd") || StringTools.endsWith(file,".fbx")) )
 					anims.push(dir+"/"+f);
 					anims.push(dir+"/"+f);
+				if (customFilter != null && customFilter(f))
+					anims.push(dir+"/"+f);
 			}
 			}
 		}
 		}
 		return anims;
 		return anims;

+ 2 - 0
hide/view/GenericGraphEditor.hx

@@ -34,6 +34,8 @@ class GenericGraphEditor extends hide.view.FileView implements IGraphEditor {
         initGraphEditor();
         initGraphEditor();
 
 
         initScenePreview();
         initScenePreview();
+
+        graphEditor.centerView();
     }
     }
 
 
     function initGraphEditor() {
     function initGraphEditor() {

+ 18 - 1
hide/view/GraphEditor.hx

@@ -335,6 +335,8 @@ class GraphEditor extends hide.comp.Component {
 		for (edge in edges) {
 		for (edge in edges) {
 			createEdge(edge);
 			createEdge(edge);
 		}
 		}
+
+		centerView();
 	}
 	}
 
 
 	var boxToPreview : Map<Box, h2d.Bitmap>;
 	var boxToPreview : Map<Box, h2d.Bitmap>;
@@ -445,6 +447,8 @@ class GraphEditor extends hide.comp.Component {
 
 
 			for (id => _ in boxesSelected) {
 			for (id => _ in boxesSelected) {
 				var box = boxes.get(id);
 				var box = boxes.get(id);
+				if (box.info.dontAddRemove)
+					continue;
 				opSelect(id, false, currentUndoBuffer);
 				opSelect(id, false, currentUndoBuffer);
 				removeBoxEdges(box, currentUndoBuffer);
 				removeBoxEdges(box, currentUndoBuffer);
 				opBox(box.node, false, currentUndoBuffer);
 				opBox(box.node, false, currentUndoBuffer);
@@ -538,6 +542,7 @@ class GraphEditor extends hide.comp.Component {
 			var posCursor = new Point(lX(ide.mouseX - 25), lY(ide.mouseY - 10));
 			var posCursor = new Point(lX(ide.mouseX - 25), lY(ide.mouseY - 10));
 
 
 			var instance = onConstructNode();
 			var instance = onConstructNode();
+			instance.editor = this;
 
 
 			var createLinkInput = edgeCreationInput;
 			var createLinkInput = edgeCreationInput;
 			var createLinkOutput = edgeCreationOutput;
 			var createLinkOutput = edgeCreationOutput;
@@ -1143,6 +1148,8 @@ class GraphEditor extends hide.comp.Component {
 
 
 	public function opBox(node: IGraphNode, doAdd: Bool, undoBuffer: UndoBuffer) : Void {
 	public function opBox(node: IGraphNode, doAdd: Bool, undoBuffer: UndoBuffer) : Void {
 		var data = editor.serializeNode(node);
 		var data = editor.serializeNode(node);
+		if (node.getInfo().dontAddRemove)
+			throw "OpBox can't be called on dontAddRemove nodes";
 
 
 		var exec = function(isUndo : Bool) : Void {
 		var exec = function(isUndo : Bool) : Void {
 			if (!doAdd) isUndo = !isUndo;
 			if (!doAdd) isUndo = !isUndo;
@@ -1561,14 +1568,22 @@ class GraphEditor extends hide.comp.Component {
 			nodes:  [],
 			nodes:  [],
 			edges: [],
 			edges: [],
 		};
 		};
+		var filteredSelection : Map<Int, Bool> = [];
 		for (nodeId => _ in boxesSelected) {
 		for (nodeId => _ in boxesSelected) {
+			var box = boxes[nodeId];
+			if (box.info.dontAddRemove)
+				continue;
+			filteredSelection.set(nodeId, true);
+		}
+
+		for (nodeId => _ in filteredSelection) {
 			var box = boxes[nodeId];
 			var box = boxes[nodeId];
 			data.nodes.push({id: nodeId, serData: editor.serializeNode(box.node)});
 			data.nodes.push({id: nodeId, serData: editor.serializeNode(box.node)});
 			for (inputId => _ in box.info.inputs) {
 			for (inputId => _ in box.info.inputs) {
 				var output = outputsToInputs.getLeft(packIO(nodeId, inputId));
 				var output = outputsToInputs.getLeft(packIO(nodeId, inputId));
 				if (output != null) {
 				if (output != null) {
 					var unpack = unpackIO(output);
 					var unpack = unpackIO(output);
-					if ( boxesSelected.get(unpack.nodeId) != null) {
+					if ( filteredSelection.get(unpack.nodeId) != null) {
 						data.edges.push({nodeFromId: unpack.nodeId, outputFromId: unpack.ioId, nodeToId: nodeId, inputToId: inputId});
 						data.edges.push({nodeFromId: unpack.nodeId, outputFromId: unpack.ioId, nodeToId: nodeId, inputToId: inputId});
 					}
 					}
 				}
 				}
@@ -1607,6 +1622,7 @@ class GraphEditor extends hide.comp.Component {
 			var data : CopySelectionData = haxe.Json.parse(str);
 			var data : CopySelectionData = haxe.Json.parse(str);
 			for (nodeInfo in data.nodes) {
 			for (nodeInfo in data.nodes) {
 				var node = editor.unserializeNode(nodeInfo.serData, true);
 				var node = editor.unserializeNode(nodeInfo.serData, true);
+				node.editor = this;
 				nodes.push(node);
 				nodes.push(node);
 				var newId = node.id;
 				var newId = node.id;
 				idRemap.set(nodeInfo.id, newId);
 				idRemap.set(nodeInfo.id, newId);
@@ -1663,6 +1679,7 @@ class GraphEditor extends hide.comp.Component {
 			var data : CopySelectionData = haxe.Json.parse(cb);
 			var data : CopySelectionData = haxe.Json.parse(cb);
 			for (nodeInfo in data.nodes) {
 			for (nodeInfo in data.nodes) {
 				var node = editor.unserializeNode(nodeInfo.serData, true);
 				var node = editor.unserializeNode(nodeInfo.serData, true);
+				node.editor = this;
 				nodes.push(node);
 				nodes.push(node);
 				var newId = node.id;
 				var newId = node.id;
 				idRemap.set(nodeInfo.id, newId);
 				idRemap.set(nodeInfo.id, newId);

+ 4 - 0
hide/view/GraphInterface.hx

@@ -38,6 +38,10 @@ typedef GraphNodeInfo = {
     **/
     **/
     ?contextMenu : (e: js.html.MouseEvent) -> Void,
     ?contextMenu : (e: js.html.MouseEvent) -> Void,
 
 
+    /**
+        If true, this node cannot be added, removed, or cloned. It must be created externaly. Used for things like fixed inputs or ouputs
+    **/
+    ?dontAddRemove: Bool,
 };
 };
 
 
 typedef NodeInput = {
 typedef NodeInput = {

+ 49 - 18
hide/view/animgraph/AnimGraphEditor.hx

@@ -22,22 +22,12 @@ class AnimGraphEditor extends GenericGraphEditor {
         animGraph = cast hide.Ide.inst.loadPrefab(state.path, null,  true);
         animGraph = cast hide.Ide.inst.loadPrefab(state.path, null,  true);
 
 
         if (animGraph.animFolder == null) {
         if (animGraph.animFolder == null) {
-            element.html("
-                <h1>Choose a folder containing the models to animate</h1>
-                <button-2></button-2>
-            ");
-
-            var button = new hide.comp.Button(null, element.find("button-2"), "Choose folder");
-            button.onClick = () -> {
-                ide.chooseDirectory((path) -> {
-                    if (path != null) {
-                        animGraph.animFolder = path;
-                        save();
-                        reloadView();
-                    }
-                });
-            }
-
+            element.html('');
+            element.append(createChooseFolderPrompt((path: String) -> {
+                animGraph.animFolder = path;
+                save();
+                reloadView();
+            }));
             return;
             return;
         }
         }
 
 
@@ -116,6 +106,45 @@ class AnimGraphEditor extends GenericGraphEditor {
         });
         });
     }
     }
 
 
+    public static function createChooseFolderPrompt(onSet: (path: String) -> Void) : Element {
+        var element = new Element("<center-content>
+                <div class='basic-border'>
+                    <h1>Choose a folder containing the models to animate</h1>
+                    <button-2></button-2>
+                <div>
+            </center-content>
+        ");
+
+        var button = new hide.comp.Button(null, element.find("button-2"), "Choose folder");
+        button.onClick = () -> {
+            hide.Ide.inst.chooseDirectory((path) -> {
+                if (path != null) {
+                    if (gatherAllPreviewModels(path).length <= 0) {
+                        hide.Ide.inst.quickError("Folder doesn't contain any valid model");
+                        return;
+                    }
+                    onSet(path);
+                }
+            });
+        }
+
+        return element;
+    }
+
+    override function buildTabMenu():Array<hide.comp.ContextMenu.MenuItem> {
+        var menu = super.buildTabMenu();
+        menu.push({isSeparator: true});
+        menu.push({label: "Reset Model Folder", click: () -> {
+            if (ide.confirm("Warning, resetting the model folder could lead to incorrect animations. Are you sure you want to proceed ?")) {
+                animGraph.animFolder = null;
+                save();
+                reloadView();
+            }
+        }});
+
+        return menu;
+    }
+
     static public function gatherAllPreviewModels(basePath : String) : Array<String> {
     static public function gatherAllPreviewModels(basePath : String) : Array<String> {
         var paths = [];
         var paths = [];
 
 
@@ -364,7 +393,9 @@ class AnimGraphEditor extends GenericGraphEditor {
 	}
 	}
 
 
     override function getDefaultContent() : haxe.io.Bytes {
     override function getDefaultContent() : haxe.io.Bytes {
-        @:privateAccess return haxe.io.Bytes.ofString(ide.toJSON(new hrt.animgraph.AnimGraph(null, null).serialize()));
+        var inst = new hrt.animgraph.AnimGraph(null, null);
+        inst.nodes.push(new hrt.animgraph.nodes.Output());
+        @:privateAccess return haxe.io.Bytes.ofString(ide.toJSON(inst.serialize()));
     }
     }
 
 
     override function save() {
     override function save() {
@@ -381,7 +412,7 @@ class AnimGraphEditor extends GenericGraphEditor {
         if (scenePreview.getObjectPath() == null) {
         if (scenePreview.getObjectPath() == null) {
             scenePreview.setObjectPath(gatherAllPreviewModels(animGraph.animFolder)[0]);
             scenePreview.setObjectPath(gatherAllPreviewModels(animGraph.animFolder)[0]);
         }
         }
-        scenePreview.resetCamera();
+        scenePreview.resetPreviewCamera();
     }
     }
 
 
     override function getNodes() : Iterator<IGraphNode> {
     override function getNodes() : Iterator<IGraphNode> {

+ 37 - 15
hide/view/animgraph/BlendSpace2DEditor.hx

@@ -52,26 +52,19 @@ class BlendSpace2DEditor extends hide.view.FileView {
 	}
 	}
 
 
 	override function onDisplay() {
 	override function onDisplay() {
+		previewModel = null;
+		animPreview = null;
 		blendSpace2D = Std.downcast(hide.Ide.inst.loadPrefab(state.path, null,  true), hrt.animgraph.BlendSpace2D);
 		blendSpace2D = Std.downcast(hide.Ide.inst.loadPrefab(state.path, null,  true), hrt.animgraph.BlendSpace2D);
 		if (blendSpace2D == null)
 		if (blendSpace2D == null)
 			throw "Invalid blendSpace2D";
 			throw "Invalid blendSpace2D";
 		super.onDisplay();
 		super.onDisplay();
 		if (blendSpace2D.animFolder == null) {
 		if (blendSpace2D.animFolder == null) {
-            element.html("
-                <h1>Choose a folder containing the models to animate</h1>
-                <button-2></button-2>
-            ");
-
-            var button = new hide.comp.Button(null, element.find("button-2"), "Choose folder");
-            button.onClick = () -> {
-                ide.chooseDirectory((path) -> {
-                    if (path != null) {
-                        blendSpace2D.animFolder = path;
-                        save();
-                        onDisplay();
-                    }
-                });
-            }
+			element.html('');
+			element.append(AnimGraphEditor.createChooseFolderPrompt((path: String) -> {
+				blendSpace2D.animFolder = path;
+				save();
+				onDisplay();
+			}));
 			return;
 			return;
 		}
 		}
 
 
@@ -266,6 +259,20 @@ class BlendSpace2DEditor extends hide.view.FileView {
 				previewModel = scenePreview.prefab?.find(hrt.prefab.Model, (f) -> StringTools.startsWith(f.source, blendSpace2D.animFolder))?.local3d;
 				previewModel = scenePreview.prefab?.find(hrt.prefab.Model, (f) -> StringTools.startsWith(f.source, blendSpace2D.animFolder))?.local3d;
 				refreshPreviewAnimation();
 				refreshPreviewAnimation();
 			}
 			}
+
+			var toolbar = new Element('
+			<div class="hide-toolbar2">
+				<div class="tb-group">
+					<div class="button2 transparent" title="More options">
+						<div class="ico ico-navicon"></div>
+					</div>
+				</div>
+			</div>').appendTo(previewContainer);
+			var menu = toolbar.find(".button2");
+
+			menu.get(0).onclick = (e: js.html.MouseEvent) -> {
+				hide.comp.ContextMenu.createDropdown(menu.get(0), []);
+			}
 		}
 		}
 
 
 		propertiesContainer = new hide.Element("<properties-container></properties-container>").appendTo(root);
 		propertiesContainer = new hide.Element("<properties-container></properties-container>").appendTo(root);
@@ -280,6 +287,20 @@ class BlendSpace2DEditor extends hide.view.FileView {
 		keys.register("delete", deleteSelection);
 		keys.register("delete", deleteSelection);
 	}
 	}
 
 
+    override function buildTabMenu():Array<hide.comp.ContextMenu.MenuItem> {
+        var menu = super.buildTabMenu();
+        menu.push({isSeparator: true});
+        menu.push({label: "Reset Model Folder", click: () -> {
+            if (ide.confirm("Warning, resetting the model folder could lead to incorrect animations. Are you sure you want to proceed ?")) {
+                blendSpace2D.animFolder = null;
+                save();
+                onDisplay();
+            }
+        }});
+
+        return menu;
+    }
+
 	override function getDefaultContent():haxe.io.Bytes {
 	override function getDefaultContent():haxe.io.Bytes {
 		var animgraph = (new hrt.animgraph.BlendSpace2D(null, null)).serialize();
 		var animgraph = (new hrt.animgraph.BlendSpace2D(null, null)).serialize();
 		return haxe.io.Bytes.ofString(ide.toJSON(animgraph));
 		return haxe.io.Bytes.ofString(ide.toJSON(animgraph));
@@ -382,6 +403,7 @@ class BlendSpace2DEditor extends hide.view.FileView {
 		}
 		}
 
 
 		var animList = new AnimList(propertiesContainer, null, scenePreview.listAnims(blendSpace2D.animFolder));
 		var animList = new AnimList(propertiesContainer, null, scenePreview.listAnims(blendSpace2D.animFolder));
+		scenePreview.resetPreviewCamera();
     }
     }
 
 
 	function deletePoint(index: Int) {
 	function deletePoint(index: Int) {

+ 2 - 3
hrt/animgraph/nodes/AnimNode.hx

@@ -106,13 +106,12 @@ class AnimNode extends Node {
 	override function getInfo():hide.view.GraphInterface.GraphNodeInfo {
 	override function getInfo():hide.view.GraphInterface.GraphNodeInfo {
 		var info = super.getInfo();
 		var info = super.getInfo();
 
 
-		var animGraphEditor : hide.view.animgraph.AnimGraphEditor = cast editor.editor;
 		info.playButton = {
 		info.playButton = {
 			getActive: () -> {
 			getActive: () -> {
-				return this == @:privateAccess animGraphEditor.previewNode;
+				return this == @:privateAccess getAnimEditor().previewNode;
 			},
 			},
 			onClick: () -> {
 			onClick: () -> {
-				animGraphEditor.setPreview(this);
+				getAnimEditor().setPreview(this);
 			}
 			}
 		};
 		};
 		return info;
 		return info;

+ 11 - 0
hrt/animgraph/nodes/Output.hx

@@ -11,6 +11,13 @@ class Output extends AnimNode {
 		// update out using inputs
 		// update out using inputs
 	}
 	}
 
 
+	override function getInfo():hide.view.GraphInterface.GraphNodeInfo {
+		var info = super.getInfo();
+		info.dontAddRemove = true;
+		info.outputs = [];
+		return info;
+	}
+
 	override function getSize() : Int {
 	override function getSize() : Int {
 		return Node.SIZE_SMALL;
 		return Node.SIZE_SMALL;
 	}
 	}
@@ -22,4 +29,8 @@ class Output extends AnimNode {
 	override function getBoneTransform(boneId:Int, outMatrix:h3d.Matrix, ctx:hrt.animgraph.nodes.AnimNode.GetBoneTransformContext) {
 	override function getBoneTransform(boneId:Int, outMatrix:h3d.Matrix, ctx:hrt.animgraph.nodes.AnimNode.GetBoneTransformContext) {
 		return a.getBoneTransform(boneId, outMatrix, ctx);
 		return a.getBoneTransform(boneId, outMatrix, ctx);
 	}
 	}
+
+	override function canCreateManually():Bool {
+		return false;
+	}
 }
 }