diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake
index ed80e03c6b..09716715f0 100644
--- a/cmake/macros/MemoryDebugger.cmake
+++ b/cmake/macros/MemoryDebugger.cmake
@@ -16,9 +16,9 @@ if (HIFI_MEMORY_DEBUGGING)
   if (UNIX)
     if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
         # for clang on Linux
-        SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -fsanitize-recover=address")
-        SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize-recover=address")
-        SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize-recover=address")
+        SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -shared-libasan -fsanitize=undefined -fsanitize=address -fsanitize-recover=address")
+        SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -shared-libasan -fsanitize=undefined -fsanitize=address -fsanitize-recover=address")
+        SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -shared-libasan -fsanitize=undefined -fsanitize=address -fsanitize-recover=address")
     else ()
         # for gcc on Linux
         SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer")
diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
index c2a4d47992..7e17d20375 100644
--- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
+++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
@@ -19,6 +19,7 @@ import controlsUit 1.0 as HifiControlsUit
 import "../../../controls" as HifiControls
 import "../wallet" as HifiWallet
 import "../common" as HifiCommerceCommon
+import "../.." as HifiCommon
 
 // references XXX from root context
 
@@ -31,6 +32,7 @@ Rectangle {
     property bool ownershipStatusReceived: false;
     property bool balanceReceived: false;
     property bool availableUpdatesReceived: false;
+    property bool itemInfoReceived: false;
     property string baseItemName: "";
     property string itemName;
     property string itemId;
@@ -181,11 +183,14 @@ Rectangle {
 
     onItemIdChanged: {
         root.ownershipStatusReceived = false;
+        root.itemInfoReceived = false;
         Commerce.alreadyOwned(root.itemId);
         root.availableUpdatesReceived = false;
         root.currentUpdatesPage = 1;
         Commerce.getAvailableUpdates(root.itemId);
-        itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
+        
+        var MARKETPLACE_API_URL = Account.metaverseServerURL + "/api/v1/marketplace/items/";
+        http.request({uri: MARKETPLACE_API_URL + root.itemId}, updateCheckoutQMLFromHTTP);
     }
 
     onItemTypeChanged: {
@@ -279,6 +284,7 @@ Rectangle {
             ownershipStatusReceived = false;
             balanceReceived = false;
             availableUpdatesReceived = false;
+            itemInfoReceived = false;
             Commerce.getWalletStatus();
         }
     }
@@ -355,7 +361,7 @@ Rectangle {
         Rectangle {
             id: loading;
             z: 997;
-            visible: !root.ownershipStatusReceived || !root.balanceReceived || !root.availableUpdatesReceived;
+            visible: !root.ownershipStatusReceived || !root.balanceReceived || !root.availableUpdatesReceived || !root.itemInfoReceived;
             anchors.fill: parent;
             color: hifi.colors.white;
 
@@ -1063,10 +1069,33 @@ Rectangle {
             }
         }
     }
+    
+
+    HifiCommon.RootHttpRequest {
+        id: http;
+    }
 
     //
     // FUNCTION DEFINITIONS START
     //
+
+    function updateCheckoutQMLFromHTTP(error, result) {
+        if (error || (result.status !== 'success')) {
+            // The QML will display a loading spinner forever if the user is stuck here.
+            console.log("Error in Checkout.qml when getting marketplace item info!");
+            return;
+        }
+
+        root.itemInfoReceived = true;
+        root.itemName = result.data.title;
+        root.itemPrice = result.data.cost;
+        root.itemHref = Account.metaverseServerURL + result.data.path;
+        root.itemAuthor = result.data.creator;
+        root.itemType = result.data.item_type || "unknown";
+        itemPreviewImage.source = result.data.thumbnail_url;
+        refreshBuyUI();
+    }
+
     //
     // Function Name: fromScript()
     //
@@ -1080,18 +1109,24 @@ Rectangle {
     // Description:
     // Called when a message is received from a script.
     //
+
     function fromScript(message) {
         switch (message.method) {
-            case 'updateCheckoutQML':
-                root.itemId = message.params.itemId;
-                root.itemName = message.params.itemName.trim();
-                root.itemPrice = message.params.itemPrice;
-                root.itemHref = message.params.itemHref;
-                root.referrer = message.params.referrer;
-                root.itemAuthor = message.params.itemAuthor;
+            case 'updateCheckoutQMLItemID':
+                if (!message.params.itemId) {
+                    console.log("A message with method 'updateCheckoutQMLItemID' was sent without an itemId!");
+                    return;
+                }
+
+                // If we end up following the referrer (i.e. in case the wallet "isn't set up" or the user cancels),
+                // we want the user to be placed back on the individual item's page - thus we set the
+                // default of the referrer in this case to "itemPage".
+                root.referrer = message.params.referrer || "itemPage";
                 root.itemEdition = message.params.itemEdition || -1;
-                root.itemType = message.params.itemType || "unknown";
-                refreshBuyUI();
+                root.itemId = message.params.itemId;
+            break;
+            case 'http.response':
+                http.handleHttpResponse(message);
             break;
             default:
                 console.log('Checkout.qml: Unrecognized message from marketplaces.js');
diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml
index 9e1a967d50..10756957d3 100644
--- a/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml
+++ b/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml
@@ -25,14 +25,15 @@ Item {
 
     id: root;
 
-    property bool isDisplayingNearby; // as opposed to 'connections'
+    // true when sending to 'nearby' or when a script raises the send asset dialog
+    property bool multiLineDisplay;
     property string displayName;
     property string userName;
     property string profilePic;
     property string textColor: hifi.colors.white;
 
     Item {
-        visible: root.isDisplayingNearby;
+        visible: root.multiLineDisplay;
         anchors.fill: parent;
 
         RalewaySemiBold {
@@ -71,7 +72,7 @@ Item {
     }
 
     Item {
-        visible: !root.isDisplayingNearby;
+        visible: !root.multiLineDisplay;
         anchors.fill: parent;
 
         Image {
diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml
index d6378f82ac..2d0bb2d87b 100644
--- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml
+++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml
@@ -39,7 +39,7 @@ Item {
     property string sendingPubliclyEffectImage;
     property var http;
     property var listModelName;
-    property var keyboardContainer: nil;
+    property var keyboardContainer;
         
     // This object is always used in a popup or full-screen Wallet section.
     // This MouseArea is used to prevent a user from being
@@ -56,7 +56,7 @@ Item {
     // Background
     Rectangle {
         z: 1;
-        visible: root.assetName !== "" && sendAssetStep.visible;
+        visible: root.assetCertID !== "" && sendAssetStep.referrer !== "payIn" && sendAssetStep.visible;
         anchors.top: parent.top;
         anchors.topMargin: root.parentAppTitleBarHeight;
         anchors.left: parent.left;
@@ -84,7 +84,6 @@ Item {
                 if (sendPubliclyCheckbox.checked && sendAssetStep.referrer === "nearby") {
                     sendSignalToParent({
                         method: 'sendAsset_sendPublicly',
-                        assetName: root.assetName,
                         recipient: sendAssetStep.selectedRecipientNodeID,
                         amount: parseInt(amountTextField.text),
                         effectImage: root.sendingPubliclyEffectImage
@@ -108,6 +107,14 @@ Item {
                 root.nextActiveView = 'paymentFailure';
             }
         }
+
+        onCertificateInfoResult: {
+            if (result.status !== 'success') {
+                console.log("Failed to get certificate info", result.data.message);
+            } else {
+                root.assetName = result.data.marketplace_item_name;
+            }
+        }
     }
 
     Connections {
@@ -155,7 +162,7 @@ Item {
 
         Item {
             id: userInfoContainer;
-            visible: root.assetName === "";
+            visible: root.assetCertID === "";
             anchors.top: parent.top;
             anchors.left: parent.left;
             anchors.right: parent.right;
@@ -251,7 +258,7 @@ Item {
     
             LinearGradient {
                 anchors.fill: parent;
-                visible: root.assetName === "";
+                visible: root.assetCertID === "";
                 start: Qt.point(0, 0);
                 end: Qt.point(0, height);
                 gradient: Gradient {
@@ -262,7 +269,7 @@ Item {
 
             RalewaySemiBold {
                 id: sendAssetText;
-                text: root.assetName === "" ? "Send Money To:" : "Gift \"" + root.assetName + "\" To:";
+                text: root.assetCertID === "" ? "Send Money To:" : "Gift \"" + root.assetName + "\" To:";
                 // Anchors
                 anchors.top: parent.top;
                 anchors.topMargin: 26;
@@ -405,7 +412,7 @@ Item {
         HifiModels.PSFListModel {
             id: connectionsModel;
             http: root.http;
-            listModelName: root.listModelName;
+            listModelName: root.listModelName || "";
             endpoint: "/api/v1/users?filter=connections";
             itemsPerPage: 9;
             listView: connectionsList;
@@ -441,7 +448,7 @@ Item {
             HiFiGlyphs {
                 id: closeGlyphButton_connections;
                 text: hifi.glyphs.close;
-                color: root.assetName === "" ? hifi.colors.lightGrayText : hifi.colors.baseGray;
+                color: root.assetCertID === "" ? hifi.colors.lightGrayText : hifi.colors.baseGray;
                 size: 26;
                 anchors.top: parent.top;
                 anchors.topMargin: 10;
@@ -684,7 +691,7 @@ Item {
             HiFiGlyphs {
                 id: closeGlyphButton_nearby;
                 text: hifi.glyphs.close;
-                color: root.assetName === "" ? hifi.colors.lightGrayText : hifi.colors.baseGray;
+                color: root.assetCertID === "" ? hifi.colors.lightGrayText : hifi.colors.baseGray;
                 size: 26;
                 anchors.top: parent.top;
                 anchors.topMargin: 10;
@@ -760,7 +767,7 @@ Item {
 
                 RalewaySemiBold {
                     id: sendToText;
-                    text: root.assetName === "" ? "Send to:" : "Gift to:";
+                    text: root.assetCertID === "" ? "Send to:" : "Gift to:";
                     // Anchors
                     anchors.top: parent.top;
                     anchors.topMargin: 36;
@@ -853,7 +860,7 @@ Item {
         id: sendAssetStep;
         z: 996;
 
-        property string referrer; // either "connections" or "nearby"
+        property string referrer; // either "connections", "nearby", or "payIn"
         property string selectedRecipientNodeID;
         property string selectedRecipientDisplayName;
         property string selectedRecipientUserName;
@@ -865,7 +872,8 @@ Item {
 
         RalewaySemiBold {
             id: sendAssetText_sendAssetStep;
-            text: root.assetName === "" ? "Send Money" : "Gift \"" + root.assetName + "\"";
+            text: sendAssetStep.referrer === "payIn" && root.assetCertID !== "" ? "Send \"" + root.assetName + "\":" :
+                (root.assetCertID === "" ? "Send Money To:" : "Gift \"" + root.assetName + "\" To:");
             // Anchors
             anchors.top: parent.top;
             anchors.topMargin: 26;
@@ -878,7 +886,7 @@ Item {
             // Text size
             size: 22;
             // Style
-            color: root.assetName === "" ? hifi.colors.white : hifi.colors.black;
+            color: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.colors.white : hifi.colors.black;
         }
 
         Item {
@@ -893,7 +901,7 @@ Item {
 
             RalewaySemiBold {
                 id: sendToText_sendAssetStep;
-                text: root.assetName === "" ? "Send to:" : "Gift to:";
+                text: (root.assetCertID === "" || sendAssetStep.referrer === "payIn") ? "Send to:" : "Gift to:";
                 // Anchors
                 anchors.top: parent.top;
                 anchors.left: parent.left;
@@ -902,7 +910,7 @@ Item {
                 // Text size
                 size: 18;
                 // Style
-                color: root.assetName === "" ? hifi.colors.white : hifi.colors.black;
+                color: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.colors.white : hifi.colors.black;
                 verticalAlignment: Text.AlignVCenter;
             }
 
@@ -912,25 +920,26 @@ Item {
                 anchors.right: changeButton.left;
                 anchors.rightMargin: 12;
                 height: parent.height;
-                textColor: root.assetName === "" ? hifi.colors.white : hifi.colors.black;
+                textColor: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.colors.white : hifi.colors.black;
 
                 displayName: sendAssetStep.selectedRecipientDisplayName;
                 userName: sendAssetStep.selectedRecipientUserName;
                 profilePic: sendAssetStep.selectedRecipientProfilePic !== "" ? ((0 === sendAssetStep.selectedRecipientProfilePic.indexOf("http")) ?
                     sendAssetStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendAssetStep.selectedRecipientProfilePic)) : "";
-                isDisplayingNearby: sendAssetStep.referrer === "nearby";
+                multiLineDisplay: sendAssetStep.referrer === "nearby" || sendAssetStep.referrer === "payIn";
             }
 
             // "CHANGE" button
             HifiControlsUit.Button {
                 id: changeButton;
-                color: root.assetName === "" ? hifi.buttons.none : hifi.buttons.white;
+                color: root.assetCertID === "" ? hifi.buttons.none : hifi.buttons.white;
                 colorScheme: hifi.colorSchemes.dark;
                 anchors.right: parent.right;
                 anchors.verticalCenter: parent.verticalCenter;
                 height: 35;
                 width: 100;
                 text: "CHANGE";
+                visible: sendAssetStep.referrer !== "payIn";
                 onClicked: {
                     if (sendAssetStep.referrer === "connections") {
                         root.nextActiveView = "chooseRecipientConnection";
@@ -944,7 +953,7 @@ Item {
 
         Item {
             id: amountContainer;
-            visible: root.assetName === "";
+            visible: root.assetCertID === "";
             anchors.top: sendToContainer.bottom;
             anchors.topMargin: 2;
             anchors.left: parent.left;
@@ -970,8 +979,9 @@ Item {
 
             HifiControlsUit.TextField {
                 id: amountTextField;
-                text: root.assetName === "" ? "" : "1";
-                colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
+                readOnly: sendAssetStep.referrer === "payIn";
+                text: root.assetCertID === "" ? "" : "1";
+                colorScheme: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
                 inputMethodHints: Qt.ImhDigitsOnly;
                 // Anchors
                 anchors.verticalCenter: parent.verticalCenter;
@@ -980,8 +990,8 @@ Item {
                 height: 50;
                 // Style
                 leftPermanentGlyph: hifi.glyphs.hfc;
-                activeFocusOnPress: true;
-                activeFocusOnTab: true;
+                activeFocusOnPress: !amountTextField.readOnly;
+                activeFocusOnTab: !amountTextField.readOnly;
 
                 validator: IntValidator { bottom: 0; }
 
@@ -1071,6 +1081,7 @@ Item {
 
             TextArea {
                 id: optionalMessage;
+                readOnly: sendAssetStep.referrer === "payIn";
                 property int maximumLength: 72;
                 property string previousText: text;
                 placeholderText: "<i>Optional Public Message (" + maximumLength + " character limit)</i>";
@@ -1081,12 +1092,13 @@ Item {
                 // Style
                 background: Rectangle {
                     anchors.fill: parent;
-                    color: root.assetName === "" ? (optionalMessage.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow) :
+                    color: (root.assetCertID === "" || sendAssetStep.referrer === "payIn") ?
+                        (optionalMessage.activeFocus && !optionalMessage.readOnly ? hifi.colors.black : hifi.colors.baseGrayShadow) :
                         (optionalMessage.activeFocus ? "#EFEFEF" : "#EEEEEE");
-                    border.width: optionalMessage.activeFocus ? 1 : 0;
-                    border.color: optionalMessage.activeFocus ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground;
+                    border.width: optionalMessage.activeFocus && !optionalMessage.readOnly ? 1 : 0;
+                    border.color: optionalMessage.activeFocus && !optionalMessage.readOnly ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground;
                 }
-                color: root.assetName === "" ? hifi.colors.white : hifi.colors.black;
+                color: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.colors.white : hifi.colors.black;
                 textFormat: TextEdit.PlainText;
                 wrapMode: TextEdit.Wrap;
                 activeFocusOnPress: true;
@@ -1122,7 +1134,8 @@ Item {
                 // Text size
                 size: 16;
                 // Style
-                color: optionalMessage.text.length === optionalMessage.maximumLength ? "#ea89a5" : (root.assetName === "" ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight);
+                color: optionalMessage.text.length === optionalMessage.maximumLength ? "#ea89a5" :
+                    (root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight);
                 verticalAlignment: Text.AlignTop;
                 horizontalAlignment: Text.AlignRight;
             }
@@ -1167,7 +1180,7 @@ Item {
                     parent.color = hifi.colors.blueAccent;
                 }
                 onClicked: {
-                    lightboxPopup.titleText = (root.assetName === "" ? "Send Effect" : "Gift Effect");
+                    lightboxPopup.titleText = (root.assetCertID === "" ? "Send Effect" : "Gift Effect");
                     lightboxPopup.bodyImageSource = "sendAsset/images/send-money-effect-sm.jpg"; // Path relative to CommerceLightbox.qml
                     lightboxPopup.bodyText = "Enabling this option will create a particle effect between you and " +
                         "your recipient that is visible to everyone nearby.";
@@ -1196,7 +1209,7 @@ Item {
             // "CANCEL" button
             HifiControlsUit.Button {
                 id: cancelButton_sendAssetStep;
-                color: root.assetName === "" ? hifi.buttons.noneBorderlessWhite : hifi.buttons.noneBorderlessGray;
+                color: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.buttons.noneBorderlessWhite : hifi.buttons.noneBorderlessGray;
                 colorScheme: hifi.colorSchemes.dark;
                 anchors.right: sendButton.left;
                 anchors.rightMargin: 24;
@@ -1205,8 +1218,12 @@ Item {
                 width: 100;
                 text: "CANCEL";
                 onClicked: {
-                    resetSendAssetData();
-                    root.nextActiveView = "sendAssetHome";
+                    if (sendAssetStep.referrer === "payIn") {
+                        sendToScript({method: "closeSendAsset"});
+                    } else {
+                        resetSendAssetData();
+                        root.nextActiveView = "sendAssetHome";
+                    }
                 }
             }
 
@@ -1214,7 +1231,7 @@ Item {
             HifiControlsUit.Button {
                 id: sendButton;
                 color: hifi.buttons.blue;
-                colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
+                colorScheme: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
                 anchors.right: parent.right;
                 anchors.rightMargin: 0;
                 anchors.verticalCenter: parent.verticalCenter;
@@ -1222,11 +1239,11 @@ Item {
                 width: 100;
                 text: "SUBMIT";
                 onClicked: {
-                    if (root.assetName === "" && parseInt(amountTextField.text) > parseInt(balanceText.text)) {
+                    if (root.assetCertID === "" && parseInt(amountTextField.text) > parseInt(balanceText.text)) {
                         amountTextField.focus = true;
                         amountTextField.error = true;
                         amountTextFieldError.text = "<i>amount exceeds available funds</i>";
-                    } else if (root.assetName === "" && (amountTextField.text === "" || parseInt(amountTextField.text) < 1)) {
+                    } else if (root.assetCertID === "" && (amountTextField.text === "" || parseInt(amountTextField.text) < 1)) {
                         amountTextField.focus = true;
                         amountTextField.error = true;
                         amountTextFieldError.text = "<i>invalid amount</i>";
@@ -1236,7 +1253,7 @@ Item {
                         root.isCurrentlySendingAsset = true;
                         amountTextField.focus = false;
                         optionalMessage.focus = false;
-                        if (sendAssetStep.referrer === "connections") {
+                        if (sendAssetStep.referrer === "connections" || sendAssetStep.referrer === "payIn") {
                             Commerce.transferAssetToUsername(sendAssetStep.selectedRecipientUserName,
                                 root.assetCertID,
                                 parseInt(amountTextField.text),
@@ -1317,18 +1334,18 @@ Item {
 
         Rectangle {
             anchors.top: parent.top;
-            anchors.topMargin: root.assetName === "" ? 15 : 125;
+            anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 125;
             anchors.left: parent.left;
-            anchors.leftMargin: root.assetName === "" ? 15 : 50;
+            anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50;
             anchors.right: parent.right;
-            anchors.rightMargin: root.assetName === "" ? 15 : 50;
+            anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50;
             anchors.bottom: parent.bottom;
-            anchors.bottomMargin: root.assetName === "" ? 15 : 125;
+            anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 125;
             color: "#FFFFFF";
 
             RalewaySemiBold {
                 id: paymentSentText;
-                text: root.assetName === "" ? "Payment Sent" : "Gift Sent";
+                text: root.assetCertID === "" ? "Payment Sent" : (sendAssetStep.referrer === "payIn" ? "Item Sent" : "Gift Sent");
                 // Anchors
                 anchors.top: parent.top;
                 anchors.topMargin: 26;
@@ -1346,7 +1363,7 @@ Item {
             
             HiFiGlyphs {
                 id: closeGlyphButton_paymentSuccess;
-                visible: root.assetName === "";
+                visible: root.assetCertID === "" && sendAssetStep.referrer !== "payIn";
                 text: hifi.glyphs.close;
                 color: hifi.colors.lightGrayText;
                 size: 26;
@@ -1364,10 +1381,14 @@ Item {
                         parent.text = hifi.glyphs.close;
                     }
                     onClicked: {
-                        root.nextActiveView = "sendAssetHome";
-                        resetSendAssetData();
-                        if (root.assetName !== "") {
-                            sendSignalToParent({method: "closeSendAsset"});
+                        if (sendAssetStep.referrer === "payIn") {
+                            sendToScript({method: "closeSendAsset"});
+                        } else {
+                            root.nextActiveView = "sendAssetHome";
+                            resetSendAssetData();
+                            if (root.assetName !== "") {
+                                sendSignalToParent({method: "closeSendAsset"});
+                            }
                         }
                     }
                 }
@@ -1409,14 +1430,14 @@ Item {
                     userName: sendAssetStep.selectedRecipientUserName;
                     profilePic: sendAssetStep.selectedRecipientProfilePic !== "" ? ((0 === sendAssetStep.selectedRecipientProfilePic.indexOf("http")) ?
                         sendAssetStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendAssetStep.selectedRecipientProfilePic)) : "";
-                    isDisplayingNearby: sendAssetStep.referrer === "nearby";
+                    multiLineDisplay: sendAssetStep.referrer === "nearby" || sendAssetStep.referrer === "payIn";
                 }
             }
             
 
             Item {
                 id: giftContainer_paymentSuccess;
-                visible: root.assetName !== "";
+                visible: root.assetCertID !== "";
                 anchors.top: sendToContainer_paymentSuccess.bottom;
                 anchors.topMargin: 8;
                 anchors.left: parent.left;
@@ -1427,7 +1448,7 @@ Item {
 
                 RalewaySemiBold {
                     id: gift_paymentSuccess;
-                    text: "Gift:";
+                    text: sendAssetStep.referrer === "payIn" ? "Item:" : "Gift:";
                     // Anchors
                     anchors.top: parent.top;
                     anchors.left: parent.left;
@@ -1458,7 +1479,7 @@ Item {
 
             Item {
                 id: amountContainer_paymentSuccess;
-                visible: root.assetName === "";
+                visible: root.assetCertID === "";
                 anchors.top: sendToContainer_paymentSuccess.bottom;
                 anchors.topMargin: 16;
                 anchors.left: parent.left;
@@ -1513,7 +1534,7 @@ Item {
 
             RalewaySemiBold {
                 id: optionalMessage_paymentSuccess;
-                visible: root.assetName === "";
+                visible: root.assetCertID === "";
                 text: optionalMessage.text;
                 // Anchors
                 anchors.top: amountContainer_paymentSuccess.visible ? amountContainer_paymentSuccess.bottom : sendToContainer_paymentSuccess.bottom;
@@ -1535,18 +1556,22 @@ Item {
             HifiControlsUit.Button {
                 id: closeButton;
                 color: hifi.buttons.blue;
-                colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
+                colorScheme: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
                 anchors.horizontalCenter: parent.horizontalCenter;
                 anchors.bottom: parent.bottom;
-                anchors.bottomMargin: root.assetName === "" ? 80 : 30;
+                anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 80 : 30;
                 height: 50;
                 width: 120;
                 text: "Close";
                 onClicked: {
-                    root.nextActiveView = "sendAssetHome";
-                    resetSendAssetData();
-                    if (root.assetName !== "") {
-                        sendSignalToParent({method: "closeSendAsset"});
+                    if (sendAssetStep.referrer === "payIn") {
+                        sendToScript({method: "closeSendAsset"});
+                    } else {
+                        root.nextActiveView = "sendAssetHome";
+                        resetSendAssetData();
+                        if (root.assetName !== "") {
+                            sendSignalToParent({method: "closeSendAsset"});
+                        }
                     }
                 }
             }
@@ -1574,18 +1599,18 @@ Item {
 
         Rectangle {
             anchors.top: parent.top;
-            anchors.topMargin: root.assetName === "" ? 15 : 150;
+            anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 150;
             anchors.left: parent.left;
-            anchors.leftMargin: root.assetName === "" ? 15 : 50;
+            anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50;
             anchors.right: parent.right;
-            anchors.rightMargin: root.assetName === "" ? 15 : 50;
+            anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50;
             anchors.bottom: parent.bottom;
-            anchors.bottomMargin: root.assetName === "" ? 15 : 300;
+            anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 300;
             color: "#FFFFFF";
 
             RalewaySemiBold {
                 id: paymentFailureText;
-                text: root.assetName === "" ? "Payment Failed" : "Failed";
+                text: root.assetCertID === "" && sendAssetStep.referrer !== "payIn" ? "Payment Failed" : "Failed";
                 // Anchors
                 anchors.top: parent.top;
                 anchors.topMargin: 26;
@@ -1603,7 +1628,7 @@ Item {
             
             HiFiGlyphs {
                 id: closeGlyphButton_paymentFailure;
-                visible: root.assetName === "";
+                visible: root.assetCertID === "" && sendAssetStep.referrer !== "payIn";
                 text: hifi.glyphs.close;
                 color: hifi.colors.lightGrayText;
                 size: 26;
@@ -1632,7 +1657,8 @@ Item {
 
             RalewaySemiBold {
                 id: paymentFailureDetailText;
-                text: "The recipient you specified was unable to receive your " + (root.assetName === "" ? "payment." : "gift.");
+                text: "The recipient you specified was unable to receive your " +
+                    (root.assetCertID === "" ? "payment." : (sendAssetStep.referrer === "payIn" ? "item." : "gift."));
                 anchors.top: paymentFailureText.bottom;
                 anchors.topMargin: 20;
                 anchors.left: parent.left;
@@ -1650,7 +1676,7 @@ Item {
 
             Item {
                 id: sendToContainer_paymentFailure;
-                visible: root.assetName === "";
+                visible: root.assetCertID === "" || sendAssetStep.referrer === "payIn";
                 anchors.top: paymentFailureDetailText.bottom;
                 anchors.topMargin: 8;
                 anchors.left: parent.left;
@@ -1685,13 +1711,13 @@ Item {
                     userName: sendAssetStep.selectedRecipientUserName;
                     profilePic: sendAssetStep.selectedRecipientProfilePic !== "" ? ((0 === sendAssetStep.selectedRecipientProfilePic.indexOf("http")) ?
                         sendAssetStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendAssetStep.selectedRecipientProfilePic)) : "";
-                    isDisplayingNearby: sendAssetStep.referrer === "nearby";
+                    multiLineDisplay: sendAssetStep.referrer === "nearby" || sendAssetStep.referrer === "payIn";
                 }
             }
 
             Item {
                 id: amountContainer_paymentFailure;
-                visible: root.assetName === "";
+                visible: root.assetCertID === "";
                 anchors.top: sendToContainer_paymentFailure.bottom;
                 anchors.topMargin: 16;
                 anchors.left: parent.left;
@@ -1746,7 +1772,7 @@ Item {
 
             RalewaySemiBold {
                 id: optionalMessage_paymentFailure;
-                visible: root.assetName === "";
+                visible: root.assetCertID === "" || sendAssetStep.referrer === "payIn";
                 text: optionalMessage.text;
                 // Anchors
                 anchors.top: amountContainer_paymentFailure.visible ? amountContainer_paymentFailure.bottom : sendToContainer_paymentFailure.bottom;
@@ -1768,19 +1794,23 @@ Item {
             HifiControlsUit.Button {
                 id: closeButton_paymentFailure;
                 color: hifi.buttons.noneBorderless;
-                colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
+                colorScheme: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
                 anchors.right: retryButton_paymentFailure.left;
                 anchors.rightMargin: 12;
                 anchors.bottom: parent.bottom;
-                anchors.bottomMargin: root.assetName === "" ? 80 : 30;
+                anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 80 : 30;
                 height: 50;
                 width: 120;
                 text: "Cancel";
                 onClicked: {
-                    root.nextActiveView = "sendAssetHome";
-                    resetSendAssetData();
-                    if (root.assetName !== "") {
-                        sendSignalToParent({method: "closeSendAsset"});
+                    if (sendAssetStep.referrer === "payIn") {
+                        sendToScript({method: "closeSendAsset"});
+                    } else {
+                        root.nextActiveView = "sendAssetHome";
+                        resetSendAssetData();
+                        if (root.assetName !== "") {
+                            sendSignalToParent({method: "closeSendAsset"});
+                        }
                     }
                 }
             }
@@ -1789,17 +1819,17 @@ Item {
             HifiControlsUit.Button {
                 id: retryButton_paymentFailure;
                 color: hifi.buttons.blue;
-                colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
+                colorScheme: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
                 anchors.right: parent.right;
                 anchors.rightMargin: 12;
                 anchors.bottom: parent.bottom;
-                anchors.bottomMargin: root.assetName === "" ? 80 : 30;
+                anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 80 : 30;
                 height: 50;
                 width: 120;
                 text: "Retry";
                 onClicked: {
                     root.isCurrentlySendingAsset = true;
-                    if (sendAssetStep.referrer === "connections") {
+                    if (sendAssetStep.referrer === "connections" || sendAssetStep.referrer === "payIn") {
                         Commerce.transferAssetToUsername(sendAssetStep.selectedRecipientUserName,
                             root.assetCertID,
                             parseInt(amountTextField.text),
@@ -1866,11 +1896,32 @@ Item {
             case 'updateSelectedRecipientUsername':
                 sendAssetStep.selectedRecipientUserName = message.userName;
             break;
+            case 'updateSendAssetQML':
+                root.assetName = "";
+                root.assetCertID = message.assetCertID || "";
+                if (root.assetCertID === "") {
+                    amountTextField.text = message.amount || 1;
+                } else {
+                    amountTextField.text = "";
+                    Commerce.certificateInfo(root.assetCertID);
+                }
+                sendAssetStep.referrer = "payIn";
+                sendAssetStep.selectedRecipientNodeID = "";
+                sendAssetStep.selectedRecipientDisplayName = "Determined by script:";
+                sendAssetStep.selectedRecipientUserName = message.username;
+                optionalMessage.text = message.message || "No Message Provided";
+
+                root.nextActiveView = "sendAssetStep";
+            break;
+            case 'inspectionCertificate_resetCert':
+                // NOP
+            break;
             default:
                 console.log('SendAsset: Unrecognized message from wallet.js');
         }
     }
     signal sendSignalToParent(var msg);
+    signal sendToScript(var message);
     //
     // FUNCTION DEFINITIONS END
     //
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index fb6e8ec566..21af28ffcb 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -927,7 +927,9 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
 #endif
     DependencyManager::set<DiscoverabilityManager>();
     DependencyManager::set<SceneScriptingInterface>();
+#if !defined(DISABLE_QML)
     DependencyManager::set<OffscreenUi>();
+#endif
     DependencyManager::set<Midi>();
     DependencyManager::set<PathUtils>();
     DependencyManager::set<InterfaceDynamicFactory>();
@@ -1000,6 +1002,14 @@ const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
 const QString DEFAULT_CURSOR_NAME = "DEFAULT";
 const bool DEFAULT_MINI_TABLET_ENABLED = true;
 
+QSharedPointer<OffscreenUi> getOffscreenUI() {
+#if !defined(DISABLE_QML)
+    return DependencyManager::get<OffscreenUi>();
+#else
+    return nullptr;
+#endif
+}
+
 Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runningMarkerExisted) :
     QApplication(argc, argv),
     _window(new MainWindow(desktop())),
@@ -1604,7 +1614,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
     auto userInputMapper = DependencyManager::get<UserInputMapper>();
     connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
         using namespace controller;
-        auto offscreenUi = DependencyManager::get<OffscreenUi>();
         auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
         {
             auto actionEnum = static_cast<Action>(action);
@@ -1743,7 +1752,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
         return qApp->getMyAvatar()->getCharacterController()->onGround() ? 1 : 0;
     });
     _applicationStateDevice->setInputVariant(STATE_NAV_FOCUSED, []() -> float {
-        return DependencyManager::get<OffscreenUi>()->navigationFocused() ? 1 : 0;
+        auto offscreenUi = getOffscreenUI();
+        return offscreenUi ? (offscreenUi->navigationFocused() ? 1 : 0) : 0;
     });
     _applicationStateDevice->setInputVariant(STATE_PLATFORM_WINDOWS, []() -> float {
 #if defined(Q_OS_WIN)
@@ -1809,9 +1819,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
     // Now that we've loaded the menu and thus switched to the previous display plugin
     // we can unlock the desktop repositioning code, since all the positions will be
     // relative to the desktop size for this plugin
-    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+    auto offscreenUi = getOffscreenUI();
     connect(offscreenUi.data(), &OffscreenUi::desktopReady, []() {
-        auto offscreenUi = DependencyManager::get<OffscreenUi>();
+        auto offscreenUi = getOffscreenUI();
         auto desktop = offscreenUi->getDesktop();
         if (desktop) {
             desktop->setProperty("repositionLocked", false);
@@ -2372,6 +2382,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
     connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
     AndroidHelper::instance().notifyLoadComplete();
 #else
+#if !defined(DISABLE_QML)
     // Do not show login dialog if requested not to on the command line
     const QString HIFI_NO_LOGIN_COMMAND_LINE_KEY = "--no-login-suggestion";
     int index = arguments().indexOf(HIFI_NO_LOGIN_COMMAND_LINE_KEY);
@@ -2396,6 +2407,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
         checkLoginTimer->start();
     }
 #endif
+#endif
 }
 
 void Application::updateVerboseLogging() {
@@ -2546,7 +2558,9 @@ void Application::onAboutToQuit() {
     DependencyManager::get<CloseEventSender>()->startThread();
 
     // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown.
-    DependencyManager::get<OffscreenUi>()->hide("RunningScripts");
+#if !defined(DISABLE_QML)
+    getOffscreenUI()->hide("RunningScripts");
+#endif
 
     _aboutToQuit = true;
 
@@ -2784,10 +2798,10 @@ void Application::initializeGL() {
     _glWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat());
 
     // When loading QtWebEngineWidgets, it creates a global share context on startup.
-    // We have to account for this possibility by checking here for an existing 
+    // We have to account for this possibility by checking here for an existing
     // global share context
     auto globalShareContext = qt_gl_global_share_context();
-    
+
 #if !defined(DISABLE_QML)
     // Build a shared canvas / context for the Chromium processes
     if (!globalShareContext) {
@@ -2974,7 +2988,9 @@ void Application::initializeRenderEngine() {
 }
 
 extern void setupPreferences();
+#if !defined(DISABLE_QML)
 static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, int index, bool active = false);
+#endif
 
 void Application::initializeUi() {
     AddressBarDialog::registerType();
@@ -3024,12 +3040,13 @@ void Application::initializeUi() {
         tabletScriptingInterface->getTablet(SYSTEM_TABLET);
     }
 
-    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+    auto offscreenUi = getOffscreenUI();
     connect(offscreenUi.data(), &hifi::qml::OffscreenSurface::rootContextCreated,
         this, &Application::onDesktopRootContextCreated);
     connect(offscreenUi.data(), &hifi::qml::OffscreenSurface::rootItemCreated,
         this, &Application::onDesktopRootItemCreated);
 
+#if !defined(DISABLE_QML)
     offscreenUi->setProxyWindow(_window->windowHandle());
     // OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to
     // support the window management and scripting proxies for VR use
@@ -3039,9 +3056,13 @@ void Application::initializeUi() {
     // FIXME either expose so that dialogs can set this themselves or
     // do better detection in the offscreen UI of what has focus
     offscreenUi->setNavigationFocused(false);
+#else
+    _window->setMenuBar(new Menu());
+#endif
 
     setupPreferences();
 
+#if !defined(DISABLE_QML)
     _glWidget->installEventFilter(offscreenUi.data());
     offscreenUi->setMouseTranslator([=](const QPointF& pt) {
         QPointF result = pt;
@@ -3054,6 +3075,7 @@ void Application::initializeUi() {
         return result.toPoint();
     });
     offscreenUi->resume();
+#endif
     connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){
         resizeGL();
         if (_touchscreenVirtualPadDevice) {
@@ -3092,6 +3114,7 @@ void Application::initializeUi() {
         }
     });
 
+#if !defined(DISABLE_QML)
     // Pre-create a couple of Web3D overlays to speed up tablet UI
     auto offscreenSurfaceCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
     offscreenSurfaceCache->setOnRootContextCreated([&](const QString& rootObject, QQmlContext* surfaceContext) {
@@ -3105,9 +3128,11 @@ void Application::initializeUi() {
 
     offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
     offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
+#endif
 
     flushMenuUpdates();
 
+#if !defined(DISABLE_QML)
     // Now that the menu is instantiated, ensure the display plugin menu is properly updated
     {
         auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
@@ -3126,6 +3151,7 @@ void Application::initializeUi() {
         auto parent = getPrimaryMenu()->getMenu(MenuOption::OutputMenu);
         parent->addSeparator();
     }
+#endif
 
     // The display plugins are created before the menu now, so we need to do this here to hide the menu bar
     // now that it exists
@@ -3230,12 +3256,12 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
 void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
     Stats::show();
     AnimStats::show();
-    auto surfaceContext = DependencyManager::get<OffscreenUi>()->getSurfaceContext();
+    auto surfaceContext = getOffscreenUI()->getSurfaceContext();
     surfaceContext->setContextProperty("Stats", Stats::getInstance());
     surfaceContext->setContextProperty("AnimStats", AnimStats::getInstance());
 
 #if !defined(Q_OS_ANDROID)
-    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+    auto offscreenUi = getOffscreenUI();
     auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml");
     offscreenUi->show(qml, "AvatarInputsBar");
 #endif
@@ -3418,7 +3444,7 @@ void Application::setPreferredCursor(const QString& cursorName) {
 
 void Application::setSettingConstrainToolbarPosition(bool setting) {
     _constrainToolbarPosition.set(setting);
-    DependencyManager::get<OffscreenUi>()->setConstrainToolbarToCenterX(setting);
+    getOffscreenUI()->setConstrainToolbarToCenterX(setting);
 }
 
 void Application::setMiniTabletEnabled(bool enabled) {
@@ -3512,7 +3538,9 @@ void Application::resizeGL() {
         _myCamera.loadViewFrustum(_viewFrustum);
     }
 
-    DependencyManager::get<OffscreenUi>()->resize(fromGlm(displayPlugin->getRecommendedUiSize()));
+#if !defined(DISABLE_QML)
+    getOffscreenUI()->resize(fromGlm(displayPlugin->getRecommendedUiSize()));
+#endif
 }
 
 void Application::handleSandboxStatus(QNetworkReply* reply) {
@@ -3912,10 +3940,12 @@ bool Application::eventFilter(QObject* object, QEvent* event) {
     }
 
     if (event->type() == QEvent::ShortcutOverride) {
-        if (DependencyManager::get<OffscreenUi>()->shouldSwallowShortcut(event)) {
+#if !defined(DISABLE_QML)
+        if (getOffscreenUI()->shouldSwallowShortcut(event)) {
             event->accept();
             return true;
         }
+#endif
 
         // Filter out captured keys before they're used for shortcut actions.
         if (_controllerScriptingInterface->isKeyCaptured(static_cast<QKeyEvent*>(event))) {
@@ -3998,7 +4028,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
 
             case Qt::Key_X:
                 if (isShifted && isMeta) {
-                    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+                    auto offscreenUi = getOffscreenUI();
                     offscreenUi->togglePinned();
                     //offscreenUi->getSurfaceContext()->engine()->clearComponentCache();
                     //OffscreenUi::information("Debugging", "Component cache cleared");
@@ -4014,7 +4044,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
 
             case Qt::Key_B:
                 if (isMeta) {
-                    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+                    auto offscreenUi = getOffscreenUI();
                     offscreenUi->load("Browser.qml");
                 } else if (isOption) {
                     controller::InputRecorder* inputRecorder = controller::InputRecorder::getInstance();
@@ -4036,7 +4066,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
             case Qt::Key_R:
                 if (isMeta && !event->isAutoRepeat()) {
                     DependencyManager::get<ScriptEngines>()->reloadAllScripts();
-                    DependencyManager::get<OffscreenUi>()->clearCache();
+                    getOffscreenUI()->clearCache();
                 }
                 break;
 
@@ -4233,9 +4263,13 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
         return; // bail
     }
 
-    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+#if !defined(DISABLE_QML)
+    auto offscreenUi = getOffscreenUI();
     auto eventPosition = compositor.getMouseEventPosition(event);
-    QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
+    QPointF transformedPos = offscreenUi ? offscreenUi->mapToVirtualScreen(eventPosition) : QPointF();
+#else
+    QPointF transformedPos;
+#endif
     auto button = event->button();
     auto buttons = event->buttons();
     // Determine if the ReticleClick Action is 1 and if so, fake include the LeftMouseButton
@@ -4273,7 +4307,8 @@ void Application::mousePressEvent(QMouseEvent* event) {
     // Inhibit the menu if the user is using alt-mouse dragging
     _altPressed = false;
 
-    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+#if !defined(DISABLE_QML)
+    auto offscreenUi = getOffscreenUI();
     // If we get a mouse press event it means it wasn't consumed by the offscreen UI,
     // hence, we should defocus all of the offscreen UI windows, in order to allow
     // keyboard shortcuts not to be swallowed by them.  In particular, WebEngineViews
@@ -4282,6 +4317,9 @@ void Application::mousePressEvent(QMouseEvent* event) {
 
     auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
     QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
+#else
+    QPointF transformedPos;
+#endif
     QMouseEvent mappedEvent(event->type(),
         transformedPos,
         event->screenPos(), event->button(),
@@ -4318,9 +4356,13 @@ void Application::mousePressEvent(QMouseEvent* event) {
 }
 
 void Application::mouseDoublePressEvent(QMouseEvent* event) {
-    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+#if !defined(DISABLE_QML)
+    auto offscreenUi = getOffscreenUI();
     auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
     QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
+#else
+    QPointF transformedPos;
+#endif
     QMouseEvent mappedEvent(event->type(),
         transformedPos,
         event->screenPos(), event->button(),
@@ -4341,9 +4383,13 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) {
 
 void Application::mouseReleaseEvent(QMouseEvent* event) {
 
-    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+#if !defined(DISABLE_QML)
+    auto offscreenUi = getOffscreenUI();
     auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
     QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
+#else
+    QPointF transformedPos;
+#endif
     QMouseEvent mappedEvent(event->type(),
         transformedPos,
         event->screenPos(), event->button(),
@@ -4739,7 +4785,8 @@ void Application::idle() {
     // Update the deadlock watchdog
     updateHeartbeat();
 
-    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+#if !defined(DISABLE_QML)
+    auto offscreenUi = getOffscreenUI();
 
     // These tasks need to be done on our first idle, because we don't want the showing of
     // overlay subwindows to do a showDesktop() until after the first time through
@@ -4748,6 +4795,7 @@ void Application::idle() {
         firstIdle = false;
         connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
     }
+#endif
 
 #ifdef Q_OS_WIN
     // If tracing is enabled then monitor the CPU in a separate thread
@@ -4764,6 +4812,7 @@ void Application::idle() {
 #endif
 
     auto displayPlugin = getActiveDisplayPlugin();
+#if !defined(DISABLE_QML)
     if (displayPlugin) {
         auto uiSize = displayPlugin->getRecommendedUiSize();
         // Bit of a hack since there's no device pixel ratio change event I can find.
@@ -4772,6 +4821,7 @@ void Application::idle() {
             offscreenUi->resize(fromGlm(uiSize));
         }
     }
+#endif
 
     if (displayPlugin) {
         PROFILE_COUNTER_IF_CHANGED(app, "present", float, displayPlugin->presentRate());
@@ -4806,6 +4856,7 @@ void Application::idle() {
     float secondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_MSEC / MSECS_PER_SECOND;
     _lastTimeUpdated.start();
 
+#if !defined(DISABLE_QML)
     // If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus.
     if (offscreenUi && offscreenUi->getWindow()) {
         auto activeFocusItem = offscreenUi->getWindow()->activeFocusItem();
@@ -4817,9 +4868,11 @@ void Application::idle() {
             _keyboardDeviceHasFocus = true;
         }
     }
+#endif
 
     checkChangeCursor();
 
+#if !defined(DISABLE_QML)
     auto stats = Stats::getInstance();
     if (stats) {
         stats->updateStats();
@@ -4828,6 +4881,7 @@ void Application::idle() {
     if (animStats) {
         animStats->updateStats();
     }
+#endif
 
     // Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
     // details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing
@@ -5163,7 +5217,9 @@ QVector<EntityItemID> Application::pasteEntities(float x, float y, float z) {
 
 void Application::init() {
     // Make sure Login state is up to date
+#if !defined(DISABLE_QML)
     DependencyManager::get<DialogsManager>()->toggleLoginDialog();
+#endif
     if (!DISABLE_DEFERRED) {
         DependencyManager::get<DeferredLightingEffect>()->init();
     }
@@ -6702,8 +6758,9 @@ void Application::nodeActivated(SharedNodePointer node) {
     if (node->getType() == NodeType::AssetServer) {
         // asset server just connected - check if we have the asset browser showing
 
-        auto offscreenUi = DependencyManager::get<OffscreenUi>();
-        auto assetDialog = offscreenUi->getRootItem()->findChild<QQuickItem*>("AssetServer");
+#if !defined(DISABLE_QML)
+        auto offscreenUi = getOffscreenUI();
+        auto assetDialog = offscreenUi ? offscreenUi->getRootItem()->findChild<QQuickItem*>("AssetServer") : nullptr;
 
         if (assetDialog) {
             auto nodeList = DependencyManager::get<NodeList>();
@@ -6716,6 +6773,7 @@ void Application::nodeActivated(SharedNodePointer node) {
                 assetDialog->setVisible(false);
             }
         }
+#endif
     }
 
     // If we get a new EntityServer activated, reset lastQueried time
@@ -6773,13 +6831,15 @@ void Application::nodeKilled(SharedNodePointer node) {
     } else if (node->getType() == NodeType::AssetServer) {
         // asset server going away - check if we have the asset browser showing
 
-        auto offscreenUi = DependencyManager::get<OffscreenUi>();
-        auto assetDialog = offscreenUi->getRootItem()->findChild<QQuickItem*>("AssetServer");
+#if !defined(DISABLE_QML)
+        auto offscreenUi = getOffscreenUI();
+        auto assetDialog = offscreenUi ? offscreenUi->getRootItem()->findChild<QQuickItem*>("AssetServer") : nullptr;
 
         if (assetDialog) {
             // call reload on the shown asset browser dialog
             QMetaObject::invokeMethod(assetDialog, "clear");
         }
+#endif
     }
 }
 
@@ -6886,8 +6946,10 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
     qScriptRegisterMetaType(scriptEngine.data(), RayToOverlayIntersectionResultToScriptValue,
                             RayToOverlayIntersectionResultFromScriptValue);
 
-    scriptEngine->registerGlobalObject("OffscreenFlags", DependencyManager::get<OffscreenUi>()->getFlags());
+#if !defined(DISABLE_QML)
+    scriptEngine->registerGlobalObject("OffscreenFlags", getOffscreenUI()->getFlags());
     scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
+#endif
 
     qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue<ToolbarProxy>, wrapperFromScriptValue<ToolbarProxy>);
     qScriptRegisterMetaType(scriptEngine.data(),
@@ -6913,14 +6975,16 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
 
     bool clientScript = scriptEngine->isClientScript();
     scriptEngine->registerFunction("OverlayWindow", clientScript ? QmlWindowClass::constructor : QmlWindowClass::restricted_constructor);
-#if !defined(Q_OS_ANDROID)
+#if !defined(Q_OS_ANDROID) && !defined(DISABLE_QML)
     scriptEngine->registerFunction("OverlayWebWindow", clientScript ? QmlWebWindowClass::constructor : QmlWebWindowClass::restricted_constructor);
 #endif
     scriptEngine->registerFunction("QmlFragment", clientScript ? QmlFragmentClass::constructor : QmlFragmentClass::restricted_constructor);
 
     scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
     scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get<DesktopPreviewProvider>().data());
+#if !defined(DISABLE_QML)
     scriptEngine->registerGlobalObject("Stats", Stats::getInstance());
+#endif
     scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
     scriptEngine->registerGlobalObject("Snapshot", DependencyManager::get<Snapshot>().data());
     scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
@@ -7116,7 +7180,7 @@ bool Application::askToSetAvatarUrl(const QString& url) {
                 qCDebug(interfaceapp) << "Declined to agree to avatar license";
             }
 
-            //auto offscreenUi = DependencyManager::get<OffscreenUi>();
+            //auto offscreenUi = getOffscreenUI();
         });
     } else {
         setAvatar(url, modelName);
@@ -7314,7 +7378,9 @@ void Application::showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const
             toggleTabletUI(true);
         }
     } else {
-        DependencyManager::get<OffscreenUi>()->show(widgetUrl, name);
+#if !defined(DISABLE_QML)
+        getOffscreenUI()->show(widgetUrl, name);
+#endif
     }
 }
 
@@ -7339,10 +7405,10 @@ void Application::showAssetServerWidget(QString filePath) {
     auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET));
     auto hmd = DependencyManager::get<HMDScriptingInterface>();
     if (tablet->getToolbarMode()) {
-        DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload);
+        getOffscreenUI()->show(url, "AssetServer", startUpload);
     } else {
         if (!hmd->getShouldShowTablet() && !isHMDMode()) {
-            DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload);
+            getOffscreenUI()->show(url, "AssetServer", startUpload);
         } else {
             static const QUrl url("hifi/dialogs/TabletAssetServer.qml");
             if (!tablet->isPathLoaded(url)) {
@@ -7697,7 +7763,7 @@ void Application::addAssetToWorldInfo(QString modelName, QString infoText) {
 
     if (!_addAssetToWorldErrorTimer.isActive()) {
         if (!_addAssetToWorldMessageBox) {
-            _addAssetToWorldMessageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION,
+            _addAssetToWorldMessageBox = getOffscreenUI()->createMessageBox(OffscreenUi::ICON_INFORMATION,
                 "Downloading Model", "", QMessageBox::NoButton, QMessageBox::NoButton);
             connect(_addAssetToWorldMessageBox, SIGNAL(destroyed()), this, SLOT(onAssetToWorldMessageBoxClosed()));
         }
@@ -7780,7 +7846,7 @@ void Application::addAssetToWorldError(QString modelName, QString errorText) {
     addAssetToWorldInfoClear(modelName);
 
     if (!_addAssetToWorldMessageBox) {
-        _addAssetToWorldMessageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION,
+        _addAssetToWorldMessageBox = getOffscreenUI()->createMessageBox(OffscreenUi::ICON_INFORMATION,
             "Downloading Model", "", QMessageBox::NoButton, QMessageBox::NoButton);
         connect(_addAssetToWorldMessageBox, SIGNAL(destroyed()), this, SLOT(onAssetToWorldMessageBoxClosed()));
     }
@@ -8274,6 +8340,8 @@ DisplayPluginPointer Application::getActiveDisplayPlugin() const {
     return _displayPlugin;
 }
 
+
+#if !defined(DISABLE_QML)
 static const char* EXCLUSION_GROUP_KEY = "exclusionGroup";
 
 static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, int index, bool active) {
@@ -8314,6 +8382,7 @@ static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, in
     action->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(displayPluginGroup));
     Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name));
 }
+#endif
 
 void Application::updateDisplayMode() {
     // Unsafe to call this method from anything but the main thread
@@ -8358,8 +8427,8 @@ void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
     // instead emit a signal that the display plugin is changing and let
     // the desktop lock itself.  Reduces coupling between the UI and display
     // plugins
-    auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    auto desktop = offscreenUi->getDesktop();
+    auto offscreenUi = getOffscreenUI();
+    auto desktop = offscreenUi ? offscreenUi->getDesktop() : nullptr;
     auto menu = Menu::getInstance();
 
     // Make the switch atomic from the perspective of other threads
@@ -8405,7 +8474,9 @@ void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
             }
         }
 
-        offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
+        if (offscreenUi) {
+            offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
+        }
         getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
         _displayPlugin = newDisplayPlugin;
         connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection);
diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp
index a87caeb9a8..646cc53cc0 100644
--- a/interface/src/Application_render.cpp
+++ b/interface/src/Application_render.cpp
@@ -156,12 +156,14 @@ void Application::paintGL() {
     renderArgs._blitFramebuffer.reset();
     renderArgs._context->enableStereo(false);
 
+#if !defined(DISABLE_QML)
     {
         auto stats = Stats::getInstance();
         if (stats) {
             stats->setRenderDetails(renderArgs._details);
         }
     }
+#endif
 
     uint64_t lastPaintDuration = usecTimestampNow() - lastPaintBegin;
     _frameTimingsScriptingInterface.addValue(lastPaintDuration);
diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp
index e86061b090..33d9fddc1b 100644
--- a/interface/src/ConnectionMonitor.cpp
+++ b/interface/src/ConnectionMonitor.cpp
@@ -48,7 +48,9 @@ void ConnectionMonitor::init() {
             emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, "", 5);
         } else {
             qDebug() << "ConnectionMonitor: Showing connection failure window";
+#if !defined(DISABLE_QML)
             DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(true);
+#endif
         }
     });
 }
@@ -59,8 +61,10 @@ void ConnectionMonitor::startTimer() {
 
 void ConnectionMonitor::stopTimer() {
     _timer.stop();
+#if !defined(DISABLE_QML)
     bool enableInterstitial = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled();
     if (!enableInterstitial) {
         DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(false);
     }
+#endif
 }
diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp
index c4f8ef5ddd..84325da473 100644
--- a/interface/src/ModelPackager.cpp
+++ b/interface/src/ModelPackager.cpp
@@ -17,6 +17,7 @@
 #include <QTemporaryDir>
 
 #include <FSTReader.h>
+#include <FBXSerializer.h>
 #include <OffscreenUi.h>
 
 #include "ModelSelector.h"
@@ -108,7 +109,7 @@ bool ModelPackager::loadModel() {
         qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath();
         QByteArray fbxContents = fbx.readAll();
 
-        _hfmModel.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath()));
+        _hfmModel = FBXSerializer().read(fbxContents, QVariantHash(), _fbxInfo.filePath());
 
         // make sure we have some basic mappings
         populateBasicMapping(_mapping, _fbxInfo.filePath(), *_hfmModel);
diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h
index 09dab5039f..58d3d6bfee 100644
--- a/interface/src/ModelPackager.h
+++ b/interface/src/ModelPackager.h
@@ -45,7 +45,7 @@ private:
     QString _scriptDir;
 
     QVariantHash _mapping;
-    std::unique_ptr<hfm::Model> _hfmModel;
+    std::shared_ptr<hfm::Model> _hfmModel;
     QStringList _textures;
     QStringList _scripts;
 };
diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h
index 1058dbfa4e..7019d239ff 100644
--- a/interface/src/ModelPropertiesDialog.h
+++ b/interface/src/ModelPropertiesDialog.h
@@ -14,7 +14,7 @@
 
 #include <QDialog>
 
-#include <FBXReader.h>
+#include <hfm/HFM.h>
 #include <FSTReader.h>
 
 #include "ui/ModelsBrowser.h"
diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp
index dfb0db1da1..d30f98051e 100644
--- a/interface/src/raypick/CollisionPick.cpp
+++ b/interface/src/raypick/CollisionPick.cpp
@@ -149,7 +149,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha
                 uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size();
                 // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up
                 //assert(numIndices % TRIANGLE_STRIDE == 0);
-                numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
+                numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer
 
                 for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) {
                     glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]];
@@ -170,7 +170,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha
                 numIndices = (uint32_t)meshPart.quadIndices.size();
                 // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up
                 //assert(numIndices % QUAD_STRIDE == 0);
-                numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
+                numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer
 
                 for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) {
                     glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]];
@@ -305,7 +305,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha
                     auto numIndices = meshPart.triangleIndices.count();
                     // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up
                     //assert(numIndices% TRIANGLE_STRIDE == 0);
-                    numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
+                    numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer
 
                     auto indexItr = meshPart.triangleIndices.cbegin();
                     while (indexItr != meshPart.triangleIndices.cend()) {
diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp
index 2fb40c8c30..26781631db 100644
--- a/interface/src/ui/ApplicationOverlay.cpp
+++ b/interface/src/ui/ApplicationOverlay.cpp
@@ -83,7 +83,9 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
         // Now render the overlay components together into a single texture
         renderDomainConnectionStatusBorder(renderArgs); // renders the connected domain line
         renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope
+#if !defined(DISABLE_QML)
         renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts
+#endif
     });
 
     renderArgs->_batch = nullptr; // so future users of renderArgs don't try to use our batch
diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp
index 6852691634..46a25a24a7 100644
--- a/interface/src/ui/Keyboard.cpp
+++ b/interface/src/ui/Keyboard.cpp
@@ -60,7 +60,7 @@ static const float MALLET_TOUCH_Y_OFFSET = 0.050f;
 static const float MALLET_Y_OFFSET = 0.160f;
 
 static const glm::quat MALLET_ROTATION_OFFSET{0.70710678f, 0.0f, -0.70710678f, 0.0f};
-static const glm::vec3 MALLET_MODEL_DIMENSIONS{0.03f, MALLET_LENGTH, 0.03f};
+static const glm::vec3 MALLET_MODEL_DIMENSIONS{0.01f, MALLET_LENGTH, 0.01f};
 static const glm::vec3 MALLET_POSITION_OFFSET{0.0f, -MALLET_Y_OFFSET / 2.0f, 0.0f};
 static const glm::vec3 MALLET_TIP_OFFSET{0.0f, MALLET_LENGTH - MALLET_TOUCH_Y_OFFSET, 0.0f};
 
diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp
index da4fdaba44..8edd8ee3a5 100644
--- a/interface/src/ui/OverlayConductor.cpp
+++ b/interface/src/ui/OverlayConductor.cpp
@@ -74,6 +74,7 @@ void OverlayConductor::centerUI() {
 }
 
 void OverlayConductor::update(float dt) {
+#if !defined(DISABLE_QML)
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
     if (!offscreenUi) {
         return;
@@ -115,4 +116,5 @@ void OverlayConductor::update(float dt) {
     if (shouldRecenter && !_suppressedByHead) {
         centerUI();
     }
+#endif
 }
diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h
index b47e23d28a..6c3732cf3c 100644
--- a/interface/src/ui/OverlayConductor.h
+++ b/interface/src/ui/OverlayConductor.h
@@ -25,8 +25,10 @@ private:
     bool headOutsideOverlay() const;
     bool updateAvatarIsAtRest();
 
+#if !defined(DISABLE_QML)
     bool _suppressedByHead { false };
     bool _hmdMode { false };
+#endif
 
     // used by updateAvatarIsAtRest
     uint64_t _desiredAtRestTimer { 0 };
diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp
index 22b123c85d..17b0895f47 100644
--- a/interface/src/ui/overlays/Overlays.cpp
+++ b/interface/src/ui/overlays/Overlays.cpp
@@ -232,11 +232,15 @@ OverlayID Overlays::addOverlay(const QString& type, const QVariant& properties)
      */
 
     if (type == ImageOverlay::TYPE) {
+#if !defined(DISABLE_QML)
         thisOverlay = Overlay::Pointer(new ImageOverlay(), [](Overlay* ptr) { ptr->deleteLater(); });
+#endif
     } else if (type == Image3DOverlay::TYPE || type == "billboard") { // "billboard" for backwards compatibility
         thisOverlay = Overlay::Pointer(new Image3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); });
     } else if (type == TextOverlay::TYPE) {
+#if !defined(DISABLE_QML)
         thisOverlay = Overlay::Pointer(new TextOverlay(), [](Overlay* ptr) { ptr->deleteLater(); });
+#endif
     } else if (type == Text3DOverlay::TYPE) {
         thisOverlay = Overlay::Pointer(new Text3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); });
     } else if (type == Shape3DOverlay::TYPE) {
diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h
index ab89eb643d..14f39eedbc 100644
--- a/libraries/animation/src/AnimSkeleton.h
+++ b/libraries/animation/src/AnimSkeleton.h
@@ -15,7 +15,7 @@
 #include <glm/glm.hpp>
 #include <glm/gtc/quaternion.hpp>
 
-#include <FBXReader.h>
+#include <FBXSerializer.h>
 #include "AnimPose.h"
 
 class AnimSkeleton {
diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp
index 501b9e964d..ec26782d0e 100644
--- a/libraries/animation/src/AnimationCache.cpp
+++ b/libraries/animation/src/AnimationCache.cpp
@@ -71,7 +71,7 @@ void AnimationReader::run() {
             // Parse the FBX directly from the QNetworkReply
             HFMModel::Pointer hfmModel;
             if (_url.path().toLower().endsWith(".fbx")) {
-                hfmModel.reset(readFBX(_data, QVariantHash(), _url.path()));
+                hfmModel = FBXSerializer().read(_data, QVariantHash(), _url.path());
             } else {
                 QString errorStr("usupported format");
                 emit onError(299, errorStr);
diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h
index 4423e8f18d..d4574d9d3b 100644
--- a/libraries/animation/src/AnimationCache.h
+++ b/libraries/animation/src/AnimationCache.h
@@ -17,7 +17,7 @@
 #include <QtScript/QScriptValue>
 
 #include <DependencyManager.h>
-#include <FBXReader.h>
+#include <FBXSerializer.h>
 #include <ResourceCache.h>
 
 class Animation;
diff --git a/libraries/animation/src/AnimationObject.h b/libraries/animation/src/AnimationObject.h
index 83880ed2ab..fc3a351832 100644
--- a/libraries/animation/src/AnimationObject.h
+++ b/libraries/animation/src/AnimationObject.h
@@ -15,7 +15,7 @@
 #include <QObject>
 #include <QScriptable>
 
-#include <FBXReader.h>
+#include <FBXSerializer.h>
 
 class QScriptEngine;
 
diff --git a/libraries/audio/src/AudioDynamics.h b/libraries/audio/src/AudioDynamics.h
index 8dbc7a75cc..6bfd249bc9 100644
--- a/libraries/audio/src/AudioDynamics.h
+++ b/libraries/audio/src/AudioDynamics.h
@@ -51,7 +51,7 @@
 #include <xmmintrin.h>
 // convert float to int using round-to-nearest
 FORCEINLINE static int32_t floatToInt(float x) {
-    return _mm_cvt_ss2si(_mm_load_ss(&x));
+    return _mm_cvt_ss2si(_mm_set_ss(x));
 }
 
 #else 
@@ -150,7 +150,7 @@ static const int IEEE754_EXPN_BIAS = 127;
 //
 // Peak detection and -log2(x) for float input (mono)
 // x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff
-// x > 2^LOG2_HEADROOM undefined
+// x > 2^LOG2_HEADROOM returns 0
 //
 FORCEINLINE static int32_t peaklog2(float* input) {
 
@@ -161,12 +161,12 @@ FORCEINLINE static int32_t peaklog2(float* input) {
     uint32_t peak = u & IEEE754_FABS_MASK;
 
     // split into e and x - 1.0
-    int e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
+    int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
     int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff;
 
-    // saturate
-    if (e > 31) {
-        return 0x7fffffff;
+    // saturate when e > 31 or e < 0
+    if ((uint32_t)e > 31) {
+        return 0x7fffffff & ~(e >> 31);
     }
 
     int k = x >> (31 - LOG2_TABBITS);
@@ -186,7 +186,7 @@ FORCEINLINE static int32_t peaklog2(float* input) {
 //
 // Peak detection and -log2(x) for float input (stereo)
 // x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff
-// x > 2^LOG2_HEADROOM undefined
+// x > 2^LOG2_HEADROOM returns 0
 //
 FORCEINLINE static int32_t peaklog2(float* input0, float* input1) {
 
@@ -200,12 +200,12 @@ FORCEINLINE static int32_t peaklog2(float* input0, float* input1) {
     uint32_t peak = MAX(u0, u1);
 
     // split into e and x - 1.0
-    int e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
+    int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
     int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff;
 
-    // saturate
-    if (e > 31) {
-        return 0x7fffffff;
+    // saturate when e > 31 or e < 0
+    if ((uint32_t)e > 31) {
+        return 0x7fffffff & ~(e >> 31);
     }
 
     int k = x >> (31 - LOG2_TABBITS);
@@ -225,7 +225,7 @@ FORCEINLINE static int32_t peaklog2(float* input0, float* input1) {
 //
 // Peak detection and -log2(x) for float input (quad)
 // x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff
-// x > 2^LOG2_HEADROOM undefined
+// x > 2^LOG2_HEADROOM returns 0
 //
 FORCEINLINE static int32_t peaklog2(float* input0, float* input1, float* input2, float* input3) {
 
@@ -243,12 +243,12 @@ FORCEINLINE static int32_t peaklog2(float* input0, float* input1, float* input2,
     uint32_t peak = MAX(MAX(u0, u1), MAX(u2, u3));
 
     // split into e and x - 1.0
-    int e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
+    int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
     int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff;
 
-    // saturate
-    if (e > 31) {
-        return 0x7fffffff;
+    // saturate when e > 31 or e < 0
+    if ((uint32_t)e > 31) {
+        return 0x7fffffff & ~(e >> 31);
     }
 
     int k = x >> (31 - LOG2_TABBITS);
diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp
index 4af6e79caf..1581990e0c 100644
--- a/libraries/audio/src/AudioInjector.cpp
+++ b/libraries/audio/src/AudioInjector.cpp
@@ -447,9 +447,9 @@ AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound,  const A
         using AudioConstants::AudioSample;
         using AudioConstants::SAMPLE_RATE;
         const int standardRate = SAMPLE_RATE;
-        // limit to 4 octaves
-        const int pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
-        const int resampledRate = SAMPLE_RATE / pitch;
+        // limit pitch to 4 octaves
+        const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
+        const int resampledRate = glm::round(SAMPLE_RATE / pitch);
 
         auto audioData = sound->getAudioData();
         auto numChannels = audioData->getNumChannels();
@@ -499,9 +499,9 @@ AudioInjectorPointer AudioInjector::playSound(AudioDataPointer audioData, const
         using AudioConstants::AudioSample;
         using AudioConstants::SAMPLE_RATE;
         const int standardRate = SAMPLE_RATE;
-        // limit to 4 octaves
-        const int pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
-        const int resampledRate = SAMPLE_RATE / pitch;
+        // limit pitch to 4 octaves
+        const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
+        const int resampledRate = glm::round(SAMPLE_RATE / pitch);
 
         auto numChannels = audioData->getNumChannels();
         auto numFrames = audioData->getNumFrames();
diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp
index cef6c9b900..afaca1dd62 100644
--- a/libraries/baking/src/FBXBaker.cpp
+++ b/libraries/baking/src/FBXBaker.cpp
@@ -27,7 +27,7 @@
 
 #include <PathUtils.h>
 
-#include <FBXReader.h>
+#include <FBXSerializer.h>
 #include <FBXWriter.h>
 
 #include "ModelBakingLoggingCategory.h"
@@ -187,10 +187,10 @@ void FBXBaker::importScene() {
         return;
     }
 
-    FBXReader reader;
+    FBXSerializer fbxSerializer;
 
     qCDebug(model_baking) << "Parsing" << _modelURL;
-    _rootNode = reader._rootNode = reader.parseFBX(&fbxFile);
+    _rootNode = fbxSerializer._rootNode = fbxSerializer.parseFBX(&fbxFile);
 
 #ifdef HIFI_DUMP_FBX
     {
@@ -206,8 +206,8 @@ void FBXBaker::importScene() {
     }
 #endif
 
-    _hfmModel = reader.extractHFMModel({}, _modelURL.toString());
-    _textureContentMap = reader._textureContent;
+    _hfmModel = fbxSerializer.extractHFMModel({}, _modelURL.toString());
+    _textureContentMap = fbxSerializer._textureContent;
 }
 
 void FBXBaker::rewriteAndBakeSceneModels() {
@@ -232,7 +232,7 @@ void FBXBaker::rewriteAndBakeSceneModels() {
                 if (objectChild.name == "Geometry") {
 
                     // TODO Pull this out of _hfmModel instead so we don't have to reprocess it
-                    auto extractedMesh = FBXReader::extractMesh(objectChild, meshIndex, false);
+                    auto extractedMesh = FBXSerializer::extractMesh(objectChild, meshIndex, false);
                     
                     // Callback to get MaterialID
                     GetMaterialIDCallback materialIDcallback = [&extractedMesh](int partIndex) {
diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp
index ca352cebae..34f302b501 100644
--- a/libraries/baking/src/ModelBaker.cpp
+++ b/libraries/baking/src/ModelBaker.cpp
@@ -13,7 +13,6 @@
 
 #include <PathUtils.h>
 
-#include <FBXReader.h>
 #include <FBXWriter.h>
 
 #ifdef _WIN32
diff --git a/libraries/baking/src/OBJBaker.cpp b/libraries/baking/src/OBJBaker.cpp
index d9f56b393e..5a1239f88f 100644
--- a/libraries/baking/src/OBJBaker.cpp
+++ b/libraries/baking/src/OBJBaker.cpp
@@ -14,7 +14,7 @@
 #include <PathUtils.h>
 #include <NetworkAccessManager.h>
 
-#include "OBJReader.h"
+#include "OBJSerializer.h"
 #include "FBXWriter.h"
 
 const double UNIT_SCALE_FACTOR = 100.0;
@@ -143,9 +143,10 @@ void OBJBaker::bakeOBJ() {
 
     QByteArray objData = objFile.readAll();
 
-    bool combineParts = true; // set true so that OBJReader reads material info from material library
-    OBJReader reader;
-    auto geometry = reader.readOBJ(objData, QVariantHash(), combineParts, _modelURL);
+    OBJSerializer serializer;
+    QVariantHash mapping;
+    mapping["combineParts"] = true; // set true so that OBJSerializer reads material info from material library
+    auto geometry = serializer.read(objData, mapping, _modelURL);
 
     // Write OBJ Data as FBX tree nodes
     createFBXNodeTree(_rootNode, *geometry);
@@ -219,7 +220,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, HFMModel& hfmModel) {
         FBXNode materialNode;
         materialNode.name = MATERIAL_NODE_NAME;
         if (hfmModel.materials.size() == 1) {
-            // case when no material information is provided, OBJReader considers it as a single default material
+            // case when no material information is provided, OBJSerializer considers it as a single default material
             for (auto& materialID : hfmModel.materials.keys()) {
                 setMaterialNodeProperties(materialNode, materialID, hfmModel);
             }
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 9a68f81b66..2b1d70f4d0 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -421,7 +421,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
                 uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size();
                 // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up
                 //assert(numIndices % TRIANGLE_STRIDE == 0);
-                numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
+                numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer
 
                 for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) {
                     glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]];
@@ -442,7 +442,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
                 numIndices = (uint32_t)meshPart.quadIndices.size();
                 // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up
                 //assert(numIndices % QUAD_STRIDE == 0);
-                numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
+                numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer
 
                 for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) {
                     glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]];
@@ -595,7 +595,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
                     if (partItr->_topology == graphics::Mesh::TRIANGLES) {
                         // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up
                         //assert(numIndices % TRIANGLE_STRIDE == 0);
-                        numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
+                        numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer
 
                         auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
                         auto indexEnd = indexItr + numIndices;
@@ -652,7 +652,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
                     if (partItr->_topology == graphics::Mesh::TRIANGLES) {
                         // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up
                         //assert(numIndices% TRIANGLE_STRIDE == 0);
-                        numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
+                        numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer
 
                         auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
                         auto indexEnd = indexItr + numIndices;
diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h
index 90de82e310..157ca5b282 100644
--- a/libraries/fbx/src/FBX.h
+++ b/libraries/fbx/src/FBX.h
@@ -33,7 +33,7 @@ using NormalType = glm::vec3;
 #define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ
 #endif
 
-// See comment in FBXReader::parseFBX().
+// See comment in FBXSerializer::parseFBX().
 static const int FBX_HEADER_BYTES_BEFORE_VERSION = 23;
 static const QByteArray FBX_BINARY_PROLOG("Kaydara FBX Binary  ");
 static const QByteArray FBX_BINARY_PROLOG2("\0\x1a\0", 3);
diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXSerializer.cpp
similarity index 98%
rename from libraries/fbx/src/FBXReader.cpp
rename to libraries/fbx/src/FBXSerializer.cpp
index dd10cd30b3..a9887cde15 100644
--- a/libraries/fbx/src/FBXReader.cpp
+++ b/libraries/fbx/src/FBXSerializer.cpp
@@ -1,6 +1,6 @@
 //
-//  FBXReader.cpp
-//  interface/src/renderer
+//  FBXSerializer.cpp
+//  libraries/fbx/src
 //
 //  Created by Andrzej Kapolka on 9/18/13.
 //  Copyright 2013 High Fidelity, Inc.
@@ -9,7 +9,7 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-#include "FBXReader.h"
+#include "FBXSerializer.h"
 
 #include <iostream>
 #include <QBuffer>
@@ -36,7 +36,7 @@
 #include <hfm/ModelFormatLogging.h>
 
 // TOOL: Uncomment the following line to enable the filtering of all the unkwnon fields of a node so we can break point easily while loading a model with problems...
-//#define DEBUG_FBXREADER
+//#define DEBUG_FBXSERIALIZER
 
 using namespace std;
 
@@ -254,13 +254,13 @@ HFMBlendshape extractBlendshape(const FBXNode& object) {
     HFMBlendshape blendshape;
     foreach (const FBXNode& data, object.children) {
         if (data.name == "Indexes") {
-            blendshape.indices = FBXReader::getIntVector(data);
+            blendshape.indices = FBXSerializer::getIntVector(data);
 
         } else if (data.name == "Vertices") {
-            blendshape.vertices = FBXReader::createVec3Vector(FBXReader::getDoubleVector(data));
+            blendshape.vertices = FBXSerializer::createVec3Vector(FBXSerializer::getDoubleVector(data));
 
         } else if (data.name == "Normals") {
-            blendshape.normals = FBXReader::createVec3Vector(FBXReader::getDoubleVector(data));
+            blendshape.normals = FBXSerializer::createVec3Vector(FBXSerializer::getDoubleVector(data));
         }
     }
     return blendshape;
@@ -384,7 +384,7 @@ HFMLight extractLight(const FBXNode& object) {
                     if (propname == "Intensity") {
                         light.intensity = 0.01f * property.properties.at(valIndex).value<float>();
                     } else if (propname == "Color") {
-                        light.color = FBXReader::getVec3(property.properties, valIndex);
+                        light.color = FBXSerializer::getVec3(property.properties, valIndex);
                     }
                 }
             }
@@ -392,7 +392,7 @@ HFMLight extractLight(const FBXNode& object) {
                    || subobject.name == "TypeFlags") {
         }
     }
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
 
     QString type = object.properties.at(0).toString();
     type = object.properties.at(1).toString();
@@ -441,7 +441,7 @@ QMap<QString, glm::quat> getJointRotationOffsets(const QVariantHash& mapping) {
     return jointRotationOffsets;
 }
 
-HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString& url) {
+HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QString& url) {
     const FBXNode& node = _rootNode;
     QMap<QString, ExtractedMesh> meshes;
     QHash<QString, QString> modelIDsToNames;
@@ -512,7 +512,7 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString&
         }
     }
     QMultiHash<QString, WeightedIndex> blendshapeChannelIndices;
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
     int unknown = 0;
 #endif
     HFMModel* hfmModelPtr = new HFMModel;
@@ -760,7 +760,7 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString&
                                 extractBlendshape(subobject) };
                             blendshapes.append(blendshape);
                         }
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
                         else if (subobject.name == "TypeFlags") {
                             QString attributetype = subobject.properties.at(0).toString();
                             if (!attributetype.empty()) {
@@ -886,7 +886,7 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString&
                                                 tex.scaling.z = 1.0f;
                                             }
                                         }
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
                                         else {
                                             QString propName = v;
                                             unknown++;
@@ -895,7 +895,7 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString&
                                     }
                                 }
                         }
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
                         else {
                             if (subobject.name == "Type") {
                             } else if (subobject.name == "Version") {
@@ -1068,7 +1068,7 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString&
                                 }
                             }
                         }
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
                         else {
                             QString propname = subobject.name.data();
                             int unknown = 0;
@@ -1085,7 +1085,7 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString&
 
 
                 } else if (object.name == "NodeAttribute") {
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
                     std::vector<QString> properties;
                     foreach(const QVariant& v, object.properties) {
                         properties.push_back(v.toString());
@@ -1148,7 +1148,7 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString&
                     animationCurves.insert(getID(object.properties), curve);
 
                 }
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
                  else {
                     QString objectname = object.name.data();
                     if ( objectname == "Pose"
@@ -1239,7 +1239,7 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString&
                 }
             }
         }
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
         else {
             QString objectname = child.name.data();
             if ( objectname == "Pose"
@@ -1833,17 +1833,11 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString&
     return hfmModelPtr;
 }
 
-HFMModel* readFBX(const QByteArray& data, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
+HFMModel::Pointer FBXSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
     QBuffer buffer(const_cast<QByteArray*>(&data));
     buffer.open(QIODevice::ReadOnly);
-    return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel);
-}
 
-HFMModel* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
-    FBXReader reader;
-    reader._rootNode = FBXReader::parseFBX(device);
-    reader._loadLightmaps = loadLightmaps;
-    reader._lightmapLevel = lightmapLevel;
+    _rootNode = parseFBX(&buffer);
 
-    return reader.extractHFMModel(mapping, url);
+    return HFMModel::Pointer(extractHFMModel(mapping, url.toString()));
 }
diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXSerializer.h
similarity index 85%
rename from libraries/fbx/src/FBXReader.h
rename to libraries/fbx/src/FBXSerializer.h
index c74b4dc8ac..c69f75cc5c 100644
--- a/libraries/fbx/src/FBXReader.h
+++ b/libraries/fbx/src/FBXSerializer.h
@@ -1,6 +1,6 @@
 //
-//  FBXReader.h
-//  interface/src/renderer
+//  FBXSerializer.h
+//  libraries/fbx/src
 //
 //  Created by Andrzej Kapolka on 9/18/13.
 //  Copyright 2013 High Fidelity, Inc.
@@ -9,8 +9,8 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-#ifndef hifi_FBXReader_h
-#define hifi_FBXReader_h
+#ifndef hifi_FBXSerializer_h
+#define hifi_FBXSerializer_h
 
 #include <QtGlobal>
 #include <QMetaType>
@@ -27,7 +27,7 @@
 #include <Transform.h>
 
 #include "FBX.h"
-#include <hfm/HFM.h>
+#include <hfm/HFMSerializer.h>
 
 #include <graphics/Geometry.h>
 #include <graphics/Material.h>
@@ -35,14 +35,6 @@
 class QIODevice;
 class FBXNode;
 
-/// Reads HFMModel from the supplied model and mapping data.
-/// \exception QString if an error occurs in parsing
-HFMModel* readFBX(const QByteArray& data, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
-
-/// Reads HFMModel from the supplied model and mapping data.
-/// \exception QString if an error occurs in parsing
-HFMModel* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
-
 class TextureParam {
 public:
     glm::vec2 UVTranslation;
@@ -102,9 +94,12 @@ public:
 
 class ExtractedMesh;
 
-class FBXReader {
+class FBXSerializer : public HFMSerializer {
 public:
     HFMModel* _hfmModel;
+    /// Reads HFMModel from the supplied model and mapping data.
+    /// \exception QString if an error occurs in parsing
+    HFMModel::Pointer read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url = QUrl()) override;
 
     FBXNode _rootNode;
     static FBXNode parseFBX(QIODevice* device);
@@ -147,9 +142,9 @@ public:
 
     void consolidateHFMMaterials(const QVariantHash& mapping);
 
-    bool _loadLightmaps = true;
-    float _lightmapOffset = 0.0f;
-    float _lightmapLevel;
+    bool _loadLightmaps { true };
+    float _lightmapOffset { 0.0f };
+    float _lightmapLevel { 1.0f };
 
     QMultiMap<QString, QString> _connectionParentMap;
     QMultiMap<QString, QString> _connectionChildMap;
@@ -166,4 +161,4 @@ public:
     static QVector<double> getDoubleVector(const FBXNode& node);
 };
 
-#endif // hifi_FBXReader_h
+#endif // hifi_FBXSerializer_h
diff --git a/libraries/fbx/src/FBXReader_Material.cpp b/libraries/fbx/src/FBXSerializer_Material.cpp
similarity index 98%
rename from libraries/fbx/src/FBXReader_Material.cpp
rename to libraries/fbx/src/FBXSerializer_Material.cpp
index 2ec8cfde75..7713b36e57 100644
--- a/libraries/fbx/src/FBXReader_Material.cpp
+++ b/libraries/fbx/src/FBXSerializer_Material.cpp
@@ -1,5 +1,5 @@
 //
-//  FBXReader_Material.cpp
+//  FBXSerializer_Material.cpp
 //  interface/src/fbx
 //
 //  Created by Sam Gateau on 8/27/2015.
@@ -9,7 +9,7 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-#include "FBXReader.h"
+#include "FBXSerializer.h"
 
 #include <iostream>
 #include <memory>
@@ -27,7 +27,7 @@
 
 #include <hfm/ModelFormatLogging.h>
 
-HFMTexture FBXReader::getTexture(const QString& textureID) {
+HFMTexture FBXSerializer::getTexture(const QString& textureID) {
     HFMTexture texture;
     const QByteArray& filepath = _textureFilepaths.value(textureID);
     texture.content = _textureContent.value(filepath);
@@ -69,7 +69,7 @@ HFMTexture FBXReader::getTexture(const QString& textureID) {
     return texture;
 }
 
-void FBXReader::consolidateHFMMaterials(const QVariantHash& mapping) {
+void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
 
     QString materialMapString = mapping.value("materialMap").toString();
     QJsonDocument materialMapDocument = QJsonDocument::fromJson(materialMapString.toUtf8());
diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXSerializer_Mesh.cpp
similarity index 98%
rename from libraries/fbx/src/FBXReader_Mesh.cpp
rename to libraries/fbx/src/FBXSerializer_Mesh.cpp
index 527e3aef75..38533dbc42 100644
--- a/libraries/fbx/src/FBXReader_Mesh.cpp
+++ b/libraries/fbx/src/FBXSerializer_Mesh.cpp
@@ -1,5 +1,5 @@
 //
-//  FBXReader_Mesh.cpp
+//  FBXSerializer_Mesh.cpp
 //  interface/src/fbx
 //
 //  Created by Sam Gateau on 8/27/2015.
@@ -33,7 +33,7 @@
 #include <LogHandler.h>
 #include <hfm/ModelFormatLogging.h>
 
-#include "FBXReader.h"
+#include "FBXSerializer.h"
 
 #include <memory>
 
@@ -191,7 +191,7 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index, bool deduplic
     }
 }
 
-ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIndex, bool deduplicate) {
+ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& meshIndex, bool deduplicate) {
     MeshData data;
     data.extracted.mesh.meshIndex = meshIndex++;
 
@@ -254,7 +254,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn
                 data.colorsByVertex = true;
             }
 
-#if defined(FBXREADER_KILL_BLACK_COLOR_ATTRIBUTE)
+#if defined(FBXSERIALIZER_KILL_BLACK_COLOR_ATTRIBUTE)
             // Potential feature where we decide to kill the color attribute is to dark?
             // Tested with the model:
             // https://hifi-public.s3.amazonaws.com/ryan/gardenLight2.fbx
@@ -281,7 +281,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn
                     } else if (subdata.name == "Name") {
                         attrib.name = subdata.properties.at(0).toString();
                     } 
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
                     else {
                         int unknown = 0;
                         QString subname = subdata.name.data();
@@ -307,7 +307,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn
                     } else if  (subdata.name == "Name") {
                         attrib.name = subdata.properties.at(0).toString();
                     }
-#if defined(DEBUG_FBXREADER)
+#if defined(DEBUG_FBXSERIALIZER)
                     else {
                         int unknown = 0;
                         QString subname = subdata.name.data();
@@ -557,7 +557,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn
     return data.extracted;
 }
 
-glm::vec3 FBXReader::normalizeDirForPacking(const glm::vec3& dir) {
+glm::vec3 FBXSerializer::normalizeDirForPacking(const glm::vec3& dir) {
     auto maxCoord = glm::max(fabsf(dir.x), glm::max(fabsf(dir.y), fabsf(dir.z)));
     if (maxCoord > 1e-6f) {
         return dir / maxCoord;
@@ -565,7 +565,7 @@ glm::vec3 FBXReader::normalizeDirForPacking(const glm::vec3& dir) {
     return dir;
 }
 
-void FBXReader::buildModelMesh(HFMMesh& extractedMesh, const QString& url) {
+void FBXSerializer::buildModelMesh(HFMMesh& extractedMesh, const QString& url) {
     unsigned int totalSourceIndices = 0;
     foreach(const HFMMeshPart& part, extractedMesh.parts) {
         totalSourceIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size());
diff --git a/libraries/fbx/src/FBXReader_Node.cpp b/libraries/fbx/src/FBXSerializer_Node.cpp
similarity index 94%
rename from libraries/fbx/src/FBXReader_Node.cpp
rename to libraries/fbx/src/FBXSerializer_Node.cpp
index cd717998dd..c982dfc7cb 100644
--- a/libraries/fbx/src/FBXReader_Node.cpp
+++ b/libraries/fbx/src/FBXSerializer_Node.cpp
@@ -1,5 +1,5 @@
 //
-//  FBXReader_Node.cpp
+//  FBXSerializer_Node.cpp
 //  interface/src/fbx
 //
 //  Created by Sam Gateau on 8/27/2015.
@@ -9,7 +9,7 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-#include "FBXReader.h"
+#include "FBXSerializer.h"
 
 #include <iostream>
 #include <QtCore/QBuffer>
@@ -345,7 +345,7 @@ FBXNode parseTextFBXNode(Tokenizer& tokenizer) {
     return node;
 }
 
-FBXNode FBXReader::parseFBX(QIODevice* device) {
+FBXNode FBXSerializer::parseFBX(QIODevice* device) {
     PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xff0000ff, device);
     // verify the prolog
     if (device->peek(FBX_BINARY_PROLOG.size()) != FBX_BINARY_PROLOG) {
@@ -398,12 +398,12 @@ FBXNode FBXReader::parseFBX(QIODevice* device) {
 }
 
 
-glm::vec3 FBXReader::getVec3(const QVariantList& properties, int index) {
+glm::vec3 FBXSerializer::getVec3(const QVariantList& properties, int index) {
     return glm::vec3(properties.at(index).value<double>(), properties.at(index + 1).value<double>(),
         properties.at(index + 2).value<double>());
 }
 
-QVector<glm::vec4> FBXReader::createVec4Vector(const QVector<double>& doubleVector) {
+QVector<glm::vec4> FBXSerializer::createVec4Vector(const QVector<double>& doubleVector) {
     QVector<glm::vec4> values;
     for (const double* it = doubleVector.constData(), *end = it + ((doubleVector.size() / 4) * 4); it != end; ) {
         float x = *it++;
@@ -416,7 +416,7 @@ QVector<glm::vec4> FBXReader::createVec4Vector(const QVector<double>& doubleVect
 }
 
 
-QVector<glm::vec4> FBXReader::createVec4VectorRGBA(const QVector<double>& doubleVector, glm::vec4& average) {
+QVector<glm::vec4> FBXSerializer::createVec4VectorRGBA(const QVector<double>& doubleVector, glm::vec4& average) {
     QVector<glm::vec4> values;
     for (const double* it = doubleVector.constData(), *end = it + ((doubleVector.size() / 4) * 4); it != end; ) {
         float x = *it++;
@@ -433,7 +433,7 @@ QVector<glm::vec4> FBXReader::createVec4VectorRGBA(const QVector<double>& double
     return values;
 }
 
-QVector<glm::vec3> FBXReader::createVec3Vector(const QVector<double>& doubleVector) {
+QVector<glm::vec3> FBXSerializer::createVec3Vector(const QVector<double>& doubleVector) {
     QVector<glm::vec3> values;
     for (const double* it = doubleVector.constData(), *end = it + ((doubleVector.size() / 3) * 3); it != end; ) {
         float x = *it++;
@@ -444,7 +444,7 @@ QVector<glm::vec3> FBXReader::createVec3Vector(const QVector<double>& doubleVect
     return values;
 }
 
-QVector<glm::vec2> FBXReader::createVec2Vector(const QVector<double>& doubleVector) {
+QVector<glm::vec2> FBXSerializer::createVec2Vector(const QVector<double>& doubleVector) {
     QVector<glm::vec2> values;
     for (const double* it = doubleVector.constData(), *end = it + ((doubleVector.size() / 2) * 2); it != end; ) {
         float s = *it++;
@@ -454,14 +454,14 @@ QVector<glm::vec2> FBXReader::createVec2Vector(const QVector<double>& doubleVect
     return values;
 }
 
-glm::mat4 FBXReader::createMat4(const QVector<double>& doubleVector) {
+glm::mat4 FBXSerializer::createMat4(const QVector<double>& doubleVector) {
     return glm::mat4(doubleVector.at(0), doubleVector.at(1), doubleVector.at(2), doubleVector.at(3),
         doubleVector.at(4), doubleVector.at(5), doubleVector.at(6), doubleVector.at(7),
         doubleVector.at(8), doubleVector.at(9), doubleVector.at(10), doubleVector.at(11),
         doubleVector.at(12), doubleVector.at(13), doubleVector.at(14), doubleVector.at(15));
 }
 
-QVector<int> FBXReader::getIntVector(const FBXNode& node) {
+QVector<int> FBXSerializer::getIntVector(const FBXNode& node) {
     foreach (const FBXNode& child, node.children) {
         if (child.name == "a") {
             return getIntVector(child);
@@ -480,7 +480,7 @@ QVector<int> FBXReader::getIntVector(const FBXNode& node) {
     return vector;
 }
 
-QVector<float> FBXReader::getFloatVector(const FBXNode& node) {
+QVector<float> FBXSerializer::getFloatVector(const FBXNode& node) {
     foreach (const FBXNode& child, node.children) {
         if (child.name == "a") {
             return getFloatVector(child);
@@ -499,7 +499,7 @@ QVector<float> FBXReader::getFloatVector(const FBXNode& node) {
     return vector;
 }
 
-QVector<double> FBXReader::getDoubleVector(const FBXNode& node) {
+QVector<double> FBXSerializer::getDoubleVector(const FBXNode& node) {
     foreach (const FBXNode& child, node.children) {
         if (child.name == "a") {
             return getDoubleVector(child);
diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFSerializer.cpp
similarity index 93%
rename from libraries/fbx/src/GLTFReader.cpp
rename to libraries/fbx/src/GLTFSerializer.cpp
index eb763cce90..28d377c605 100644
--- a/libraries/fbx/src/GLTFReader.cpp
+++ b/libraries/fbx/src/GLTFSerializer.cpp
@@ -1,5 +1,5 @@
 //
-//  GLTFReader.cpp
+//  GLTFSerializer.cpp
 //  libraries/fbx/src
 //
 //  Created by Luis Cuenca on 8/30/17.
@@ -9,7 +9,7 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-#include "GLTFReader.h"
+#include "GLTFSerializer.h"
 
 #include <QtCore/QBuffer>
 #include <QtCore/QIODevice>
@@ -33,14 +33,14 @@
 #include <ResourceManager.h>
 #include <PathUtils.h>
 
-#include "FBXReader.h"
+#include "FBXSerializer.h"
 
 
-GLTFReader::GLTFReader() {
+GLTFSerializer::GLTFSerializer() {
 
 }
 
-bool GLTFReader::getStringVal(const QJsonObject& object, const QString& fieldname, 
+bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fieldname,
                               QString& value, QMap<QString, bool>&  defined) {
     bool _defined = (object.contains(fieldname) && object[fieldname].isString());
     if (_defined) {
@@ -50,7 +50,7 @@ bool GLTFReader::getStringVal(const QJsonObject& object, const QString& fieldnam
     return _defined;
 }
 
-bool GLTFReader::getBoolVal(const QJsonObject& object, const QString& fieldname,
+bool GLTFSerializer::getBoolVal(const QJsonObject& object, const QString& fieldname,
                             bool& value, QMap<QString, bool>&  defined) {
     bool _defined = (object.contains(fieldname) && object[fieldname].isBool());
     if (_defined) {
@@ -60,7 +60,7 @@ bool GLTFReader::getBoolVal(const QJsonObject& object, const QString& fieldname,
     return _defined;
 }
 
-bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, 
+bool GLTFSerializer::getIntVal(const QJsonObject& object, const QString& fieldname,
                            int& value, QMap<QString, bool>&  defined) {
     bool _defined = (object.contains(fieldname) && !object[fieldname].isNull());
     if (_defined) {
@@ -70,7 +70,7 @@ bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname,
     return _defined;
 }
 
-bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldname, 
+bool GLTFSerializer::getDoubleVal(const QJsonObject& object, const QString& fieldname,
                               double& value, QMap<QString, bool>&  defined) {
     bool _defined = (object.contains(fieldname) && object[fieldname].isDouble());
     if (_defined) {
@@ -79,7 +79,7 @@ bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldnam
     defined.insert(fieldname, _defined);
     return _defined;
 }
-bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldname, 
+bool GLTFSerializer::getObjectVal(const QJsonObject& object, const QString& fieldname,
                               QJsonObject& value, QMap<QString, bool>&  defined) {
     bool _defined = (object.contains(fieldname) && object[fieldname].isObject());
     if (_defined) {
@@ -89,7 +89,7 @@ bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldnam
     return _defined;
 }
 
-bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldname, 
+bool GLTFSerializer::getIntArrayVal(const QJsonObject& object, const QString& fieldname,
                                 QVector<int>& values, QMap<QString, bool>&  defined) {
     bool _defined = (object.contains(fieldname) && object[fieldname].isArray());
     if (_defined) {
@@ -104,7 +104,7 @@ bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldn
     return _defined;
 }
 
-bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, 
+bool GLTFSerializer::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname,
                                    QVector<double>& values, QMap<QString, bool>&  defined) {
     bool _defined = (object.contains(fieldname) && object[fieldname].isArray());
     if (_defined) {
@@ -119,7 +119,7 @@ bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fie
     return _defined;
 }
 
-bool GLTFReader::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, 
+bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, const QString& fieldname,
                                    QJsonArray& objects, QMap<QString, bool>& defined) {
     bool _defined = (object.contains(fieldname) && object[fieldname].isArray());
     if (_defined) {
@@ -129,7 +129,7 @@ bool GLTFReader::getObjectArrayVal(const QJsonObject& object, const QString& fie
     return _defined;
 }
 
-int GLTFReader::getMeshPrimitiveRenderingMode(const QString& type)
+int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type)
 {
     if (type == "POINTS") {
         return GLTFMeshPrimitivesRenderingMode::POINTS;
@@ -155,7 +155,7 @@ int GLTFReader::getMeshPrimitiveRenderingMode(const QString& type)
     return GLTFMeshPrimitivesRenderingMode::TRIANGLES;
 }
 
-int GLTFReader::getAccessorType(const QString& type)
+int GLTFSerializer::getAccessorType(const QString& type)
 {
     if (type == "SCALAR") {
         return GLTFAccessorType::SCALAR;
@@ -181,7 +181,7 @@ int GLTFReader::getAccessorType(const QString& type)
     return GLTFAccessorType::SCALAR;
 }
 
-int GLTFReader::getMaterialAlphaMode(const QString& type)
+int GLTFSerializer::getMaterialAlphaMode(const QString& type)
 {
     if (type == "OPAQUE") {
         return GLTFMaterialAlphaMode::OPAQUE;
@@ -195,7 +195,7 @@ int GLTFReader::getMaterialAlphaMode(const QString& type)
     return GLTFMaterialAlphaMode::OPAQUE;
 }
 
-int GLTFReader::getCameraType(const QString& type)
+int GLTFSerializer::getCameraType(const QString& type)
 {
     if (type == "orthographic") {
         return GLTFCameraTypes::ORTHOGRAPHIC;
@@ -206,7 +206,7 @@ int GLTFReader::getCameraType(const QString& type)
     return GLTFCameraTypes::PERSPECTIVE;
 }
 
-int GLTFReader::getImageMimeType(const QString& mime)
+int GLTFSerializer::getImageMimeType(const QString& mime)
 {
     if (mime == "image/jpeg") {
         return GLTFImageMimetype::JPEG;
@@ -217,7 +217,7 @@ int GLTFReader::getImageMimeType(const QString& mime)
     return GLTFImageMimetype::JPEG;
 }
 
-int GLTFReader::getAnimationSamplerInterpolation(const QString& interpolation)
+int GLTFSerializer::getAnimationSamplerInterpolation(const QString& interpolation)
 {
     if (interpolation == "LINEAR") {
         return GLTFAnimationSamplerInterpolation::LINEAR;
@@ -225,7 +225,7 @@ int GLTFReader::getAnimationSamplerInterpolation(const QString& interpolation)
     return GLTFAnimationSamplerInterpolation::LINEAR;
 }
 
-bool GLTFReader::setAsset(const QJsonObject& object) {
+bool GLTFSerializer::setAsset(const QJsonObject& object) {
     QJsonObject jsAsset;
     bool isAssetDefined = getObjectVal(object, "asset", jsAsset, _file.defined);
     if (isAssetDefined) {
@@ -239,7 +239,7 @@ bool GLTFReader::setAsset(const QJsonObject& object) {
     return isAssetDefined;
 }
 
-bool GLTFReader::addAccessor(const QJsonObject& object) {
+bool GLTFSerializer::addAccessor(const QJsonObject& object) {
     GLTFAccessor accessor;
     
     getIntVal(object, "bufferView", accessor.bufferView, accessor.defined);
@@ -259,7 +259,7 @@ bool GLTFReader::addAccessor(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::addAnimation(const QJsonObject& object) {
+bool GLTFSerializer::addAnimation(const QJsonObject& object) {
     GLTFAnimation animation;
     
     QJsonArray channels;
@@ -297,7 +297,7 @@ bool GLTFReader::addAnimation(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::addBufferView(const QJsonObject& object) {
+bool GLTFSerializer::addBufferView(const QJsonObject& object) {
     GLTFBufferView bufferview;
     
     getIntVal(object, "buffer", bufferview.buffer, bufferview.defined);
@@ -310,7 +310,7 @@ bool GLTFReader::addBufferView(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::addBuffer(const QJsonObject& object) {
+bool GLTFSerializer::addBuffer(const QJsonObject& object) {
     GLTFBuffer buffer;
    
     getIntVal(object, "byteLength", buffer.byteLength, buffer.defined);
@@ -324,7 +324,7 @@ bool GLTFReader::addBuffer(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::addCamera(const QJsonObject& object) {
+bool GLTFSerializer::addCamera(const QJsonObject& object) {
     GLTFCamera camera;
     
     QJsonObject jsPerspective;
@@ -352,7 +352,7 @@ bool GLTFReader::addCamera(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::addImage(const QJsonObject& object) {
+bool GLTFSerializer::addImage(const QJsonObject& object) {
     GLTFImage image;
     
     QString mime;
@@ -367,7 +367,7 @@ bool GLTFReader::addImage(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::getIndexFromObject(const QJsonObject& object, const QString& field, 
+bool GLTFSerializer::getIndexFromObject(const QJsonObject& object, const QString& field,
                                     int& outidx, QMap<QString, bool>& defined) {
     QJsonObject subobject;
     if (getObjectVal(object, field, subobject, defined)) {
@@ -377,7 +377,7 @@ bool GLTFReader::getIndexFromObject(const QJsonObject& object, const QString& fi
     return false;
 }
 
-bool GLTFReader::addMaterial(const QJsonObject& object) {
+bool GLTFSerializer::addMaterial(const QJsonObject& object) {
     GLTFMaterial material;
 
     getStringVal(object, "name", material.name, material.defined);
@@ -413,7 +413,7 @@ bool GLTFReader::addMaterial(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::addMesh(const QJsonObject& object) {
+bool GLTFSerializer::addMesh(const QJsonObject& object) {
     GLTFMesh mesh;
 
     getStringVal(object, "name", mesh.name, mesh.defined);
@@ -467,7 +467,7 @@ bool GLTFReader::addMesh(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::addNode(const QJsonObject& object) {
+bool GLTFSerializer::addNode(const QJsonObject& object) {
     GLTFNode node;
     
     getStringVal(object, "name", node.name, node.defined);
@@ -487,7 +487,7 @@ bool GLTFReader::addNode(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::addSampler(const QJsonObject& object) {
+bool GLTFSerializer::addSampler(const QJsonObject& object) {
     GLTFSampler sampler;
 
     getIntVal(object, "magFilter", sampler.magFilter, sampler.defined);
@@ -501,7 +501,7 @@ bool GLTFReader::addSampler(const QJsonObject& object) {
 
 }
 
-bool GLTFReader::addScene(const QJsonObject& object) {
+bool GLTFSerializer::addScene(const QJsonObject& object) {
     GLTFScene scene;
 
     getStringVal(object, "name", scene.name, scene.defined);
@@ -511,7 +511,7 @@ bool GLTFReader::addScene(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::addSkin(const QJsonObject& object) {
+bool GLTFSerializer::addSkin(const QJsonObject& object) {
     GLTFSkin skin;
 
     getIntVal(object, "inverseBindMatrices", skin.inverseBindMatrices, skin.defined);
@@ -523,7 +523,7 @@ bool GLTFReader::addSkin(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::addTexture(const QJsonObject& object) {
+bool GLTFSerializer::addTexture(const QJsonObject& object) {
     GLTFTexture texture; 
     getIntVal(object, "sampler", texture.sampler, texture.defined);
     getIntVal(object, "source", texture.source, texture.defined);
@@ -533,7 +533,7 @@ bool GLTFReader::addTexture(const QJsonObject& object) {
     return true;
 }
 
-bool GLTFReader::parseGLTF(const QByteArray& data) {
+bool GLTFSerializer::parseGLTF(const QByteArray& data) {
     PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr);
     
     QJsonDocument d = QJsonDocument::fromJson(data);
@@ -664,7 +664,7 @@ bool GLTFReader::parseGLTF(const QByteArray& data) {
     return true;
 }
 
-glm::mat4 GLTFReader::getModelTransform(const GLTFNode& node) {
+glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) {
     glm::mat4 tmat = glm::mat4(1.0);
 
     if (node.defined["matrix"] && node.matrix.size() == 16) {
@@ -697,7 +697,7 @@ glm::mat4 GLTFReader::getModelTransform(const GLTFNode& node) {
     return tmat;
 }
 
-bool GLTFReader::buildGeometry(HFMModel& hfmModel, const QUrl& url) {
+bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) {
 
     //Build dependencies
     QVector<QVector<int>> nodeDependencies(_file.nodes.size());
@@ -899,7 +899,7 @@ bool GLTFReader::buildGeometry(HFMModel& hfmModel, const QUrl& url) {
                 }
 
                 mesh.meshIndex = hfmModel.meshes.size();
-                FBXReader::buildModelMesh(mesh, url.toString());
+                FBXSerializer::buildModelMesh(mesh, url.toString());
             }
             
         }
@@ -910,13 +910,12 @@ bool GLTFReader::buildGeometry(HFMModel& hfmModel, const QUrl& url) {
     return true;
 }
 
-HFMModel* GLTFReader::readGLTF(QByteArray& data, const QVariantHash& mapping, 
-                                  const QUrl& url, bool loadLightmaps, float lightmapLevel) {
+HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
     
     _url = url;
 
     // Normalize url for local files
-    QUrl normalizeUrl = DependencyManager::get<ResourceManager>()->normalizeURL(url);
+    QUrl normalizeUrl = DependencyManager::get<ResourceManager>()->normalizeURL(_url);
     if (normalizeUrl.scheme().isEmpty() || (normalizeUrl.scheme() == "file")) {
         QString localFileName = PathUtils::expandToLocalDataAbsolutePath(normalizeUrl).toLocalFile();
         _url = QUrl(QFileInfo(localFileName).absoluteFilePath());
@@ -924,17 +923,17 @@ HFMModel* GLTFReader::readGLTF(QByteArray& data, const QVariantHash& mapping,
 
     parseGLTF(data);
     //_file.dump();
-    HFMModel* hfmModelPtr = new HFMModel();
+    auto hfmModelPtr = std::make_shared<HFMModel>();
     HFMModel& hfmModel = *hfmModelPtr;
 
-    buildGeometry(hfmModel, url);
+    buildGeometry(hfmModel, _url);
     
     //hfmDebugDump(data);
     return hfmModelPtr;
     
 }
 
-bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) {
+bool GLTFSerializer::readBinary(const QString& url, QByteArray& outdata) {
     QUrl binaryUrl = _url.resolved(url);
 
     bool success;
@@ -943,7 +942,7 @@ bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) {
     return success;
 }
 
-bool GLTFReader::doesResourceExist(const QString& url) {
+bool GLTFSerializer::doesResourceExist(const QString& url) {
     if (_url.isEmpty()) {
         return false;
     }
@@ -951,9 +950,9 @@ bool GLTFReader::doesResourceExist(const QString& url) {
     return DependencyManager::get<ResourceManager>()->resourceExists(candidateUrl);
 }
 
-std::tuple<bool, QByteArray> GLTFReader::requestData(QUrl& url) {
+std::tuple<bool, QByteArray> GLTFSerializer::requestData(QUrl& url) {
     auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
-        nullptr, url, true, -1, "GLTFReader::requestData");
+        nullptr, url, true, -1, "GLTFSerializer::requestData");
 
     if (!request) {
         return std::make_tuple(false, QByteArray());
@@ -972,7 +971,7 @@ std::tuple<bool, QByteArray> GLTFReader::requestData(QUrl& url) {
 }
 
 
-QNetworkReply* GLTFReader::request(QUrl& url, bool isTest) {
+QNetworkReply* GLTFSerializer::request(QUrl& url, bool isTest) {
     if (!qApp) {
         return nullptr;
     }
@@ -996,7 +995,7 @@ QNetworkReply* GLTFReader::request(QUrl& url, bool isTest) {
     return netReply;                // trying to sync later on.
 }
 
-HFMTexture GLTFReader::getHFMTexture(const GLTFTexture& texture) {
+HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) {
     HFMTexture fbxtex = HFMTexture();
     fbxtex.texcoordSet = 0;
     
@@ -1011,7 +1010,7 @@ HFMTexture GLTFReader::getHFMTexture(const GLTFTexture& texture) {
     return fbxtex;
 }
 
-void GLTFReader::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& material) {
+void GLTFSerializer::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& material) {
 
 
     if (material.defined["name"]) {
@@ -1074,7 +1073,7 @@ void GLTFReader::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& materia
 }
 
 template<typename T, typename L>
-bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, 
+bool GLTFSerializer::readArray(const QByteArray& bin, int byteOffset, int count,
                            QVector<L>& outarray, int accessorType) {
     
     QDataStream blobstream(bin);
@@ -1131,7 +1130,7 @@ bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count,
     return true;
 }
 template<typename T>
-bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count, 
+bool GLTFSerializer::addArrayOfType(const QByteArray& bin, int byteOffset, int count,
                                 QVector<T>& outarray, int accessorType, int componentType) {
     
     switch (componentType) {
@@ -1155,7 +1154,7 @@ bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count
     return false;
 }
 
-void GLTFReader::retriangulate(const QVector<int>& inIndices, const QVector<glm::vec3>& in_vertices, 
+void GLTFSerializer::retriangulate(const QVector<int>& inIndices, const QVector<glm::vec3>& in_vertices,
                                const QVector<glm::vec3>& in_normals, QVector<int>& outIndices, 
                                QVector<glm::vec3>& out_vertices, QVector<glm::vec3>& out_normals) {
     for (int i = 0; i < inIndices.size(); i = i + 3) {
@@ -1178,7 +1177,7 @@ void GLTFReader::retriangulate(const QVector<int>& inIndices, const QVector<glm:
     }
 }
 
-void GLTFReader::hfmDebugDump(const HFMModel& hfmModel) {
+void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) {
     qCDebug(modelformat) << "---------------- hfmModel ----------------";
     qCDebug(modelformat) << "  hasSkeletonJoints =" << hfmModel.hasSkeletonJoints;
     qCDebug(modelformat) << "  offset =" << hfmModel.offset;
diff --git a/libraries/fbx/src/GLTFReader.h b/libraries/fbx/src/GLTFSerializer.h
similarity index 98%
rename from libraries/fbx/src/GLTFReader.h
rename to libraries/fbx/src/GLTFSerializer.h
index 016735778d..1ec1183e36 100644
--- a/libraries/fbx/src/GLTFReader.h
+++ b/libraries/fbx/src/GLTFSerializer.h
@@ -1,5 +1,5 @@
 //
-//  GLTFReader.h
+//  GLTFSerializer.h
 //  libraries/fbx/src
 //
 //  Created by Luis Cuenca on 8/30/17.
@@ -9,13 +9,14 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-#ifndef hifi_GLTFReader_h
-#define hifi_GLTFReader_h
+#ifndef hifi_GLTFSerializer_h
+#define hifi_GLTFSerializer_h
 
 #include <memory.h>
 #include <QtNetwork/QNetworkReply>
 #include <hfm/ModelFormatLogging.h>
-#include "FBXReader.h"
+#include <hfm/HFMSerializer.h>
+#include "FBXSerializer.h"
 
 
 struct GLTFAsset {
@@ -699,12 +700,11 @@ struct GLTFFile {
     }
 };
 
-class GLTFReader : public QObject {
+class GLTFSerializer : public QObject, public HFMSerializer {
     Q_OBJECT
 public:
-    GLTFReader();
-    HFMModel* readGLTF(QByteArray& data, const QVariantHash& mapping, 
-                          const QUrl& url, bool loadLightmaps = true, float lightmapLevel = 1.0f);
+    GLTFSerializer();
+    HFMModel::Pointer read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url = QUrl()) override;
 private:
     GLTFFile _file;
     QUrl _url;
@@ -780,4 +780,4 @@ private:
     void hfmDebugDump(const HFMModel& hfmModel);
 };
 
-#endif // hifi_GLTFReader_h
\ No newline at end of file
+#endif // hifi_GLTFSerializer_h
\ No newline at end of file
diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJSerializer.cpp
similarity index 92%
rename from libraries/fbx/src/OBJReader.cpp
rename to libraries/fbx/src/OBJSerializer.cpp
index e140f1b0b3..af8dfb5562 100644
--- a/libraries/fbx/src/OBJReader.cpp
+++ b/libraries/fbx/src/OBJSerializer.cpp
@@ -1,5 +1,5 @@
 //
-//  OBJReader.cpp
+//  OBJSerializer.cpp
 //  libraries/fbx/src/
 //
 //  Created by Seth Alves on 3/7/15.
@@ -12,7 +12,7 @@
 // http://www.scratchapixel.com/old/lessons/3d-advanced-lessons/obj-file-format/obj-file-format/
 // http://paulbourke.net/dataformats/obj/
 
-#include "OBJReader.h"
+#include "OBJSerializer.h"
 
 #include <ctype.h>  // .obj files are not locale-specific. The C/ASCII charset applies.
 #include <sstream> 
@@ -27,7 +27,7 @@
 #include <NetworkAccessManager.h>
 #include <ResourceManager.h>
 
-#include "FBXReader.h"
+#include "FBXSerializer.h"
 #include <hfm/ModelFormatLogging.h>
 #include <shared/PlatformHacks.h>
 
@@ -238,7 +238,7 @@ void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f
     }
 }
 
-bool OBJReader::isValidTexture(const QByteArray &filename) {
+bool OBJSerializer::isValidTexture(const QByteArray &filename) {
     if (_url.isEmpty()) {
         return false;
     }
@@ -247,7 +247,7 @@ bool OBJReader::isValidTexture(const QByteArray &filename) {
     return DependencyManager::get<ResourceManager>()->resourceExists(candidateUrl);
 }
 
-void OBJReader::parseMaterialLibrary(QIODevice* device) {
+void OBJSerializer::parseMaterialLibrary(QIODevice* device) {
     OBJTokenizer tokenizer(device);
     QString matName = SMART_DEFAULT_MATERIAL_NAME;
     OBJMaterial& currentMaterial = materials[matName];
@@ -255,7 +255,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
         switch (tokenizer.nextToken()) {
             case OBJTokenizer::COMMENT_TOKEN:
                 #ifdef WANT_DEBUG
-                qCDebug(modelformat) << "OBJ Reader MTLLIB comment:" << tokenizer.getComment();
+                qCDebug(modelformat) << "OBJSerializer MTLLIB comment:" << tokenizer.getComment();
                 #endif
                 break;
             case OBJTokenizer::DATUM_TOKEN:
@@ -264,7 +264,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
                 materials[matName] = currentMaterial;
                 #ifdef WANT_DEBUG
                 qCDebug(modelformat) << 
-                                     "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel <<
+                                     "OBJSerializer Last material illumination model:" << currentMaterial.illuminationModel <<
                                      " shininess:" << currentMaterial.shininess << 
                                      " opacity:" << currentMaterial.opacity <<
                                      " diffuse color:" << currentMaterial.diffuseColor << 
@@ -287,7 +287,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
             matName = tokenizer.getDatum();
             currentMaterial = materials[matName];
             #ifdef WANT_DEBUG
-            qCDebug(modelformat) << "OBJ Reader Starting new material definition " << matName;
+            qCDebug(modelformat) << "OBJSerializer Starting new material definition " << matName;
             #endif
             currentMaterial.diffuseTextureFilename = "";
             currentMaterial.emissiveTextureFilename = "";
@@ -299,7 +299,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
             currentMaterial.shininess = tokenizer.getFloat();
         } else if (token == "Ni") {
             #ifdef WANT_DEBUG
-            qCDebug(modelformat) << "OBJ Reader Ignoring material Ni " << tokenizer.getFloat();
+            qCDebug(modelformat) << "OBJSerializer Ignoring material Ni " << tokenizer.getFloat();
             #else
             tokenizer.getFloat();
             #endif
@@ -311,13 +311,13 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
             currentMaterial.illuminationModel = tokenizer.getFloat();
         } else if (token == "Tf") {
             #ifdef WANT_DEBUG
-            qCDebug(modelformat) << "OBJ Reader Ignoring material Tf " << tokenizer.getVec3();
+            qCDebug(modelformat) << "OBJSerializer Ignoring material Tf " << tokenizer.getVec3();
             #else
             tokenizer.getVec3();
             #endif
         } else if (token == "Ka") {
             #ifdef WANT_DEBUG
-            qCDebug(modelformat) << "OBJ Reader Ignoring material Ka " << tokenizer.getVec3();;
+            qCDebug(modelformat) << "OBJSerializer Ignoring material Ka " << tokenizer.getVec3();;
             #else
             tokenizer.getVec3();
             #endif
@@ -334,7 +334,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
             parseTextureLine(textureLine, filename, textureOptions);
             if (filename.endsWith(".tga")) {
                 #ifdef WANT_DEBUG
-                qCDebug(modelformat) << "OBJ Reader WARNING: currently ignoring tga texture " << filename << " in " << _url;
+                qCDebug(modelformat) << "OBJSerializer WARNING: currently ignoring tga texture " << filename << " in " << _url;
                 #endif
                 break;
             }
@@ -354,7 +354,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
     }
 } 
 
-void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions) {
+void OBJSerializer::parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions) {
     // Texture options reference http://paulbourke.net/dataformats/mtl/
     // and https://wikivisually.com/wiki/Material_Template_Library
 
@@ -368,7 +368,7 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file
             if (option == "-blendu" || option == "-blendv") {
                 #ifdef WANT_DEBUG
                 const std::string& onoff = parser[i++];
-                qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option" << option.c_str() << onoff.c_str();
+                qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << onoff.c_str();
                 #endif
             } else if (option == "-bm") {
                 const std::string& bm = parser[i++];
@@ -377,22 +377,22 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file
                 #ifdef WANT_DEBUG
                 const std::string& boost = parser[i++];
                 float boostFloat = std::stof(boost);
-                qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option" << option.c_str() << boost.c_str();
+                qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << boost.c_str();
                 #endif
             } else if (option == "-cc") {
                 #ifdef WANT_DEBUG
                 const std::string& onoff = parser[i++];
-                qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option" << option.c_str() << onoff.c_str();
+                qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << onoff.c_str();
                 #endif
             } else if (option == "-clamp") {
                 #ifdef WANT_DEBUG
                 const std::string& onoff = parser[i++];
-                qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option" << option.c_str() << onoff.c_str();
+                qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << onoff.c_str();
                 #endif
             } else if (option == "-imfchan") {
                 #ifdef WANT_DEBUG
                 const std::string& imfchan = parser[i++];
-                qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option" << option.c_str() << imfchan.c_str();
+                qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << imfchan.c_str();
                 #endif
             } else if (option == "-mm") {
                 if (i + 1 < parser.size()) {
@@ -401,7 +401,7 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file
                     const std::string& mmGain = parser[i++];
                     float mmBaseFloat = std::stof(mmBase);
                     float mmGainFloat = std::stof(mmGain);
-                    qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option" << option.c_str() << mmBase.c_str() << mmGain.c_str();
+                    qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << mmBase.c_str() << mmGain.c_str();
                     #endif
                 }
             } else if (option == "-o" || option == "-s" || option == "-t") {
@@ -413,23 +413,23 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file
                     float uFloat = std::stof(u);
                     float vFloat = std::stof(v);
                     float wFloat = std::stof(w);
-                    qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option" << option.c_str() << u.c_str() << v.c_str() << w.c_str();
+                    qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << u.c_str() << v.c_str() << w.c_str();
                     #endif
                 }
             } else if (option == "-texres") {
                 #ifdef WANT_DEBUG
                 const std::string& texres = parser[i++];
                 float texresFloat = std::stof(texres);
-                qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option" << option.c_str() << texres.c_str();
+                qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << texres.c_str();
                 #endif
             } else if (option == "-type") {
                 #ifdef WANT_DEBUG
                 const std::string& type = parser[i++];
-                qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option" << option.c_str() << type.c_str();
+                qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << type.c_str();
                 #endif
             } else if (option[0] == '-') {
                 #ifdef WANT_DEBUG
-                qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring unsupported texture option" << option.c_str();
+                qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring unsupported texture option" << option.c_str();
                 #endif
             }
         } else { // assume filename at end when no more options
@@ -444,7 +444,7 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file
 
 std::tuple<bool, QByteArray> requestData(QUrl& url) {
     auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
-        nullptr, url, true, -1, "(OBJReader) requestData");
+        nullptr, url, true, -1, "(OBJSerializer) requestData");
 
     if (!request) {
         return std::make_tuple(false, QByteArray());
@@ -488,7 +488,7 @@ QNetworkReply* request(QUrl& url, bool isTest) {
 }
 
 
-bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, HFMModel& hfmModel,
+bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, HFMModel& hfmModel,
                               float& scaleGuess, bool combineParts) {
     FaceGroup faces;
     HFMMesh& mesh = hfmModel.meshes[0];
@@ -557,7 +557,7 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi
                     currentMaterialName = nextName;
                 }
                 #ifdef WANT_DEBUG
-                qCDebug(modelformat) << "OBJ Reader new current material:" << currentMaterialName;
+                qCDebug(modelformat) << "OBJSerializer new current material:" << currentMaterialName;
                 #endif
             }
         } else if (token == "v") {
@@ -652,12 +652,12 @@ done:
 }
 
 
-HFMModel::Pointer OBJReader::readOBJ(QByteArray& data, const QVariantHash& mapping, bool combineParts, const QUrl& url) {
+HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
     PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr);
-    QBuffer buffer { &data };
+    QBuffer buffer { const_cast<QByteArray*>(&data) };
     buffer.open(QIODevice::ReadOnly);
 
-    auto hfmModelPtr { std::make_shared<HFMModel>() };
+    auto hfmModelPtr = std::make_shared<HFMModel>();
     HFMModel& hfmModel { *hfmModelPtr };
     OBJTokenizer tokenizer { &buffer };
     float scaleGuess = 1.0f;
@@ -665,6 +665,7 @@ HFMModel::Pointer OBJReader::readOBJ(QByteArray& data, const QVariantHash& mappi
     bool needsMaterialLibrary = false;
 
     _url = url;
+    bool combineParts = mapping.value("combineParts").toBool();
     hfmModel.meshExtents.reset();
     hfmModel.meshes.append(HFMMesh());
 
@@ -720,7 +721,7 @@ HFMModel::Pointer OBJReader::readOBJ(QByteArray& data, const QVariantHash& mappi
                     QString groupMaterialName = face.materialName;
                     if (groupMaterialName.isEmpty() && specifiesUV) {
 #ifdef WANT_DEBUG
-                        qCDebug(modelformat) << "OBJ Reader WARNING: " << url
+                        qCDebug(modelformat) << "OBJSerializer WARNING: " << url
                             << " needs a texture that isn't specified. Using default mechanism.";
 #endif
                         groupMaterialName = SMART_DEFAULT_MATERIAL_NAME;
@@ -822,11 +823,11 @@ HFMModel::Pointer OBJReader::readOBJ(QByteArray& data, const QVariantHash& mappi
         }
 
         // Build the single mesh.
-        FBXReader::buildModelMesh(mesh, url.toString());
+        FBXSerializer::buildModelMesh(mesh, _url.toString());
 
         // hfmDebugDump(hfmModel);
     } catch(const std::exception& e) {
-        qCDebug(modelformat) << "OBJ reader fail: " << e.what();
+        qCDebug(modelformat) << "OBJSerializer fail: " << e.what();
     }
 
     QString queryPart = _url.query();
@@ -838,14 +839,14 @@ HFMModel::Pointer OBJReader::readOBJ(QByteArray& data, const QVariantHash& mappi
     }
     // Some .obj files use the convention that a group with uv coordinates that doesn't define a material, should use
     // a texture with the same basename as the .obj file.
-    if (preDefinedMaterial.userSpecifiesUV && !url.isEmpty()) {
-        QString filename = url.fileName();
+    if (preDefinedMaterial.userSpecifiesUV && !_url.isEmpty()) {
+        QString filename = _url.fileName();
         int extIndex = filename.lastIndexOf('.'); // by construction, this does not fail
         QString basename = filename.remove(extIndex + 1, sizeof("obj"));
         preDefinedMaterial.diffuseColor = glm::vec3(1.0f);
         QVector<QByteArray> extensions = { "jpg", "jpeg", "png", "tga" };
         QByteArray base = basename.toUtf8(), textName = "";
-        qCDebug(modelformat) << "OBJ Reader looking for default texture";
+        qCDebug(modelformat) << "OBJSerializer looking for default texture";
         for (int i = 0; i < extensions.count(); i++) {
             QByteArray candidateString = base + extensions[i];
             if (isValidTexture(candidateString)) {
@@ -856,7 +857,7 @@ HFMModel::Pointer OBJReader::readOBJ(QByteArray& data, const QVariantHash& mappi
 
         if (!textName.isEmpty()) {
             #ifdef WANT_DEBUG
-            qCDebug(modelformat) << "OBJ Reader found a default texture: " << textName;
+            qCDebug(modelformat) << "OBJSerializer found a default texture: " << textName;
             #endif
             preDefinedMaterial.diffuseTextureFilename = textName;
         }
@@ -866,7 +867,7 @@ HFMModel::Pointer OBJReader::readOBJ(QByteArray& data, const QVariantHash& mappi
         foreach (QString libraryName, librariesSeen.keys()) {
             // Throw away any path part of libraryName, and merge against original url.
             QUrl libraryUrl = _url.resolved(QUrl(libraryName).fileName());
-            qCDebug(modelformat) << "OBJ Reader material library" << libraryName;
+            qCDebug(modelformat) << "OBJSerializer material library" << libraryName;
             bool success;
             QByteArray data;
             std::tie<bool, QByteArray>(success, data) = requestData(libraryUrl);
@@ -875,7 +876,7 @@ HFMModel::Pointer OBJReader::readOBJ(QByteArray& data, const QVariantHash& mappi
                 buffer.open(QIODevice::ReadOnly);
                 parseMaterialLibrary(&buffer);
             } else {
-                qCDebug(modelformat) << "OBJ Reader WARNING:" << libraryName << "did not answer";
+                qCDebug(modelformat) << "OBJSerializer WARNING:" << libraryName << "did not answer";
             }
         }
     }
diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJSerializer.h
similarity index 85%
rename from libraries/fbx/src/OBJReader.h
rename to libraries/fbx/src/OBJSerializer.h
index 0088e8e9d7..a6fe3817ca 100644
--- a/libraries/fbx/src/OBJReader.h
+++ b/libraries/fbx/src/OBJSerializer.h
@@ -1,6 +1,20 @@
+//
+//  OBJSerializer.h
+//  libraries/fbx/src/
+//
+//  Created by Seth Alves on 3/6/15.
+//  Copyright 2015 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_OBJSerializer_h
+#define hifi_OBJSerializer_h
 
 #include <QtNetwork/QNetworkReply>
-#include "FBXReader.h"
+#include <hfm/HFMSerializer.h>
+#include "FBXSerializer.h"
 
 class OBJTokenizer {
 public:
@@ -75,7 +89,7 @@ public:
     OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f), illuminationModel(-1) {}
 };
 
-class OBJReader: public QObject { // QObject so we can make network requests.
+class OBJSerializer: public QObject, public HFMSerializer { // QObject so we can make network requests.
     Q_OBJECT
 public:
     typedef QVector<OBJFace> FaceGroup;
@@ -86,8 +100,8 @@ public:
     QVector<FaceGroup> faceGroups;
     QString currentMaterialName;
     QHash<QString, OBJMaterial> materials;
-
-    HFMModel::Pointer readOBJ(QByteArray& data, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl());
+    
+    HFMModel::Pointer read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url = QUrl()) override;
 
 private:
     QUrl _url;
@@ -105,3 +119,5 @@ private:
 // What are these utilities doing here? One is used by fbx loading code in VHACD Utils, and the other a general debugging utility.
 void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID);
 void hfmDebugDump(const HFMModel& hfmModel);
+
+#endif // hifi_OBJSerializer_h
diff --git a/libraries/graphics/src/graphics/Stage.cpp b/libraries/graphics/src/graphics/Stage.cpp
index 312ece6889..21929a61dd 100644
--- a/libraries/graphics/src/graphics/Stage.cpp
+++ b/libraries/graphics/src/graphics/Stage.cpp
@@ -223,8 +223,8 @@ void SunSkyStage::setSunDirection(const Vec3& direction) {
     }
 }
 
-// THe sun declinaison calculus is taken from https://en.wikipedia.org/wiki/Position_of_the_Sun
-double evalSunDeclinaison(double dayNumber) {
+// The sun declination calculus is taken from https://en.wikipedia.org/wiki/Position_of_the_Sun
+double evalSunDeclination(double dayNumber) {
     return -(23.0 + 44.0/60.0)*cos(glm::radians((360.0/365.0)*(dayNumber + 10.0)));
 }
 
@@ -235,8 +235,8 @@ void SunSkyStage::updateGraphicsObject() const {
     float sunLongitude = _earthSunModel.getLongitude() + (MAX_LONGITUDE * signedNormalizedDayTime);
     _earthSunModel.setSunLongitude(sunLongitude);
 
-    // And update the sunLAtitude as the declinaison depending of the time of the year
-    _earthSunModel.setSunLatitude(evalSunDeclinaison(_yearTime)); 
+    // And update the sunLatitude as the declination depending of the time of the year
+    _earthSunModel.setSunLatitude(evalSunDeclination(_yearTime)); 
 
     if (isSunModelEnabled()) {
         Vec3d sunLightDir = -_earthSunModel.getSurfaceSunDir();
diff --git a/libraries/hfm/src/hfm/HFMSerializer.h b/libraries/hfm/src/hfm/HFMSerializer.h
new file mode 100644
index 0000000000..db18f21e06
--- /dev/null
+++ b/libraries/hfm/src/hfm/HFMSerializer.h
@@ -0,0 +1,29 @@
+//
+//  FBXSerializer.h
+//  libraries/hfm/src/hfm
+//
+//  Created by Sabrina Shanman on 2018/11/07.
+//  Copyright 2018 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_HFMSerializer_h
+#define hifi_HFMSerializer_h
+
+#include <shared/HifiTypes.h>
+
+#include "HFM.h"
+
+namespace hfm {
+
+class Serializer {
+    virtual Model::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) = 0;
+};
+
+};
+
+using HFMSerializer = hfm::Serializer;
+
+#endif // hifi_HFMSerializer_h
diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp
index d3f24073c4..254f61eba9 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.cpp
+++ b/libraries/model-networking/src/model-networking/ModelCache.cpp
@@ -12,9 +12,9 @@
 #include "ModelCache.h"
 #include <Finally.h>
 #include <FSTReader.h>
-#include "FBXReader.h"
-#include "OBJReader.h"
-#include "GLTFReader.h"
+#include "FBXSerializer.h"
+#include "OBJSerializer.h"
+#include "GLTFSerializer.h"
 
 #include <gpu/Batch.h>
 #include <gpu/Stream.h>
@@ -193,24 +193,26 @@ void GeometryReader::run() {
 
             HFMModel::Pointer hfmModel;
 
+            QVariantHash serializerMapping = _mapping;
+            serializerMapping["combineParts"] = _combineParts;
+
             if (_url.path().toLower().endsWith(".fbx")) {
-                hfmModel.reset(readFBX(_data, _mapping, _url.path()));
+                hfmModel = FBXSerializer().read(_data, serializerMapping, _url);
                 if (hfmModel->meshes.size() == 0 && hfmModel->joints.size() == 0) {
                     throw QString("empty geometry, possibly due to an unsupported FBX version");
                 }
             } else if (_url.path().toLower().endsWith(".obj")) {
-                hfmModel = OBJReader().readOBJ(_data, _mapping, _combineParts, _url);
+                hfmModel = OBJSerializer().read(_data, serializerMapping, _url);
             } else if (_url.path().toLower().endsWith(".obj.gz")) {
                 QByteArray uncompressedData;
                 if (gunzip(_data, uncompressedData)){
-                    hfmModel = OBJReader().readOBJ(uncompressedData, _mapping, _combineParts, _url);
+                    hfmModel = OBJSerializer().read(uncompressedData, serializerMapping, _url);
                 } else {
                     throw QString("failed to decompress .obj.gz");
                 }
 
             } else if (_url.path().toLower().endsWith(".gltf")) {
-                std::shared_ptr<GLTFReader> glreader = std::make_shared<GLTFReader>();
-                hfmModel.reset(glreader->readGLTF(_data, _mapping, _url));
+                hfmModel = GLTFSerializer().read(_data, serializerMapping, _url);
                 if (hfmModel->meshes.size() == 0 && hfmModel->joints.size() == 0) {
                     throw QString("empty geometry, possibly due to an unsupported GLTF version");
                 }
diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h
index 1bb340b83c..9d458e7512 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.h
+++ b/libraries/model-networking/src/model-networking/ModelCache.h
@@ -18,7 +18,7 @@
 #include <graphics/Material.h>
 #include <graphics/Asset.h>
 
-#include "FBXReader.h"
+#include "FBXSerializer.h"
 #include "TextureCache.h"
 
 // Alias instead of derive to avoid copying
diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp
index 9cba4970ac..3178217a36 100644
--- a/libraries/networking/src/udt/SendQueue.cpp
+++ b/libraries/networking/src/udt/SendQueue.cpp
@@ -134,6 +134,7 @@ void SendQueue::stop() {
 }
     
 int SendQueue::sendPacket(const Packet& packet) {
+    _lastPacketSentAt = std::chrono::high_resolution_clock::now();
     return _socket->writeDatagram(packet.getData(), packet.getDataSize(), _destination);
 }
     
@@ -501,12 +502,31 @@ bool SendQueue::isInactive(bool attemptedToSendPacket) {
                 // We think the client is still waiting for data (based on the sequence number gap)
                 // Let's wait either for a response from the client or until the estimated timeout
                 // (plus the sync interval to allow the client to respond) has elapsed
-                auto waitDuration = std::chrono::microseconds(_estimatedTimeout + _syncInterval);
-                
+
+                auto estimatedTimeout = std::chrono::microseconds(_estimatedTimeout);
+
+                // cap our maximum estimated timeout to the already unreasonable 5 seconds
+                const auto MAXIMUM_ESTIMATED_TIMEOUT = std::chrono::seconds(5);
+
+                if (estimatedTimeout > MAXIMUM_ESTIMATED_TIMEOUT) {
+                    estimatedTimeout = MAXIMUM_ESTIMATED_TIMEOUT;
+                }
+
                 // use our condition_variable_any to wait
-                auto cvStatus = _emptyCondition.wait_for(locker, waitDuration);
-                
-                if (cvStatus == std::cv_status::timeout && (_packets.isEmpty() || isFlowWindowFull()) && _naks.isEmpty()
+                auto cvStatus = _emptyCondition.wait_for(locker, estimatedTimeout);
+
+                // when we wake-up check if we're "stuck" either if we've slept for the estimated timeout
+                // or it has been that long since the last time we sent a packet
+
+                // we are stuck if all of the following are true
+                // - there are no new packets to send or the flow window is full and we can't send any new packets
+                // - there are no packets to resend
+                // - the client has yet to ACK some sent packets
+                auto now = std::chrono::high_resolution_clock::now();
+
+                if ((cvStatus == std::cv_status::timeout || (now - _lastPacketSentAt > estimatedTimeout))
+                    && (_packets.isEmpty() || isFlowWindowFull())
+                    && _naks.isEmpty()
                     && SequenceNumber(_lastACKSequenceNumber) < _currentSequenceNumber) {
                     // after a timeout if we still have sent packets that the client hasn't ACKed we
                     // add them to the loss list
diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h
index 65b1b89c7e..c1faac3b22 100644
--- a/libraries/networking/src/udt/SendQueue.h
+++ b/libraries/networking/src/udt/SendQueue.h
@@ -68,7 +68,6 @@ public:
     void setPacketSendPeriod(int newPeriod) { _packetSendPeriod = newPeriod; }
     
     void setEstimatedTimeout(int estimatedTimeout) { _estimatedTimeout = estimatedTimeout; }
-    void setSyncInterval(int syncInterval) { _syncInterval = syncInterval; }
     
 public slots:
     void stop();
@@ -124,7 +123,6 @@ private:
     std::atomic<State> _state { State::NotStarted };
     
     std::atomic<int> _estimatedTimeout { 0 }; // Estimated timeout, set from CC
-    std::atomic<int> _syncInterval { udt::DEFAULT_SYN_INTERVAL_USECS }; // Sync interval, set from CC
     
     std::atomic<int> _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC
     
@@ -140,6 +138,8 @@ private:
     std::condition_variable _handshakeACKCondition;
     
     std::condition_variable_any _emptyCondition;
+
+    std::chrono::high_resolution_clock::time_point _lastPacketSentAt;
 };
     
 }
diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp
index 2cdfc20647..399ccf1919 100644
--- a/libraries/script-engine/src/ScriptEngines.cpp
+++ b/libraries/script-engine/src/ScriptEngines.cpp
@@ -191,7 +191,6 @@ void ScriptEngines::shutdownScripting() {
 
             // Gracefully stop the engine's scripting thread
             scriptEngine->stop();
-            removeScriptEngine(scriptEngine);
 
             // We need to wait for the engine to be done running before we proceed, because we don't
             // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing
@@ -370,13 +369,10 @@ QStringList ScriptEngines::getRunningScripts() {
 }
 
 void ScriptEngines::stopAllScripts(bool restart) {
-    QVector<QString> toReload;
     QReadLocker lock(&_scriptEnginesHashLock);
 
     if (_isReloading) {
         return;
-    } else {
-        _isReloading = true;
     }
 
     for (QHash<QUrl, ScriptEnginePointer>::const_iterator it = _scriptEnginesHash.constBegin();
@@ -389,29 +385,27 @@ void ScriptEngines::stopAllScripts(bool restart) {
 
         // queue user scripts if restarting
         if (restart && scriptEngine->isUserLoaded()) {
-            toReload << it.key().toString();
+            _isReloading = true;
+            bool lastScript = (it == _scriptEnginesHash.constEnd() - 1);
+            ScriptEngine::Type type = scriptEngine->getType();
+
+            connect(scriptEngine.data(), &ScriptEngine::finished, this, [this, type, lastScript] (QString scriptName) {
+                reloadScript(scriptName, true)->setType(type);
+
+                if (lastScript) {
+                    _isReloading = false;
+                }
+            });
         }
 
         // stop all scripts
         scriptEngine->stop();
-        removeScriptEngine(scriptEngine);
     }
-    // wait for engines to stop (ie: providing time for .scriptEnding cleanup handlers to run) before
-    // triggering reload of any Client scripts / Entity scripts
-    QTimer::singleShot(1000, this, [=]() {
-        for(const auto &scriptName : toReload) {
-            auto scriptEngine = getScriptEngine(scriptName);
-            if (scriptEngine && !scriptEngine->isFinished()) {
-                scriptEngine->waitTillDoneRunning();
-            }
-            reloadScript(scriptName);
-        }
-        if (restart) {
-            qCDebug(scriptengine) << "stopAllScripts -- emitting scriptsReloading";
-            emit scriptsReloading();
-        }
-        _isReloading = false;
-    });
+
+    if (restart) {
+        qCDebug(scriptengine) << "stopAllScripts -- emitting scriptsReloading";
+        emit scriptsReloading();
+    }
 }
 
 bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) {
@@ -439,7 +433,6 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) {
                 }
             }
             scriptEngine->stop();
-            removeScriptEngine(scriptEngine);
             stoppedScript = true;
         }
     }
diff --git a/libraries/shared/src/DependencyManager.h b/libraries/shared/src/DependencyManager.h
index da877f7b3b..978732fd50 100644
--- a/libraries/shared/src/DependencyManager.h
+++ b/libraries/shared/src/DependencyManager.h
@@ -27,16 +27,16 @@
 class Dependency {
 public:
     typedef std::function<void(Dependency* pointer)> DeleterFunction;
-    
+
 protected:
     virtual ~Dependency() {}
     virtual void customDeleter() {
         _customDeleter(this);
     }
-    
+
     void setCustomDeleter(DeleterFunction customDeleter) { _customDeleter = customDeleter; }
     DeleterFunction _customDeleter = [](Dependency* pointer) { delete pointer; };
-    
+
     friend class DependencyManager;
 };
 
@@ -49,10 +49,10 @@ class DependencyManager {
 public:
     template<typename T>
     static QSharedPointer<T> get();
-    
+
     template<typename T>
     static bool isSet();
-    
+
     template<typename T, typename ...Args>
     static QSharedPointer<T> set(Args&&... args);
 
@@ -61,10 +61,10 @@ public:
 
     template<typename T>
     static void destroy();
-    
+
     template<typename Base, typename Derived>
     static void registerInheritance();
-    
+
     template <typename T>
     static size_t typeHash() {
 #ifdef Q_OS_ANDROID
@@ -79,9 +79,9 @@ private:
 
     template<typename T>
     size_t getHashCode();
-    
+
     QSharedPointer<Dependency>& safeGet(size_t hashCode);
-    
+
     QHash<size_t, QSharedPointer<Dependency>> _instanceHash;
     QHash<size_t, size_t> _inheritanceHash;
 };
@@ -90,15 +90,17 @@ template <typename T>
 QSharedPointer<T> DependencyManager::get() {
     static size_t hashCode = manager().getHashCode<T>();
     static QWeakPointer<T> instance;
-    
+
     if (instance.isNull()) {
         instance = qSharedPointerCast<T>(manager().safeGet(hashCode));
-        
+
+#ifndef QT_NO_DEBUG
         if (instance.isNull()) {
             qWarning() << "DependencyManager::get(): No instance available for" << typeid(T).name();
         }
+#endif
     }
-    
+
     return instance.toStrongRef();
 }
 
@@ -159,12 +161,12 @@ template<typename T>
 size_t DependencyManager::getHashCode() {
     size_t hashCode = typeHash<T>();
     auto derivedHashCode = _inheritanceHash.find(hashCode);
-    
+
     while (derivedHashCode != _inheritanceHash.end()) {
         hashCode = derivedHashCode.value();
         derivedHashCode = _inheritanceHash.find(hashCode);
     }
-    
+
     return hashCode;
 }
 
diff --git a/libraries/shared/src/shared/HifiTypes.h b/libraries/shared/src/shared/HifiTypes.h
new file mode 100644
index 0000000000..500170c88b
--- /dev/null
+++ b/libraries/shared/src/shared/HifiTypes.h
@@ -0,0 +1,25 @@
+//
+//  HifiTypes.h
+//  libraries/shared/src/shared
+//
+//  Created by Sabrina Shanman on 2018/11/12.
+//  Copyright 2018 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_HifiTypes_h
+#define hifi_HifiTypes_h
+
+#include <QVarLengthArray>
+#include <QVariant>
+#include <QUrl>
+
+namespace hifi {
+    using ByteArray = QByteArray;
+    using VariantHash = QVariantHash;
+    using URL = QUrl;
+};
+
+#endif // hifi_HifiTypes_h
diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp
index 0aad297587..a13bdf0657 100644
--- a/libraries/ui/src/OffscreenUi.cpp
+++ b/libraries/ui/src/OffscreenUi.cpp
@@ -616,7 +616,9 @@ bool OffscreenUi::navigationFocused() {
 }
 
 void OffscreenUi::setNavigationFocused(bool focused) {
-    offscreenFlags->setNavigationFocused(focused);
+    if (offscreenFlags) {
+        offscreenFlags->setNavigationFocused(focused);
+    }
 }
 
 // FIXME HACK....
diff --git a/libraries/ui/src/ui/Menu.cpp b/libraries/ui/src/ui/Menu.cpp
index 848b2362ee..b3b76ab534 100644
--- a/libraries/ui/src/ui/Menu.cpp
+++ b/libraries/ui/src/ui/Menu.cpp
@@ -422,9 +422,11 @@ void Menu::removeMenu(const QString& menuName) {
         } else {
             QMenuBar::removeAction(action);
             auto offscreenUi = DependencyManager::get<OffscreenUi>();
-            offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
-                vrMenu->removeAction(action);
-            });
+            if (offscreenUi) {
+                offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
+                    vrMenu->removeAction(action);
+                });
+            }
         }
 
         QMenuBar::repaint();
@@ -532,9 +534,11 @@ void Menu::setGroupingIsVisible(const QString& grouping, bool isVisible) {
 
 MenuWrapper::MenuWrapper(ui::Menu& rootMenu, QMenu* menu) : _rootMenu(rootMenu), _realMenu(menu) {
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
-        vrMenu->addMenu(menu);
-    });
+    if (offscreenUi) {
+        offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
+            vrMenu->addMenu(menu);
+        });
+    }
     _rootMenu._backMap[menu] = this;
 }
 
@@ -553,50 +557,62 @@ void MenuWrapper::setEnabled(bool enabled) {
 QAction* MenuWrapper::addSeparator() {
     QAction* action = _realMenu->addSeparator();
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
-        vrMenu->addSeparator(_realMenu);
-    });
+    if (offscreenUi) {
+        offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
+            vrMenu->addSeparator(_realMenu);
+        });
+    }
     return action;
 }
 
 void MenuWrapper::addAction(QAction* action) {
     _realMenu->addAction(action);
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
-        vrMenu->addAction(_realMenu, action);
-    });
+    if (offscreenUi) {
+        offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
+            vrMenu->addAction(_realMenu, action);
+        });
+    }
 }
 
 QAction* MenuWrapper::addAction(const QString& menuName) {
     QAction* action = _realMenu->addAction(menuName);
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
-        vrMenu->addAction(_realMenu, action);
-    });
+    if (offscreenUi) {
+        offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
+            vrMenu->addAction(_realMenu, action);
+        });
+    }
     return action;
 }
 
 QAction* MenuWrapper::addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut) {
     QAction* action = _realMenu->addAction(menuName, receiver, member, shortcut);
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
-        vrMenu->addAction(_realMenu, action);
-    });
+    if (offscreenUi) {
+        offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
+            vrMenu->addAction(_realMenu, action);
+        });
+    }
     return action;
 }
 
 void MenuWrapper::removeAction(QAction* action) {
     _realMenu->removeAction(action);
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
-        vrMenu->removeAction(action);
-    });
+    if (offscreenUi) {
+        offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
+            vrMenu->removeAction(action);
+        });
+    }
 }
 
 void MenuWrapper::insertAction(QAction* before, QAction* action) {
     _realMenu->insertAction(before, action);
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
-        vrMenu->insertAction(before, action);
-    });
+    if (offscreenUi) {
+        offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {
+            vrMenu->insertAction(before, action);
+        });
+    }
 }
diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp
index 8fc2d069ec..13b0498e76 100644
--- a/libraries/ui/src/ui/TabletScriptingInterface.cpp
+++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp
@@ -366,6 +366,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
 
     if (toolbarMode) {
+#if !defined(DISABLE_QML)
         // create new desktop window
         auto tabletRootWindow = new TabletRootWindow();
         tabletRootWindow->initQml(QVariantMap());
@@ -379,6 +380,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
 
         // forward qml surface events to interface js
         connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml);
+#endif
     } else {
         if (_currentPathLoaded != TABLET_HOME_SOURCE_URL) {
             loadHomeScreen(true);
diff --git a/prebuild.py b/prebuild.py
index dacc49a86e..b8cb2c96ae 100644
--- a/prebuild.py
+++ b/prebuild.py
@@ -83,12 +83,17 @@ class VcpkgRepo:
         self.sourcePortsPath = os.path.join(scriptPath, 'cmake', 'ports')
         # FIXME Revert to ports hash before release
         self.id = hashFolder(self.sourcePortsPath)[:8]
+        # OS dependent information
+        system = platform.system()
 
         if args.vcpkg_root is not None:
             print("override vcpkg path with " + args.vcpkg_root)
             self.path = args.vcpkg_root
         else:
-            defaultBasePath = os.path.join(tempfile.gettempdir(), 'hifi', 'vcpkg')
+            if 'Darwin' == system:
+                defaultBasePath = os.path.expanduser('~/hifi/vcpkg')
+            else:
+                defaultBasePath = os.path.join(tempfile.gettempdir(), 'hifi', 'vcpkg')
             basePath = os.getenv('HIFI_VCPKG_BASE', defaultBasePath)
             if (not os.path.isdir(basePath)):
                 os.makedirs(basePath)
@@ -101,8 +106,6 @@ class VcpkgRepo:
         self.tagContents = "{}_{}".format(self.id, self.version)
 
         print("prebuild path: " + self.path)
-        # OS dependent information
-        system = platform.system()
         if 'Windows' == system:
             self.exe = os.path.join(self.path, 'vcpkg.exe')
             self.vcpkgUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/vcpkg-win32.tar.gz?versionId=YZYkDejDRk7L_hrK_WVFthWvisAhbDzZ'
diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
index 5e0cdbb94b..d5365133b6 100644
--- a/scripts/system/commerce/wallet.js
+++ b/scripts/system/commerce/wallet.js
@@ -539,6 +539,9 @@ function fromQml(message) {
     case 'http.request':
         // Handled elsewhere, don't log.
         break;
+    case 'closeSendAsset':
+        ui.close();
+        break;
     default:
         print('wallet.js: Unrecognized message from QML');
     }
@@ -663,6 +666,7 @@ function uninstallMarketplaceItemTester() {
 
 var BUTTON_NAME = "INVENTORY";
 var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
+var SENDASSET_QML_SOURCE = "hifi/commerce/common/sendAsset/SendAsset.qml";
 var NOTIFICATION_POLL_TIMEOUT = 300000;
 var ui;
 function startup() {
@@ -686,6 +690,7 @@ function startup() {
         buttonName: BUTTON_NAME,
         sortOrder: 10,
         home: WALLET_QML_SOURCE,
+        additionalAppScreens: SENDASSET_QML_SOURCE,
         onOpened: walletOpened,
         onClosed: walletClosed,
         onMessage: fromQml,
diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js
index b657faefba..2658f11989 100644
--- a/scripts/system/controllers/controllerDispatcher.js
+++ b/scripts/system/controllers/controllerDispatcher.js
@@ -129,9 +129,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
             return getControllerWorldLocation(Controller.Standard.RightHand, true);
         };
 
-        Selection.enableListHighlight(DISPATCHER_HOVERING_LIST, DISPATCHER_HOVERING_STYLE);
-        Selection.enableListToScene(DISPATCHER_HOVERING_LIST);
-
         this.updateTimings = function () {
             _this.intervalCount++;
             var thisInterval = Date.now();
@@ -525,7 +522,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
             Controller.disableMapping(MAPPING_NAME);
             _this.pointerManager.removePointers();
             Pointers.removePointer(this.mouseRayPick);
-            Selection.disableListHighlight(DISPATCHER_HOVERING_LIST);
         };
     }
 
diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js
index 12a69d7b27..c61e46c8eb 100644
--- a/scripts/system/controllers/controllerModules/equipEntity.js
+++ b/scripts/system/controllers/controllerModules/equipEntity.js
@@ -459,13 +459,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
             this.dropGestureReset();
             this.clearEquipHaptics();
             Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
-            unhighlightTargetEntity(this.targetEntityID);
-            var message = {
-                hand: this.hand,
-                entityID: this.targetEntityID
-            };
 
-            Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message));
             var grabbedProperties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
             var grabData = getGrabbableData(grabbedProperties);
 
diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js
index 91119a4292..1eaed44ce2 100644
--- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js
+++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js
@@ -84,7 +84,6 @@ Script.include("/~/system/libraries/controllers.js");
         this.entityWithContextOverlay = false;
         this.contextOverlayTimer = false;
         this.locked = false;
-        this.highlightedEntity = null;
         this.reticleMinX = MARGIN;
         this.reticleMaxX = null;
         this.reticleMinY = MARGIN;
@@ -410,9 +409,6 @@ Script.include("/~/system/libraries/controllers.js");
             if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE ||
                 (this.notPointingAtEntity(controllerData) && Window.isPhysicsEnabled()) || this.targetIsNull()) {
                 this.endFarGrabAction();
-                Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
-                    this.highlightedEntity);
-                this.highlightedEntity = null;
                 this.restoreIgnoredEntities();
                 return makeRunningValues(false, [], []);
             }
@@ -466,9 +462,6 @@ Script.include("/~/system/libraries/controllers.js");
                 if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) {
                     if (controllerData.triggerClicks[this.hand]) {
                         var entityID = rayPickInfo.objectID;
-                        Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
-                            this.highlightedEntity);
-                        this.highlightedEntity = null;
                         var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);
                         if (targetProps.href !== "") {
                             AddressManager.handleLookupString(targetProps.href);
@@ -513,66 +506,43 @@ Script.include("/~/system/libraries/controllers.js");
                                 this.startFarGrabAction(controllerData, targetProps);
                             }
                         }
-                    } else {
-                        var targetEntityID = rayPickInfo.objectID;
-                        if (this.highlightedEntity !== targetEntityID) {
-                            Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
-                                this.highlightedEntity);
-                            var selectionTargetProps = Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES);
+                    } else if (!this.entityWithContextOverlay) {
+                        var _this = this;
 
-                            var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps);
-                            selectionTargetObject.parentProps = getEntityParents(selectionTargetProps);
-                            var selectionTargetEntity = selectionTargetObject.getTargetEntity();
-
-                            if (entityIsGrabbable(selectionTargetEntity.props) ||
-                                entityIsGrabbable(selectionTargetObject.entityProps)) {
-
-                                Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID);
+                        if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) {
+                            if (_this.contextOverlayTimer) {
+                                Script.clearTimeout(_this.contextOverlayTimer);
                             }
-                            this.highlightedEntity = rayPickInfo.objectID;
+                            _this.contextOverlayTimer = false;
+                            _this.potentialEntityWithContextOverlay = rayPickInfo.objectID;
                         }
 
-                        if (!this.entityWithContextOverlay) {
-                            var _this = this;
-
-                            if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) {
-                                if (_this.contextOverlayTimer) {
-                                    Script.clearTimeout(_this.contextOverlayTimer);
+                        if (!_this.contextOverlayTimer) {
+                            _this.contextOverlayTimer = Script.setTimeout(function () {
+                                if (!_this.entityWithContextOverlay &&
+                                    _this.contextOverlayTimer &&
+                                    _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) {
+                                    var props = Entities.getEntityProperties(rayPickInfo.objectID, DISPATCHER_PROPERTIES);
+                                    var pointerEvent = {
+                                        type: "Move",
+                                        id: _this.hand + 1, // 0 is reserved for hardware mouse
+                                        pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID,
+                                                                        rayPickInfo.intersection, props),
+                                        pos3D: rayPickInfo.intersection,
+                                        normal: rayPickInfo.surfaceNormal,
+                                        direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal),
+                                        button: "Secondary"
+                                    };
+                                    if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) {
+                                        _this.entityWithContextOverlay = rayPickInfo.objectID;
+                                    }
                                 }
                                 _this.contextOverlayTimer = false;
-                                _this.potentialEntityWithContextOverlay = rayPickInfo.objectID;
-                            }
-
-                            if (!_this.contextOverlayTimer) {
-                                _this.contextOverlayTimer = Script.setTimeout(function () {
-                                    if (!_this.entityWithContextOverlay &&
-                                        _this.contextOverlayTimer &&
-                                        _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) {
-                                        var props = Entities.getEntityProperties(rayPickInfo.objectID, DISPATCHER_PROPERTIES);
-                                        var pointerEvent = {
-                                            type: "Move",
-                                            id: _this.hand + 1, // 0 is reserved for hardware mouse
-                                            pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID,
-                                                                            rayPickInfo.intersection, props),
-                                            pos3D: rayPickInfo.intersection,
-                                            normal: rayPickInfo.surfaceNormal,
-                                            direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal),
-                                            button: "Secondary"
-                                        };
-                                        if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) {
-                                            _this.entityWithContextOverlay = rayPickInfo.objectID;
-                                        }
-                                    }
-                                    _this.contextOverlayTimer = false;
-                                }, 500);
-                            }
+                            }, 500);
                         }
                     }
                 } else if (this.distanceRotating) {
                     this.distanceRotate(otherFarGrabModule);
-                } else if (this.highlightedEntity) {
-                    Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity);
-                    this.highlightedEntity = null;
                 }
             }
             return this.exitIfDisabled(controllerData);
diff --git a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js
deleted file mode 100644
index 403b5d5149..0000000000
--- a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js
+++ /dev/null
@@ -1,155 +0,0 @@
-//
-//  highlightNearbyEntities.js
-//
-//  Created by Dante Ruiz 2018-4-10
-//  Copyright 2017 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-
-
-/* global Script, MyAvatar, entityIsCloneable, Messages, print */
-
-"use strict";
-
-(function () {
-    Script.include("/~/system/libraries/controllerDispatcherUtils.js");
-    Script.include("/~/system/libraries/controllers.js");
-    Script.include("/~/system/libraries/cloneEntityUtils.js");
-    var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
-
-    function differenceInArrays(firstArray, secondArray) {
-        var differenceArray = firstArray.filter(function(element) {
-            return secondArray.indexOf(element) < 0;
-        });
-
-        return differenceArray;
-    }
-
-    function HighlightNearbyEntities(hand) {
-        this.hand = hand;
-        this.otherHand = hand === dispatcherUtils.RIGHT_HAND ? dispatcherUtils.LEFT_HAND :
-            dispatcherUtils.RIGHT_HAND;
-        this.highlightedEntities = [];
-
-        this.parameters = dispatcherUtils.makeDispatcherModuleParameters(
-            480,
-            this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"],
-            [],
-            100);
-
-
-        this.isGrabable = function(controllerData, props) {
-            var canGrabEntity = false;
-            if (dispatcherUtils.entityIsGrabbable(props) || entityIsCloneable(props)) {
-                // if we've attempted to grab a child, roll up to the root of the tree
-                var groupRootProps = dispatcherUtils.findGroupParent(controllerData, props);
-                canGrabEntity = true;
-                if (!dispatcherUtils.entityIsGrabbable(groupRootProps)) {
-                    canGrabEntity = false;
-                }
-            }
-            return canGrabEntity;
-        };
-
-        this.clearAll = function() {
-            this.highlightedEntities.forEach(function(entity) {
-                dispatcherUtils.unhighlightTargetEntity(entity);
-            });
-        };
-
-        this.hasHyperLink = function(props) {
-            return (props.href !== "" && props.href !== undefined);
-        };
-
-        this.removeEntityFromHighlightList = function(entityID) {
-            var index = this.highlightedEntities.indexOf(entityID);
-            if (index > -1) {
-                this.highlightedEntities.splice(index, 1);
-            }
-        };
-
-        this.getOtherModule = function() {
-            var otherModule = this.hand === dispatcherUtils.RIGHT_HAND ? leftHighlightNearbyEntities :
-                rightHighlightNearbyEntities;
-            return otherModule;
-        };
-
-        this.getOtherHandHighlightedEntities = function() {
-            return this.getOtherModule().highlightedEntities;
-        };
-
-        this.highlightEntities = function(controllerData) {
-            var nearbyEntitiesProperties = controllerData.nearbyEntityProperties[this.hand];
-            var otherHandHighlightedEntities = this.getOtherHandHighlightedEntities();
-            var newHighlightedEntities = [];
-            var sensorScaleFactor = MyAvatar.sensorToWorldScale;
-            for (var i = 0; i < nearbyEntitiesProperties.length; i++) {
-                var props = nearbyEntitiesProperties[i];
-                if (props.distance > dispatcherUtils.NEAR_GRAB_RADIUS * sensorScaleFactor) {
-                    continue;
-                }
-                if (this.isGrabable(controllerData, props) || this.hasHyperLink(props)) {
-                    dispatcherUtils.highlightTargetEntity(props.id);
-                    if (newHighlightedEntities.indexOf(props.id) < 0) {
-                        newHighlightedEntities.push(props.id);
-                    }
-                }
-            }
-
-            var unhighlightEntities = differenceInArrays(this.highlightedEntities, newHighlightedEntities);
-
-            unhighlightEntities.forEach(function(entityID) {
-                if (otherHandHighlightedEntities.indexOf(entityID) < 0 ) {
-                    dispatcherUtils.unhighlightTargetEntity(entityID);
-                }
-            });
-            this.highlightedEntities = newHighlightedEntities;
-        };
-
-        this.isReady = function(controllerData) {
-            this.highlightEntities(controllerData);
-            return dispatcherUtils.makeRunningValues(false, [], []);
-        };
-
-        this.run = function(controllerData) {
-            return this.isReady(controllerData);
-        };
-    }
-
-    var handleMessage = function(channel, message, sender) {
-        var data;
-        if (sender === MyAvatar.sessionUUID) {
-            if (channel === 'Hifi-unhighlight-entity') {
-                try {
-                    data = JSON.parse(message);
-                    var hand = data.hand;
-                    if (hand === dispatcherUtils.LEFT_HAND) {
-                        leftHighlightNearbyEntities.removeEntityFromHighlightList(data.entityID);
-                    } else if (hand === dispatcherUtils.RIGHT_HAND) {
-                        rightHighlightNearbyEntities.removeEntityFromHighlightList(data.entityID);
-                    }
-                } catch (e) {
-                    print("highlightNearbyEntities -- Failed to parse message: " + JSON.stringify(message));
-                }
-            } else if (channel === 'Hifi-unhighlight-all') {
-                leftHighlightNearbyEntities.clearAll();
-                rightHighlightNearbyEntities.clearAll();
-            }
-        }
-    };
-    var leftHighlightNearbyEntities = new HighlightNearbyEntities(dispatcherUtils.LEFT_HAND);
-    var rightHighlightNearbyEntities = new HighlightNearbyEntities(dispatcherUtils.RIGHT_HAND);
-
-    dispatcherUtils.enableDispatcherModule("LeftHighlightNearbyEntities", leftHighlightNearbyEntities);
-    dispatcherUtils.enableDispatcherModule("RightHighlightNearbyEntities", rightHighlightNearbyEntities);
-
-    function cleanup() {
-        dispatcherUtils.disableDispatcherModule("LeftHighlightNearbyEntities");
-        dispatcherUtils.disableDispatcherModule("RightHighlightNearbyEntities");
-    }
-    Messages.subscribe('Hifi-unhighlight-entity');
-    Messages.subscribe('Hifi-unhighlight-all');
-    Messages.messageReceived.connect(handleMessage);
-    Script.scriptEnding.connect(cleanup);
-}());
diff --git a/scripts/system/controllers/controllerModules/mouseHighlightEntities.js b/scripts/system/controllers/controllerModules/mouseHighlightEntities.js
deleted file mode 100644
index 59a68d98a4..0000000000
--- a/scripts/system/controllers/controllerModules/mouseHighlightEntities.js
+++ /dev/null
@@ -1,104 +0,0 @@
-//
-//  mouseHighlightEntities.js
-//
-//  scripts/system/controllers/controllerModules/
-//
-//  Created by Dante Ruiz 2018-4-11
-//  Copyright 2017 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-/* jslint bitwise: true */
-
-/* global Script, print, Entities, Messages, Picks, HMD, MyAvatar, isInEditMode, DISPATCHER_PROPERTIES */
-
-
-(function() {
-    Script.include("/~/system/libraries/utils.js");
-    var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
-
-    function MouseHighlightEntities() {
-        this.highlightedEntity = null;
-        this.grabbedEntity = null;
-
-        this.parameters = dispatcherUtils.makeDispatcherModuleParameters(
-            5,
-            ["mouse"],
-            [],
-            100);
-
-        this.setGrabbedEntity = function(entity) {
-            this.grabbedEntity = entity;
-            this.highlightedEntity = null;
-        };
-
-        this.isReady = function(controllerData) {
-            if (HMD.active) {
-                if (this.highlightedEntity) {
-                    dispatcherUtils.unhighlightTargetEntity(this.highlightedEntity);
-                    this.highlightedEntity = null;
-                }
-            } else if (!this.grabbedEntity && !isInEditMode()) {
-                var pickResult = controllerData.mouseRayPick;
-                if (pickResult.type === Picks.INTERSECTED_ENTITY) {
-                    var targetEntityID = pickResult.objectID;
-
-                    if (this.highlightedEntity !== targetEntityID) {
-                        var targetProps = Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES);
-
-                        if (this.highlightedEntity) {
-                            dispatcherUtils.unhighlightTargetEntity(this.highlightedEntity);
-                            this.highlightedEntity = null;
-                        }
-
-                        if (dispatcherUtils.entityIsGrabbable(targetProps)) {
-                            // highlight entity
-                            dispatcherUtils.highlightTargetEntity(targetEntityID);
-                            this.highlightedEntity = targetEntityID;
-                        }
-                    }
-                } else if (this.highlightedEntity) {
-                    dispatcherUtils.unhighlightTargetEntity(this.highlightedEntity);
-                    this.highlightedEntity = null;
-                }
-            }
-
-            return dispatcherUtils.makeRunningValues(false, [], []);
-        };
-
-        this.run = function(controllerData) {
-            return this.isReady(controllerData);
-        };
-    }
-
-    var mouseHighlightEntities = new MouseHighlightEntities();
-    dispatcherUtils.enableDispatcherModule("MouseHighlightEntities", mouseHighlightEntities);
-
-    var handleMessage = function(channel, message, sender) {
-        var data;
-        if (sender === MyAvatar.sessionUUID) {
-            if (channel === 'Hifi-Object-Manipulation') {
-                try {
-                    data = JSON.parse(message);
-                    if (data.action === 'grab') {
-                        var grabbedEntity = data.grabbedEntity;
-                        mouseHighlightEntities.setGrabbedEntity(grabbedEntity);
-                    } else if (data.action === 'release') {
-                        mouseHighlightEntities.setGrabbedEntity(null);
-                    }
-                } catch (e) {
-                    print("Warning: mouseHighlightEntities -- error parsing Hifi-Object-Manipulation: " + message);
-                }
-            }
-        }
-    };
-
-    function cleanup() {
-        dispatcherUtils.disableDispatcherModule("MouseHighlightEntities");
-    }
-    Messages.subscribe('Hifi-Object-Manipulation');
-    Messages.messageReceived.connect(handleMessage);
-    Script.scriptEnding.connect(cleanup);
-})();
diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js
index 1d1f89e2c7..bb563a269c 100644
--- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js
+++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js
@@ -114,13 +114,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
 
             var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
             Entities.callEntityMethod(this.targetEntityID, "startNearGrab", args);
-            unhighlightTargetEntity(this.targetEntityID);
-            var message = {
-                hand: this.hand,
-                entityID: this.targetEntityID
-            };
-
-            Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message));
         };
 
         // this is for when the action is going to time-out
diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js
index 433ad9ef53..13557bdb7e 100644
--- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js
+++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js
@@ -11,9 +11,8 @@
    TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS,
    findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH,
    HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME,
-   TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, highlightTargetEntity, unhighlightTargetEntity,
-   distanceBetweenEntityLocalPositionAndBoundingBox, getGrabbableData, getGrabPointSphereOffset, DISPATCHER_PROPERTIES,
-   NEAR_GRAB_DISTANCE
+   TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, NEAR_GRAB_DISTANCE,
+   distanceBetweenEntityLocalPositionAndBoundingBox, getGrabbableData, getGrabPointSphereOffset, DISPATCHER_PROPERTIES
 */
 
 Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@@ -36,7 +35,6 @@ Script.include("/~/system/libraries/controllers.js");
         this.autoUnequipCounter = 0;
         this.lastUnexpectedChildrenCheckTime = 0;
         this.robbed = false;
-        this.highlightedEntity = null;
         this.cloneAllowed = true;
 
         this.parameters = makeDispatcherModuleParameters(
@@ -86,14 +84,7 @@ Script.include("/~/system/libraries/controllers.js");
         this.startNearParentingGrabEntity = function (controllerData, targetProps) {
             var grabData = getGrabbableData(targetProps);
             Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
-            unhighlightTargetEntity(this.targetEntityID);
-            this.highlightedEntity = null;
-            var message = {
-                hand: this.hand,
-                entityID: this.targetEntityID
-            };
 
-            Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message));
             var handJointIndex;
             if (grabData.grabFollowsController) {
                 handJointIndex = getControllerJointIndex(this.hand);
@@ -154,8 +145,7 @@ Script.include("/~/system/libraries/controllers.js");
                 grabbedEntity: this.targetEntityID,
                 joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
             }));
-            unhighlightTargetEntity(this.targetEntityID);
-            this.highlightedEntity = null;
+
             this.grabbing = false;
             this.targetEntityID = null;
             this.robbed = false;
@@ -279,15 +269,9 @@ Script.include("/~/system/libraries/controllers.js");
                     return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it
                 } else {
                     this.targetEntityID = targetProps.id;
-                    this.highlightedEntity = this.targetEntityID;
-                    highlightTargetEntity(this.targetEntityID);
                     return makeRunningValues(true, [this.targetEntityID], []);
                 }
             } else {
-                if (this.highlightedEntity) {
-                    unhighlightTargetEntity(this.highlightedEntity);
-                    this.highlightedEntity = null;
-                }
                 this.robbed = false;
                 return makeRunningValues(false, [], []);
             }
@@ -304,8 +288,6 @@ Script.include("/~/system/libraries/controllers.js");
                 var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
                 if (!props) {
                     // entity was deleted
-                    unhighlightTargetEntity(this.targetEntityID);
-                    this.highlightedEntity = null;
                     this.grabbing = false;
                     this.targetEntityID = null;
                     this.robbed = false;
@@ -322,12 +304,10 @@ Script.include("/~/system/libraries/controllers.js");
                 var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
                 Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args);
             } else {
-                // still searching / highlighting
+                // still searching
                 var readiness = this.isReady(controllerData);
                 if (!readiness.active) {
                     this.robbed = false;
-                    unhighlightTargetEntity(this.highlightedEntity);
-                    this.highlightedEntity = null;
                     return readiness;
                 }
                 if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) {
diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js
index f4e39cfbb9..4bff4ea3f0 100644
--- a/scripts/system/controllers/controllerModules/nearTrigger.js
+++ b/scripts/system/controllers/controllerModules/nearTrigger.js
@@ -7,7 +7,7 @@
 
 
 /* global Script, Entities, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, getGrabbableData,
-   Vec3, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS, unhighlightTargetEntity
+   Vec3, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS
 */
 
 Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@@ -55,7 +55,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
         this.startNearTrigger = function (controllerData) {
             var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
             Entities.callEntityMethod(this.targetEntityID, "startNearTrigger", args);
-            unhighlightTargetEntity(this.targetEntityID);
         };
 
         this.continueNearTrigger = function (controllerData) {
diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js
index 999e448171..83fa455519 100644
--- a/scripts/system/controllers/controllerScripts.js
+++ b/scripts/system/controllers/controllerScripts.js
@@ -35,9 +35,7 @@ var CONTOLLER_SCRIPTS = [
     "controllerModules/hudOverlayPointer.js",
     "controllerModules/mouseHMD.js",
     "controllerModules/scaleEntity.js",
-    "controllerModules/highlightNearbyEntities.js",
     "controllerModules/nearGrabHyperLinkEntity.js",
-    "controllerModules/mouseHighlightEntities.js",
     "controllerModules/nearTabletHighlight.js"
 ];
 
diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js
index 1bfa9d4b20..e3da1c2577 100644
--- a/scripts/system/html/js/marketplacesInject.js
+++ b/scripts/system/html/js/marketplacesInject.js
@@ -219,17 +219,12 @@
         });
     }
 
-    function buyButtonClicked(id, name, author, price, href, referrer, edition, type) {
+    function buyButtonClicked(id, referrer, edition) {
         EventBridge.emitWebEvent(JSON.stringify({
             type: "CHECKOUT",
             itemId: id,
-            itemName: name,
-            itemPrice: price ? parseInt(price, 10) : 0,
-            itemHref: href,
             referrer: referrer,
-            itemAuthor: author,
-            itemEdition: edition,
-            itemType: type.trim()
+            itemEdition: edition
         }));
     }
 
@@ -313,13 +308,8 @@
                 return false;
             }
             buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'),
-                $(this).closest('.grid-item').find('.item-title').text(),
-                $(this).closest('.grid-item').find('.creator').find('.value').text(),
-                $(this).closest('.grid-item').find('.item-cost').text(),
-                $(this).attr('data-href'),
                 "mainPage",
-                -1,
-                $(this).closest('.grid-item').find('.item-type').text());
+                -1);
         });
     }
 
@@ -427,13 +417,8 @@
                 purchaseButton.on('click', function () {
                     if ('available' === availability || isUpdating) {
                         buyButtonClicked(window.location.pathname.split("/")[3],
-                            $('#top-center').find('h1').text(),
-                            $('#creator').find('.value').text(),
-                            cost,
-                            href,
                             "itemPage",
-                            urlParams.get('edition'),
-                            type);
+                            urlParams.get('edition'));
                     }
                 });
             }
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index be25d1265f..e4891a9bae 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -471,7 +471,7 @@ function onWebEventReceived(message) {
         wireQmlEventBridge(true);
         ui.open(MARKETPLACE_CHECKOUT_QML_PATH);
         ui.tablet.sendToQml({
-            method: 'updateCheckoutQML',
+            method: 'updateCheckoutQMLItemID',
             params: message
         });
     } else if (message.type === "REQUEST_SETTING") {
@@ -649,8 +649,8 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
     case 'purchases_openGoTo':
     case 'purchases_itemInfoClicked':
     case 'purchases_itemCertificateClicked':
-        case 'clearShouldShowDotHistory':
-        case 'giftAsset':
+    case 'clearShouldShowDotHistory':
+    case 'giftAsset':
         break;
     default:
         print('marketplaces.js: Unrecognized message from Checkout.qml');
diff --git a/tests-manual/gpu/src/TestFbx.cpp b/tests-manual/gpu/src/TestFbx.cpp
index 9253f8bc91..d3eab9e8e3 100644
--- a/tests-manual/gpu/src/TestFbx.cpp
+++ b/tests-manual/gpu/src/TestFbx.cpp
@@ -12,7 +12,7 @@
 
 #include <QtCore/QFile>
 
-#include <FBXReader.h>
+#include <FBXSerializer.h>
 
 struct MyVertex {
     vec3 position;
@@ -100,7 +100,7 @@ bool TestFbx::isReady() const {
 
 void TestFbx::parseFbx(const QByteArray& fbxData) {
     QVariantHash mapping;
-    HFMModel* hfmModel = readFBX(fbxData, mapping);
+    HFMModel::Pointer hfmModel = FBXSerializer().read(fbxData, mapping);
     size_t totalVertexCount = 0;
     size_t totalIndexCount = 0;
     size_t totalPartCount = 0;
@@ -163,7 +163,6 @@ void TestFbx::parseFbx(const QByteArray& fbxData) {
     _vertexBuffer->append(vertices);
     _indexBuffer->append(indices);
     _indirectBuffer->append(parts);
-    delete hfmModel;
 }
 
 void TestFbx::renderTest(size_t testId, RenderArgs* args) {
diff --git a/tools/dissectors/1-hfudt.lua b/tools/dissectors/1-hfudt.lua
index 9ad89462f9..3993b2d7a0 100644
--- a/tools/dissectors/1-hfudt.lua
+++ b/tools/dissectors/1-hfudt.lua
@@ -39,9 +39,9 @@ p_hfudt.fields = {
 
 local control_types = {
   [0] = { "ACK", "Acknowledgement" },
-  [5] = { "Handshake", "Handshake" },
-  [6] = { "HandshakeACK", "Acknowledgement of Handshake" },
-  [8] = { "HandshakeRequest", "Request a Handshake" }
+  [1] = { "Handshake", "Handshake" },
+  [2] = { "HandshakeACK", "Acknowledgement of Handshake" },
+  [3] = { "HandshakeRequest", "Request a Handshake" }
 }
 
 local message_positions = {
diff --git a/tools/skeleton-dump/src/SkeletonDumpApp.cpp b/tools/skeleton-dump/src/SkeletonDumpApp.cpp
index 10b13aef36..42a1c78090 100644
--- a/tools/skeleton-dump/src/SkeletonDumpApp.cpp
+++ b/tools/skeleton-dump/src/SkeletonDumpApp.cpp
@@ -12,7 +12,7 @@
 #include "SkeletonDumpApp.h"
 #include <QCommandLineParser>
 #include <QFile>
-#include <FBXReader.h>
+#include <FBXSerializer.h>
 #include <AnimSkeleton.h>
 
 SkeletonDumpApp::SkeletonDumpApp(int argc, char* argv[]) : QCoreApplication(argc, argv) {
@@ -54,7 +54,7 @@ SkeletonDumpApp::SkeletonDumpApp(int argc, char* argv[]) : QCoreApplication(argc
         return;
     }
     QByteArray blob = file.readAll();
-    std::unique_ptr<HFMModel> geometry(readFBX(blob, QVariantHash()));
+    HFMModel::Pointer geometry = FBXSerializer().read(blob, QVariantHash());
     std::unique_ptr<AnimSkeleton> skeleton(new AnimSkeleton(*geometry));
     skeleton->dump(verbose);
 }
diff --git a/tools/vhacd-util/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp
index 8de9c39da9..9401da4314 100644
--- a/tools/vhacd-util/src/VHACDUtil.cpp
+++ b/tools/vhacd-util/src/VHACDUtil.cpp
@@ -15,9 +15,11 @@
 #include <QVector>
 
 #include <NumericalConstants.h>
+#include <FBXSerializer.h>
+#include <OBJSerializer.h>
 
 
-// FBXReader jumbles the order of the meshes by reading them back out of a hashtable.  This will put
+// FBXSerializer jumbles the order of the meshes by reading them back out of a hashtable.  This will put
 // them back in the order in which they appeared in the file.
 bool HFMModelLessThan(const HFMMesh& e1, const HFMMesh& e2) {
     return e1.meshIndex < e2.meshIndex;
@@ -43,10 +45,9 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, HFMModel& result) {
         QByteArray fbxContents = fbx.readAll();
         HFMModel::Pointer hfmModel;
         if (filename.toLower().endsWith(".obj")) {
-            bool combineParts = false;
-            hfmModel = OBJReader().readOBJ(fbxContents, QVariantHash(), combineParts);
+            hfmModel = OBJSerializer().read(fbxContents, QVariantHash(), filename);
         } else if (filename.toLower().endsWith(".fbx")) {
-            hfmModel.reset(readFBX(fbxContents, QVariantHash(), filename));
+            hfmModel = FBXSerializer().read(fbxContents, QVariantHash(), filename);
         } else {
             qWarning() << "file has unknown extension" << filename;
             return false;
diff --git a/tools/vhacd-util/src/VHACDUtil.h b/tools/vhacd-util/src/VHACDUtil.h
index dd8f606756..0fb70e8af7 100644
--- a/tools/vhacd-util/src/VHACDUtil.h
+++ b/tools/vhacd-util/src/VHACDUtil.h
@@ -18,10 +18,10 @@
 #include <vector>
 #include <chrono> //c++11 feature
 #include <QFile>
-#include <FBXReader.h>
-#include <OBJReader.h>
 #include <VHACD.h>
 
+#include <hfm/HFM.h>
+
 namespace vhacd {
     class VHACDUtil {
     public:
diff --git a/tools/vhacd-util/src/VHACDUtilApp.h b/tools/vhacd-util/src/VHACDUtilApp.h
index 7dadad20b3..1cbb29bb88 100644
--- a/tools/vhacd-util/src/VHACDUtilApp.h
+++ b/tools/vhacd-util/src/VHACDUtilApp.h
@@ -15,7 +15,7 @@
 
 #include <QCoreApplication>
 
-#include <FBXReader.h>
+#include <FBXSerializer.h>
 
 const int VHACD_RETURN_CODE_FAILURE_TO_READ = 1;
 const int VHACD_RETURN_CODE_FAILURE_TO_WRITE = 2;