Bläddra i källkod

Fixing vertex alpha for 3dsmax exporter

David Catuhe 10 år sedan
förälder
incheckning
5328446d3f

BIN
Exporters/3ds Max/Max2Babylon-0.16.zip


+ 80 - 0
Exporters/3ds Max/Max2Babylon/BabylonActionsBuilderActionItem.cs

@@ -0,0 +1,80 @@
+using Autodesk.Max;
+using ActionItem = Autodesk.Max.Plugins.ActionItem;
+
+namespace Max2Babylon
+{
+    public class BabylonActionsBuilderActionItem : ActionItem
+    {
+        public override bool ExecuteAction()
+        {
+            if (Loader.Core.SelNodeCount > 1)
+            {
+                Loader.Core.PushPrompt("Actions Builder only supports one Node");
+            }
+            else
+            {
+                IINode node = null;
+                SClass_ID type;
+
+                if (Loader.Core.SelNodeCount == 0)
+                    type = SClass_ID.Scene;
+                else
+                {
+                    node = Loader.Core.GetSelNode(0);
+                    type = node.ObjectRef.Eval(0).Obj.SuperClassID;
+                }
+
+                if (type == SClass_ID.Geomobject || type == SClass_ID.Light || type == SClass_ID.Camera || type == SClass_ID.Scene)
+                {
+                    using (var ab = new ActionsBuilderForm())
+                    {
+                        // Just show dialog
+                        ab.ShowDialog();
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        public override int Id_
+        {
+            get { return 2; }
+        }
+
+        public override string ButtonText
+        {
+            get { return "Babylon Actions Builder"; }
+        }
+
+        public override string MenuText
+        {
+            get { return "Babylon Actions Builder"; }
+        }
+
+        public override string DescriptionText
+        {
+            get { return "UI graph to build custom actions on selected object"; }
+        }
+
+        public override string CategoryText
+        {
+            get { return "Babylon"; }
+        }
+
+        public override bool IsChecked_
+        {
+            get { return false; }
+        }
+
+        public override bool IsItemVisible
+        {
+            get { return true; }
+        }
+
+        public override bool IsEnabled_
+        {
+            get { return true; }
+        }
+    }
+}

+ 76 - 0
Exporters/3ds Max/Max2Babylon/Exporter/ActionBuilder/ActionsBuilder/action.js

@@ -0,0 +1,76 @@
+/// <reference path="raphael.js" />
+/// <reference path="viewer.js" />
+/// <reference path="actionKinds.js" />
+
+var AB;
+(function (AB) {
+
+    var Node = (function () {
+        function Node() {
+            this.rect = null;
+            this.text = null;
+            this.line = null;
+
+            this.action = null;
+        }
+
+        Node.prototype.attr = function(element, attribute, value) {
+            if (value)
+                element.attr(attribute, value);
+            else
+                return element.attr(attribute);
+        }
+
+        Node.prototype.isPointInside = function (x, y) {
+            return this.rect.isPointInside(x, y) || this.text.isPointInside(x, y);
+        }
+
+        return Node;
+    })();
+
+    var Action = (function() {
+        function Action(node) {
+            // Graph related
+            this.parent = null;
+            this.children = new Array();
+            this.node = node;
+
+            this.name = '';
+            this.type = AB.ActionsBuilder.Type.OBJECT;
+            this.propertiesResults = new Array();
+            this.properties = new Array();
+        }
+
+        Action.prototype.addChild = function (object) {
+            if (object == null)
+                return false;
+
+            this.children.push(object);
+            object.parent = this;
+
+            return true;
+        }
+
+        Action.prototype.removeChild = function (object) {
+            var indice = this.children.indexOf(object);
+
+            if (indice != 1) {
+                this.children.splice(indice, 1);
+                return true;
+            }
+
+            return false;
+        }
+
+        Action.prototype.clearChildren = function () {
+            this.children = new Array();
+        }
+
+        return Action;
+
+    })();
+
+    AB.Action = Action;
+    AB.Node = Node;
+
+})(AB || (AB = { }));

+ 90 - 0
Exporters/3ds Max/Max2Babylon/Exporter/ActionBuilder/ActionsBuilder/actionkinds.js

@@ -0,0 +1,90 @@
+/// <reference path="raphael.js" />
+/// <reference path="viewer.js" />
+/// <reference path="action.js" />
+
+var AB;
+(function (AB) {
+
+    var directActionTemplate = function (parameters, secondArgument) {
+        var template = [
+            { text: 'target', value: 'Object name?' }
+        ];
+
+        if (secondArgument)
+            template.push({text: secondArgument, value: 'value...'});
+
+        if (parameters)
+            template.push.apply(template, parameters);
+
+        return template;
+    }
+
+    var directActionWidthPropertyPath = function (parameters) {
+        return directActionTemplate(parameters, 'propertyPath');
+    }
+
+    var ActionsBuilder = (function () {
+        function ActionsBuilder()
+        { }
+
+        //
+        // Types
+        //
+        ActionsBuilder.Type = ActionsBuilder.Type || {};
+        ActionsBuilder.Type.TRIGGER = 0;
+        ActionsBuilder.Type.ACTION = 1;
+        ActionsBuilder.Type.FLOW_CONTROL = 2;
+        ActionsBuilder.Type.OBJECT = 3;
+        ActionsBuilder.Type.SCENE = 4;
+
+        //
+        // Triggers
+        //
+        ActionsBuilder.Trigger = ActionsBuilder.Trigger || {};
+        ActionsBuilder.Trigger[0] = { name: 'NothingTrigger', properties: [] };
+        ActionsBuilder.Trigger[1] = { name: 'OnPickTrigger', properties: [] };
+        ActionsBuilder.Trigger[2] = { name: 'OnLeftPickTrigger', properties: [] };
+        ActionsBuilder.Trigger[3] = { name: 'OnRightPickTrigger', properties: [] };
+        ActionsBuilder.Trigger[4] = { name: 'OnCenterPickTrigger', properties: [] };
+        ActionsBuilder.Trigger[5] = { name: 'OnPointerOverTrigger', properties: [] };
+        ActionsBuilder.Trigger[6] = { name: 'OnPointerOutTrigger', properties: [] };
+        ActionsBuilder.Trigger[7] = { name: 'OnEveryFrameTrigger', properties: [] };
+        ActionsBuilder.Trigger[8] = { name: 'OnIntersectionEnterTrigger', properties: [{ text: 'parameter', value: 'Object name?' }] };
+        ActionsBuilder.Trigger[9] = { name: 'OnIntersectionExitTrigger', properties: [{ text: 'parameter', value: 'Object name?' }] };
+        ActionsBuilder.Trigger[10] = { name: 'OnKeyDownTrigger', properties: [{ text: 'parameter:', value: '' }] };
+        ActionsBuilder.Trigger[11] = { name: 'OnKeyUpTrigger', properties: [{ text: 'parameter:', value: '' }] };
+        ActionsBuilder.Trigger.COUNT = 12;
+
+        //
+        // Actions (direct & interpolate)
+        //
+        ActionsBuilder.Action = ActionsBuilder.Action || {};
+        ActionsBuilder.Action[0] = { name: 'SwitchBooleanAction', properties: directActionWidthPropertyPath() };
+        ActionsBuilder.Action[1] = { name: 'SetStateAction', properties: directActionTemplate(null, 'value') };
+        ActionsBuilder.Action[2] = { name: 'SetValueAction', properties: directActionWidthPropertyPath([{ text: 'value', value: 'value?' }]) };
+        ActionsBuilder.Action[3] = { name: 'IncrementValueAction', properties: directActionWidthPropertyPath([{ text: 'value', value: 'value?' }]) };
+        ActionsBuilder.Action[4] = { name: 'PlayAnimationAction', properties: directActionTemplate([{ text: 'from', value: '0' }, { text: 'to', value: '150' }, { text: 'loop', value: 'false' }]) };
+        ActionsBuilder.Action[5] = { name: 'StopAnimationAction', properties: directActionTemplate() };
+        ActionsBuilder.Action[6] = { name: 'DoNothingAction', properties: [] };
+        ActionsBuilder.Action[7] = { name: 'CombineAction', properties: [] };
+        ActionsBuilder.Action[8] = {
+            name: 'InterpolateValueAction', properties: directActionWidthPropertyPath([
+                { text: 'value', value: 'value' },
+                { text: 'duration', value: '1000' },
+                { text: 'stopOtherAnimations', value: 'false' }])
+        };
+        ActionsBuilder.Action.COUNT = 9;
+
+        //
+        // Flow Control
+        //
+        ActionsBuilder.FlowAction = ActionsBuilder.FlowAction || {};
+        ActionsBuilder.FlowAction[0] = { name: 'ValueCondition', properties: directActionWidthPropertyPath([{ text: 'value', value: 'value?' }, { text: 'operator', value: 'IsEqual' }]) };
+        ActionsBuilder.FlowAction[1] = { name: 'StateCondition', properties: directActionTemplate([{ text: 'value', value: 'value?' }]) };
+        ActionsBuilder.FlowAction.COUNT = 2;
+
+        return ActionsBuilder;
+    })();
+
+    AB.ActionsBuilder = ActionsBuilder;
+})(AB || (AB = {}));

+ 68 - 0
Exporters/3ds Max/Max2Babylon/Exporter/ActionBuilder/ActionsBuilder/index.css

@@ -0,0 +1,68 @@
+
+html, body {
+    height: 100%;
+    width: 100%;
+
+    max-width: 99%;
+    max-height: 99%;
+
+    overflow: hidden;
+}
+
+/*Context Menu*/
+.hide {
+    display: none;
+}
+.show {
+    z-index:1;
+    position: relative;
+    background-color:#C0C0C0;
+    border: 1px solid black;
+    padding: 2px;
+    display: block;
+    margin: 0;
+    list-style-type: none;
+    list-style: none;
+}
+.show li{ list-style: none; }
+.show a { border: 0 !important; text-decoration: none; }
+.show a:hover { text-decoration: underline !important; }
+
+/*Elements*/
+#List {
+    height: 100%;
+    width: 25%;
+
+    max-height: 100%;
+    max-width: 25%;
+
+    overflow-y: scroll;
+
+    float: left;
+}
+
+#Graph {
+    width: 50%;
+    height: 100%;
+
+    max-height: 100%;
+    max-width: 50%;
+    
+    overflow-x: scroll;
+    overflow-y: scroll;
+
+    float: right;
+}
+
+#Parameters {
+    width: 25%;
+    height: 100%;
+
+    max-height: 100%;
+    max-width: 25%;
+
+    overflow-y: scroll;
+    overflow-x: scroll;
+
+    float: right;
+}

+ 80 - 0
Exporters/3ds Max/Max2Babylon/Exporter/ActionBuilder/ActionsBuilder/index.html

@@ -0,0 +1,80 @@
+<!-- saved from url=(0014)about:internet -->
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <title>Actions Manager</title>
+
+    <link href="./index.css" rel="stylesheet" />
+
+    <script src="raphael.js" type="text/javascript"></script>
+    <script src="actionKinds.js" type="text/javascript"></script>
+    <script src="action.js" type="text/javascript"></script>
+    <script src="viewer.js" type="text/javascript"></script>
+    <script src="list.js" type="text/javascript"></script>
+
+</head>
+
+<body>
+
+    <div id="Parameters"></div>
+    <div id="Graph"></div>
+    <div id="List"></div>
+
+    <input id="ActionsBuilderObjectName" type="hidden" />
+    <input id="ActionsBuilderJSON" type="hidden" />
+
+    <script type="text/javascript">
+
+        var graph = new AB.Graph();
+
+        var list = new AB.List(graph);
+        list.createListElements();
+
+        graph.update();
+
+        function resetList(type) {
+            list.objectType = type;
+            list.clearList();
+            list.createListElements();
+        }
+
+        function updateObjectName() {
+            var name = document.getElementById('ActionsBuilderObjectName').value;
+            graph.root.text.attr('text', name);
+            graph.objectName = name;
+        }
+
+        function setIsScene() {
+            resetList(AB.ActionsBuilder.Type.SCENE);
+        }
+
+        function setIsObject() {
+            resetList(AB.ActionsBuilder.Type.OBJECT);
+        }
+
+        function updateJSONFromGraph() {
+            var json = graph.createJSON();
+            json.name = graph.objectName;
+
+            document.getElementById('ActionsBuilderJSON').value = JSON.stringify(json);
+        }
+
+        function updateGraphFromJSON() {
+            var value = document.getElementById('ActionsBuilderJSON').value;
+
+            if (value == null || value == "")
+                return;
+
+            graph.loadFromJSON(value);
+        }
+
+        // Manage color theme
+        graph.graph.canvas.style.backgroundColor = '#444444';
+        list.list.canvas.style.backgroundColor = '#565656';
+        graph.parametersElement.style.backgroundColor = '#565656';
+
+    </script>
+
+</body>
+</html>

+ 190 - 0
Exporters/3ds Max/Max2Babylon/Exporter/ActionBuilder/ActionsBuilder/list.js

@@ -0,0 +1,190 @@
+/// <reference path="raphael.js" />
+/// <reference path="actionKinds.js" />
+/// <reference path="viewer.js" />
+
+var AB;
+(function (AB) {
+
+    var ListElement = (function () {
+        function ListElement() {
+            // Members
+            this.rect = null;
+            this.text = null;
+
+            this.name = new String();
+            this.type = AB.ActionsBuilder.Type.SCENE;
+            this.kind = null;
+            this.properties = null;
+        }
+
+        ListElement.prototype.attr = function (element, attribute, value) {
+            if (value)
+                element.attr(attribute, value);
+            else
+                return element.attr(attribute);
+        }
+
+        return ListElement;
+    })();
+
+    var List = (function () {
+
+        //
+        // Public functions
+        //
+        function List(graph) {
+            // Members
+            this.element = document.getElementById('List');
+            this.list = Raphael('List', (25 * screen.width) / 100, screen.height);
+            this.graph = graph;
+
+            this.listElements = new Array();
+            this.objectType = AB.ActionsBuilder.Type.OBJECT;
+
+            var scope = this;
+            window.onresize = function () {
+                for (var i = 0; i < scope.listElements.length; i++) {
+                    scope.listElements[i].attr(scope.listElements[i].rect, 'width', scope.element.getBoundingClientRect().width - 20);
+                }
+            }
+        }
+
+        List.prototype.clearList = function () {
+            for (var i = 0; i < this.listElements.length; i++) {
+                this._removeListElement(this.listElements[i]);
+            }
+        }
+
+        List.prototype.createListElements = function () {
+            var excludedTriggers = [7, 10, 11]; // If objectType === Scene
+            var yOffset = 10;
+
+            // Create Triggers
+            var t = this._createListElement('Triggers', AB.ActionsBuilder.Type.OBJECT, null, yOffset);
+            t.attr(t.rect, 'fill', Raphael.rgb(100, 149, 237));
+            yOffset += 15;
+
+            for (var i = 0; i < AB.ActionsBuilder.Trigger.COUNT; i++) {
+                if (excludedTriggers.indexOf(i) != -1 && this.objectType != AB.ActionsBuilder.Type.SCENE)
+                    continue;
+
+                var e = this._createListElement(AB.ActionsBuilder.Trigger[i].name, AB.ActionsBuilder.Type.TRIGGER,
+                                                AB.ActionsBuilder.Trigger[i].properties, yOffset, true);
+
+                yOffset += 15;
+            }
+
+            // Create Actions
+            var a = this._createListElement('Actions', AB.ActionsBuilder.Type.OBJECT, null, yOffset);
+            a.attr(a.rect, 'fill', Raphael.rgb(240, 230, 140));
+            yOffset += 15;
+
+            for (var i = 0; i < AB.ActionsBuilder.Action.COUNT; i++) {
+                var e = this._createListElement(AB.ActionsBuilder.Action[i].name, AB.ActionsBuilder.Type.ACTION,
+                                                AB.ActionsBuilder.Action[i].properties, yOffset, true);
+
+                yOffset += 15;
+            }
+
+            // Create flow control
+            var f = this._createListElement('Flow Control', AB.ActionsBuilder.Type.OBJECT, null, yOffset);
+            f.attr(f.rect, 'fill', Raphael.rgb(205, 92, 92));
+            yOffset += 15;
+
+            for (var i = 0; i < AB.ActionsBuilder.FlowAction.COUNT; i++) {
+                var e = this._createListElement(AB.ActionsBuilder.FlowAction[i].name, AB.ActionsBuilder.Type.FLOW_CONTROL,
+                                                AB.ActionsBuilder.FlowAction[i].properties, yOffset, true);
+
+                yOffset += 15;
+            }
+        }
+
+        //
+        // Private functions
+        //
+        List.prototype._createListElement = function (name, type, properties, yOffset, drag) {
+            var e = new ListElement();
+
+            e.rect = this.list.rect(0, yOffset, this.element.getBoundingClientRect().width - 20, 15);
+            e.text = this.list.text(e.rect.attr('x') + 20, yOffset + e.rect.attr('height') / 2, name);
+            e.name = name;
+            e.type = type;
+            e.properties = properties;
+
+            e.rect.attr('fill', Raphael.rgb(200, 200, 200));
+            e.text.attr('font-size', '12');
+            e.text.attr('text-anchor', 'start');
+
+            if (drag) {
+                this._createListElementAnimation(e);
+            }
+
+            this.listElements.push(e);
+            return e;
+        }
+
+        List.prototype._removeListElement = function (element) {
+            element.rect.remove();
+            element.text.remove();
+        }
+
+        List.prototype._createListElementAnimation = function (element) {
+            var scope = this;
+            var mousex, mousey;
+
+            var onMove = function (dx, dy, x, y, event) {
+                mousex = x;
+                mousey = y;
+            };
+
+            var onStart = function (x, y, event) {
+                element.rect.animate({
+                    x: -20,
+                    opacity: 0.25
+                }, 500, '>');
+                element.text.animate({
+                    x: 0,
+                    opacity: 0.25
+                }, 500, '>');
+            };
+
+            var onEnd = function (event) {
+                element.rect.animate({
+                    x: 0,
+                    opacity: 1.0
+                }, 500, '<');
+                element.text.animate({
+                    x: 20,
+                    opacity: 1.0
+                }, 500, '<');
+
+                var x = mousex - scope.graph.graph.canvas.getBoundingClientRect().left;
+                var y = mousey - scope.graph.graph.canvas.getBoundingClientRect().top;
+                var dragResult = scope.graph.traverseGraph(null, x, y);
+
+                if (dragResult.hit) {
+                    if (element.type == AB.ActionsBuilder.Type.TRIGGER && dragResult.element != scope.graph.root.action)
+                        return;
+
+                    if (element.type == AB.ActionsBuilder.Type.ACTION && dragResult.element == scope.graph.root.action)
+                        return;
+
+                    if (element.type == AB.ActionsBuilder.Type.FLOW_CONTROL && dragResult.element == scope.graph.root.action)
+                        return;
+
+                    scope.graph.addNode(dragResult.element, element);
+                    scope.graph.update();
+                }
+            };
+
+            element.rect.drag(onMove, onStart, onEnd);
+            element.text.drag(onMove, onStart, onEnd);
+        }
+
+        return List;
+    })();
+
+    AB.List = List;
+    AB.ListElement = ListElement;
+
+})(AB || (AB = { }));

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 11 - 0
Exporters/3ds Max/Max2Babylon/Exporter/ActionBuilder/ActionsBuilder/raphael.js


+ 411 - 0
Exporters/3ds Max/Max2Babylon/Exporter/ActionBuilder/ActionsBuilder/viewer.js

@@ -0,0 +1,411 @@
+/// <reference path="raphael.js" />
+/// <reference path="action.js" />
+
+var AB;
+(function (AB) {
+
+    var Graph = (function () {
+
+        //
+        // Public functions
+        //
+        function Graph() {
+            var scope = this;
+
+            // Members
+            this.element = document.getElementById('Graph');
+            this.graph = Raphael('Graph', (50 * screen.width) / 100, screen.height);
+            this.root = this._createNode('Object name', Raphael.rgb(200, 200, 200), true);
+            this.parametersElement = document.getElementById('Parameters');
+
+            this.mousex = 0;
+            this.mousey = 0;
+            this.objectName = '';
+
+            // Context menu (to remove a node)
+            this.contextMenu = null;
+            this.selectedNode = null;
+
+            this.graph.canvas.addEventListener('contextmenu', function (event) {
+                var result = scope.traverseGraph(null, scope.mousex, scope.mousey);
+                if (result.hit && result.element != scope.root.action) {
+                    scope.selectedNode = result.element;
+                    scope.contextMenu = scope._createNode('Remove', Raphael.rgb(255, 255, 255), true);
+                    scope._setNodePosition(scope.contextMenu, scope.mousex, scope.mousey);
+                }
+
+                window.event.returnValue = false;
+            });
+            document.onclick = function (event) {
+                if (scope.contextMenu) {
+                    if (scope.contextMenu.isPointInside(scope.mousex, scope.mousey)) {
+                        scope.selectedNode.parent.removeChild(scope.selectedNode);
+                        scope._removeAction(scope.selectedNode, true);
+                        scope.update();
+                    }
+
+                    scope.selectedNode = null;
+                    scope._removeNode(scope.contextMenu);
+                }
+
+                scope.contextMenu = null;
+            };
+            document.onmousemove = function (event) {
+                scope.mousex = event.clientX - scope.graph.canvas.getBoundingClientRect().left;
+                scope.mousey = event.clientY - scope.graph.canvas.getBoundingClientRect().top;
+
+                if (!scope.contextMenu)
+                    return;
+
+                if (scope.contextMenu.isPointInside(scope.mousex, scope.mousey))
+                    scope.contextMenu.attr(scope.contextMenu.rect, 'fill', Raphael.rgb(140, 200, 230));
+                else
+                    scope.contextMenu.attr(scope.contextMenu.rect, 'fill', Raphael.rgb(255, 255, 255));
+            };
+
+            // Set properties
+            this.element.scrollLeft = (this.element.getBoundingClientRect().width / 2) + (Graph.NODE_WIDTH / 2);
+        }
+
+        Graph.prototype.addNode = function (parent, listElement) {
+            var color = Raphael.rgb(200, 200, 200);
+            switch (listElement.type) {
+                case AB.ActionsBuilder.Type.TRIGGER: color = Raphael.rgb(100, 149, 237); break;
+                case AB.ActionsBuilder.Type.ACTION: color = Raphael.rgb(240, 230, 140); break;
+                case AB.ActionsBuilder.Type.FLOW_CONTROL: color = Raphael.rgb(205, 92, 92); break;
+            }
+
+            var n = this._createNode(listElement.name, color, false);
+            n.action.name = listElement.name;
+            n.action.type = listElement.type;
+            n.action.properties = listElement.properties;
+
+            for (var i = 0; i < listElement.properties.length; i++)
+                n.action.propertiesResults.push(listElement.properties[i].value);
+
+            if (parent)
+                parent.addChild(n.action);
+
+            this._createNodeAnimation(n);
+        }
+
+        Graph.prototype.update = function (node, yOffset, childID, rootChildID) {
+            if (!yOffset)
+                yOffset = 10;
+            else
+                yOffset += Graph.VERTICAL_OFFSET;
+
+            if (!node) node = this.root;
+
+            if (node == this.root) {
+                this._setNodePosition(node, (this.graph.width / 2) - (Graph.NODE_WIDTH / 2), yOffset);
+            }
+            else {
+                var length = node.action.parent.children.length;
+                var parentx = node.action.parent.node.attr(node.action.parent.node.rect, 'x');
+                var totalLength = Graph.NODE_WIDTH * length;
+                var offset = ( Graph.NODE_WIDTH * (length - childID - 1) );
+                var posx = parentx;
+                posx += offset - ((Graph.NODE_WIDTH / 2) * (length - 1));
+
+                this._setNodePosition(node, posx, yOffset);
+                this._setLine(node.action);
+            }
+
+            for (var i = 0; i < node.action.children.length; i++) {
+                if (node == this.root)
+                    rootChildID = i;
+
+                this.update(node.action.children[i].node, yOffset, i, rootChildID);
+
+                var n = node.action.children[i].node;
+                if (n.action.children.length > 1) {
+                    for (var j = rootChildID; j >= 0; j--) {
+                        if (node.action.children.length < 2 || n.action.children.length < 2)
+                            continue;
+
+                        var rx = this.root.attr(this.root.rect, 'x');
+                        var x = this.root.action.children[j].node.attr(this.root.action.children[j].node.rect, 'x');
+                        var y = this.root.action.children[j].node.attr(this.root.action.children[j].node.rect, 'y');
+
+                        x -= ((Graph.NODE_WIDTH / 2) * (node.action.children.length - 1)) * (x > rx ? -1 : 1);
+
+                        this._setNodePosition(this.root.action.children[j].node, x, y);
+                        this._setLine(this.root.action.children[j]);
+
+                    }
+                }
+                
+            }
+        }
+
+        Graph.prototype.createJSON = function (root, graph) {
+            if (!root) root = this.root.action;
+            if (!graph) graph = {};
+
+            var action = {};
+            action.type = root.type;
+            action.name = root.name;
+            action.children = new Array();
+
+            action.properties = new Array();
+            for (var i = 0; i < root.properties.length; i++)
+                action.properties[i] = { name: root.properties[i].text, value: root.propertiesResults[i] };
+            
+            for (var i = 0; i < root.children.length; i++) {
+                action.children.push(this.createJSON(root.children[i], action));
+            }
+
+            return action;
+        }
+
+        Graph.prototype.loadFromJSON = function (graph) {
+            var scope = this;
+
+            for (var i = 0; i < this.root.action.children.length; i++)
+                this._removeAction(this.root.action.children[i], true);
+
+            this.root.action.clearChildren();
+
+            graph = JSON.parse(graph);
+            console.log(graph);
+
+            var load = function (root, parent) {
+                if (!parent) parent = scope.root.action;
+                if (!root) root = graph;
+
+                if (root.type != AB.ActionsBuilder.Type.OBJECT) { // Means it is the root (the edited object)
+                    var e = {};
+                    e.type = root.type;
+                    e.name = root.name;
+                    e.properties = new Array();
+
+                    for (var i = 0; i < root.properties.length; i++)
+                        e.properties.push({ text: root.properties[i].name, value: root.properties[i].value });
+
+                    var n = scope.addNode(parent, e);
+                    parent = parent.children[parent.children.length - 1];
+                }
+
+                for (var i = 0; i < root.children.length; i++) {
+                    load(root.children[i], parent);
+                }
+            }
+
+            load();
+            this.update();
+        }
+
+        Graph.prototype.traverseGraph = function (start, x, y) {
+            if (!start) start = this.root.action;
+            var result = {
+                hit: true,
+                element: start
+            };
+
+            if (start.node.isPointInside(x, y))
+                return result;
+
+            for (var i = 0; i < start.children.length; i++) {
+
+                if (start.children[i].node.isPointInside(x, y)) {
+                    result.hit = true;
+                    result.element = start.children[i];
+                    return result;
+                }
+
+                result = this.traverseGraph(start.children[i], x, y);
+                if (result.hit)
+                    return result;
+
+            }
+
+            result.hit = false;
+            result.element = null;
+            return result;
+        }
+
+        //
+        // Private functions
+        //
+        Graph.prototype._setLine = function (element) {
+            var linex = element.node.attr(element.node.rect, 'x') + Graph.NODE_WIDTH / 2;
+            var liney = element.node.attr(element.node.rect, 'y');
+            var linex2 = element.parent.node.attr(element.parent.node.rect, 'x') + Graph.NODE_WIDTH / 2;
+            var liney2 = element.parent.node.attr(element.parent.node.rect, 'y') + Graph.NODE_HEIGHT;
+
+            element.node.attr(element.node.line, 'path', 'M' + linex + ' ' + liney + 'L' + linex2 + ' ' + liney2);
+        }
+
+        Graph.prototype._setNodePosition = function (node, x, y) {
+            var offsetx = node.attr(node.rect, 'x') - x;
+
+            node.attr(node.rect, 'x', x);
+            node.attr(node.rect, 'y', y);
+
+            node.attr(node.text, 'x', x + 5);
+            node.attr(node.text, 'y', y + Graph.NODE_HEIGHT / 2);
+
+            for (var i = 0; i < node.action.children.length; i++) {
+                this._setNodePosition(node.action.children[i].node, node.action.children[i].node.attr(node.action.children[i].node.rect, 'x') - offsetx, y + Graph.VERTICAL_OFFSET);
+                this._setLine(node.action.children[i]);
+            }
+        }
+
+        Graph.prototype._removeNode = function (element) {
+            element.rect.remove();
+            element.text.remove();
+            if (element.line)
+                element.line.remove();
+        }
+
+        Graph.prototype._removeAction = function (action, removeChildren) {
+            if (!removeChildren)
+                removeChildren = false;
+
+            this._removeNode(action.node);
+
+            if (removeChildren) {
+                for (var i = 0; i < action.children.length; i++)
+                    this._removeAction(action.children[i], removeChildren);
+
+                action.clearChildren();
+            }
+            else {
+                for (var i = 0; i < action.children.length; i++)
+                    action.children[i].parent = action.parent;
+            }
+        }
+
+        Graph.prototype._createNode = function (text, color, noLine) {
+            var n = new AB.Node();
+            n.rect = this.graph.rect(0, 0, Graph.NODE_WIDTH, Graph.NODE_HEIGHT, 5);
+            n.text = this.graph.text(0, 0, text);
+            if (!noLine)
+                n.line = this.graph.path('M10 10L90 90');
+            n.action = new AB.Action(n);
+
+            n.rect.attr('fill', color);
+            n.text.attr('font-size', 12);
+            n.text.attr('text-anchor', 'start');
+
+            return n;
+        }
+
+        Graph.prototype._createParameters = function (element) {
+            var onChange = function (input, propertyID) {
+                return function () {
+                    var value = input.value;
+                    element.action.propertiesResults[propertyID] = value;
+                }
+            }
+
+            while (this.parametersElement.childNodes.length)
+                this.parametersElement.removeChild(this.parametersElement.firstChild);
+
+            var p = element.action.properties;
+            var pr = element.action.propertiesResults;
+
+            if (p.length == 0)
+                return;
+
+            for (var i = 0; i < p.length; i++) {
+                var el = document.createElement('input');
+                el.setAttribute('value', pr[i]);
+                el.onchange = onChange(el, i);
+
+                var text = document.createElement('a');
+                text.text = p[i].text;
+                this.parametersElement.appendChild(document.createElement('br'));
+                this.parametersElement.appendChild(text);
+                this.parametersElement.appendChild(document.createElement('br'));
+                this.parametersElement.appendChild(el);
+            }
+        }
+
+        Graph.prototype._createNodeAnimation = function (element) {
+            var scope = this;
+            var mousex, mousey;
+            var finished = true;
+            var elementx = 0;
+            var elementy = 0;
+
+            var onMove = function (dx, dy, x, y, event) {
+                mousex = x;
+                mousey = y;
+            };
+
+            var onStart = function (x, y, event) {
+                mousex = x;
+                mousey = y;
+
+                if (finished) {
+                    elementx = element.attr(element.rect, 'x');
+                    elementy = element.attr(element.rect, 'y');
+                }
+                finished = false;
+
+                element.rect.animate({
+                    x: element.attr(element.rect, 'x') - 10,
+                    y: element.attr(element.rect, 'y') - 5,
+                    width: Graph.NODE_WIDTH + 20,
+                    height: Graph.NODE_HEIGHT + 10,
+                    opacity: 0.25
+                }, 500, '>');
+            };
+
+            var onEnd = function (event) {
+                element.rect.animate({
+                    x: elementx,
+                    y: elementy,
+                    width: Graph.NODE_WIDTH,
+                    height: Graph.NODE_HEIGHT,
+                    opacity: 1.0
+                }, 500, '>', function () { finished = true; });
+
+                var x = mousex - scope.graph.canvas.getBoundingClientRect().left;
+                var y = mousey - scope.graph.canvas.getBoundingClientRect().top;
+                var dragResult = scope.traverseGraph(null, x, y);
+
+                var json = JSON.stringify(scope.createJSON());
+                console.log(json);
+
+                if (dragResult.hit && dragResult.element == element.action || !dragResult.hit) {
+                    scope._createParameters(element);
+                }
+                else {
+                    if (element.action.type == AB.ActionsBuilder.Type.TRIGGER && dragResult.element != scope.root.action)
+                        return;
+
+                    if (element.action.type == AB.ActionsBuilder.Type.ACTION && dragResult.element == scope.root.action)
+                        return;
+
+                    if (element.action.type == AB.ActionsBuilder.Type.FLOW_CONTROL && dragResult.element == scope.root.action)
+                        return;
+
+                    // Reset node
+                    element.rect.stop(element.rect.animation);
+                    element.attr(element.rect, 'opacity', 1.0);
+                    element.attr(element.rect, 'width', Graph.NODE_WIDTH);
+                    element.attr(element.rect, 'height', Graph.NODE_HEIGHT);
+
+                    element.action.parent.removeChild(element.action);
+                    dragResult.element.addChild(element.action);
+                    scope.update();
+                }
+            };
+
+            element.rect.drag(onMove, onStart, onEnd);
+            element.text.drag(onMove, onStart, onEnd);
+        }
+
+        Graph.NODE_WIDTH = 150;
+        Graph.NODE_HEIGHT = 25;
+        Graph.VERTICAL_OFFSET = 70;
+
+        return Graph;
+    })();
+
+    AB.Graph = Graph;
+
+})(AB || (AB = {}));

+ 27 - 0
Exporters/3ds Max/Max2Babylon/Exporter/ActionBuilder/BabylonExporter.Action.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Autodesk.Max;
+using BabylonExport.Entities;
+
+namespace Max2Babylon
+{
+    partial class BabylonExporter
+    {
+        private BabylonActions ExportNodeAction(IIGameNode node)
+        {
+            string prop;
+            if (node != null)
+                prop = node.MaxNode.GetStringProperty("babylon_actionsbuilder", "");
+            else
+                prop = Loader.Core.RootNode.GetStringProperty("babylon_actionsbuilder", "");
+
+            if (String.IsNullOrEmpty(prop))
+                return null;
+
+            var result = JsonConvert.DeserializeObject<BabylonActions>(prop);
+
+            return result;
+        }
+    }
+}

+ 14 - 3
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Mesh.cs

@@ -121,6 +121,9 @@ namespace Max2Babylon
             babylonMesh.applyFog = meshNode.MaxNode.ApplyAtmospherics == 1;
             babylonMesh.alphaIndex = (int)meshNode.MaxNode.GetFloatProperty("babylonjs_alphaindex", 1000);
 
+            // Actions
+            babylonMesh.actions = ExportNodeAction(meshNode);
+
             // Collisions
             babylonMesh.checkCollisions = meshNode.MaxNode.GetBoolProperty("babylonjs_checkcollisions");
 
@@ -560,9 +563,17 @@ namespace Max2Babylon
                 float alpha = 1;
                 if (hasAlpha)
                 {
-                    IPoint3 p = Loader.Global.Point3.Create();
-                    mesh.GetMapFaceIndex(-2, face.MeshFaceIndex, p.GetNativeHandle());
-                    alpha = p.X;
+                    var indices = new int[3];
+                    unsafe
+                    {
+                        fixed (int* indicesPtr = indices)
+                        {
+                            mesh.GetMapFaceIndex(-2, face.MeshFaceIndex, new IntPtr(indicesPtr));
+                        }
+                    }
+                    var color = mesh.GetMapVertex(-2, indices[facePart]);
+
+                    alpha = color.X;
                 }
 
                 vertex.Color = new[] { vertexColor.X, vertexColor.Y, vertexColor.Z, alpha };

+ 3 - 0
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.cs

@@ -255,6 +255,9 @@ namespace Max2Babylon
                 }
             }
 
