Moving Arch BIMServer, Arch Git and Web Sketchfab to new external WebTools workbench

This commit is contained in:
Yorik van Havre
2017-04-16 16:37:25 -03:00
parent a3154480ff
commit 30188fec9e
9 changed files with 2 additions and 844 deletions

View File

@@ -58,7 +58,6 @@ from ArchFrame import *
from ArchPanel import *
from ArchEquipment import *
from ArchCutPlane import *
from ArchServer import *
from ArchMaterial import *
from ArchSchedule import *
from ArchPrecast import *

View File

@@ -1,500 +0,0 @@
#***************************************************************************
#* *
#* Copyright (c) 2015 *
#* Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import FreeCAD, os, time, tempfile, base64, Draft
from PySide import QtCore, QtGui
if FreeCAD.GuiUp:
import FreeCADGui
from DraftTools import translate
from PySide.QtCore import QT_TRANSLATE_NOOP
else:
# \cond
def translate(ctxt,txt):
return txt
def QT_TRANSLATE_NOOP(ctxt,txt):
return txt
# \endcond
## @package ArchServer
# \ingroup ARCH
# \brief The Server object and tools
#
# This module provides utility functions to connect with
# online or local servers like BimServer or GIT
__title__="FreeCAD Arch Server commands"
__author__ = "Yorik van Havre"
__url__ = "http://www.freecadweb.org"
# BIMSERVER ###########################################################
class _CommandBimserver:
"the Arch Bimserver command definition"
def GetResources(self):
return {'Pixmap' : 'Arch_Bimserver',
'MenuText': QT_TRANSLATE_NOOP("Arch_Bimserver","BIM server"),
'ToolTip': QT_TRANSLATE_NOOP("Arch_Bimserver","Connects and interacts with a BIM server instance")}
def Activated(self):
try:
import requests
except:
FreeCAD.Console.PrintError(translate("Arch","requests python module not found, aborting. Please install python-requests\n"))
return
try:
import json
except:
FreeCAD.Console.PrintError(translate("Arch","json python module not found, aborting. Please install python-json\n"))
else:
FreeCADGui.Control.showDialog(_BimServerTaskPanel())
class _BimServerTaskPanel:
'''The TaskPanel for the BimServer command'''
def __init__(self):
self.form = FreeCADGui.PySideUic.loadUi(":/ui/BimServerTaskPanel.ui")
self.form.setWindowIcon(QtGui.QIcon(":/icons/Arch_Bimserver.svg"))
self.form.labelStatus.setText("")
QtCore.QObject.connect(self.form.buttonServer, QtCore.SIGNAL("clicked()"), self.login)
QtCore.QObject.connect(self.form.buttonBrowser, QtCore.SIGNAL("clicked()"), self.browse)
QtCore.QObject.connect(self.form.comboProjects, QtCore.SIGNAL("currentIndexChanged(int)"), self.getRevisions)
QtCore.QObject.connect(self.form.buttonOpen, QtCore.SIGNAL("clicked()"), self.openFile)
QtCore.QObject.connect(self.form.buttonUpload, QtCore.SIGNAL("clicked()"), self.uploadFile)
self.prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
self.Projects = []
self.Revisions = []
self.RootObjects = Draft.getObjectsOfType(FreeCAD.ActiveDocument.Objects,"Site")+Draft.getObjectsOfType(FreeCAD.ActiveDocument.Objects,"Building")
for o in self.RootObjects:
self.form.comboRoot.addItem(o.Label)
self.setLogged(False)
url,token = self.getPrefs()
if url and token:
self.getProjects()
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Close)
def accept(self):
FreeCADGui.Control.closeDialog()
def getPrefs(self):
url = self.prefs.GetString("BimServerUrl","http://localhost:8082")
if hasattr(self,"token"):
token = self.token
else:
token = self.prefs.GetString("BimServerToken","")
if token:
self.token = token
return url,token
def setLogged(self,logged):
if logged:
self.form.buttonServer.setText("Connected")
self.form.buttonServer.setIcon(QtGui.QIcon(":/icons/edit_OK.svg"))
self.form.buttonServer.setToolTip("Click to log out")
self.Connected = True
else:
self.form.buttonServer.setText("Not connected")
self.form.buttonServer.setIcon(QtGui.QIcon(":/icons/edit_Cancel.svg"))
self.form.buttonServer.setToolTip("Click to log in")
self.Connected = False
def login(self):
self.setLogged(False)
self.form.labelStatus.setText("")
if self.Connected:
# if the user pressed logout, delete the token
self.prefs.SetString("BimServerToken","")
else:
url,token = self.getPrefs()
loginform = FreeCADGui.PySideUic.loadUi(":/ui/DialogBimServerLogin.ui")
loginform.editUrl.setText(url)
dlg = loginform.exec_()
if dlg:
url = loginform.editUrl.text()
login = loginform.editLogin.text()
passwd = loginform.editPassword.text()
store = loginform.checkStore.isChecked()
import requests, json
self.form.labelStatus.setText("Logging in...")
url2 = url + "/json"
data = {'request': {'interface': 'AuthInterface', 'method': 'login', 'parameters': {'username': login, 'password': passwd}}}
try:
resp = requests.post(url2,data = json.dumps(data))
except:
FreeCAD.Console.PrintError(translate("Arch","Unable to connect to BimServer at")+" "+url+"\n")
self.form.labelStatus.setText(translate("Arch","Connection failed."))
return
if resp.ok:
try:
token = resp.json()["response"]["result"]
except:
return
else:
if store:
self.prefs.SetString("BimServerUrl",url)
if token:
self.prefs.SetString("BimServerToken",token)
else:
self.prefs.SetString("BimServerToken","")
if token:
self.token = token
self.getProjects()
self.form.labelStatus.setText("")
def browse(self):
url = self.prefs.GetString("BimServerUrl","http://localhost:8082")+"/apps/bimviews"
if self.prefs.GetBool("BimServerBrowser",False):
FreeCADGui.addModule("WebGui")
FreeCADGui.doCommand("WebGui.openBrowser(\""+url+"\")")
else:
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtCore.QUrl.TolerantMode))
def getProjects(self):
self.setLogged(False)
self.Projects = []
self.form.labelStatus.setText("")
import requests, json
url,token = self.getPrefs()
if url and token:
self.form.labelStatus.setText(translate("Arch","Getting projects list..."))
url += "/json"
data = { "token": token, "request": { "interface": "SettingsInterface", "method": "getServerSettings", "parameters": { } } }
try:
resp = requests.post(url,data = json.dumps(data))
except:
FreeCAD.Console.PrintError(translate("Arch","Unable to connect to BimServer at")+" "+url[:-5]+"\n")
self.form.labelStatus.setText(translate("Arch","Connection failed."))
return
if resp.ok:
try:
name = resp.json()["response"]["result"]["name"]
except:
pass # unable to get the server name
else:
self.form.labelServerName.setText(name)
data = { "token": token, "request": { "interface": "ServiceInterface", "method": "getAllProjects", "parameters": { "onlyTopLevel": "false", "onlyActive": "true" } } }
resp = requests.post(url,data = json.dumps(data))
if resp.ok:
try:
projects = resp.json()["response"]["result"]
except:
FreeCAD.Console.PrintError(translate("Arch","Unable to get projects list from BimServer\n"))
else:
self.setLogged(True)
self.form.comboProjects.clear()
for p in projects:
self.form.comboProjects.addItem(p["name"])
self.Projects = projects
self.form.comboProjects.setCurrentIndex(0)
self.getRevisions(0)
self.form.labelStatus.setText("")
def getRevisions(self,index):
self.form.labelStatus.setText("")
self.form.listRevisions.clear()
self.Revisions = []
import requests, json
url,token = self.getPrefs()
if url and token:
url += "/json"
if (index >= 0) and (len(self.Projects) > index):
p = self.Projects[index]
self.form.labelStatus.setText(translate("Arch","Getting revisions..."))
for rev in p["revisions"]:
data = { "token": token, "request": { "interface": "ServiceInterface", "method": "getRevision", "parameters": { "roid": rev } } }
resp = requests.post(url,data = json.dumps(data))
if resp.ok:
try:
name = resp.json()["response"]["result"]["comment"]
date = resp.json()["response"]["result"]["date"]
except:
pass # unable to get the revision
else:
date = time.strftime("%a %d %b %Y %H:%M:%S GMT", time.gmtime(int(date)/1000.0))
self.form.listRevisions.addItem(date+" - "+name)
self.Revisions.append(resp.json()["response"]["result"])
self.form.labelStatus.setText("")
def openFile(self):
self.form.labelStatus.setText("")
if (self.form.listRevisions.currentRow() >= 0) and (len(self.Revisions) > self.form.listRevisions.currentRow()):
rev = self.Revisions[self.form.listRevisions.currentRow()]
import requests, json
url,token = self.getPrefs()
if url and token:
FreeCAD.Console.PrintMessage(translate("Arch","Downloading file from Bimserver...\n"))
self.form.labelStatus.setText(translate("Arch","Checking available serializers..."))
url += "/json"
serializer = None
for s in ["Ifc2x3tc1"]: # Ifc4 seems unreliable ATM, let's stick with good old Ifc2x3...
data = { "token": token, "request": { "interface": "ServiceInterface", "method": "getSerializerByName", "parameters": { "serializerName": s } } }
resp = requests.post(url,data = json.dumps(data))
if resp.ok:
try:
srl = resp.json()["response"]["result"]
except:
pass # unable to get this serializer
else:
serializer = srl
break
if not serializer:
FreeCAD.Console.PrintError(translate("Arch","Unable to get a valid serializer from the BimServer\n"))
return
tf = QtGui.QFileDialog.getSaveFileName(QtGui.qApp.activeWindow(), "Save the downloaded IFC file?", None, "IFC files (*.ifc)")
if tf:
tf = tf[0]
self.form.labelStatus.setText(translate("Arch","Downloading file..."))
data = { "token": token, "request": { "interface": "ServiceInterface", "method": "downloadRevisions", "parameters": { "roids": [rev["oid"]], "serializerOid": serializer["oid"], "sync": "false" } } }
resp = requests.post(url,data = json.dumps(data))
if resp.ok:
try:
downloadid = resp.json()["response"]["result"]
except:
FreeCAD.Console.PrintError(translate("Arch","Unable to obtain a valid download for this revision from the BimServer\n"))
return
data = { "token": token, "request": { "interface": "ServiceInterface", "method": "getDownloadData", "parameters": { "topicId": downloadid } } }
resp = requests.post(url,data = json.dumps(data))
if resp.ok:
try:
downloaddata = resp.json()["response"]["result"]["file"]
except:
FreeCAD.Console.PrintError(translate("Arch","Unable to download the data for this revision.\n"))
return
else:
FreeCAD.Console.PrintMessage(translate("Arch","Opening file...\n"))
self.form.labelStatus.setText(translate("Arch","Opening file..."))
if not tf:
th,tf = tempfile.mkstemp(suffix=".ifc")
f = open(tf,"wb")
f.write(base64.b64decode(downloaddata))
f.close()
os.close(th)
import importIFC
importIFC.open(tf)
os.remove(tf)
self.form.labelStatus.setText("")
def uploadFile(self):
self.form.labelStatus.setText("")
if (self.form.comboProjects.currentIndex() >= 0) and (len(self.Projects) > self.form.comboProjects.currentIndex()) and (self.form.comboRoot.currentIndex() >= 0):
project = self.Projects[self.form.comboProjects.currentIndex()]
import requests, json
url,token = self.getPrefs()
if url and token:
url += "/json"
deserializer = None
FreeCAD.Console.PrintMessage(translate("Arch","Saving file...\n"))
self.form.labelStatus.setText(translate("Arch","Checking available deserializers..."))
import ifcopenshell
schema = ifcopenshell.schema_identifier.lower()
data = { "token": token, "request": { "interface": "PluginInterface", "method": "getAllDeserializers", "parameters": { "onlyEnabled": "true" } } }
resp = requests.post(url,data = json.dumps(data))
if resp.ok:
try:
for d in resp.json()["response"]["result"]:
if schema in d["name"].lower():
deserializer = d
break
except:
pass
if not deserializer:
FreeCAD.Console.PrintError(translate("Arch","Unable to get a valid deserializer for the schema")+" "+schema+"\n")
return
tf = QtGui.QFileDialog.getSaveFileName(QtGui.qApp.activeWindow(), translate("Arch","Save the IFC file before uploading?"), None, translate("Arch","IFC files (*.ifc)"))
if tf:
tf = tf[0]
if not tf:
tf = os.path.join(tempfile._get_default_tempdir(),next(tempfile._get_candidate_names())+".ifc")
import importIFC
self.form.labelStatus.setText(translate("Arch","Saving file..."))
importIFC.export([self.RootObjects[self.form.comboRoot.currentIndex()]],tf)
f = open(tf,"rb")
ifcdata = base64.b64encode(f.read())
f.close()
FreeCAD.Console.PrintMessage(translate("Arch","Uploading file to Bimserver...\n"))
self.form.labelStatus.setText(translate("Arch","Uploading file..."))
data = { "token": token, "request": { "interface": "ServiceInterface", "method": "checkin", "parameters": { "poid": project["oid"], "comment": self.form.editComment.text(), "deserializerOid": deserializer["oid"], "fileSize": os.path.getsize(tf), "fileName": os.path.basename(tf), "data": ifcdata, "merge": "false", "sync": "true" } } }
resp = requests.post(url,data = json.dumps(data))
if resp.ok:
if resp.json()["response"]["result"]:
FreeCAD.Console.PrintMessage(translate("Arch","File upload successful\n"))
self.getRevisions(self.form.comboProjects.currentIndex())
else:
FreeCAD.Console.PrintError(translate("Arch","File upload failed\n"))
self.form.labelStatus.setText("")
# GIT ###########################################################
class _CommandGit:
"the Arch Git Commit command definition"
def GetResources(self):
return {'Pixmap' : 'Git',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Arch_Git","Git"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Arch_Git","Manages the current document with Git")}
def Activated(self):
f = FreeCAD.ActiveDocument.FileName
if not f:
FreeCAD.Console.PrintError(translate("Arch","This document is not saved. Please save it first.\n"))
return
try:
import git
except:
FreeCAD.Console.PrintError(translate("Arch","The Python Git module was not found. Please install the python-git package.\n"))
return
try:
repo = git.Repo(os.path.dirname(f))
except:
FreeCAD.Console.PrintError(translate("Arch","This document doesn't appear to be part of a Git repository.\n"))
return
else:
FreeCADGui.Control.showDialog(_GitTaskPanel(repo))
class _GitTaskPanel:
'''The TaskPanel for the Git command'''
def __init__(self,repo):
self.form = FreeCADGui.PySideUic.loadUi(":/ui/GitTaskPanel.ui")
self.form.setWindowIcon(QtGui.QIcon(":/icons/Git.svg"))
self.form.labelStatus.setText("")
QtCore.QObject.connect(self.form.buttonRefresh, QtCore.SIGNAL("clicked()"), self.getFiles)
QtCore.QObject.connect(self.form.buttonLog, QtCore.SIGNAL("clicked()"), self.getLog)
QtCore.QObject.connect(self.form.buttonSelectAll, QtCore.SIGNAL("clicked()"), self.form.listFiles.selectAll)
QtCore.QObject.connect(self.form.buttonDiff, QtCore.SIGNAL("clicked()"), self.getDiff)
QtCore.QObject.connect(self.form.buttonCommit, QtCore.SIGNAL("clicked()"), self.commit)
QtCore.QObject.connect(self.form.buttonPush, QtCore.SIGNAL("clicked()"), self.push)
QtCore.QObject.connect(self.form.buttonPull, QtCore.SIGNAL("clicked()"), self.pull)
self.repo = repo
self.getRemotes()
self.getFiles()
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Close)
def accept(self):
FreeCADGui.Control.closeDialog()
def getFiles(self):
self.form.labelStatus.setText("")
self.form.listFiles.clear()
self.modified = self.repo.git.diff("--name-only").split()
self.untracked = self.repo.git.ls_files("--other","--exclude-standard").split()
for f in self.modified:
self.form.listFiles.addItem(f)
for f in self.untracked:
self.form.listFiles.addItem(f+" *")
self.form.labelStatus.setText(translate("Arch","Branch")+": "+self.repo.active_branch.name)
def getLog(self):
textform = FreeCADGui.PySideUic.loadUi(":/ui/DialogDisplayText.ui")
textform.setWindowTitle("Git log")
textform.browserText.setPlainText(self.repo.git.log())
textform.exec_()
def getDiff(self):
if (self.form.listFiles.currentRow() >= 0):
f = (self.modified+self.untracked)[self.form.listFiles.currentRow()]
textform = FreeCADGui.PySideUic.loadUi(":/ui/DialogDisplayText.ui")
textform.setWindowTitle("Diff: "+f)
textform.browserText.setPlainText(self.repo.git.diff(f))
textform.exec_()
def getRemotes(self):
self.form.listRepos.clear()
if self.repo.remotes:
for r in self.repo.remotes:
self.form.listRepos.addItem(r.name+": "+r.url)
else:
FreeCAD.Console.PrintWarning(translate("Arch","Warning: no remote repositories.\n"))
def commit(self):
if not self.form.listFiles.selectedItems():
FreeCAD.Console.PrintError(translate("Arch","Please select file(s) to commit.\n"))
self.form.labelStatus.setText(translate("Arch","No file selected"))
return
if not self.form.editMessage.text():
FreeCAD.Console.PrintError(translate("Arch","Please write a commit message.\n"))
self.form.labelStatus.setText(translate("Arch","No commit message"))
return
for it in self.form.listFiles.selectedItems():
f = it.text()
if f[-2:] == " *":
f = f[:-2]
self.repo.git.add(f)
s = self.repo.git.commit(m=self.form.editMessage.text())
FreeCAD.Console.PrintMessage(translate("Arch","Successfully committed %i files.\n") % len(self.form.listFiles.selectedItems()))
self.form.labelStatus.setText(translate("Arch","Files committed."))
if s:
FreeCAD.Console.PrintMessage(s+"\n")
self.getFiles()
def push(self):
if len(self.form.listRepos.selectedItems()) != 1:
FreeCAD.Console.PrintError(translate("Arch","Please select a repo to push to.\n"))
self.form.labelStatus.setText(translate("Arch","No repo selected"))
return
self.form.labelStatus.setText(translate("Arch","Pushing files..."))
r = self.form.listRepos.selectedItems()[0].text().split(":")[0]
s = self.repo.git.push(r)
FreeCAD.Console.PrintMessage(translate("Arch","Successfully pushed to")+" "+r+"\n")
self.form.labelStatus.setText(translate("Arch","Files pushed."))
if s:
FreeCAD.Console.PrintMessage(s+"\n")
self.getFiles()
def pull(self):
if len(self.form.listRepos.selectedItems()) != 1:
FreeCAD.Console.PrintError(translate("Arch","Please select a repo to pull from.\n"))
self.form.labelStatus.setText(translate("Arch","No repo selected"))
return
self.form.labelStatus.setText(translate("Arch","Pulling files..."))
r = self.form.listRepos.selectedItems()[0].text().split(":")[0]
s = self.repo.git.pull(r)
FreeCAD.Console.PrintMessage(translate("Arch","Successfully pulled from")+" "+r+"\n")
self.form.labelStatus.setText(translate("Arch","Files pulled."))
if s:
FreeCAD.Console.PrintMessage(s+"\n")
if os.path.basename(FreeCAD.ActiveDocument.FileName) in s:
FreeCAD.Console.PrintWarning(translate("Arch","Warning: the current document file has been changed by this pull. Please save your document to keep your changes.\n"))
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Arch_Bimserver',_CommandBimserver())
FreeCADGui.addCommand('Arch_Git',_CommandGit())

