From 7556427bed199e2f50a6fc9c038146749616bb1b Mon Sep 17 00:00:00 2001 From: travisapple Date: Sat, 7 Nov 2020 09:56:12 -0800 Subject: [PATCH 01/16] New WebGL Exporter This is a complete rewrite of this file. --- src/Mod/Arch/importWebGL.py | 965 ++++++++++++++++++++++++++++-------- 1 file changed, 757 insertions(+), 208 deletions(-) diff --git a/src/Mod/Arch/importWebGL.py b/src/Mod/Arch/importWebGL.py index 09e70e57aa..798a786f9e 100644 --- a/src/Mod/Arch/importWebGL.py +++ b/src/Mod/Arch/importWebGL.py @@ -1,5 +1,5 @@ #*************************************************************************** -#* Copyright (c) 2013 Yorik van Havre * +#* Copyright (c) 2020 Travis Apple * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * @@ -18,233 +18,782 @@ #* USA * #* * #*************************************************************************** +# +# REFS: +# https://github.com/mrdoob/three.js/blob/master/examples/webgl_interactive_buffergeometry.html +# https://threejs.org/examples/#webgl_buffergeometry_lines +# https://forum.freecadweb.org/viewtopic.php?t=51245 +# https://forum.freecadweb.org/viewtopic.php?t=29487 +# https://threejs.org/examples/#webgl_raycast_sprite +# +# Params for export() +# 'colors' is of the form: {'Body': [1,0,0], 'Body001': [1,1,0], 'Body002': [1,0,1] } +# 'camera' is of the form: "PerspectiveCamera {\n viewportMapping ADJUST_CAMERA\n position 30.242626 -51.772324 85.63475\n orientation -0.4146691 0.088459305 -0.90566254 4.7065201\nnearDistance 53.126431\n farDistance 123.09125\n aspectRatio 1\n focalDistance 104.53851\n heightAngle 0.78539819\n\n}" +# The 'camera' string for the active document may be generated from: import OfflineRenderingUtils; OfflineRenderingUtils.getCamera(FreeCAD.ActiveDocument.FileName); +# +# Development reload oneliner: +# def re(): from importlib import reload;import importWebGL;reload(importWebGL);o=FreeCAD.getDocument("curve");importWebGL.export([o.getObject("Body")],u"C:/Users/Travis/Desktop/test.htm"); -"""FreeCAD webgl exporter +"""FreeCAD WebGL Exporter""" -options: importWebGL.wireframeStyle = "faceloop" (can also be "multimaterial" or None) -importWebGL.template = a complete html file, where $CameraData is a placeholder for the -FreeCAD camera, and $ObjectsData a placeholder for the FreeCAD objects. -importWebGL.linewidth = an integer, specifying the width of lines in "faceloop" mode""" - -import FreeCAD,Draft,Part,DraftGeomUtils +import FreeCAD,Mesh,Draft,Part,DraftGeomUtils,Arch,OfflineRenderingUtils,json,six if FreeCAD.GuiUp: import FreeCADGui from DraftTools import translate else: FreeCADGui = None - # \cond - def translate(ctxt,txt,utf8_decode=True): - return txt - # \endcond + def translate(ctxt, txt): return txt -## @package importWebGL -# \ingroup ARCH -# \brief WebGL file format exporter -# -# This module provides tools to export HTML files containing the -# exported objects in WebGL format and a simple three.js-based viewer. +if open.__module__ in ['__builtin__','io']: pythonopen = open -tab = " " # the tab size -wireframeStyle = "faceloop" # this can be "faceloop", "multimaterial", or None -cameraPosition = None # set this to a tuple to change, for ex. (0,0,0) -linewidth = 1 -template = """ - - - FreeCAD model - +disableCompression = False # Compress object data before sending to JS +base = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!#$%&()*+-:;/=>?@[]^_,.{|}~`' # safe str chars for js in all cases +baseFloat = ',.-0123456789' - + + +""" - var camera, controls, scene, renderer; - - window.onload = function() { - - var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight; - var VIEW_ANGLE = 35, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 200000; - - renderer = new THREE.WebGLRenderer(); - renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT ); - document.body.appendChild( renderer.domElement ); - - scene = new THREE.Scene(); - - camera = new THREE.PerspectiveCamera( - VIEW_ANGLE, // Field of view - ASPECT, // Aspect ratio - NEAR, // Near plane - FAR // Far plane - ); - $CameraData // placeholder for the FreeCAD camera - - controls = new THREE.TrackballControls( camera ); - controls.rotateSpeed = 1.0; - controls.zoomSpeed = 1.2; - controls.panSpeed = 0.8; - controls.noZoom = false; - controls.noPan = false; - controls.staticMoving = true; - controls.dynamicDampingFactor = 0.3; - controls.keys = [ 65, 83, 68 ]; - - $ObjectsData // placeholder for the FreeCAD objects - - var light = new THREE.PointLight( 0xFFFF00 ); - light.position.set( -10000, -10000, 10000 ); - scene.add( light ); - - renderer.render( scene, camera ); - - animate(); - }; - - function animate(){ - requestAnimationFrame( animate ); - render(); - }; - - function render(){ - controls.update(); - renderer.render( scene, camera ); - }; - - - -""" - - -if open.__module__ in ['__builtin__','io']: - pythonopen = open +def export( exportList, filename, colors = None, camera = None ): + """Exports objects to an html file""" -def export(exportList,filename,colors=None,camera=None): - "exports the given objects to an .html file" - - html = getHTML(exportList,colors,camera) - outfile = pythonopen(filename,"w") - outfile.write(html) - outfile.close() - FreeCAD.Console.PrintMessage(translate("Arch", "Successfully written", utf8_decode=True) + ' ' + filename + "\n") + global html, disableCompression, base, baseFloat -def getHTML(objectsList,colors=None,camera=None): - "returns the complete HTML code of a viewer for the given objects" + data = { 'camera':{}, 'file':{}, 'objects':[] } - # get objects data - objectsData = '' - for obj in objectsList: - colordata = None - if colors: - if obj.Name in colors: - colordata = colors[obj.Name] - objectsData += getObjectData(obj,color=colordata) - t = template.replace("$CameraData",getCameraData(camera)) - t = t.replace("$ObjectsData",objectsData) - return t + if not FreeCADGui and not camera: + camera = OfflineRenderingUtils.getCamera(FreeCAD.ActiveDocument.FileName) -def getCameraData(camera=None): - "returns the position and direction of the camera as three.js snippet" - - result = "" if camera: - global cameraPosition - if isinstance(camera,str): - import OfflineRenderingUtils - camnode = OfflineRenderingUtils.getCoinCamera(camera) - cameraPosition = camnode.position.getValue().getValue() - elif hasattr(camera,"position"): - cameraPosition = camera.position.getValue().getValue() - if cameraPosition: - result += "camera.position.set("+str(cameraPosition[0])+","+str(cameraPosition[1])+","+str(cameraPosition[2])+");\n" - elif FreeCADGui: - # getting camera position - pos = FreeCADGui.ActiveDocument.ActiveView.viewPosition().Base - result += "camera.position.set( " - result += str(pos.x) + ", " - result += str(pos.y) + ", " - result += str(pos.z) + " );\n" + # REF: https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/Arch/OfflineRenderingUtils.py + camnode = OfflineRenderingUtils.getCoinCamera(camera) + cameraPosition = camnode.position.getValue().getValue() + data['camera']['type'] = 'Orthographic' + if 'PerspectiveCamera' in camera: data['camera']['type'] = 'Perspective' + data['camera']['focalDistance'] = camnode.focalDistance.getValue() + data['camera']['position_x'] = cameraPosition[0] + data['camera']['position_y'] = cameraPosition[1] + data['camera']['position_z'] = cameraPosition[2] else: - result += "camera.position.set(0,0,1000);\n" - result += tab+"camera.lookAt( scene.position );\n"+tab - # print(result) - return result + v = FreeCADGui.ActiveDocument.ActiveView + data['camera']['type'] = v.getCameraType() + data['camera']['focalDistance'] = v.getCameraNode().focalDistance.getValue() + data['camera']['position_x'] = v.viewPosition().Base.x + data['camera']['position_y'] = v.viewPosition().Base.y + data['camera']['position_z'] = v.viewPosition().Base.z -def getObjectData(obj,wireframeMode=wireframeStyle,color=None): - """returns the geometry data of an object as three.js snippet. - wireframeMode can be multimaterial, faceloop, or None""" + # Take the objects out of groups + objectslist = Draft.get_group_contents(exportList, walls=True, addgroups=False) + objectslist = Arch.pruneIncluded(objectslist) - result = "" - wires = [] - - if hasattr(obj,'Shape'): - fcmesh = obj.Shape.tessellate(0.1) - result = "var geom = new THREE.Geometry();\n" - # adding vertices data - for i in range(len(fcmesh[0])): - v = fcmesh[0][i] - result += tab+"var v"+str(i)+" = new THREE.Vector3("+str(v.x)+","+str(v.y)+","+str(v.z)+");\n" - result += tab+"console.log(geom.vertices)\n" - for i in range(len(fcmesh[0])): - result += tab+"geom.vertices.push(v"+str(i)+");\n" - # adding facets data - for f in fcmesh[1]: - result += tab+"geom.faces.push( new THREE.Face3"+str(f).replace("L","")+" );\n" - for f in obj.Shape.Faces: - for w in f.Wires: - wo = Part.Wire(Part.__sortEdges__(w.Edges)) - wires.append(wo.discretize(QuasiDeflection=0.1)) - - elif obj.isDerivedFrom("Mesh::Feature"): - mesh = obj.Mesh - result = "var geom = new THREE.Geometry();\n" - # adding vertices data - for p in mesh.Points: - v = p.Vector - i = p.Index - result += tab+"var v"+str(i)+" = new THREE.Vector3("+str(v.x)+","+str(v.y)+","+str(v.z)+");\n" - result += tab+"console.log(geom.vertices)\n" - for p in mesh.Points: - result += tab+"geom.vertices.push(v"+str(p.Index)+");\n" - # adding facets data + for obj in objectslist: + + # Pull all obj data before we dig down the links + label = obj.Label + + color = '#cccccc'; + opacity = 1.0 + if FreeCADGui: + color = Draft.getrgb(obj.ViewObject.ShapeColor, testbw = False) + opacity = int((100 - obj.ViewObject.Transparency)/5) / 20 # 0>>1 with step of 0.05 + elif colors: + if label in colors: + color = Draft.getrgb(colors[label], testbw = False) + + validObject = False + if obj.isDerivedFrom('Mesh::Feature'): + mesh = obj.Mesh + validObject = True + if obj.isDerivedFrom('Part::Feature'): + objShape = obj.Shape + validObject = True + if obj.isDerivedFrom('App::Link'): + linkPlacement = obj.LinkPlacement + while True: # drill down to get to the actual obj + if obj.isDerivedFrom("App::Link"): + if obj.ViewObject.OverrideMaterial: color = Draft.getrgb(obj.ViewObject.ShapeMaterial.DiffuseColor, testbw = False) + obj = obj.LinkedObject + if hasattr(obj, "__len__"): + FreeCAD.Console.PrintMessage(label + ": Sub-Links are Unsupported.\n") + break + elif obj.isDerivedFrom('Part::Feature'): + objShape = obj.Shape.copy(False) + objShape.Placement = linkPlacement + validObject = True + break + elif obj.isDerivedFrom("Mesh::Feature"): + mesh = obj.Mesh.copy() + mesh.Placement = linkPlacement + validObject = True + break + + if not validObject: continue + + objdata = { 'name': label, 'color': color, 'opacity': opacity, 'verts':'', 'facets':'', 'wires':[], 'faceColors':[], 'facesToFacets':[], 'floats':[] } + + if obj.isDerivedFrom('Part::Feature'): + + deviation = 0.5 + if FreeCADGui: + deviation = obj.ViewObject.Deviation + + # obj.ViewObject.DiffuseColor is length=1 when all faces are the same color, length=len(faces) for when they're not + if len(obj.ViewObject.DiffuseColor) == len(objShape.Faces): + for fc in obj.ViewObject.DiffuseColor: + objdata['faceColors'].append( Draft.getrgb(fc, testbw = False) ) + + # get verts and facets for ENTIRE object + shapeData = objShape.tessellate( deviation ) + mesh = Mesh.Mesh(shapeData) + + if len(objShape.Faces) > 1: + # Map each Facet created by tessellate() to a Face so that it can be colored correctly using faceColors + # This is done by matching the results of a tessellate() on EACH FACE to the overall tessellate stored in shapeData + # if there is any error in matching these two then we display the whole object as one face and forgo the face colors + for f in objShape.Faces: + faceData = f.tessellate( deviation ) + found = True + for fv in range( len(faceData[0]) ): # face verts. List of type Vector() + found = False + for sv in range( len(shapeData[0]) ): #shape verts + if faceData[0][fv] == shapeData[0][sv]: # do not use isEqual() here + faceData[0][fv] = sv # replace with the index of shapeData[0] + found = True + break + if not found: break + if not found: + FreeCAD.Console.PrintMessage("Facet to Face Mismach.\n") + objdata['facesToFacets'] = [] + break + + # map each of the face facets to the shape facets and make a list of shape facet indices that belong to this face + facetList = [] + for ff in faceData[1]: # face facets + found = False + for sf in range( len(shapeData[1]) ): #shape facets + if faceData[0][ff[0]] in shapeData[1][sf] and faceData[0][ff[1]] in shapeData[1][sf] and faceData[0][ff[2]] in shapeData[1][sf]: + facetList.append(sf) + found = True + break + if not found: break + if not found: + FreeCAD.Console.PrintMessage("Facet List Mismach.\n") + objdata['facesToFacets'] = [] + break + + objdata['facesToFacets'].append( baseEncode(facetList) ) + + wires = [] # Add wires + for f in objShape.Faces: + for w in f.Wires: + wo = Part.Wire(Part.__sortEdges__(w.Edges)) + wire = [] + for v in wo.discretize(QuasiDeflection = 0.005): + wire.append( '{:.5f}'.format(v.x) ) # use strings to avoid 0.00001 written as 1e-05 + wire.append( '{:.5f}'.format(v.y) ) + wire.append( '{:.5f}'.format(v.z) ) + wires.append( wire ) + + if not disableCompression: + for w in range( len(wires) ): + for wv in range( len(wires[w]) ): + found = False + for f in range( len(objdata['floats']) ): + if objdata['floats'][f] == wires[w][wv]: + wires[w][wv] = f + found = True + break + if not found: + objdata['floats'].append( wires[w][wv] ) + wires[w][wv] = len(objdata['floats'])-1 + wires[w] = baseEncode(wires[w]) + objdata['wires'] = wires + + vIndex = {} + verts = [] + for p in range( len(mesh.Points) ): + vIndex[ mesh.Points[p].Index ] = p + verts.append( '{:.5f}'.format(mesh.Points[p].Vector.x) ) + verts.append( '{:.5f}'.format(mesh.Points[p].Vector.y) ) + verts.append( '{:.5f}'.format(mesh.Points[p].Vector.z) ) + + # create floats list to compress verts and wires being written into the JS + if not disableCompression: + for v in range( len(verts) ): + found = False + for f in range( len(objdata['floats']) ): + if objdata['floats'][f] == verts[v]: + verts[v] = f + found = True + break + if not found: + objdata['floats'].append( verts[v] ) + verts[v] = len(objdata['floats'])-1 + objdata['verts'] = baseEncode(verts) + + facets = [] for f in mesh.Facets: - pointIndices = tuple([ int(i) for i in f.PointIndices ]) - result += tab+"geom.faces.push( new THREE.Face3"+str(pointIndices).replace("L","")+" );\n" - - if result: - # adding a base material - if color: - rgb = Draft.getrgb(color,testbw=False) - elif FreeCADGui: - col = obj.ViewObject.ShapeColor - rgb = Draft.getrgb(col,testbw=False) - else: - rgb = "#888888" # test color - result += tab+"var basematerial = new THREE.MeshBasicMaterial( { color: 0x"+str(rgb)[1:]+" } );\n" - #result += tab+"var basematerial = new THREE.MeshLambertMaterial( { color: 0x"+str(rgb)[1:]+" } );\n" + for i in f.PointIndices: + facets.append( vIndex[i] ) + objdata['facets'] = baseEncode(facets) - if wireframeMode == "faceloop": - # adding the mesh to the scene with a wireframe copy - result += tab+"var mesh = new THREE.Mesh( geom, basematerial );\n" - result += tab+"scene.add( mesh );\n" - result += tab+"var linematerial = new THREE.LineBasicMaterial({linewidth: %d, color: 0x000000,});\n" % linewidth - for w in wires: - result += tab+"var wire = new THREE.Geometry();\n" - for p in w: - result += tab+"wire.vertices.push(new THREE.Vector3(" - result += str(p.x)+", "+str(p.y)+", "+str(p.z)+"));\n" - result += tab+"var line = new THREE.Line(wire, linematerial);\n" - result += tab+"scene.add(line);\n" - - elif wireframeMode == "multimaterial": - # adding a wireframe material - result += tab+"var wireframe = new THREE.MeshBasicMaterial( { color: " - result += "0x000000, wireframe: true, transparent: true } );\n" - result += tab+"var material = [ basematerial, wireframe ];\n" - result += tab+"var mesh = new THREE.SceneUtils.createMultiMaterialObject( geom, material );\n" - result += tab+"scene.add( mesh );\n"+tab - - else: - # adding the mesh to the scene with simple material - result += tab+"var mesh = new THREE.Mesh( geom, basematerial );\n" - result += tab+"scene.add( mesh );\n"+tab + # compress floats + if not disableCompression: + # use ratio of 7x base13 to 4x base90 because 13^7 ~ 90^4 + fullstr = json.dumps(objdata['floats'], separators=(',', ':')) + fullstr = fullstr.replace('[', '').replace(']', '').replace('"', '') + floatStr = '' + baseFloatCt = len(baseFloat) + baseCt = len(base) + for fs in range( 0, len(fullstr), 7 ): # chunks of 7 chars, skip the first one + str7 = fullstr[fs:(fs+7)] + quotient = 0 + for s in range( len(str7) ): + quotient += baseFloat.find(str7[s]) * pow(baseFloatCt, (6-s)) + for v in range(4): + floatStr += base[ quotient % baseCt ] + quotient = int(quotient / baseCt) + objdata['floats'] = floatStr - return result + data['objects'].append( objdata ) + + html = html.replace('$pagetitle',FreeCAD.ActiveDocument.Label) + version = FreeCAD.Version() + html = html.replace('$version',version[0] + '.' + version[1] + '.' + version[2]) + + # Remove data compression in JS + if disableCompression: html = html.replace('$disableCompression','true') + else: html = html.replace('$disableCompression','false') + + html = html.replace('$base', base) + html = html.replace('$float', baseFloat) + html = html.replace('$data', json.dumps(data, separators=(',', ':')) ) # Shape Data + + if six.PY2: + outfile = pythonopen(filename, "wb") + else: + outfile = pythonopen(filename, "w") + outfile.write( html ) + outfile.close() + FreeCAD.Console.PrintMessage( translate("Arch", "Successfully written") + ' ' + filename + "\n" ) + +def baseEncode( arr ): + """Compresses an array of ints into a base90 string""" + + global disableCompression, base + if disableCompression: return arr + if len(arr) == 0: return '' + + longest = 0 + output = [] + baseCt = len(base) + for v in range( len(arr) ): + buffer = '' + quotient = arr[v] + while True: + buffer += base[ quotient % baseCt ] + quotient = int(quotient / baseCt) + if quotient == 0: break + output.append( buffer ) + if len(buffer) > longest: longest = len(buffer) + output = [('{:>'+str(longest)+'}').format(x) for x in output] # pad each element + return str(longest) + ('').join(output) From 92643200df9f689b6c91b2153463b9279b22f31a Mon Sep 17 00:00:00 2001 From: travisapple Date: Sat, 7 Nov 2020 13:42:16 -0800 Subject: [PATCH 02/16] Update importWebGL.py --- src/Mod/Arch/importWebGL.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Arch/importWebGL.py b/src/Mod/Arch/importWebGL.py index 798a786f9e..4c29483dfe 100644 --- a/src/Mod/Arch/importWebGL.py +++ b/src/Mod/Arch/importWebGL.py @@ -1,4 +1,5 @@ #*************************************************************************** +#* Copyright (c) 2013 Yorik van Havre * #* Copyright (c) 2020 Travis Apple * #* * #* This program is free software; you can redistribute it and/or modify * From b66b873c6ca3eb66c4e5758218063decbcb30d09 Mon Sep 17 00:00:00 2001 From: travisapple Date: Sun, 8 Nov 2020 10:25:30 -0800 Subject: [PATCH 03/16] Update importWebGL.py --- src/Mod/Arch/importWebGL.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Mod/Arch/importWebGL.py b/src/Mod/Arch/importWebGL.py index 4c29483dfe..4992d14e7b 100644 --- a/src/Mod/Arch/importWebGL.py +++ b/src/Mod/Arch/importWebGL.py @@ -48,6 +48,13 @@ else: if open.__module__ in ['__builtin__','io']: pythonopen = open +## @package importWebGL +# \ingroup ARCH +# \brief FreeCAD WebGL Exporter +# +# This module provides tools to export HTML files containing the +# exported objects in WebGL format and a simple three.js-based viewer. + disableCompression = False # Compress object data before sending to JS base = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!#$%&()*+-:;/=>?@[]^_,.{|}~`' # safe str chars for js in all cases baseFloat = ',.-0123456789' From f69031297c949cd4ed96d4bdb08462825bfe35c5 Mon Sep 17 00:00:00 2001 From: travisapple Date: Sun, 8 Nov 2020 10:29:15 -0800 Subject: [PATCH 04/16] Update importWebGL.py --- src/Mod/Arch/importWebGL.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Arch/importWebGL.py b/src/Mod/Arch/importWebGL.py index 4992d14e7b..7eed983803 100644 --- a/src/Mod/Arch/importWebGL.py +++ b/src/Mod/Arch/importWebGL.py @@ -33,7 +33,7 @@ # The 'camera' string for the active document may be generated from: import OfflineRenderingUtils; OfflineRenderingUtils.getCamera(FreeCAD.ActiveDocument.FileName); # # Development reload oneliner: -# def re(): from importlib import reload;import importWebGL;reload(importWebGL);o=FreeCAD.getDocument("curve");importWebGL.export([o.getObject("Body")],u"C:/Users/Travis/Desktop/test.htm"); +# def re(): from importlib import reload;import importWebGL;reload(importWebGL);o=FreeCAD.getDocument("YourDocName");importWebGL.export([o.getObject("YourBodyName")],u"C:/path/to/your/file.htm"); """FreeCAD WebGL Exporter""" From 5a11a7a5fd7e3a08dc28c35e2eb593894f67d56b Mon Sep 17 00:00:00 2001 From: travisapple Date: Tue, 17 Nov 2020 12:24:11 -0800 Subject: [PATCH 05/16] Update importWebGL.py --- src/Mod/Arch/importWebGL.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Arch/importWebGL.py b/src/Mod/Arch/importWebGL.py index 7eed983803..c07a123fc9 100644 --- a/src/Mod/Arch/importWebGL.py +++ b/src/Mod/Arch/importWebGL.py @@ -591,7 +591,7 @@ def export( exportList, filename, colors = None, camera = None ): # Take the objects out of groups objectslist = Draft.get_group_contents(exportList, walls=True, addgroups=False) - objectslist = Arch.pruneIncluded(objectslist) + # objectslist = Arch.pruneIncluded(objectslist) for obj in objectslist: From 51a30c7e742a87d56229be6b8fdf66686e1b202b Mon Sep 17 00:00:00 2001 From: travisapple Date: Mon, 7 Dec 2020 12:57:53 -0800 Subject: [PATCH 06/16] Update importWebGL.py --- src/Mod/Arch/importWebGL.py | 973 ++++++++++++++++++------------------ 1 file changed, 488 insertions(+), 485 deletions(-) diff --git a/src/Mod/Arch/importWebGL.py b/src/Mod/Arch/importWebGL.py index c07a123fc9..046d78d11d 100644 --- a/src/Mod/Arch/importWebGL.py +++ b/src/Mod/Arch/importWebGL.py @@ -59,512 +59,513 @@ disableCompression = False # Compress object data before sending to JS base = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!#$%&()*+-:;/=>?@[]^_,.{|}~`' # safe str chars for js in all cases baseFloat = ',.-0123456789' -html = """ - - - $pagetitle - - - - - - - - - -""" + data = false; // free up some ram + + + + """ def export( exportList, filename, colors = None, camera = None ): """Exports objects to an html file""" - global html, disableCompression, base, baseFloat + global disableCompression, base, baseFloat data = { 'camera':{}, 'file':{}, 'objects':[] } @@ -764,6 +765,8 @@ def export( exportList, filename, colors = None, camera = None ): data['objects'].append( objdata ) + html = getHTMLTemplate() + html = html.replace('$pagetitle',FreeCAD.ActiveDocument.Label) version = FreeCAD.Version() html = html.replace('$version',version[0] + '.' + version[1] + '.' + version[2]) From ea5d2efabed40d20105f2de7747989d8c4df740f Mon Sep 17 00:00:00 2001 From: Mitch Roote Date: Tue, 15 Dec 2020 11:10:40 -0500 Subject: [PATCH 07/16] Update fcinfo script for py3 compatibility --- src/Tools/fcinfo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tools/fcinfo b/src/Tools/fcinfo index 0b71f0b76f..9fb2834768 100755 --- a/src/Tools/fcinfo +++ b/src/Tools/fcinfo @@ -174,7 +174,7 @@ class FreeCADFileHandler(xml.sax.ContentHandler): # Print all the contents of the document properties items = self.contents.items() - items.sort() + items = sorted(items) for key,value in items: key = self.clean(key) value = self.clean(value) @@ -186,7 +186,7 @@ class FreeCADFileHandler(xml.sax.ContentHandler): if (tag == "Document") and (self.short != 2): items = self.contents.items() - items.sort() + items = sorted(items) for key,value in items: key = self.clean(key) if "00000000::" in key: From 5b4c61e175bd2f24258939745878d8a9312166f6 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 16 Dec 2020 10:34:24 +0100 Subject: [PATCH 08/16] Path: [skip ci] fix TooltablePy::getTools to avoid possible double destruction of a Tool instance --- src/Mod/Path/App/TooltablePyImp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/App/TooltablePyImp.cpp b/src/Mod/Path/App/TooltablePyImp.cpp index a48223a516..b20fb539c3 100644 --- a/src/Mod/Path/App/TooltablePyImp.cpp +++ b/src/Mod/Path/App/TooltablePyImp.cpp @@ -105,7 +105,7 @@ Py::Dict TooltablePy::getTools(void) const { Py::Dict dict; for(std::map::iterator i = getTooltablePtr()->Tools.begin(); i != getTooltablePtr()->Tools.end(); ++i) { - PyObject *tool = new Path::ToolPy(i->second); + PyObject *tool = new Path::ToolPy(new Tool(*i->second)); dict.setItem(Py::Long(i->first), Py::asObject(tool)); } return dict; From 40a2e52bc0d9716d24ac9c63e52e9d97839e63b1 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Wed, 16 Dec 2020 14:20:02 +0100 Subject: [PATCH 09/16] Sketcher: Fix crash on applying angle constraint on arc ======================================================= The GeoId passed was Constraint::GeoUndef (-2000). Fixes: https://forum.freecadweb.org/viewtopic.php?f=10&t=51716&p=458202#p458160 --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 970341f1ea..34dacb6ca6 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -5987,7 +5987,7 @@ void CmdSketcherConstrainAngle::activated(int iMsg) std::swap(PosId1,PosId2); } - if(isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) { + if(isBsplinePole(Obj, GeoId1) || (GeoId2 != Constraint::GeoUndef && isBsplinePole(Obj, GeoId2))) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select an edge that is not a B-spline weight")); return; From b7f9cbaf8b0911ef7d2006ba8f73f4e0510c0e00 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Wed, 16 Dec 2020 15:21:21 +0100 Subject: [PATCH 10/16] Sketcher: Fix equality constraint command ========================================= For select constraint then click elements mode. Fixes: https://forum.freecadweb.org/viewtopic.php?f=10&t=51716&p=458207#p457974 --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 34dacb6ca6..acf6ec759b 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -6538,14 +6538,14 @@ void CmdSketcherConstrainEqual::applyConstraint(std::vector &selSeq, const Part::Geometry *geo1 = Obj->getGeometry(GeoId1); const Part::Geometry *geo2 = Obj->getGeometry(GeoId2); - if ( (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) || - (geo1->getTypeId() == Part::GeomHyperbola::getClassTypeId() && geo2->getTypeId() != Part::GeomHyperbola::getClassTypeId()) || - (geo1->getTypeId() == Part::GeomParabola::getClassTypeId() && geo2->getTypeId() != Part::GeomParabola::getClassTypeId()) || - (isBsplinePole(geo1) && !isBsplinePole(geo1)) || + if ( (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) || + (geo1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() && geo2->getTypeId() != Part::GeomArcOfHyperbola::getClassTypeId()) || + (geo1->getTypeId() == Part::GeomArcOfParabola::getClassTypeId() && geo2->getTypeId() != Part::GeomArcOfParabola::getClassTypeId()) || + (isBsplinePole(geo1) && !isBsplinePole(geo2)) || ( (geo1->getTypeId() == Part::GeomCircle::getClassTypeId() || geo1->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) && - (geo2->getTypeId() != Part::GeomCircle::getClassTypeId() || geo2->getTypeId() != Part::GeomArcOfCircle::getClassTypeId())) || + !(geo2->getTypeId() == Part::GeomCircle::getClassTypeId() || geo2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId())) || ( (geo1->getTypeId() == Part::GeomEllipse::getClassTypeId() || geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) && - (geo2->getTypeId() != Part::GeomEllipse::getClassTypeId() || geo2->getTypeId() != Part::GeomArcOfEllipse::getClassTypeId())) ){ + !(geo2->getTypeId() == Part::GeomEllipse::getClassTypeId() || geo2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId())) ){ QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select two or more edges of similar type")); From ccd8551f2babd06bb958c7558d665d0f2f517b9a Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Wed, 16 Dec 2020 19:30:48 +0100 Subject: [PATCH 11/16] Sketch: Fix exception on redraw =============================== Fixes: https://forum.freecadweb.org/viewtopic.php?p=458293#p458293 Rationale: In order to fix B-Spline pole dragging, the order was inverted. This fixed the B-Spline pole dragging issue, but introduced a draw before solve approach that is not consistent with the rest of the Sketcher. In my parallel development I had already identified this inconsistency, switched the order, and provided a new mechanism to fix the issue with the B-Spline pole dragging. This will be merged as part of another PR. In the meantime, this PR restores the intended behaviour, and let us identify if the particular reported exception also happens in other situations. --- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index b62fd15bc6..9f378b3247 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -6106,9 +6106,9 @@ bool ViewProviderSketch::setEdit(int ModNum) // is loaded into the solver, which ensures that any prospective draw using temporal // geometry (draw with first parameter true) has the right ViewProvider geometry extensions // set - This fixes Weight constraint dragging on a just opened sketch. - draw(false,true); getSketchObject()->solve(false); UpdateSolverInformation(); + draw(false,true); connectUndoDocument = getDocument() ->signalUndoDocument.connect(boost::bind(&ViewProviderSketch::slotUndoDocument, this, bp::_1)); From b7bd7d229b1f960afe2bec95837176b66471e56f Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 17 Dec 2020 16:27:36 +0100 Subject: [PATCH 12/16] Gui: [skip ci] add option to use software OpenGL --- src/Gui/Application.cpp | 9 +++++++++ src/Gui/DlgSettings3DView.ui | 18 ++++++++++++++++++ src/Gui/DlgSettings3DViewImp.cpp | 2 ++ 3 files changed, 29 insertions(+) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 0f24076cb5..ea4c6a76d9 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -1923,6 +1923,15 @@ void Application::runApplication(void) QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif + // Use software rendering for OpenGL +#if QT_VERSION >= 0x050400 + ParameterGrp::handle hOpenGL = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/OpenGL"); + bool useSoftwareOpenGL = hOpenGL->GetBool("UseSoftwareOpenGL", false); + if (useSoftwareOpenGL) { + QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); + } +#endif // QT_VERSION >= 0x050400 + // A new QApplication Base::Console().Log("Init: Creating Gui::Application and QApplication\n"); diff --git a/src/Gui/DlgSettings3DView.ui b/src/Gui/DlgSettings3DView.ui index 65cc2d9fe6..9df215caba 100644 --- a/src/Gui/DlgSettings3DView.ui +++ b/src/Gui/DlgSettings3DView.ui @@ -108,6 +108,24 @@ will be shown at the lower left corner in opened files Rendering + + + + This option is useful for troubleshooting graphics card and driver problems. + +Changing this option requires a restart of the application. + + + Use software OpenGL + + + UseSoftwareOpenGL + + + OpenGL + + + diff --git a/src/Gui/DlgSettings3DViewImp.cpp b/src/Gui/DlgSettings3DViewImp.cpp index 134aae1336..b1fe1d71aa 100644 --- a/src/Gui/DlgSettings3DViewImp.cpp +++ b/src/Gui/DlgSettings3DViewImp.cpp @@ -93,6 +93,7 @@ void DlgSettings3DViewImp::saveSettings() ui->CheckBox_WbByTab->onSave(); ui->CheckBox_ShowFPS->onSave(); ui->spinPickRadius->onSave(); + ui->CheckBox_use_SW_OpenGL->onSave(); ui->CheckBox_useVBO->onSave(); ui->FloatSpinBox_EyeDistance->onSave(); ui->checkBoxBacklight->onSave(); @@ -109,6 +110,7 @@ void DlgSettings3DViewImp::loadSettings() ui->CheckBox_WbByTab->onRestore(); ui->CheckBox_ShowFPS->onRestore(); ui->spinPickRadius->onRestore(); + ui->CheckBox_use_SW_OpenGL->onRestore(); ui->CheckBox_useVBO->onRestore(); ui->FloatSpinBox_EyeDistance->onRestore(); ui->checkBoxBacklight->onRestore(); From f5be329c3f1e9f36de6dd3bb0269b15599e4802c Mon Sep 17 00:00:00 2001 From: M G Berberich Date: Thu, 17 Dec 2020 15:15:24 +0100 Subject: [PATCH 13/16] make the hole-cut-type dropdown-box wider This drop-down box contains long names and did not use the space of the dialog. --- src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index 33def9abea..123eb607ab 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui @@ -509,7 +509,7 @@ Only available for holes without thread - + @@ -519,7 +519,7 @@ Only available for holes without thread - 140 + 16777215 16777215 From 0341137e4f3dd9a842d628ecee7ce133aa139bc3 Mon Sep 17 00:00:00 2001 From: Przemo Firszt Date: Tue, 15 Dec 2020 16:20:18 +0000 Subject: [PATCH 14/16] Switch off mac builds - temporary travis fix Signed-off-by: Przemo Firszt --- .travis.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index e58c8fee13..d59f4398dd 100755 --- a/.travis.yml +++ b/.travis.yml @@ -102,19 +102,19 @@ jobs: - CMAKE_ARGS="-DCMAKE_CXX_COMPILER=/usr/bin/c++ -DCMAKE_C_COMPILER=/usr/bin/cc" - CACHE_NAME=JOB3 - - os: osx - osx_image: xcode11.6 - language: cpp - cache: - - ccache: true - - directories: - - $HOME/.ccache - - $HOME/Library/Caches/Homebrew - - /usr/local/Homebrew - env: - - CMAKE_OPTS="-DBUILD_QT5=ON -DBUILD_ENABLE_CXX_STD='C++17' -DUSE_PYTHON3=1 -DCMAKE_CXX_FLAGS='-Wno-deprecated-declarations' -DBUILD_FEM_NETGEN=1 -DBUILD_FEM=1 -DBUILD_TECHDRAW=0 -DCMAKE_PREFIX_PATH='/usr/local/opt/qt/lib/cmake;/usr/local/opt/nglib/Contents/Resources' -DBUILD_FEM_NETGEN:BOOL=ON -DFREECAD_USE_EXTERNAL_KDL=ON -DCMAKE_BUILD_TYPE=Release" - - PATH=/usr/local/bin:$PATH - - CACHE_NAME=OSX1 + # - os: osx + # osx_image: xcode11.6 + # language: cpp + # cache: + # - ccache: true + # - directories: + # - $HOME/.ccache + # - $HOME/Library/Caches/Homebrew + # - /usr/local/Homebrew + # env: + # - CMAKE_OPTS="-DBUILD_QT5=ON -DBUILD_ENABLE_CXX_STD='C++17' -DUSE_PYTHON3=1 -DCMAKE_CXX_FLAGS='-Wno-deprecated-declarations' -DBUILD_FEM_NETGEN=1 -DBUILD_FEM=1 -DBUILD_TECHDRAW=0 -DCMAKE_PREFIX_PATH='/usr/local/opt/qt/lib/cmake;/usr/local/opt/nglib/Contents/Resources' -DBUILD_FEM_NETGEN:BOOL=ON -DFREECAD_USE_EXTERNAL_KDL=ON -DCMAKE_BUILD_TYPE=Release" + # - PATH=/usr/local/bin:$PATH + # - CACHE_NAME=OSX1 - os: windows language: cpp From b62aeaf6b7c96e37a58d31058757aa1a25039b58 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 17 Dec 2020 17:10:42 +0100 Subject: [PATCH 15/16] PartDesign: [skip ci] make the spin box for the angle of the drill point wider --- src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index 123eb607ab..7d0a694760 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui @@ -267,7 +267,7 @@ Only available for holes without thread - + @@ -277,7 +277,7 @@ Only available for holes without thread - 140 + 16777215 16777215 From 2077709329f095ea22a9b9c508b816e40886ab95 Mon Sep 17 00:00:00 2001 From: Aapo Date: Wed, 16 Dec 2020 00:54:05 +0200 Subject: [PATCH 16/16] [TD] Make Dimension Tolerances respect the Prefs formatSpec when creating a Dimension. --- src/Mod/TechDraw/App/DrawViewDimension.cpp | 10 +++++++--- src/Mod/TechDraw/App/DrawViewDimension.h | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index 8849cedead..cea5225be9 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -106,8 +106,8 @@ DrawViewDimension::DrawViewDimension(void) References3D.setScope(App::LinkScope::Global); ADD_PROPERTY_TYPE(FormatSpec,(getDefaultFormatSpec()) , "Format", App::Prop_Output,"Dimension Format"); - ADD_PROPERTY_TYPE(FormatSpecOverTolerance,("%+g") , "Format", App::Prop_Output,"Dimension Overtolerance Format"); - ADD_PROPERTY_TYPE(FormatSpecUnderTolerance,("%+g") , "Format", App::Prop_Output,"Dimension Undertolerance Format"); + ADD_PROPERTY_TYPE(FormatSpecOverTolerance,(getDefaultFormatSpec(true)) , "Format", App::Prop_Output,"Dimension Overtolerance Format"); + ADD_PROPERTY_TYPE(FormatSpecUnderTolerance,(getDefaultFormatSpec(true)) , "Format", App::Prop_Output,"Dimension Undertolerance Format"); ADD_PROPERTY_TYPE(Arbitrary,(false) ,"Format", App::Prop_Output,"Value overridden by user"); ADD_PROPERTY_TYPE(ArbitraryTolerances,(false) ,"Format", App::Prop_Output,"Tolerance values overridden by user"); @@ -1300,7 +1300,7 @@ std::string DrawViewDimension::getPrefix() const return result; } -std::string DrawViewDimension::getDefaultFormatSpec() const +std::string DrawViewDimension::getDefaultFormatSpec(bool isToleranceFormat) const { Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Dimensions"); @@ -1333,6 +1333,10 @@ std::string DrawViewDimension::getDefaultFormatSpec() const } + if (isToleranceFormat) { + formatSpec.replace(QString::fromUtf8("%"), QString::fromUtf8("%+")); + } + return Base::Tools::toStdString(formatSpec); } diff --git a/src/Mod/TechDraw/App/DrawViewDimension.h b/src/Mod/TechDraw/App/DrawViewDimension.h index 865c2601a4..20cad696d2 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.h +++ b/src/Mod/TechDraw/App/DrawViewDimension.h @@ -171,7 +171,7 @@ protected: virtual void onChanged(const App::Property* prop) override; virtual void onDocumentRestored() override; std::string getPrefix() const; - std::string getDefaultFormatSpec() const; + std::string getDefaultFormatSpec(bool isToleranceFormat = false) const; virtual pointPair getPointsOneEdge(); virtual pointPair getPointsTwoEdges(); virtual pointPair getPointsTwoVerts();