+            // Actions
+            babylonScene.actions = ExportNodeAction(gameScene.GetIGameNode(rawScene));
+
             // Output
             RaiseMessage("Saving to output file");
             babylonScene.Prepare(false);

+ 94 - 0
Exporters/3ds Max/Max2Babylon/Forms/ActionsBuilderForm.Designer.cs

@@ -0,0 +1,94 @@
+namespace Max2Babylon
+{
+    partial class ActionsBuilderForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.ActionsBuilderWebView = new System.Windows.Forms.WebBrowser();
+            this.butCancel = new System.Windows.Forms.Button();
+            this.butOK = new System.Windows.Forms.Button();
+            this.SuspendLayout();
+            // 
+            // ActionsBuilderWebView
+            // 
+            this.ActionsBuilderWebView.AllowNavigation = false;
+            this.ActionsBuilderWebView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.ActionsBuilderWebView.IsWebBrowserContextMenuEnabled = false;
+            this.ActionsBuilderWebView.Location = new System.Drawing.Point(0, 0);
+            this.ActionsBuilderWebView.MinimumSize = new System.Drawing.Size(20, 20);
+            this.ActionsBuilderWebView.Name = "ActionsBuilderWebView";
+            this.ActionsBuilderWebView.Size = new System.Drawing.Size(723, 537);
+            this.ActionsBuilderWebView.TabIndex = 0;
+            this.ActionsBuilderWebView.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.ActionsBuilderWebView_DocumentCompleted);
+            // 
+            // butCancel
+            // 
+            this.butCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+            this.butCancel.Location = new System.Drawing.Point(636, 544);
+            this.butCancel.Name = "butCancel";
+            this.butCancel.Size = new System.Drawing.Size(75, 23);
+            this.butCancel.TabIndex = 1;
+            this.butCancel.Text = "Cancel";
+            this.butCancel.UseVisualStyleBackColor = true;
+            // 
+            // butOK
+            // 
+            this.butOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.butOK.DialogResult = System.Windows.Forms.DialogResult.OK;
+            this.butOK.Location = new System.Drawing.Point(555, 544);
+            this.butOK.Name = "butOK";
+            this.butOK.Size = new System.Drawing.Size(75, 23);
+            this.butOK.TabIndex = 2;
+            this.butOK.Text = "OK";
+            this.butOK.UseVisualStyleBackColor = true;
+            this.butOK.Click += new System.EventHandler(this.butOK_Click);
+            // 
+            // ActionsBuilderForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(723, 579);
+            this.Controls.Add(this.butOK);
+            this.Controls.Add(this.butCancel);
+            this.Controls.Add(this.ActionsBuilderWebView);
+            this.Name = "ActionsBuilderForm";
+            this.Text = "Actions Builder";
+            this.Load += new System.EventHandler(this.ActionsBuilderForm_Load);
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.WebBrowser ActionsBuilderWebView;
+        private System.Windows.Forms.Button butCancel;
+        private System.Windows.Forms.Button butOK;
+    }
+}