View File

@@ -31,7 +31,6 @@ SET(Arch_SRCS
ArchPanel.py
ArchEquipment.py
ArchCutPlane.py
ArchServer.py
ArchMaterial.py
ArchSchedule.py
ArchProfile.py

View File

@@ -44,7 +44,7 @@ class ArchWorkbench(Workbench):
"Arch_SelectNonSolidMeshes","Arch_RemoveShape",
"Arch_CloseHoles","Arch_MergeWalls","Arch_Check",
"Arch_IfcExplorer","Arch_ToggleIfcBrepFlag","Arch_3Views",
"Arch_Bimserver","Arch_Git","Arch_IfcSpreadsheet","Arch_ToggleSubs"]
"Arch_IfcSpreadsheet","Arch_ToggleSubs"]
# draft tools
self.drafttools = ["Draft_Line","Draft_Wire","Draft_Circle","Draft_Arc","Draft_Ellipse",

View File

@@ -1,10 +1,6 @@
add_subdirectory(App)
if(BUILD_GUI)
SET(Web_Scripts
Webscripts/__init__.py
Webscripts/Sketchfab.py
)
if(Qt5WebKitWidgets_FOUND OR QT_QTWEBKIT_FOUND)
add_subdirectory(Gui)
endif()
@@ -17,11 +13,3 @@ INSTALL(
DESTINATION
Mod/Web
)
INSTALL(
FILES
Webscripts/__init__.py
Webscripts/Sketchfab.py
DESTINATION
Mod/Web/Webscripts
)

View File

@@ -237,35 +237,6 @@ bool CmdWebBrowserZoomOut::isActive(void)
return getGuiApplication()->sendHasMsgToActiveView("ZoomOut");
}
//===========================================================================
// CmdWebSketchfab
//===========================================================================
DEF_STD_CMD_A(CmdWebSketchfab);
CmdWebSketchfab::CmdWebSketchfab()
: Command("Web_Sketchfab")
{
sAppModule = "Web";
sGroup = QT_TR_NOOP("Web");
sMenuText = QT_TR_NOOP("Sketchfab");
sToolTipText = QT_TR_NOOP("Uploads a model to your sketchfab account");
sWhatsThis = sToolTipText;
sStatusTip = sToolTipText;
sPixmap = "actions/web-sketchfab";
}
void CmdWebSketchfab::activated(int iMsg)
{
Q_UNUSED(iMsg);
doCommand(Command::Gui,"from Webscripts import Sketchfab");
doCommand(Command::Gui,"FreeCADGui.Control.showDialog(Sketchfab.SketchfabTaskPanel())");
}
bool CmdWebSketchfab::isActive(void)
{
return hasActiveDocument();
}
void CreateWebCommands(void)
{
@@ -278,5 +249,4 @@ void CreateWebCommands(void)
rcCmdMgr.addCommand(new CmdWebBrowserStop());
rcCmdMgr.addCommand(new CmdWebBrowserZoomIn());
rcCmdMgr.addCommand(new CmdWebBrowserZoomOut());
rcCmdMgr.addCommand(new CmdWebSketchfab());
}

