38
from . import TranslationTexts
39
from PySide import QtCore, QtGui
42
from addonmanager_macro import Macro as AM_Macro
49
FreeCADGui.addLanguagePath(":/translations")
50
FreeCADGui.updateLocale()
52
iconprovider = QtGui.QFileIconProvider()
59
parent_dir = FreeCAD.getUserCachePath()
60
return os.path.join(parent_dir, "FreeCADStartThumbnails")
63
def createThumbnailDir():
64
path = getThumbnailDir()
65
if not os.path.exists(path):
70
def getSha1Hash(path, encode="utf-8"):
71
sha_hash = hashlib.sha1()
72
hashed = hashlib.sha1(path.encode(encode))
73
hex_digest = hashed.hexdigest().encode(encode)
74
sha_hash.update(hex_digest)
75
return sha_hash.hexdigest()
78
def getUniquePNG(filename):
79
parent_dir = getThumbnailDir()
80
sha1 = getSha1Hash(filename) + ".png"
81
return os.path.join(parent_dir, sha1)
84
def useCachedPNG(image, project):
85
if not os.path.exists(image):
87
if not os.path.exists(project):
90
stamp = os.path.getmtime
91
return stamp(image) > stamp(project)
94
def gethexcolor(color):
96
"returns a color hex value #000000"
98
r = str(hex(int(((color >> 24) & 0xFF))))[2:].zfill(2)
99
g = str(hex(int(((color >> 16) & 0xFF))))[2:].zfill(2)
100
b = str(hex(int(((color >> 8) & 0xFF))))[2:].zfill(2)
101
return "#" + r + g + b
104
def isOpenableByFreeCAD(filename):
106
"check if FreeCAD can handle this file type"
108
if os.path.isdir(filename):
110
if os.path.basename(filename)[0] == ".":
112
extensions = [key.lower() for key in FreeCAD.getImportType().keys()]
113
ext = os.path.splitext(filename)[1].lower()
117
if ext in extensions:
122
def getInfo(filename):
124
"returns available file information"
126
global iconbank, tempfolder
128
tformat = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetString(
132
def getLocalTime(timestamp):
133
"returns a local time from a timestamp"
134
return time.strftime(tformat, time.localtime(timestamp))
137
"returns a human-readable size"
138
if size > 1024 * 1024:
139
hsize = str(int(size / (1024 * 1024))) + "Mb"
141
hsize = str(int(size / 1024)) + "Kb"
143
hsize = str(int(size)) + "b"
146
def getFreeDesktopThumbnail(filename):
147
"if we have gnome libs available, try to find a system-generated thumbnail"
148
path = os.path.abspath(filename)
158
bytes(urllib.parse.quote("file://" + path, safe=":/"), "ascii")
160
thumb = os.path.join(os.path.expanduser("~"), ".thumbnails", "normal", fhash + ".png")
162
uri = gnomevfs.get_uri_from_local_path(path)
163
thumb = gnome.ui.thumbnail_path_for_uri(uri, "normal")
164
if thumb and os.path.exists(thumb):
168
if os.path.exists(filename):
170
if os.path.isdir(filename):
174
s = os.stat(filename)
175
size = getSize(s.st_size)
176
ctime = getLocalTime(s.st_ctime)
177
mtime = getLocalTime(s.st_mtime)
179
company = TranslationTexts.get("T_UNKNOWN")
180
lic = TranslationTexts.get("T_UNKNOWN")
183
path = os.path.abspath(filename)
186
if filename.lower().endswith(".fcstd"):
188
zfile = zipfile.ZipFile(filename)
190
print("Cannot read file: ", filename)
192
files = zfile.namelist()
194
if files[0] == "Document.xml":
196
doc = zfile.read(files[0]).decode("utf-8")
199
"Fail to load corrupted FCStd file: '{0}' with this error: {1}".format(
204
doc = doc.replace("\n", " ")
205
r = re.findall('Property name="CreatedBy.*?String value="(.*?)"/>', doc)
210
author = author.split("<")[0].strip()
211
r = re.findall('Property name="Company.*?String value="(.*?)"/>', doc)
214
r = re.findall('Property name="License.*?String value="(.*?)"/>', doc)
217
r = re.findall('Property name="Comment.*?String value="(.*?)"/>', doc)
220
if "thumbnails/Thumbnail.png" in files:
221
image_png = getUniquePNG(filename)
222
if filename in iconbank:
223
image = iconbank[filename]
224
elif useCachedPNG(image_png, filename):
226
iconbank[filename] = image
228
imagedata = zfile.read("thumbnails/Thumbnail.png")
230
thumb = open(image, "wb")
231
thumb.write(imagedata)
233
iconbank[filename] = image
235
elif filename.lower().endswith(".fcmacro"):
238
image = os.path.join(tempfolder, "fcmacro_icon.svg")
239
if not os.path.exists(image):
240
f = QtCore.QFile(":/icons/MacroEditor.svg")
242
iconbank[filename] = image
245
macro = AM_Macro(os.path.basename(filename))
246
macro.fill_details_from_file(filename)
247
author = macro.author
249
elif QtGui.QImageReader.imageFormat(filename):
252
iconbank[filename] = image
256
fdthumb = getFreeDesktopThumbnail(filename)
259
iconbank[filename] = fdthumb
263
i = QtCore.QFileInfo(filename)
264
t = iconprovider.type(i)
265
filename_png = getUniquePNG(filename)
270
elif os.path.exists(filename_png):
274
icon = iconprovider.icon(i)
275
if icon.availableSizes():
276
preferred = icon.actualSize(QtCore.QSize(128, 128))
277
px = icon.pixmap(preferred)
281
image = getDefaultIcon()
283
return [image, size, author, ctime, mtime, descr, company, lic, path]
290
"retrieves or creates a default file icon"
295
i = QtCore.QFileInfo(__file__)
296
icon = iconprovider.icon(i)
297
preferred = icon.actualSize(QtCore.QSize(128, 128))
298
px = icon.pixmap(preferred)
299
image = getUniquePNG("default_icon")
306
def build_new_file_card(template):
308
"""builds an html <li> element representing a new file
309
quick start button"""
313
TranslationTexts.get("T_TEMPLATE_EMPTYFILE_NAME"),
314
TranslationTexts.get("T_TEMPLATE_EMPTYFILE_DESC"),
317
TranslationTexts.get("T_TEMPLATE_OPENFILE_NAME"),
318
TranslationTexts.get("T_TEMPLATE_OPENFILE_DESC"),
321
TranslationTexts.get("T_TEMPLATE_PARAMETRICPART_NAME"),
322
TranslationTexts.get("T_TEMPLATE_PARAMETRICPART_DESC"),
325
TranslationTexts.get("T_TEMPLATE_ASSEMBLY_NAME"),
326
TranslationTexts.get("T_TEMPLATE_ASSEMBLY_DESC"),
330
TranslationTexts.get("T_TEMPLATE_2DDRAFT_NAME"),
331
TranslationTexts.get("T_TEMPLATE_2DDRAFT_DESC"),
334
TranslationTexts.get("T_TEMPLATE_ARCHITECTURE_NAME"),
335
TranslationTexts.get("T_TEMPLATE_ARCHITECTURE_DESC"),
339
if template not in templates:
342
image = "file:///" + os.path.join(
343
os.path.join(FreeCAD.getResourceDir(), "Mod", "Start", "StartPage"),
344
"images/new_" + template + ".png",
348
result += '<li class="quickstart-button-card">'
349
result += '<a href="LoadNew.py?template=' + urllib.parse.quote(template) + '">'
350
result += '<img src="' + image + '" alt="' + template + '">'
351
result += '<div class="caption">'
352
result += "<h3>" + templates[template][0] + "</h3>"
353
result += "<p>" + templates[template][1] + "</p>"
360
def buildCard(filename, method, arg=None):
362
"""builds an html <li> element representing a file.
363
method is a script + a keyword, for ex. url.py?key="""
366
if os.path.exists(filename) and isOpenableByFreeCAD(filename):
367
basename = os.path.basename(filename)
370
finfo = getInfo(filename)
372
image, size, author, ctime, mtime, descr, company, lic, path = finfo
373
infostring = TranslationTexts.get("T_CREATIONDATE") + ": " + ctime + "\n"
374
infostring += TranslationTexts.get("T_LASTMODIFIED") + ": " + mtime + "\n"
375
infostring += TranslationTexts.get("T_SIZE") + ": " + size + "\n"
376
infostring += TranslationTexts.get("T_AUTHOR") + ": " + author + "\n"
377
infostring += TranslationTexts.get("T_LICENSE") + ": " + lic + "\n"
378
infostring += TranslationTexts.get("T_FILEPATH") + ": " + path + "\n"
380
infostring += "\n\n" + descr
382
result += '<li class="file-card">'
384
'<a href="' + method + urllib.parse.quote(arg) + '" title="' + infostring + '">'
387
'<img src="file:///' + image.replace("\\", "/") + '" alt="' + basename + '">'
389
result += '<div class="caption">'
390
result += "<h4>" + basename + "</h4>"
391
result += "<p>" + author + "</p>"
392
result += "<p>" + size + "</p>"
401
"builds the HTML code of the start page"
403
global iconbank, tempfolder
409
if hasattr(Start, "iconbank"):
410
iconbank = Start.iconbank
411
if hasattr(Start, "tempfolder"):
412
tempfolder = Start.tempfolder
414
tempfolder = createThumbnailDir()
418
resources_dir = os.path.join(FreeCAD.getResourceDir(), "Mod", "Start", "StartPage")
419
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start")
420
template = p.GetString("Template", "")
422
html_filename = template
424
html_filename = os.path.join(resources_dir, "StartPage.html")
425
js_filename = os.path.join(resources_dir, "StartPage.js")
426
css_filename = p.GetString("CSSFile", os.path.join(resources_dir, "StartPage.css"))
427
with open(html_filename, "r") as f:
429
with open(js_filename, "r") as f:
431
with open(css_filename, "r") as f:
433
HTML = HTML.replace("JS", JS)
434
HTML = HTML.replace("DEFAULT_CSS", CSS)
437
FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start")
438
.GetString("CustomCSS", "")
444
HTML = HTML.replace("BCP47_LANGUAGE", QtCore.QLocale().bcp47Name())
448
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool(
449
"UseStyleSheet", False
451
qssfile = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/MainWindow").GetString(
456
user = os.path.join(FreeCAD.getUserAppDataDir(), "Gui", "Stylesheets")
457
system = os.path.join(FreeCAD.getResourceDir(), "Gui", "Stylesheets")
458
resources = ":/stylesheets"
461
if QtCore.QFile.exists(os.path.join(user, qssfile)):
462
path = os.path.join(user, qssfile)
463
elif QtCore.QFile.exists(os.path.join(system, qssfile)):
464
path = os.path.join(system, qssfile)
465
elif QtCore.QFile.exists(os.path.join(resources, qssfile)):
467
path = os.path.join(resources, qssfile)
473
f = QtCore.QFile(path)
474
if f.open(QtCore.QIODevice.ReadOnly | QtCore.QFile.Text):
475
ALTCSS = QtCore.QTextStream(f).readAll()
477
"<!--QSS-->", '<style type="text/css">' + ALTCSS + "</style>"
480
with codecs.open(path, encoding="utf-8") as f:
483
"<!--QSS-->", '<style type="text/css">' + ALTCSS + "</style>"
488
if not FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool(
489
"ShowFileThumbnailIcons", True
492
"display: block; /* thumb icons display */", "display: none; /* thumb icons display */"
494
HTML = HTML.replace("THUMBCARDSIZE", "75px")
496
thumb_icons_size = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetInt(
497
"FileThumbnailIconsSize", 128
499
HTML = HTML.replace("THUMBSIZE", str(thumb_icons_size) + "px")
500
HTML = HTML.replace("THUMBCARDSIZE", str(thumb_icons_size + 75) + "px")
504
if not FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool(
508
"display: block; /* footnote tips display */",
509
"display: none; /* footnote tips display */",
514
v = FreeCAD.Version()
516
TranslationTexts.get("T_VERSION")
524
+ TranslationTexts.get("T_BUILD")
528
HTML = HTML.replace("VERSIONSTRING", VERSIONSTRING)
532
texts = [t for t in TranslationTexts.get("index") if t.startswith("T_")]
534
HTML = HTML.replace(text, TranslationTexts.get(text))
538
if not "createimg" in iconbank:
539
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View")
540
c1 = gethexcolor(p.GetUnsigned("BackgroundColor2"))
541
c2 = gethexcolor(p.GetUnsigned("BackgroundColor3"))
542
gradient = QtGui.QLinearGradient(0, 0, 0, 128)
543
gradient.setColorAt(0.0, QtGui.QColor(c1))
544
gradient.setColorAt(1.0, QtGui.QColor(c2))
545
i = QtGui.QImage(128, 128, QtGui.QImage.Format_RGB16)
546
pa = QtGui.QPainter(i)
547
pa.fillRect(i.rect(), gradient)
549
createimg = getUniquePNG("createimg")
551
iconbank["createimg"] = createimg
555
SECTION_NEW_FILE = "<h2>" + TranslationTexts.get("T_NEWFILE") + "</h2>"
556
SECTION_NEW_FILE += "<ul>"
557
SECTION_NEW_FILE += build_new_file_card("parametric_part")
558
SECTION_NEW_FILE += build_new_file_card("assembly")
560
SECTION_NEW_FILE += build_new_file_card("2d_draft")
561
SECTION_NEW_FILE += build_new_file_card("architecture")
562
SECTION_NEW_FILE += build_new_file_card("empty_file")
563
SECTION_NEW_FILE += build_new_file_card("open_file")
564
SECTION_NEW_FILE += "</ul>"
565
HTML = HTML.replace("SECTION_NEW_FILE", SECTION_NEW_FILE)
569
rf = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/RecentFiles")
570
rfcount = rf.GetInt("RecentFiles", 0)
571
SECTION_RECENTFILES = "<h2>" + TranslationTexts.get("T_RECENTFILES") + "</h2>"
572
SECTION_RECENTFILES += "<ul>"
573
for i in range(rfcount):
574
filename = rf.GetString("MRU%d" % (i))
575
SECTION_RECENTFILES += buildCard(filename, method="LoadMRU.py?MRU=", arg=str(i))
576
SECTION_RECENTFILES += "</ul>"
577
HTML = HTML.replace("SECTION_RECENTFILES", SECTION_RECENTFILES)
581
SECTION_EXAMPLES = ""
582
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool(
585
SECTION_EXAMPLES = "<h2>" + TranslationTexts.get("T_EXAMPLES") + "</h2>"
586
SECTION_EXAMPLES += "<ul>"
587
examples_path = FreeCAD.getResourceDir() + "examples"
588
if os.path.exists(examples_path):
589
examples = os.listdir(examples_path)
590
for basename in examples:
591
filename = FreeCAD.getResourceDir() + "examples" + os.sep + basename
592
SECTION_EXAMPLES += buildCard(filename, method="LoadExample.py?filename=")
593
SECTION_EXAMPLES += "</ul>"
594
HTML = HTML.replace("SECTION_EXAMPLES", SECTION_EXAMPLES)
599
cfolders = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetString(
600
"ShowCustomFolder", ""
604
for cfolder in cfolders.split(";;"):
605
if not os.path.isdir(cfolder):
606
cfolder = os.path.dirname(cfolder)
607
if not os.path.exists(cfolder):
608
FreeCAD.Console.PrintWarning("Custom folder not found: %s" % cfolder)
610
SECTION_CUSTOM += "<h2>" + os.path.basename(os.path.normpath(cfolder)) + "</h2>"
611
SECTION_CUSTOM += "<ul>"
612
for basename in os.listdir(cfolder):
613
filename = os.path.join(cfolder, basename)
614
SECTION_CUSTOM += buildCard(
615
filename, method="LoadCustom.py?filename=" + str(dn) + "_"
617
SECTION_CUSTOM += "</ul>"
620
'id="customtip" class', 'id="customtip" style="display:none;" class'
623
HTML = HTML.replace("SECTION_CUSTOM", SECTION_CUSTOM)
629
"file:///" + os.path.join(resources_dir, "images/freecad.png").replace("\\", "/"),
632
"IMAGE_SRC_ICON_DOCUMENTS",
633
"file:///" + os.path.join(resources_dir, "images/icon_documents.png").replace("\\", "/"),
636
"IMAGE_SRC_ICON_HELP",
637
"file:///" + os.path.join(resources_dir, "images/icon_help.png").replace("\\", "/"),
640
"IMAGE_SRC_ICON_ACTIVITY",
641
"file:///" + os.path.join(resources_dir, "images/icon_activity.png").replace("\\", "/"),
644
"IMAGE_SRC_ICON_BLOG",
645
"file:///" + os.path.join(resources_dir, "images/icon_blog.png").replace("\\", "/"),
649
"file:///" + os.path.join(resources_dir, "images/userhub.png").replace("\\", "/"),
652
"IMAGE_SRC_POWERHUB",
653
"file:///" + os.path.join(resources_dir, "images/poweruserhub.png").replace("\\", "/"),
657
"file:///" + os.path.join(resources_dir, "images/developerhub.png").replace("\\", "/"),
661
"file:///" + os.path.join(resources_dir, "images/manual.png").replace("\\", "/"),
664
"IMAGE_SRC_SETTINGS",
665
"file:///" + os.path.join(resources_dir, "images/icon_settings.png").replace("\\", "/"),
668
"IMAGE_SRC_INSTALLED",
669
"file:///" + os.path.join(resources_dir, "images/installed.png").replace("\\", "/"),
675
UL_WORKBENCHES = '<ul class="workbenches">'
676
FreeCAD.getResourceDir()
677
for wb in sorted(FreeCADGui.listWorkbenches().keys()):
678
if wb.endswith("Workbench"):
683
if wn == "flamingoTools":
689
elif wn == "ArchTexture":
691
elif wn == "CadQuery":
692
wn = "cadquery_module"
693
elif wn == "DefeaturingWB":
697
elif wn == "ManipulatorWB":
699
elif wn == "PartOMagic":
707
elif wn == "ReverseEngineering":
708
wn = "Reverse_Engineering"
711
wblist.append(wn.lower())
716
FreeCAD.getResourceDir(), "Mod", wn, "Resources", "icons", wn + "Workbench.svg"
718
if not os.path.exists(img):
719
w = FreeCADGui.listWorkbenches()[wb]
720
if hasattr(w, "Icon") and w.Icon:
728
for s in re.findall("(?s){(.*?)};", xpm)[0].split("\n")[1:]
732
img = getUniquePNG(wb)
737
img = os.path.join(resources_dir, "images/freecad.png")
739
UL_WORKBENCHES += "<li>"
741
'<img src="file:///' + img.replace("\\", "/") + '" alt="' + wn + '"> '
744
'<a href="https://www.freecad.org/wiki/'
747
+ wn.replace("Reverse_Engineering", "ReverseEng")
750
UL_WORKBENCHES += "</li>"
751
UL_WORKBENCHES += "</ul>"
752
HTML = HTML.replace("UL_WORKBENCHES", UL_WORKBENCHES)
761
wblist.append("dxf-library")
767
wblist.append("reinforcement")
769
import CADExchangerIO
773
wblist.append("cadexchanger")
774
HTML = HTML.replace("var wblist = [];", "var wblist = " + str(wblist) + ";")
778
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start")
779
if p.GetString("BackgroundImage", ""):
781
gethexcolor(p.GetUnsigned("BackgroundColor1", 1331197183))
783
+ p.GetString("BackgroundImage", "")
787
BACKGROUND = gethexcolor(p.GetUnsigned("BackgroundColor1", 1331197183))
789
LINKCOLOR = gethexcolor(p.GetUnsigned("LinkColor", 65535))
790
BASECOLOR = gethexcolor(p.GetUnsigned("PageColor", 4294967295))
791
BOXCOLOR = gethexcolor(p.GetUnsigned("BoxColor", 3722305023))
792
TEXTCOLOR = gethexcolor(p.GetUnsigned("PageTextColor", 255))
793
BGTCOLOR = gethexcolor(p.GetUnsigned("BackgroundTextColor", 1600086015))
795
if QtGui.QColor(BASECOLOR).valueF() < 0.5:
797
FONTFAMILY = p.GetString("FontFamily", "Arial,Helvetica,sans")
799
FONTFAMILY = "Arial,Helvetica,sans"
800
FONTSIZE = p.GetInt("FontSize", 13)
801
HTML = HTML.replace("BASECOLOR", BASECOLOR)
802
HTML = HTML.replace("BOXCOLOR", BOXCOLOR)
803
HTML = HTML.replace("LINKCOLOR", LINKCOLOR)
804
HTML = HTML.replace("TEXTCOLOR", TEXTCOLOR)
805
HTML = HTML.replace("BGTCOLOR", BGTCOLOR)
806
HTML = HTML.replace("BACKGROUND", BACKGROUND)
807
HTML = HTML.replace("SHADOW", SHADOW)
808
HTML = HTML.replace("FONTFAMILY", FONTFAMILY)
809
HTML = HTML.replace("FONTSIZE", str(FONTSIZE) + "px")
813
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool(
814
"AllowDownload", False
816
HTML = HTML.replace("var allowDownloads = 0;", "var allowDownloads = 1;")
820
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("ShowForum", False):
821
HTML = HTML.replace("var showForum = 0;", "var showForum = 1;")
823
"display: none; /* forum display */", "display: block; /* forum display */"
828
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("ShowNotes", False):
830
"display: none; /* notes display */", "display: block; /* notes display */"
832
HTML = HTML.replace("width: 100%; /* thumbs display */", "width: 70%; /* thumbs display */")
836
Start.iconbank = iconbank
837
Start.tempfolder = tempfolder
844
"Allow to check if everything is Ok"
847
os.path.expanduser("~") + os.sep + "freecad-startpage.html",
855
def postStart(switch_wb=True):
857
"executes needed operations after loading a file"
859
param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start")
863
wb = param.GetString("AutoloadModule", "")
864
if "$LastModule" == wb:
865
wb = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/General").GetString(
870
if FreeCADGui.activeWorkbench() and (
871
FreeCADGui.activeWorkbench().name() == "StartWorkbench"
873
FreeCADGui.activateWorkbench(wb)
876
cl = param.GetBool("closeStart", False)
878
title = QtGui.QApplication.translate("Workbench", "Start page")
879
mw = FreeCADGui.getMainWindow()
881
mdi = mw.findChild(QtGui.QMdiArea)
883
for mdichild in mdi.children():
884
for subw in mdichild.findChildren(QtGui.QMdiSubWindow):
885
if subw.windowTitle() == title:
889
def checkPostOpenStartPage():
891
"on Start WB startup, check if we are loading a file and therefore need to close the StartPage"
895
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool(
896
"DoNotShowOnOpen", False
897
) and (not hasattr(Start, "CanOpenStartPage")):
898
if len(sys.argv) > 1:
900
Start.CanOpenStartPage = True