Add Addon Manager to Tools menu

This commit is contained in:
Kurt Kremitzki
2017-02-01 20:20:38 -06:00
committed by wmayer
parent 66982e21bd
commit 25496adcbe
11 changed files with 1018 additions and 0 deletions

View File

@@ -194,6 +194,7 @@ endif(APPLE)
OPTION(BUILD_FEM "Build the FreeCAD FEM module" ON)
OPTION(BUILD_SANDBOX "Build the FreeCAD Sandbox module which is only for testing purposes" OFF)
OPTION(BUILD_TEMPLATE "Build the FreeCAD template module which is only for testing purposes" OFF)
OPTION(BUILD_ADDONMGR "Build the FreeCAD addon manager module" ON)
OPTION(BUILD_ARCH "Build the FreeCAD Architecture module" ON)
OPTION(BUILD_ASSEMBLY "Build the FreeCAD Assembly module" OFF)
OPTION(BUILD_COMPLETE "Build the FreeCAD complete module" ON)

View File

@@ -22,6 +22,10 @@ if (BUILD_VR)
add_definitions(-DBUILD_VR )
endif(BUILD_VR)
if (BUILD_ADDONMGR)
add_definitions(-DBUILD_ADDONMGR )
endif(BUILD_ADDONMGR)
include_directories(
${CMAKE_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}

View File

@@ -0,0 +1,378 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64px"
height="64px"
id="svg3057"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="addon-manager.svg">
<defs
id="defs3059">
<linearGradient
id="linearGradient3816"
inkscape:collect="always">
<stop
id="stop3818"
offset="0"
style="stop-color:#000000;stop-opacity:1" />
<stop
id="stop3820"
offset="1"
style="stop-color:#2e3436;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient3878">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3880" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop3882" />
</linearGradient>
<linearGradient
id="linearGradient3870">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3872" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop3874" />
</linearGradient>
<linearGradient
id="linearGradient3858">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3868" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop3862" />
</linearGradient>
<linearGradient
id="linearGradient3839">
<stop
style="stop-color:#73d216;stop-opacity:0;"
offset="0"
id="stop3841" />
<stop
style="stop-color:#73d216;stop-opacity:0;"
offset="1"
id="stop3843" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3839"
id="linearGradient3856"
gradientUnits="userSpaceOnUse"
x1="46.097534"
y1="44.755539"
x2="8.6358585"
y2="30.117304" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3858"
id="linearGradient3864"
x1="9.78771"
y1="39.660793"
x2="47.374119"
y2="39.660793"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3870"
id="linearGradient3876"
x1="9.923306"
y1="34.257634"
x2="46.865743"
y2="34.257634"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3878"
id="linearGradient3884"
x1="28.433661"
y1="48.904176"
x2="29.433661"
y2="48.904176"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3887"
id="radialGradient3893"
cx="32.5"
cy="52"
fx="32.5"
fy="52"
r="28.5"
gradientTransform="matrix(0.96428571,-4.7464116e-8,3.0929704e-8,0.24561404,1.1607127,39.228072)"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
id="linearGradient3887">
<stop
style="stop-color:#2e3436;stop-opacity:1;"
offset="0"
id="stop3889" />
<stop
style="stop-color:#2e3436;stop-opacity:0;"
offset="1"
id="stop3891" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.8782269,0,0,0.88301047,5.1375817,6.0680873)"
inkscape:collect="always"
xlink:href="#linearGradient3863"
id="linearGradient3869"
x1="22"
y1="35"
x2="20"
y2="15"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
id="linearGradient3863">
<stop
style="stop-color:#271903;stop-opacity:1"
offset="0"
id="stop3865" />
<stop
style="stop-color:#c17d11;stop-opacity:1"
offset="1"
id="stop3867" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.8782269,0,0,0.88301047,5.1375817,6.0680873)"
inkscape:collect="always"
xlink:href="#linearGradient3873"
id="linearGradient3879"
x1="39"
y1="27"
x2="38"
y2="17"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
id="linearGradient3873">
<stop
style="stop-color:#8f5902;stop-opacity:1"
offset="0"
id="stop3875" />
<stop
style="stop-color:#e9b96e;stop-opacity:1"
offset="1"
id="stop3877" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3813"
id="linearGradient3835"
gradientUnits="userSpaceOnUse"
x1="19"
y1="22"
x2="22"
y2="48" />
<linearGradient
inkscape:collect="always"
id="linearGradient3813">
<stop
style="stop-color:#e9b96e;stop-opacity:1"
offset="0"
id="stop3815" />
<stop
style="stop-color:#c17d11;stop-opacity:1"
offset="1"
id="stop3817" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.8782269,0,0,0.88301047,5.1375817,6.0680873)"
inkscape:collect="always"
xlink:href="#linearGradient3827"
id="linearGradient3833"
x1="22.181818"
y1="24.545454"
x2="23.272728"
y2="33.772724"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
id="linearGradient3827">
<stop
style="stop-color:#e9b96e;stop-opacity:1"
offset="0"
id="stop3829" />
<stop
style="stop-color:#c17d11;stop-opacity:1"
offset="1"
id="stop3831" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3839-1"
id="linearGradient3881"
gradientUnits="userSpaceOnUse"
x1="47"
y1="23"
x2="53"
y2="47" />
<linearGradient
inkscape:collect="always"
id="linearGradient3839-1">
<stop
style="stop-color:#c17d11;stop-opacity:1"
offset="0"
id="stop3841-7" />
<stop
style="stop-color:#8f5902;stop-opacity:1"
offset="1"
id="stop3843-4" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.8782269,0,0,0.88301047,5.1375817,6.0680873)"
inkscape:collect="always"
xlink:href="#linearGradient3853"
id="linearGradient3859"
x1="51.5"
y1="32.5"
x2="45.5"
y2="24.636364"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
id="linearGradient3853">
<stop
style="stop-color:#8f5902;stop-opacity:1"
offset="0"
id="stop3855" />
<stop
style="stop-color:#c17d11;stop-opacity:1"
offset="1"
id="stop3857" />
</linearGradient>
<radialGradient
r="28.5"
fy="52"
fx="32.5"
cy="52"
cx="32.5"
gradientTransform="matrix(0.96428571,-4.7464116e-8,3.0929704e-8,0.24561404,1.1607127,39.228072)"
gradientUnits="userSpaceOnUse"
id="radialGradient3079"
xlink:href="#linearGradient3816"
inkscape:collect="always" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.391268"
inkscape:cx="10.804307"
inkscape:cy="12.839628"
inkscape:current-layer="g3851"
showgrid="true"
inkscape:document-units="px"
inkscape:grid-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1137"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3015"
empspacing="2"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata3062">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<g
id="g3851"
transform="matrix(1.1386579,0,0,1.1324894,-5.849948,-6.8720445)">
<path
sodipodi:type="arc"
style="fill:url(#radialGradient3079);fill-opacity:1;stroke:none"
id="path3885"
sodipodi:cx="32.5"
sodipodi:cy="52"
sodipodi:rx="28.5"
sodipodi:ry="7"
d="M 61,52 A 28.5,7 0 1 1 4,52 28.5,7 0 1 1 61,52 z"
transform="matrix(0.98607933,0,0,0.88301034,1.1932644,6.9511055)" />
<g
transform="matrix(0.8782269,0,0,0.88301047,5.1375817,7.8341081)"
id="g3821">
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path2995"
d="M 37,55 3,47 3,15 37,23 z"
style="fill:url(#linearGradient3835);fill-opacity:1;stroke:#271903;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path3811"
d="m 5,17.6 0,27.9 30,6.9 0,-27.9 z"
style="fill:none;stroke:#e9b96e;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
transform="matrix(0.8782269,0,0,0.88301047,5.1375817,7.8341081)"
id="g3847">
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path2997"
d="m 37,23 24,-8 0,32 -24,8 z"
style="fill:url(#linearGradient3881);fill-opacity:1;stroke:#271903;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path3837"
d="M 39,24.3 39,52 59,45.9 59,18 z"
style="fill:none;stroke:#c17d11;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<path
style="fill:#e9b96e;stroke:#271903;stroke-width:1.76123083;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
d="M 7.7722624,21.079265 28.849708,14.015182 58.709423,21.079265 37.631977,28.143349 z"
id="path3044"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#271903;stroke-width:1.76123083;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
d="m 18.310985,17.547223 28.981488,7.064084"
id="path3814"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -149,6 +149,7 @@
<file>WhatsThis.svg</file>
<file>colors.svg</file>
<file>px.svg</file>
<file>AddonManager.svg</file>
</qresource>
<!-- Alternative directory names for icons that should be loaded from a theme -->
<!-- See also http://permalink.gmane.org/gmane.comp.lib.qt.general/26374 -->