View File

@@ -333,9 +333,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
<< "Web_BrowserStop"
<< "Separator"
<< "Web_BrowserZoomIn"
<< "Web_BrowserZoomOut"
<< "Separator"
<< "Web_Sketchfab";
<< "Web_BrowserZoomOut";
return root;

View File

@@ -1,296 +0,0 @@
#***************************************************************************
#* *
#* Copyright (c) 2017 - Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
from __future__ import print_function
__title__ = "Sketchfab uploader"
__author__ = "Yorik van Havre"
__url__ = "http://www.freecadweb.org"
import FreeCAD, FreeCADGui, WebGui, os, zipfile, requests, tempfile, json, time, re
from PySide import QtCore, QtGui
# \cond
try:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, QtGui.QApplication.UnicodeUTF8)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
# \endcond
SKETCHFAB_UPLOAD_URL = "https://api.sketchfab.com/v3/models"
SKETCHFAB_TOKEN_URL = "https://sketchfab.com/settings/password"
SKETCHFAB_MODEL_URL = "https://sketchfab.com/show/"
class SketchfabTaskPanel:
'''The TaskPanel for Sketchfab upload'''
def __init__(self):
self.url = None
self.form = FreeCADGui.PySideUic.loadUi(":/ui/TaskDlgSketchfab.ui")
self.form.ProgressBar.hide()
self.form.Button_View.hide()
QtCore.QObject.connect(self.form.Button_Token,QtCore.SIGNAL("pressed()"),self.getToken)
QtCore.QObject.connect(self.form.Button_Upload,QtCore.SIGNAL("pressed()"),self.upload)
QtCore.QObject.connect(self.form.Button_View,QtCore.SIGNAL("pressed()"),self.viewModel)
self.form.Text_Name.setText(FreeCAD.ActiveDocument.Label)
self.form.Text_Token.setText(FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Web").GetString("SketchfabToken",""))
def isAllowedAlterSelection(self):
return True
def isAllowedAlterView(self):
return True
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Close)
def accept(self):
FreeCADGui.ActiveDocument.resetEdit()
return True
def getToken(self):
QtGui.QDesktopServices.openUrl(SKETCHFAB_TOKEN_URL)
def get_request_payload(self, token, data={}, files={}, json_payload=False):
"""Helper method that returns the authentication token and proper content
type depending on whether or not we use JSON payload."""
headers = {'Authorization': 'Token {}'.format(token)}
if json_payload:
headers.update({'Content-Type': 'application/json'})
data = json.dumps(data)
return {'data': data, 'files': files, 'headers': headers}
def saveFile(self):
import FreeCADGui
if self.form.Radio_Selection.isChecked():
objects = FreeCADGui.Selection.getSelection()
else:
objects = [obj for obj in FreeCAD.ActiveDocument.Objects if obj.ViewObject.isVisible()]
if not objects:
QtGui.QMessageBox.critical(None,translate("Web","Nothing to upload"),translate("The selection of the document contains no object to upload"))
return None
filename = os.path.join(tempfile._get_default_tempdir(),next(tempfile._get_candidate_names()))
filetype = self.form.Combo_Filetype.currentIndex()
# 0 = obj + mtl, 1 = obj, 2 = dae, 3 = stl, 4 = IGES, 5 = iv (currently not working)
if filetype == 0: # OBJ + MTL
import importOBJ
importOBJ.export(objects,filename+".obj")
return self.packFiles(filename,[filename+".obj",filename+".mtl"])
elif filetype == 1: # OBJ (mesh exporter)
import Mesh
Mesh.export(objects,filename+".obj")
return self.packFiles(filename,[filename+".obj"])
elif filetype == 2: # DAE
import importDAE
importDAE.export(objects,filename+".dae")
return self.packFiles(filename,[filename+".dae"])
elif filetype == 3: # STL
import Mesh
Mesh.export(objects,filename+".stl")
return self.packFiles(filename,[filename+".stl"])
elif filetype == 4: # IGES
import Part
Part.export(objects,filename+".iges")
return self.packFiles(filename,[filename+".iges"])
elif filetype == 5: # IV
import FreeCADGui
FreeCADGui.export(objects,filename+".iv")
# removing FreeCAD-specific nodes
f = open(filename+".iv","rb")
s = f.read()
f.close()
ver = FreeCAD.Version()
vinfo = "# Exported by FreeCAD v" + ver[0] + "." + ver[1] + " build" + ver[2] + "\n"
vinfo += "# http://www.freecadweb.org\n"
s = s.replace("#Inventor V2.1 ascii","#Inventor V2.1 ascii\n"+vinfo)
s = s.replace("SoBrepEdgeSet","SoIndexedLineSet")
s = s.replace("SoBrepFaceSet","SoIndexedFaceSet")
s = s.replace("\n","--endl--")
s = re.sub("highlightIndex .*?\]"," ",s)
s = re.sub("partIndex .*?\]"," ",s)
s = s.replace("--endl--","\n")
f = open(filename+".iv","wb")
f.write(s)
f.close()
return self.packFiles(filename,[filename+".iv"])
def packFiles(self,filename,fileslist):
for f in fileslist:
if not os.path.exists(f):
return None
z = zipfile.ZipFile(filename+".zip","w")
for f in fileslist:
z.write(f)
z.close()
for f in fileslist:
os.remove(f)
s = os.path.getsize(filename+".zip")
if s > 1048576:
size = str(s >> 20)+" MB"
else:
size = str(s >> 10)+" KB"
return (filename+".zip",size)
def upload(self):
if not self.form.Text_Name.text():
QtGui.QMessageBox.critical(None,translate("Web","Model name is empty"),translate("You must provide a name for your model"))
return
if not self.form.Text_Token.text():
QtGui.QMessageBox.critical(None,translate("Web","No token provided"),translate("The token is empty. Please press the Obtain button to get your user API token from Sketchfab, then copy / paste the API token to the field below"))
return
FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Web").SetString("SketchfabToken",self.form.Text_Token.text())
pack = self.saveFile()
if not pack:
QtGui.QMessageBox.critical(None,translate("Web","File packing error"),translate("Unable to save and zip a file for upload"))
return
if os.path.getsize(pack[0]) >= 52428800:
b = QtGui.QMessageBox()
b.setText(translate("Web","Big upload"))
b.setInformativeText(translate("Web","The file to be uploaded is %s, which is above the maximum 50Mb allowed by free Sketchfab accounts. Pro accounts allow for up to 200Mb. Continue?") % pack[1])
b.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
b.setDefaultButton(QtGui.QMessageBox.Cancel)
ret = b.exec_()
if ret != QtGui.QMessageBox.Ok:
return
data = {
"name": self.form.Text_Name.text(),
"description": self.form.Text_Description.text(),
"tags": ["freecad"]+[t.strip() for t in self.form.Text_Tags.text().split(",")],
"private": self.form.Check_Private.isChecked(),
"source":"freecad",
}
files = {
"modelFile": open(pack[0], 'rb')
}
self.form.Button_Upload.hide()
# for now this is a fake progress bar, it won't move, just to show the user that the upload is in progress
self.form.ProgressBar.setFormat(translate("Web","Uploading")+" "+pack[1]+"...")
self.form.ProgressBar.show()
QtGui.qApp.processEvents()
try:
r = requests.post(SKETCHFAB_UPLOAD_URL, **self.get_request_payload(self.form.Text_Token.text(), data, files=files))
except requests.exceptions.RequestException as e:
QtGui.QMessageBox.critical(None,translate("Web","Upload error"),translate("Web","Upload failed:")+" "+str(e))
self.form.ProgressBar.hide()
self.form.Button_Upload.show()
return
if r.status_code != requests.codes.created:
QtGui.QMessageBox.critical(None,translate("Web","Upload error"),translate("Web","Upload failed:")+" "+str(r.json()))
self.form.ProgressBar.hide()
self.form.Button_Upload.show()
return
self.url = r.headers['Location']
if self.form.Combo_Filetype.currentIndex() in [0,1,5]: # OBJ format, sketchfab expects inverted Y/Z axes
self.form.ProgressBar.setFormat(translate("Web","Awaiting confirmation..."))
self.form.ProgressBar.setValue(75)
if self.poll(self.url):
self.form.ProgressBar.setFormat(translate("Web","Fixing model..."))
QtGui.qApp.processEvents()
self.patch(self.url)
else:
QtGui.QMessageBox.warning(None,translate("Web","Patch error"),translate("Web","Patching failed. The model was successfully uploaded, but might still require manual adjustments:"))
self.form.ProgressBar.hide()
self.form.Button_View.show()
def poll(self,url):
"""GET the model endpoint to check the processing status."""
max_errors = 10
errors = 0
retry = 0
max_retries = 50
retry_timeout = 5 # seconds
while (retry < max_retries) and (errors < max_errors):
try:
r = requests.get(url, **self.get_request_payload(self.form.Text_Token.text()))
except requests.exceptions.RequestException as e:
print ('Sketchfab: Polling failed with error: ',str(e))
errors += 1
retry += 1
continue
result = r.json()
if "error" in result:
e = result["error"]
else:
e = result
if r.status_code != requests.codes.ok:
print ('Sketchfab: Polling failed with error: ',str(e))
errors += 1
retry += 1
continue
processing_status = result['status']['processing']
if processing_status == 'PENDING':
retry += 1
time.sleep(retry_timeout)
continue
elif processing_status == 'PROCESSING':
retry += 1
time.sleep(retry_timeout)
continue
elif processing_status == 'FAILED':
print ('Sketchfab: Polling failed: ',str(e))
return False
elif processing_status == 'SUCCEEDED':
return True
retry += 1
print ('Sketchfab: Stopped polling after too many retries or too many errors')
return False
def patch(self,url):
"applies different fixes to the uploaded model"
options_url = os.path.join(url, 'options')
data = {
'orientation': '{"axis": [1, 0, 0], "angle": 270}'
}
try:
r = requests.patch(options_url, **self.get_request_payload(self.form.Text_Token.text(), data, json_payload=True))
except requests.exceptions.RequestException as e:
QtGui.QMessageBox.warning(None,translate("Web","Patch error"),translate("Web","Patching failed. The model was successfully uploaded, but might still require manual adjustments:")+" "+str(e))
else:
if r.status_code != 204:
QtGui.QMessageBox.warning(None,translate("Web","Patch error"),translate("Web","Patching failed. The model was successfully uploaded, but might still require manual adjustments:")+" "+str(r.content))
def viewModel(self):
if self.url:
url = self.url.replace("api","www")
url = url.replace("/v3","")
QtGui.QDesktopServices.openUrl(url)