Moving Arch BIMServer, Arch Git and Web Sketchfab to new external WebTools workbench
This commit is contained in:
@@ -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 *
|
||||
|
||||
@@ -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())
|
||||
@@ -31,7 +31,6 @@ SET(Arch_SRCS
|
||||
ArchPanel.py
|
||||
ArchEquipment.py
|
||||
ArchCutPlane.py
|
||||
ArchServer.py
|
||||
ArchMaterial.py
|
||||
ArchSchedule.py
|
||||
ArchProfile.py
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -333,9 +333,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
|
||||
<< "Web_BrowserStop"
|
||||
<< "Separator"
|
||||
<< "Web_BrowserZoomIn"
|
||||
<< "Web_BrowserZoomOut"
|
||||
<< "Separator"
|
||||
<< "Web_Sketchfab";
|
||||
<< "Web_BrowserZoomOut";
|
||||
|
||||
return root;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user