+ 84 - 0
Exporters/3ds Max/Max2Babylon/Forms/ActionsBuilderForm.cs

@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Windows.Forms;
+using Autodesk.Max;
+
+namespace Max2Babylon
+{
+    public partial class ActionsBuilderForm : Form
+    {
+        private IINode _node = null;
+
+        private HtmlDocument _document;
+        private string _objectName;
+        private string _propertyName = "babylon_actionsbuilder";
+        private string _jsonResult = "";
+        private bool isRootNode;
+
+        public ActionsBuilderForm()
+        {
+            InitializeComponent();
+        }
+
+        private void ActionsBuilderForm_Load(object sender, EventArgs e)
+        {
+            if (Loader.Core.SelNodeCount > 0)
+            {
+                isRootNode = false;
+                _node = Loader.Core.GetSelNode(0);
+            }
+            else
+            {
+                isRootNode = true;
+                _node = Loader.Core.RootNode;
+            }
+            _objectName = _node.Name;
+
+            string currentDirectory = System.IO.Directory.GetCurrentDirectory();
+            ActionsBuilderWebView.Url = new Uri(string.Format("file:///{0}/bin/assemblies/BabylonActionsBuilder/index.html", currentDirectory), System.UriKind.Absolute);
+        }
+
+        private void ActionsBuilderWebView_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
+        {
+            _document = ActionsBuilderWebView.Document;
+            _document.GetElementById("ActionsBuilderObjectName").SetAttribute("value", _objectName);
+
+            if (isRootNode)
+                _document.InvokeScript("setIsScene");
+            else
+                _document.InvokeScript("setIsObject");
+
+            _document.InvokeScript("updateObjectName");
+
+            if (getProperty())
+            {
+                _document.GetElementById("ActionsBuilderJSON").SetAttribute("value", _jsonResult);
+                _document.InvokeScript("updateGraphFromJSON");
+            }
+        }
+
+        private void butOK_Click(object sender, EventArgs e)
+        {
+            _document.InvokeScript("updateJSONFromGraph");
+            _jsonResult = _document.GetElementById("ActionsBuilderJSON").GetAttribute("value");
+
+            setProperty();
+        }
+
+        private void setProperty()
+        {
+            if (_node != null)
+                Tools.SetStringProperty(_node, _propertyName, _jsonResult);
+        }
+
+        private bool getProperty()
+        {
+            if (_node != null)
+                _jsonResult = Tools.GetStringProperty(_node, _propertyName, _jsonResult);
+            else
+                return false;
+
+            return true;
+        }
+    }
+}