View File

@@ -560,6 +560,9 @@ MenuItem* StdWorkbench::setupMenuBar() const
<< "Std_ExportGraphviz" << "Std_ProjectUtil" << "Separator"
<< "Std_MeasureDistance" << "Separator"
<< "Std_DemoMode" << "Std_UnitsCalculator" << "Separator" << "Std_DlgCustomize";
#ifdef BUILD_ADDONMGR
*tool << "Std_AddonMgr";
#endif
// Macro
MenuItem* macro = new MenuItem( menuBar );

View File

@@ -0,0 +1,577 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* 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 *
#* *
#***************************************************************************
from __future__ import print_function
__title__="FreeCAD Addon Manager Module"
__author__ = "Yorik van Havre","Jonathan Wiedemann","Kurt Kremitzki"
__url__ = "http://www.freecadweb.org"
'''
FreeCAD Addon Manager Module
It will fetch its contents from https://github.com/FreeCAD/FreeCAD-addons
You need a working internet connection, and the python-git package
installed.
'''
from PySide import QtCore, QtGui
import FreeCAD,urllib2,re,os,shutil
NOGIT = False # for debugging purposes, set this to True to always use http downloads
MACROS_BLACKLIST = ["BOLTS","WorkFeatures","how to install","PartsLibrary"]
def symlink(source, link_name):
if os.path.exists(link_name):
print("symlink already exists")
else:
os_symlink = getattr(os, "symlink", None)
if callable(os_symlink):
os_symlink(source, link_name)
else:
import ctypes
csl = ctypes.windll.kernel32.CreateSymbolicLinkW
csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
csl.restype = ctypes.c_ubyte
flags = 1 if os.path.isdir(source) else 0
if csl(link_name, source, flags) == 0:
raise ctypes.WinError()
class AddonsInstaller(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self)
self.repos = []
self.macros = []
self.setObjectName("AddonsInstaller")
self.resize(326, 304)
self.verticalLayout = QtGui.QVBoxLayout(self)
self.tabWidget = QtGui.QTabWidget()
self.verticalLayout.addWidget(self.tabWidget)
self.listWorkbenches = QtGui.QListWidget()
self.listWorkbenches.setIconSize(QtCore.QSize(16,16))
self.tabWidget.addTab(self.listWorkbenches,"")
self.listMacros = QtGui.QListWidget()
self.listMacros.setIconSize(QtCore.QSize(16,16))
self.tabWidget.addTab(self.listMacros,"")
self.labelDescription = QtGui.QLabel()
self.labelDescription.setMinimumSize(QtCore.QSize(0, 75))
self.labelDescription.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.labelDescription.setWordWrap(True)
self.verticalLayout.addWidget(self.labelDescription)
self.progressBar = QtGui.QProgressBar(self)
#self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
#self.progressBar.hide()
self.progressBar.setRange(0,0)
self.verticalLayout.addWidget(self.progressBar)
self.horizontalLayout = QtGui.QHBoxLayout()
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.buttonInstall = QtGui.QPushButton()
icon = QtGui.QIcon.fromTheme("download")
self.buttonInstall.setIcon(icon)
self.horizontalLayout.addWidget(self.buttonInstall)
self.buttonRemove = QtGui.QPushButton()
icon = QtGui.QIcon.fromTheme("edit-delete")
self.buttonRemove.setIcon(icon)
self.horizontalLayout.addWidget(self.buttonRemove)
self.buttonCancel = QtGui.QPushButton()
icon = QtGui.QIcon.fromTheme("cancel")
self.buttonCancel.setIcon(icon)
self.horizontalLayout.addWidget(self.buttonCancel)
self.verticalLayout.addLayout(self.horizontalLayout)
self.retranslateUi()
QtCore.QObject.connect(self.buttonCancel, QtCore.SIGNAL("clicked()"), self.reject)
QtCore.QObject.connect(self.buttonInstall, QtCore.SIGNAL("clicked()"), self.install)
QtCore.QObject.connect(self.buttonRemove, QtCore.SIGNAL("clicked()"), self.remove)
QtCore.QObject.connect(self.labelDescription, QtCore.SIGNAL("linkActivated(QString)"), self.showlink)
QtCore.QObject.connect(self.listWorkbenches, QtCore.SIGNAL("currentRowChanged(int)"), self.show)
QtCore.QObject.connect(self.tabWidget, QtCore.SIGNAL("currentChanged(int)"), self.switchtab)
QtCore.QObject.connect(self.listMacros, QtCore.SIGNAL("currentRowChanged(int)"), self.show_macro)
QtCore.QMetaObject.connectSlotsByName(self)
self.update()
def retranslateUi(self):
self.setWindowTitle(QtGui.QApplication.translate("AddonsInstaller",
"Addon manager", None, QtGui.QApplication.UnicodeUTF8))
self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Downloading addon list...", None, QtGui.QApplication.UnicodeUTF8))
self.buttonCancel.setText(QtGui.QApplication.translate("AddonsInstaller", "Close", None, QtGui.QApplication.UnicodeUTF8))
self.buttonInstall.setText(QtGui.QApplication.translate("AddonsInstaller", "Install / update", None, QtGui.QApplication.UnicodeUTF8))
self.buttonRemove.setText(QtGui.QApplication.translate("AddonsInstaller", "Remove", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.listWorkbenches), QtGui.QApplication.translate("AddonsInstaller", "Workbenches", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.listMacros), QtGui.QApplication.translate("AddonsInstaller", "Macros", None, QtGui.QApplication.UnicodeUTF8))
def update(self):
self.listWorkbenches.clear()
self.repos = []
self.info_worker = InfoWorker()
self.info_worker.addon_repos.connect(self.update_repos)
self.update_worker = UpdateWorker()
self.update_worker.info_label.connect(self.set_information_label)
self.update_worker.addon_repo.connect(self.add_addon_repo)
self.update_worker.progressbar_show.connect(self.show_progress_bar)
self.update_worker.start()
def add_addon_repo(self, addon_repo):
self.repos.append(addon_repo)
if addon_repo[2] == 1 :
self.listWorkbenches.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(addon_repo[0]) + str(" (Installed)")))
else:
self.listWorkbenches.addItem(" "+str(addon_repo[0]))
def set_information_label(self, label):
self.labelDescription.setText(label)
def show(self,idx):
if self.repos and idx >= 0:
self.show_worker = ShowWorker(self.repos, idx)
self.show_worker.info_label.connect(self.set_information_label)
self.show_worker.addon_repos.connect(self.update_repos)
self.show_worker.progressbar_show.connect(self.show_progress_bar)
self.show_worker.start()
def show_macro(self,idx):
if self.macros and idx >= 0:
self.showmacro_worker = ShowMacroWorker(self.macros, idx)
self.showmacro_worker.info_label.connect(self.set_information_label)
self.showmacro_worker.update_macro.connect(self.update_macro)
self.showmacro_worker.progressbar_show.connect(self.show_progress_bar)
self.showmacro_worker.start()
def switchtab(self,idx):
if idx == 1:
if not self.macros:
self.listMacros.clear()
self.macros = []
self.macro_worker = MacroWorker()
self.macro_worker.add_macro.connect(self.add_macro)
self.macro_worker.info_label.connect(self.set_information_label)
self.macro_worker.progressbar_show.connect(self.show_progress_bar)
self.macro_worker.start()
def update_repos(self, repos):
self.repos = repos
def add_macro(self, macro):
self.macros.append(macro)
if macro[1] == 1:
self.listMacros.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(macro[0]) + str(" (Installed)")))
else:
self.listMacros.addItem(" "+str(macro[0]))
def update_macro(self, idx, macro):
self.macros[idx] = macro
def showlink(self,link):
"opens a link with the system browser"
#print("clicked: ",link)
QtGui.QDesktopServices.openUrl(QtCore.QUrl(link, QtCore.QUrl.TolerantMode))
def install(self):
if self.tabWidget.currentIndex() == 0:
idx = self.listWorkbenches.currentRow()
self.install_worker = InstallWorker(self.repos, idx)
self.install_worker.info_label.connect(self.set_information_label)
self.install_worker.progressbar_show.connect(self.show_progress_bar)
self.install_worker.start()
elif self.tabWidget.currentIndex() == 1:
macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro"))
macro = self.macros[self.listMacros.currentRow()]
if len(macro) < 5:
self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Unable to install", None, QtGui.QApplication.UnicodeUTF8))
return
macroname = "Macro_"+macro[0]+".FCMacro"
macroname = macroname.replace(" ","_")
macrofilename = os.path.join(macropath,macroname)
macrofile = open(macrofilename,"wb")
macrofile.write(macro[3])
macrofile.close()
self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Macro successfully installed. The macro is now available from the Macros dialog.", None, QtGui.QApplication.UnicodeUTF8))
self.update_status()
def show_progress_bar(self, state):
if state == True:
self.listWorkbenches.setEnabled(False)
self.listMacros.setEnabled(False)
self.buttonInstall.setEnabled(False)
self.buttonRemove.setEnabled(False)
self.progressBar.show()
else:
self.progressBar.hide()
self.listWorkbenches.setEnabled(True)
self.listMacros.setEnabled(True)
self.buttonInstall.setEnabled(True)
self.buttonRemove.setEnabled(True)
def remove(self):
if self.tabWidget.currentIndex() == 0:
idx = self.listWorkbenches.currentRow()
basedir = FreeCAD.ConfigGet("UserAppData")
moddir = basedir + os.sep + "Mod"
clonedir = basedir + os.sep + "Mod" + os.sep + self.repos[idx][0]
if os.path.exists(clonedir):
shutil.rmtree(clonedir)
self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Addon successfully removed. Please restart FreeCAD", None, QtGui.QApplication.UnicodeUTF8))
else:
self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Unable to remove this addon", None, QtGui.QApplication.UnicodeUTF8))
elif self.tabWidget.currentIndex() == 1:
macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro"))
macro = self.macros[self.listMacros.currentRow()]
if macro[1] != 1:
return
macroname = "Macro_"+macro[0]+".FCMacro"
macroname = macroname.replace(" ","_")
macrofilename = os.path.join(macropath,macroname)
if os.path.exists(macrofilename):
os.remove(macrofilename)
self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Macro successfully removed.", None, QtGui.QApplication.UnicodeUTF8))
self.update_status()
def update_status(self):
self.listWorkbenches.clear()
self.listMacros.clear()
moddir = FreeCAD.ConfigGet("UserAppData") + os.sep + "Mod"
macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro"))
for wb in self.repos:
if os.path.exists(os.path.join(moddir,wb[0])):
self.listWorkbenches.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(wb[0]) + str(" (Installed)")))
wb[2] = 1
else:
self.listWorkbenches.addItem(" "+str(wb[0]))
wb[2] = 0
for macro in self.macros:
if os.path.exists(os.path.join(macropath,"Macro_"+macro[0].replace(" ","_")+".FCMacro")):
self.listMacros.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(macro[0]) + str(" (Installed)")))
macro[1] = 1
else:
self.listMacros.addItem(" "+str(macro[0]))
macro[1] = 0
class UpdateWorker(QtCore.QThread):
info_label = QtCore.Signal(str)
addon_repo = QtCore.Signal(object)
progressbar_show = QtCore.Signal(bool)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
"populates the list of addons"
self.progressbar_show.emit(True)
u = urllib2.urlopen("https://github.com/FreeCAD/FreeCAD-addons")
p = u.read()
u.close()
p = p.replace("\n"," ")
p = re.findall("octicon-file-submodule(.*?)message",p)
basedir = FreeCAD.ConfigGet("UserAppData")
moddir = basedir + os.sep + "Mod"
repos = []
for l in p:
#name = re.findall("data-skip-pjax=\"true\">(.*?)<",l)[0]
name = re.findall("title=\"(.*?) @",l)[0]
self.info_label.emit(name)
#url = re.findall("title=\"(.*?) @",l)[0]
url = "https://github.com/" + re.findall("href=\"\/(.*?)\/tree",l)[0]
addondir = moddir + os.sep + name
if not os.path.exists(addondir):
state = 0
else:
state = 1
repos.append([name,url,state])
self.addon_repo.emit([name,url,state])
if not repos:
self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Unable to download addon list.", None, QtGui.QApplication.UnicodeUTF8))
else:
self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Workbenches list was updated.", None, QtGui.QApplication.UnicodeUTF8))
self.progressbar_show.emit(False)
self.stop = True
class InfoWorker(QtCore.QThread):
addon_repos = QtCore.Signal(object)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
i = 0
for repo in self.repos:
url = repo[1]
u = urllib2.urlopen(url)
p = u.read()
u.close()
desc = re.findall("<meta content=\"(.*?)\" name", p)[3]
self.repos[i].append(desc)
i += 1
self.addon_repos.emit(self.repos)
self.stop = True
class MacroWorker(QtCore.QThread):
add_macro = QtCore.Signal(object)
info_label = QtCore.Signal(str)
progressbar_show = QtCore.Signal(bool)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
"populates the list of addons"
self.info_label.emit("Downloading list of macros...")
self.progressbar_show.emit(True)
macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro"))
u = urllib2.urlopen("http://www.freecadweb.org/wiki/index.php?title=Macros_recipes")
p = u.read()
u.close()
macros = re.findall("title=\"(Macro.*?)\"",p)
macros = [mac for mac in macros if (not("translated" in mac))]
macros.sort()
for mac in macros:
macname = mac[6:]
macname = macname.replace("&amp;","&")
if not (macname in MACROS_BLACKLIST):
macfile = mac.replace(" ","_")+".FCMacro"
if os.path.exists(os.path.join(macropath,macfile)):
installed = 1
else:
installed = 0
self.add_macro.emit([macname,installed])
self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "List of macros successfully retrieved.", None, QtGui.QApplication.UnicodeUTF8))
self.progressbar_show.emit(False)
self.stop = True
class ShowWorker(QtCore.QThread):
info_label = QtCore.Signal(str)
addon_repos = QtCore.Signal(object)
progressbar_show = QtCore.Signal(bool)
def __init__(self, repos, idx):
QtCore.QThread.__init__(self)
self.repos = repos
self.idx = idx
def run(self):
self.progressbar_show.emit(True)
self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Retrieving description...", None, QtGui.QApplication.UnicodeUTF8))
if len(self.repos[self.idx]) == 4:
desc = self.repos[self.idx][3]
else:
url = self.repos[self.idx][1]
self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Retrieving info from ", None, QtGui.QApplication.UnicodeUTF8) + str(url))
u = urllib2.urlopen(url)
p = u.read()
u.close()
desc = re.findall("<meta content=\"(.*?)\" name",p)[4]
self.repos[self.idx].append(desc)
self.addon_repos.emit(self.repos)
if self.repos[self.idx][2] == 1 :
message = "<strong>" + QtGui.QApplication.translate("AddonsInstaller", "<strong>This addon is already installed.", None, QtGui.QApplication.UnicodeUTF8) + "</strong><br>" + desc + ' - <a href="' + self.repos[self.idx][1] + '"><span style="word-wrap: break-word;width:15em;text-decoration: underline; color:#0000ff;">' + self.repos[self.idx][1] + '</span></a>'
else:
message = desc + ' - <a href="' + self.repos[self.idx][1] + '"><span style="word-wrap: break-word;width:15em;text-decoration: underline; color:#0000ff;">' + self.repos[self.idx][1] + '</span></a>'
self.info_label.emit( message )
self.progressbar_show.emit(False)
self.stop = True
class ShowMacroWorker(QtCore.QThread):
info_label = QtCore.Signal(str)
update_macro = QtCore.Signal(int,object)
progressbar_show = QtCore.Signal(bool)
def __init__(self, macros, idx):
QtCore.QThread.__init__(self)
self.macros = macros
self.idx = idx
def run(self):
self.progressbar_show.emit(True)
self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Retrieving description...", None, QtGui.QApplication.UnicodeUTF8))
if len(self.macros[self.idx]) > 2:
desc = self.macros[self.idx][2]
url = self.macros[self.idx][4]
else:
mac = self.macros[self.idx][0].replace(" ","_")
mac = mac.replace("&","%26")
mac = mac.replace("+","%2B")
url = "http://www.freecadweb.org/wiki/index.php?title=Macro_"+mac
self.info_label.emit("Retrieving info from " + str(url))
u = urllib2.urlopen(url)
p = u.read()
u.close()
code = re.findall("<pre>(.*?)<\/pre>",p.replace("\n","--endl--"))
if code:
code = code[0]
code = code.replace("--endl--","\n")
else:
self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Unable to fetch the code of this macro.", None, QtGui.QApplication.UnicodeUTF8))
self.progressbar_show.emit(False)
self.stop = True
return
desc = re.findall("<td class=\"ctEven left macro-description\">(.*?)<\/td>",p.replace("\n"," "))
if desc:
desc = desc[0]
else:
self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Unable to retrieve a description for this macro.", None, QtGui.QApplication.UnicodeUTF8))
desc = "No description available"
# clean HTML escape codes
try:
from HTMLParser import HTMLParser
except ImportError:
from html.parser import HTMLParser
try:
code = code.decode("utf8")
code = HTMLParser().unescape(code)
code = code.encode("utf8")
code = code.replace("\xc2\xa0", " ")
except:
FreeCAD.Console.PrintWarning("Unable to clean macro code: "+mac+"\n")
self.update_macro.emit(self.idx,self.macros[self.idx]+[desc,code,url])
if self.macros[self.idx][1] == 1 :
message = "<strong>" + QtGui.QApplication.translate("AddonsInstaller", "<strong>This addon is already installed.", None, QtGui.QApplication.UnicodeUTF8) + "</strong><br>" + desc + ' - <a href="' + url + '"><span style="word-wrap: break-word;width:15em;text-decoration: underline; color:#0000ff;">' + url + '</span></a>'
else:
message = desc + ' - <a href="' + url + '"><span style="word-wrap: break-word;width:15em;text-decoration: underline; color:#0000ff;">' + url + '</span></a>'
self.info_label.emit( message )
self.progressbar_show.emit(False)
self.stop = True
class InstallWorker(QtCore.QThread):
info_label = QtCore.Signal(str)
progressbar_show = QtCore.Signal(bool)
def __init__(self, repos, idx):
QtCore.QThread.__init__(self)
self.idx = idx
self.repos = repos
def run(self):
"installs or updates the selected addon"
git = None
try:
import git
except:
self.info_label.emit("python-git not found.")
FreeCAD.Console.PrintWarning("python-git not found. Using standard download instead.\n")
try:
import zipfile,StringIO
except:
self.info_label.emit("no zip support.")
FreeCAD.Console.PrintError("your version of python doesn't appear to support ZIP files. Unable to proceed.\n")
return
if self.idx < 0:
return
if not self.repos:
return
if NOGIT:
git = None
basedir = FreeCAD.ConfigGet("UserAppData")
moddir = basedir + os.sep + "Mod"
if not os.path.exists(moddir):
os.makedirs(moddir)
clonedir = moddir + os.sep + self.repos[self.idx][0]
self.progressbar_show.emit(True)
if os.path.exists(clonedir):
self.info_label.emit("Updating module...")
if git:
repo = git.Git(clonedir)
answer = repo.pull()
else:
answer = self.download(self.repos[self.idx][1],clonedir)
else:
if git:
self.info_label.emit("Cloning module...")
repo = git.Repo.clone_from(self.repos[self.idx][1], clonedir, branch='master')
else:
self.info_label.emit("Downloading module...")
self.download(self.repos[self.idx][1],clonedir)
answer = QtGui.QApplication.translate("AddonsInstaller", "Workbench successfully installed. Please restart FreeCAD to apply the changes.", None, QtGui.QApplication.UnicodeUTF8)
# symlink any macro contained in the module to the macros folder
macrodir = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro").GetString("MacroPath")
for f in os.listdir(clonedir):
if f.lower().endswith(".fcmacro"):
symlink(clonedir+os.sep+f,macrodir+os.sep+f)
FreeCAD.ParamGet('User parameter:Plugins/'+self.repos[self.idx][0]).SetString("destination",clonedir)
answer += QtGui.QApplication.translate("AddonsInstaller", "A macro has been installed and is available the Macros menu", None, QtGui.QApplication.UnicodeUTF8) + ": <b>"
answer += f + "</b>"
self.info_label.emit(answer)
self.progressbar_show.emit(False)
self.stop = True
def download(self,giturl,clonedir):
"downloads and unzip from github"
import StringIO,zipfile
bakdir = None
if os.path.exists(clonedir):
bakdir = clonedir+".bak"
if os.path.exists(bakdir):
shutil.rmtree(bakdir)
os.rename(clonedir,bakdir)
os.makedirs(clonedir)
zipurl = giturl+"/archive/master.zip"
try:
print("Downloading "+zipurl)
u = urllib2.urlopen(zipurl)
except:
return QtGui.QApplication.translate("AddonsInstaller", "Error: Unable to download", None, QtGui.QApplication.UnicodeUTF8) + " " + zipurl
zfile = StringIO.StringIO()
zfile.write(u.read())
zfile = zipfile.ZipFile(zfile)
master = zfile.namelist()[0] # github will put everything in a subfolder
zfile.extractall(clonedir)
u.close()
zfile.close()
for filename in os.listdir(clonedir+os.sep+master):
shutil.move(clonedir+os.sep+master+os.sep+filename, clonedir+os.sep+filename)
os.rmdir(clonedir+os.sep+master)
if bakdir:
shutil.rmtree(bakdir)
return QtGui.QApplication.translate("AddonsInstaller", "Successfully installed", None, QtGui.QApplication.UnicodeUTF8) + " " + zipurl
def launchAddonMgr():
# first use dialog
readWarning = FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').GetBool('readWarning',False)
if not readWarning:
if QtGui.QMessageBox.warning(None,"FreeCAD",QtGui.QApplication.translate("AddonsInstaller", "The addons that can be installed here are not officially part of FreeCAD, and are not reviewed by the FreeCAD team. Make sure you know what you are installing!", None, QtGui.QApplication.UnicodeUTF8), QtGui.QMessageBox.Cancel | QtGui.QMessageBox.Ok) != QtGui.QMessageBox.StandardButton.Cancel:
FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').SetBool('readWarning',True)
readWarning = True
if readWarning:
dialog = AddonsInstaller()
dialog.exec_()

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# FreeCAD tools of the AddonManager workbench
# (c) 2001 Juergen Riegel
# License LGPL
import FreeCAD, FreeCADGui
class CmdAddonMgr:
def Activated(self):
import AddonManager
AddonManager.launchAddonMgr()
def IsActive(self):
return True
def GetResources(self):
return {'Pixmap': 'AddonManager', 'MenuText': '&Addon manager',
'ToolTip': 'Manage FreeCAD workbenches and macros',
'Group': 'Tools'}

View File

@@ -0,0 +1,21 @@
SET(AddonManager_SRCS
Init.py
InitGui.py
AddonManager.py
AddonManagerGui.py
)
SOURCE_GROUP("" FILES ${AddonManager_SRCS})
ADD_CUSTOM_TARGET(AddonManager ALL
SOURCES ${AddonManager_SRCS}
)
fc_copy_sources(AddonManager "${CMAKE_BINARY_DIR}/Mod/AddonManager" ${AddonManager_SRCS})
INSTALL(
FILES
${AddonManager_SRCS}
DESTINATION
Mod/AddonManager
)

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# FreeCAD init script of the AddonManager module
# (c) 2001 Juergen Riegel
# License LGPL

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# AddonManager gui init module
# (c) 2001 Juergen Riegel
# License LGPL
from AddonManagerGui import CmdAddonMgr
FreeCADGui.addCommand('Std_AddonMgr', CmdAddonMgr())

View File

@@ -134,3 +134,8 @@ if(BUILD_TECHDRAW)
add_subdirectory(Measure)
add_subdirectory(TechDraw)
endif(BUILD_TECHDRAW)
if(BUILD_ADDONMGR)
add_subdirectory(AddonManager)
endif(BUILD_ADDONMGR)