+ 120 - 0
Exporters/3ds Max/Max2Babylon/Forms/ActionsBuilderForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 5 - 0
Exporters/3ds Max/Max2Babylon/GlobalUtility.cs

@@ -54,6 +54,7 @@ namespace Max2Babylon
                 actionTable = Loader.Global.ActionTable.Create(idActionTable, 0, ref actionTableName);
                 actionTable.AppendOperation(new BabylonExportActionItem());
                 actionTable.AppendOperation(new BabylonPropertiesActionItem());
+                actionTable.AppendOperation(new BabylonActionsBuilderActionItem());
                 actionCallback = new BabylonActionCallback();
 
                 actionManager.RegisterActionTable(actionTable);
@@ -106,7 +107,11 @@ namespace Max2Babylon
                 menuItemBabylon = Loader.Global.IMenuItem;
                 menuItemBabylon.Title = "Babylon Properties";
                 menuItemBabylon.ActionItem = actionTable[1];
+                menu.AddItem(menuItemBabylon, -1);
 
+                menuItemBabylon = Loader.Global.IMenuItem;
+                menuItemBabylon.Title = "Babylon Actions Builder";
+                menuItemBabylon.ActionItem = actionTable[2];
                 menu.AddItem(menuItemBabylon, -1);
 
                 menuItem = Loader.Global.IMenuItem;

+ 9 - 1
Exporters/3ds Max/Max2Babylon/Tools/Tools.cs

@@ -558,7 +558,15 @@ namespace Max2Babylon
             return true;
         }
 
-     
+        public static void SetStringProperty(this IINode node, string propertyName, string defaultState)
+        {
+            string state = defaultState;
+#if MAX2015
+            node.SetUserPropString(propertyName, state);
+#else
+            node.SetUserPropString(ref propertyName, ref state);
+#endif
+        }
 
         public static bool GetBoolProperty(this IINode node, string propertyName, int defaultState = 0)
         {