From 7556427bed199e2f50a6fc9c038146749616bb1b Mon Sep 17 00:00:00 2001 From: travisapple Date: Sat, 7 Nov 2020 09:56:12 -0800 Subject: [PATCH 01/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] [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(); From 6b5a99319a753876f588a2f75d4b9ff940462fe0 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 19:14:44 -0800 Subject: [PATCH 17/32] Only update the shape if it has changed. --- src/Mod/Path/PathScripts/PathToolBitEdit.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 0ee54747bb..c9cf238ebd 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -194,12 +194,14 @@ class ToolBitEditor(object): def updateShape(self): PathLog.track() - self.tool.BitShape = str(self.form.shapePath.text()) - self.setupTool(self.tool) - self.form.toolName.setText(self.tool.Label) + shapePath = str(self.form.shapePath.text()) + if self.tool.BitShape != shapePath: + self.tool.BitShape = shapePath + self.setupTool(self.tool) + self.form.toolName.setText(self.tool.Label) - for editor in self.bitEditor: - self.bitEditor[editor].updateSpinBox() + for editor in self.bitEditor: + self.bitEditor[editor].updateSpinBox() def updateTool(self): PathLog.track() From 7e2f088833e0bc0222f6a3a644fdd0c8df1b012f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 19:55:59 -0800 Subject: [PATCH 18/32] Hide document when loading a ToolBit. --- src/Mod/Path/PathScripts/PathToolBit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 9cd6f488de..4c8929b26f 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -252,7 +252,7 @@ class ToolBit(object): p = findShape(p) if not path and p != obj.BitShape: obj.BitShape = p - doc = FreeCAD.open(p) + doc = FreeCAD.openDocument(p, True) obj.ShapeName = doc.Name docOpened = True return (doc, docOpened) From 1a2249387e08ebe829118e7bc8849e50205e71bb Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 19:59:44 -0800 Subject: [PATCH 19/32] Make BitShape read/writeable, so the tool can be edited when shared to a different system --- src/Mod/Path/PathScripts/PathToolBit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 4c8929b26f..4c502c528a 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -200,7 +200,9 @@ class ToolBit(object): return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupAttribute] def onDocumentRestored(self, obj): - obj.setEditorMode('BitShape', 1) + # when files are shared it is essential to be able to change/set the shape file, + # otherwise the file is hard to use + # obj.setEditorMode('BitShape', 1) obj.setEditorMode('BitBody', 2) obj.setEditorMode('File', 1) obj.setEditorMode('Shape', 2) From be221dd25d7b540d91fa28374c73257d190947e1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 20:27:42 -0800 Subject: [PATCH 20/32] Rearranged BitTool shape update so dependent ops don't execute with invalid tool. --- src/Mod/Path/PathScripts/PathToolBit.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 4c502c528a..5ffe7503dd 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -288,21 +288,22 @@ class ToolBit(object): self._removeBitBody(obj) def _setupBitShape(self, obj, path=None): + PathLog.track(obj.Label) + activeDoc = FreeCAD.ActiveDocument (doc, docOpened) = self._loadBitBody(obj, path) obj.Label = doc.RootObjects[0].Label self._deleteBitSetup(obj) - obj.BitBody = obj.Document.copyObject(doc.RootObjects[0], True) + bitBody = obj.Document.copyObject(doc.RootObjects[0], True) if docOpened: FreeCAD.setActiveDocument(activeDoc.Name) FreeCAD.closeDocument(doc.Name) - if obj.BitBody.ViewObject: - obj.BitBody.ViewObject.Visibility = False - self._copyBitShape(obj) + if bitBody.ViewObject: + bitBody.ViewObject.Visibility = False - for sketch in [o for o in obj.BitBody.Group if o.TypeId == 'Sketcher::SketchObject']: + for sketch in [o for o in bitBody.Group if o.TypeId == 'Sketcher::SketchObject']: for constraint in [c for c in sketch.Constraints if c.Name != '']: typ = ParameterTypeConstraint.get(constraint.Type) PathLog.track(constraint, typ) @@ -318,6 +319,9 @@ class ToolBit(object): if constraint.Type == 'Angle': value = value * 180 / math.pi PathUtil.setProperty(obj, prop, value) + # has to happen last because it could trigger op.execute evaluations + obj.BitBody = bitBody + self._copyBitShape(obj) def getBitThumbnail(self, obj): if obj.BitShape: From b2dce8c76a3c52e5941bf726a3256b62b9c346e1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 22:56:42 -0800 Subject: [PATCH 21/32] Allowing QuantitySpinBox to be reused for different attribute; using properties to get values to work around build differences. --- src/Mod/Path/PathScripts/PathGui.py | 41 ++++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index d7fdd6be32..c9fee42062 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -35,13 +35,8 @@ __doc__ = "A collection of helper and utility functions for the Path GUI." def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) -LOGLEVEL = False - -if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) -else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) def updateInputField(obj, prop, widget, onBeforeChange=None): @@ -53,7 +48,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): If onBeforeChange is specified it is called before a new value is assigned to the property. Returns True if a new value was assigned, False otherwise (new value is the same as the current). ''' - value = FreeCAD.Units.Quantity(widget.text()).Value + value = widget.property('rawValue') attr = PathUtil.getProperty(obj, prop) attrValue = attr.Value if hasattr(attr, 'Value') else attr @@ -72,10 +67,10 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): isDiff = True break if noExpr: - widget.setReadOnly(False) + widget.setProperty('readonly', False) widget.setStyleSheet("color: black") else: - widget.setReadOnly(True) + widget.setProperty('readonly', True) widget.setStyleSheet("color: gray") widget.update() @@ -100,19 +95,26 @@ class QuantitySpinBox: ''' def __init__(self, widget, obj, prop, onBeforeChange=None): - self.obj = obj + PathLog.track(widget) self.widget = widget - self.prop = prop self.onBeforeChange = onBeforeChange + self.attachTo(obj, prop) - attr = PathUtil.getProperty(self.obj, self.prop) - if attr is not None: - if hasattr(attr, 'Value'): - widget.setProperty('unit', attr.getUserPreferred()[2]) - widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) - self.valid = True + def attachTo(self, obj, prop = None): + '''attachTo(obj, prop=None) ... use an existing editor for the given object and property''' + self.obj = obj + self.prop = prop + if obj and prop: + attr = PathUtil.getProperty(obj, prop) + if attr is not None: + if hasattr(attr, 'Value'): + self.widget.setProperty('unit', attr.getUserPreferred()[2]) + self.widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) + self.valid = True + else: + PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) + self.valid = False else: - PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) self.valid = False def expression(self): @@ -122,6 +124,7 @@ class QuantitySpinBox: return '' def setMinimum(self, quantity): + '''setMinimum(quantity) ... set the minimum''' if self.valid: value = quantity.Value if hasattr(quantity, 'Value') else quantity self.widget.setProperty('setMinimum', value) From 1f839e5ea11a62716142481f009aa12732aa728f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 23:01:55 -0800 Subject: [PATCH 22/32] Reuse existing QuantitySpinBox'es in order to avoid segfault on focus change. --- src/Mod/Path/PathScripts/PathToolBitEdit.py | 67 ++++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index c9cf238ebd..3488bcc95d 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -62,24 +62,60 @@ class ToolBitEditor(object): if self.loadbitbody: self.tool.Proxy.loadBitBody(self.tool) + # remove example widgets + layout = self.form.bitParams.layout() + for i in range(layout.rowCount() - 1, -1, -1): + layout.removeRow(i) + # used to track property widgets and editors + self.widgets = [] + self.setupTool(self.tool) self.setupAttributes(self.tool) def setupTool(self, tool): PathLog.track() + # Can't delete and add fields to the form because of dangling references in case of + # a focus change. see https://forum.freecadweb.org/viewtopic.php?f=10&t=52246#p458583 + # Instead we keep widgets once created and use them for new properties, and hide all + # which aren't being needed anymore. + + def labelText(name): + return re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', name)) + layout = self.form.bitParams.layout() - for i in range(layout.rowCount() - 1, -1, -1): - layout.removeRow(i) - editor = {} ui = FreeCADGui.UiLoader() + nr = 0 + + # for all properties either assign them to existing labels and editors + # or create additional ones for them if not enough have already been + # created. for name in tool.PropertiesList: if tool.getGroupOfProperty(name) == PathToolBit.PropertyGroupBit: - qsb = ui.createWidget('Gui::QuantitySpinBox') - editor[name] = PathGui.QuantitySpinBox(qsb, tool, name) - label = QtGui.QLabel(re.sub('([A-Z][a-z]+)', r' \1', - re.sub('([A-Z]+)', r' \1', name))) - layout.addRow(label, qsb) - self.bitEditor = editor + if nr < len(self.widgets): + PathLog.debug("re-use row: {} [{}]".format(nr, name)) + label, qsb, editor = self.widgets[nr] + label.setText(labelText(name)) + editor.attachTo(tool, name) + label.show() + qsb.show() + else: + qsb = ui.createWidget('Gui::QuantitySpinBox') + editor = PathGui.QuantitySpinBox(qsb, tool, name) + label = QtGui.QLabel(labelText(name)) + self.widgets.append((label, qsb, editor)) + PathLog.debug("create row: {} [{}]".format(nr, name)) + if nr >= layout.rowCount(): + layout.addRow(label, qsb) + nr = nr + 1 + + # hide all rows which aren't being used + for i in range(nr, len(self.widgets)): + label, qsb, editor = self.widgets[i] + label.hide() + qsb.hide() + editor.attachTo(None) + PathLog.debug(" hide row: {}".format(i)) + img = tool.Proxy.getBitThumbnail(tool) if img: self.form.image.setPixmap(QtGui.QPixmap(QtGui.QImage.fromData(img))) @@ -189,27 +225,28 @@ class ToolBitEditor(object): self.form.toolName.setText(self.tool.Label) self.form.shapePath.setText(self.tool.BitShape) - for editor in self.bitEditor: - self.bitEditor[editor].updateSpinBox() + for lbl, qsb, editor in self.widgets: + editor.updateSpinBox() def updateShape(self): PathLog.track() shapePath = str(self.form.shapePath.text()) + # Only need to go through this exercise if the shape actually changed. if self.tool.BitShape != shapePath: self.tool.BitShape = shapePath self.setupTool(self.tool) self.form.toolName.setText(self.tool.Label) - for editor in self.bitEditor: - self.bitEditor[editor].updateSpinBox() + for lbl, qsb, editor in self.widgets: + editor.updateSpinBox() def updateTool(self): PathLog.track() self.tool.Label = str(self.form.toolName.text()) self.tool.BitShape = str(self.form.shapePath.text()) - for editor in self.bitEditor: - self.bitEditor[editor].updateProperty() + for lbl, qsb, editor in self.widgets: + editor.updateProperty() # self.tool.Proxy._updateBitShape(self.tool) From 08f708d2bd2617a155e7e7866ab662ee04562de3 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 17 Dec 2020 23:11:02 -0800 Subject: [PATCH 23/32] Added provision for gcc peculiarities in unit tests --- src/Mod/Path/PathTests/TestPathCore.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathTests/TestPathCore.py b/src/Mod/Path/PathTests/TestPathCore.py index dae7c091e5..34d84d6477 100644 --- a/src/Mod/Path/PathTests/TestPathCore.py +++ b/src/Mod/Path/PathTests/TestPathCore.py @@ -152,7 +152,9 @@ G0 Z0.500000 table.addTools(t2) self.assertEqual(len(table.Tools), 2) - self.assertEqual(str(table.Tools), '{1: Tool 12.7mm Drill Bit, 2: Tool my other tool}' ) + # gcc7 build needs some special treatment (makes 1L out of a 1) ... + if str(table.Tools) != '{1L: Tool 12.7mm Drill Bit, 2L: Tool my other tool}': + self.assertEqual(str(table.Tools), '{1: Tool 12.7mm Drill Bit, 2: Tool my other tool}') def test50(self): """Test Path.Length calculation""" From 0385e56b695754df1a7c4d645a96ba1d8b146555 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 18 Dec 2020 11:46:31 +0100 Subject: [PATCH 24/32] Gui: [skip ci] fix crash when using a dialog instance twice for a task panel --- src/Gui/TaskView/TaskDialogPython.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Gui/TaskView/TaskDialogPython.cpp b/src/Gui/TaskView/TaskDialogPython.cpp index b353fcb0a1..5a9cb4b864 100644 --- a/src/Gui/TaskView/TaskDialogPython.cpp +++ b/src/Gui/TaskView/TaskDialogPython.cpp @@ -337,8 +337,20 @@ TaskDialogPython::~TaskDialogPython() std::vector< QPointer > guarded; guarded.insert(guarded.begin(), Content.begin(), Content.end()); Content.clear(); + Base::PyGILStateLocker lock; + + // The widgets stored in the 'form' attribute will be deleted. + // Thus, set this attribute to None to make sure that when using + // the same dialog instance for a task panel won't segfault. + if (this->dlg.hasAttr(std::string("form"))) { + this->dlg.setAttr(std::string("form"), Py::None()); + } this->dlg = Py::None(); + + // Assigning None to 'dlg' may destroy some of the stored widgets. + // By guarding them with QPointer their pointers will be set to null + // so that the destructor of the base class can reliably call 'delete'. Content.insert(Content.begin(), guarded.begin(), guarded.end()); } From 7d260787b9abfc7452112040cb370fe17134cd65 Mon Sep 17 00:00:00 2001 From: Aapo Date: Tue, 15 Dec 2020 22:56:51 +0200 Subject: [PATCH 25/32] [TD] Balloon, change ordering of the data properties to more logical. --- src/Mod/TechDraw/App/DrawViewBalloon.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.cpp b/src/Mod/TechDraw/App/DrawViewBalloon.cpp index 9f8a397c39..ed9f6ca837 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.cpp +++ b/src/Mod/TechDraw/App/DrawViewBalloon.cpp @@ -99,15 +99,15 @@ DrawViewBalloon::DrawViewBalloon(void) EndType.setEnums(ArrowPropEnum::ArrowTypeEnums); ADD_PROPERTY(EndType,(prefEnd())); + ADD_PROPERTY_TYPE(EndTypeScale,(1.0),"",(App::PropertyType)(App::Prop_None),"EndType shape scale"); + ShapeScale.setConstraints(&SymbolScaleRange); + BubbleShape.setEnums(balloonTypeEnums); ADD_PROPERTY(BubbleShape,(prefShape())); ADD_PROPERTY_TYPE(ShapeScale,(1.0),"",(App::PropertyType)(App::Prop_None),"Balloon shape scale"); ShapeScale.setConstraints(&SymbolScaleRange); - ADD_PROPERTY_TYPE(EndTypeScale,(1.0),"",(App::PropertyType)(App::Prop_None),"EndType shape scale"); - ShapeScale.setConstraints(&SymbolScaleRange); - ADD_PROPERTY_TYPE(TextWrapLen,(-1),"",(App::PropertyType)(App::Prop_None),"Text wrap length; -1 means no wrap"); ADD_PROPERTY_TYPE(KinkLength,(prefKinkLength()),"",(App::PropertyType)(App::Prop_None), From 294136e9bd2ce5695534190d141eebd3567a13e3 Mon Sep 17 00:00:00 2001 From: Aapo Date: Tue, 15 Dec 2020 23:32:11 +0200 Subject: [PATCH 26/32] [TD] Balloon, move property LineVisible to View tab and fix a refresh bug. --- src/Mod/TechDraw/App/DrawViewBalloon.cpp | 2 -- src/Mod/TechDraw/App/DrawViewBalloon.h | 1 - src/Mod/TechDraw/Gui/QGIViewBalloon.cpp | 2 +- src/Mod/TechDraw/Gui/ViewProviderBalloon.cpp | 4 +++- src/Mod/TechDraw/Gui/ViewProviderBalloon.h | 1 + 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.cpp b/src/Mod/TechDraw/App/DrawViewBalloon.cpp index ed9f6ca837..2f40d08e37 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.cpp +++ b/src/Mod/TechDraw/App/DrawViewBalloon.cpp @@ -113,8 +113,6 @@ DrawViewBalloon::DrawViewBalloon(void) ADD_PROPERTY_TYPE(KinkLength,(prefKinkLength()),"",(App::PropertyType)(App::Prop_None), "Distance from symbol to leader kink"); - ADD_PROPERTY_TYPE(LineVisible,(true),"",(App::PropertyType)(App::Prop_None),"Balloon line visible or hidden"); - SourceView.setScope(App::LinkScope::Global); Rotation.setStatus(App::Property::Hidden,true); Caption.setStatus(App::Property::Hidden,true); diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.h b/src/Mod/TechDraw/App/DrawViewBalloon.h index 0892aac32a..ac72ae8699 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.h +++ b/src/Mod/TechDraw/App/DrawViewBalloon.h @@ -59,7 +59,6 @@ public: App::PropertyDistance OriginY; App::PropertyFloat TextWrapLen; App::PropertyDistance KinkLength; - App::PropertyBool LineVisible; short mustExecute() const override; diff --git a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp index 1b7b062a0e..205cbfe093 100644 --- a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp @@ -766,7 +766,7 @@ void QGIViewBalloon::draw() balloonLines->setPath(dLinePath); // This overwrites the previously created QPainterPath with empty one, in case it should be hidden. Should be refactored. - if (!balloon->LineVisible.getValue()) { + if (!vp->LineVisible.getValue()) { arrow->hide(); balloonLines->setPath(QPainterPath()); } diff --git a/src/Mod/TechDraw/Gui/ViewProviderBalloon.cpp b/src/Mod/TechDraw/Gui/ViewProviderBalloon.cpp index f88dbb6f09..f79910b5e5 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderBalloon.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderBalloon.cpp @@ -75,6 +75,7 @@ ViewProviderBalloon::ViewProviderBalloon() double weight = lg->getWeight("Thin"); delete lg; //Coverity CID 174670 ADD_PROPERTY_TYPE(LineWidth,(weight),group,(App::PropertyType)(App::Prop_None),"Leader line width"); + ADD_PROPERTY_TYPE(LineVisible,(true),group,(App::PropertyType)(App::Prop_None),"Balloon line visible or hidden"); ADD_PROPERTY_TYPE(Color,(PreferencesGui::dimColor()), group,App::Prop_None,"Color of the balloon"); @@ -148,7 +149,8 @@ void ViewProviderBalloon::onChanged(const App::Property* p) if ((p == &Font) || (p == &Fontsize) || (p == &Color) || - (p == &LineWidth)) { + (p == &LineWidth) || + (p == &LineVisible)) { QGIView* qgiv = getQView(); if (qgiv) { qgiv->updateView(true); diff --git a/src/Mod/TechDraw/Gui/ViewProviderBalloon.h b/src/Mod/TechDraw/Gui/ViewProviderBalloon.h index fd9cb6ad27..2f88a50434 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderBalloon.h +++ b/src/Mod/TechDraw/Gui/ViewProviderBalloon.h @@ -48,6 +48,7 @@ public: App::PropertyFont Font; App::PropertyLength Fontsize; App::PropertyLength LineWidth; + App::PropertyBool LineVisible; App::PropertyColor Color; virtual void attach(App::DocumentObject *); From d81e377546be6fd0073bc39e68f575d05a5a1e7a Mon Sep 17 00:00:00 2001 From: Aapo Date: Tue, 15 Dec 2020 23:39:14 +0200 Subject: [PATCH 27/32] [TD] Balloon, fix EndTypeScale refresh bug. --- src/Mod/TechDraw/App/DrawViewBalloon.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.cpp b/src/Mod/TechDraw/App/DrawViewBalloon.cpp index 2f40d08e37..8331d0809d 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.cpp +++ b/src/Mod/TechDraw/App/DrawViewBalloon.cpp @@ -129,7 +129,8 @@ void DrawViewBalloon::onChanged(const App::Property* prop) if ( (prop == &EndType) || (prop == &BubbleShape) || (prop == &Text) || - (prop == &KinkLength) ) { + (prop == &KinkLength) || + (prop == &EndTypeScale) ) { requestPaint(); } } From 5f497ab37a2e13fd5ea144120f272f59ba6f0f47 Mon Sep 17 00:00:00 2001 From: Aapo Date: Wed, 16 Dec 2020 14:10:41 +0200 Subject: [PATCH 28/32] [TD] Balloon, fix Origin and scale refresh bugs, make Balloon scale change more reasonable per UI click. --- src/Mod/TechDraw/App/DrawViewBalloon.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.cpp b/src/Mod/TechDraw/App/DrawViewBalloon.cpp index 8331d0809d..2a19855a21 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.cpp +++ b/src/Mod/TechDraw/App/DrawViewBalloon.cpp @@ -65,7 +65,7 @@ using namespace TechDraw; App::PropertyFloatConstraint::Constraints DrawViewBalloon::SymbolScaleRange = { Precision::Confusion(), std::numeric_limits::max(), - (1.0) }; + (0.1) }; //=========================================================================== // DrawViewBalloon @@ -100,7 +100,7 @@ DrawViewBalloon::DrawViewBalloon(void) ADD_PROPERTY(EndType,(prefEnd())); ADD_PROPERTY_TYPE(EndTypeScale,(1.0),"",(App::PropertyType)(App::Prop_None),"EndType shape scale"); - ShapeScale.setConstraints(&SymbolScaleRange); + EndTypeScale.setConstraints(&SymbolScaleRange); BubbleShape.setEnums(balloonTypeEnums); ADD_PROPERTY(BubbleShape,(prefShape())); @@ -128,9 +128,12 @@ void DrawViewBalloon::onChanged(const App::Property* prop) if (!isRestoring()) { if ( (prop == &EndType) || (prop == &BubbleShape) || + (prop == &ShapeScale) || (prop == &Text) || (prop == &KinkLength) || - (prop == &EndTypeScale) ) { + (prop == &EndTypeScale) || + (prop == &OriginX) || + (prop == &OriginY) ) { requestPaint(); } } From a7506326c9af96db4c4a8a345d184077ea2e8ff6 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 18 Dec 2020 14:35:17 +0100 Subject: [PATCH 29/32] Gui: [skip ci] avoid adding a wrong file name to the recent files list --- src/Gui/Document.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index e6463eec6a..51e97c648a 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -1189,6 +1189,8 @@ bool Document::saveAs(void) escapedstr = Base::Tools::escapeEncodeFilename(escapedstr); Command::doCommand(Command::Doc,"App.getDocument(\"%s\").saveAs(u\"%s\")" , DocName, escapedstr.c_str()); + // App::Document::saveAs() may modify the passed file name + fi.setFile(QString::fromUtf8(d->_pcDocument->FileName.getValue())); setModified(false); getMainWindow()->appendRecentFile(fi.filePath()); } From d4bd0098c21a20a17c1a434d13a3234691b8db14 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 18 Dec 2020 16:28:35 +0100 Subject: [PATCH 30/32] PartDesign: [skip ci] fix layout of Drill point controls --- src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 60 +++++++++----------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index 7d0a694760..eef3d883f5 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 - + @@ -314,41 +314,37 @@ Only available for holes without thread - - - - - - 0 - 0 - - - - Angled - - - - - - - - 0 - 0 - - - - deg - - - 0.000000000000000 - - - - + + + + 0 + 0 + + + + Angled + + + + + + + 0 + 0 + + + + deg + + + 0.000000000000000 + + + From a2b1e6226aae5d0b4900c92365be5ca3a62d5564 Mon Sep 17 00:00:00 2001 From: M G Berberich Date: Fri, 18 Dec 2020 15:19:31 +0100 Subject: [PATCH 31/32] cleanup of hole dialog * replaced widget with radiobuttons by button Group, to improve alignemt of DrillPointAngle with grid-layout * made Thread Pitch/Angle/Cutoffs widgets wider --- src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 972 +++++++++---------- 1 file changed, 461 insertions(+), 511 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index eef3d883f5..e0c34186be 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui @@ -6,8 +6,8 @@ 0 0 - 373 - 560 + 441 + 710 @@ -20,21 +20,65 @@ Task Hole Parameters - - - - false - - - mm - - - 0.000000000000000 + + + + Clearance - - + + + + + 0 + 0 + + + + <b>Drill point</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Depth + + + + + + + false + + + Angle + + + + + + + false + + + Cutoff outer + + + + + + + Tapered + + + + + 0 @@ -43,48 +87,36 @@ - 140 + 16777215 16777215 - - - Dimension - - - - - Through all - - - - - - - 0 - 0 - - - - - 110 - 16777215 - - + + - Hole diameter + - - mm - - - 0.000000000000000 + + Right hand + + directionButtonGroup + - + + + + Left hand + + + directionButtonGroup + + + + @@ -119,94 +151,91 @@ Only available for holes without thread - - - - false + + + + + 0 + 0 + - Angle + Profile - - - - Qt::Horizontal + + + + + 0 + 0 + - - QSizePolicy::Fixed + + Direction - + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + 0 + 0 + + + - 13 - 20 + 140 + 16777215 - - - - - - <b>Hole cut</b> - + + + Dimension + + + + + Through all + + - - - - Thread direction + + + + + 0 + 0 + - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Right hand - - - - - - - Left hand - - - - - - - - - - false + + + 16777215 + 16777215 + - mm + deg 0.000000000000000 - + + + + Countersink angle + + + + @@ -231,281 +260,7 @@ Only available for holes without thread - - - - false - - - Model actual thread - - - - - - - - 0 - 0 - - - - Depth - - - - - - - - 0 - 0 - - - - Profile - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Ending of the hole if 'Depth' is set to 'Dimension' - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Flat - - - - - - - - 0 - 0 - - - - Angled - - - - - - - - - - - 0 - 0 - - - - deg - - - 0.000000000000000 - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - - - 0 - 0 - - - - Class - - - - - - - Tapered - - - - - - - false - - - Cutoff inner - - - - - - - - 0 - 0 - - - - - 140 - 16777215 - - - - Tolerance class for threaded holes according to hole profile - - - - - - - - 0 - 0 - - - - mm - - - - - - - - 0 - 0 - - - - Size - - - - - - - - 0 - 0 - - - - <b>Drill point</b> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Type - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - false - - - Cutoff outer - - - - - - - - 0 - 0 - - - - Type - - - - - - - false - - - deg - - - 0.000000000000000 - - - - - - - <b>Threading and size</b> - - - - + @@ -524,102 +279,13 @@ Only available for holes without thread - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - deg - - - 0.000000000000000 - - - - - - - <b>Misc</b> - - - - - - - - 0 - 0 - - - - Direction - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - 0 - 0 - - - - Diameter - - - - - - - Depth - - - - - - - Countersink angle - - - - - - - Diameter - - - - - - - Clearance - - - - - + + false - - mm - - - 0.000000000000000 + + Pitch @@ -633,7 +299,174 @@ Only available for holes without thread - + + + + + 0 + 0 + + + + Class + + + + + + + false + + + Cutoff inner + + + + + + + Taper angle for the hole +90 degree: straight hole +under 90: smaller hole radius at the bottom +over 90: larger hole radius at the bottom + + + deg + + + 0.000000000000000 + + + + + + + false + + + mm + + + 0.000000000000000 + + + + + + + <b>Hole cut</b> + + + + + + + false + + + deg + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + deg + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + Flat + + + drillPointButtonGroup + + + + + + + <b>Threading and size</b> + + + + + + + + 0 + 0 + + + + Diameter + + + + + + + + 0 + 0 + + + + Type + + + + + + + <b>Misc</b> + + + + + + + + 0 + 0 + + + + mm + + + + + + + false + + + Model actual thread + + + + @@ -649,39 +482,23 @@ Only available for holes without thread - - - - - 120 - 16777215 - - - - Taper angle for the hole -90 degree: straight hole -under 90: smaller hole radius at the bottom -over 90: larger hole radius at the bottom - - - deg - - - 0.000000000000000 - - - - - - - false + + + + + 0 + 0 + - Pitch + Angled + + drillPointButtonGroup + - + @@ -709,7 +526,136 @@ over 90: larger hole radius at the bottom - + + + + + 0 + 0 + + + + Depth + + + + + + + Diameter + + + + + + + + 0 + 0 + + + + + 110 + 16777215 + + + + Hole diameter + + + mm + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + + 140 + 16777215 + + + + Tolerance class for threaded holes according to hole profile + + + + + + + Type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + false + + + mm + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + Size + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 13 + 20 + + + + + + + + false + + + mm + + + 0.000000000000000 + + + + Reverses the hole direction @@ -784,4 +730,8 @@ over 90: larger hole radius at the bottom + + + + From 2db10d82368a1b7423dff83deaef2ba28003b6d4 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 18 Dec 2020 17:04:27 +0100 Subject: [PATCH 32/32] PartDesign: [skip ci] fix order of controls so that by pressing TAB always the next control gets focus --- src/Mod/PartDesign/Gui/TaskHoleParameters.ui | 732 +++++++++---------- 1 file changed, 366 insertions(+), 366 deletions(-) diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index e0c34186be..c723acb05c 100644 --- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui @@ -20,15 +20,15 @@ Task Hole Parameters - - + + - Clearance + <b>Threading and size</b> - - + + 0 @@ -36,44 +36,7 @@ - <b>Drill point</b> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Depth - - - - - - - false - - - Angle - - - - - - - false - - - Cutoff outer - - - - - - - Tapered + Profile @@ -93,6 +56,141 @@ + + + + Whether the hole gets a thread + + + Threaded + + + + + + + false + + + Model actual thread + + + + + + + false + + + Pitch + + + + + + + false + + + mm + + + 0.000000000000000 + + + + + + + <b>Hole cut</b> + + + + + + + false + + + Angle + + + + + + + false + + + deg + + + 0.000000000000000 + + + + + + + false + + + Cutoff inner + + + + + + + false + + + mm + + + 0.000000000000000 + + + + + + + false + + + Cutoff outer + + + + + + + false + + + mm + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + Direction + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + @@ -116,6 +214,42 @@ + + + + + 0 + 0 + + + + Size + + + + + + + + 0 + 0 + + + + + 140 + 16777215 + + + + + + + + Clearance + + + @@ -151,8 +285,8 @@ Only available for holes without thread - - + + 0 @@ -160,12 +294,72 @@ Only available for holes without thread - Profile + Class - - + + + + + 0 + 0 + + + + + 140 + 16777215 + + + + Tolerance class for threaded holes according to hole profile + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 13 + 20 + + + + + + + + + 0 + 0 + + + + + 110 + 16777215 + + + + Hole diameter + + + mm + + + 0.000000000000000 + + + + + 0 @@ -173,10 +367,20 @@ Only available for holes without thread - Direction + Diameter - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + 0 + 0 + + + + Depth @@ -206,57 +410,29 @@ Only available for holes without thread - - + + 0 0 - - - 16777215 - 16777215 - - - - deg - - - 0.000000000000000 - - - - - - - Countersink angle - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - mm - - 0.000000000000000 + + + + + + + 0 + 0 + - - 0.100000000000000 + + Type @@ -279,225 +455,13 @@ Only available for holes without thread - - - - false - - - Pitch - - - - - - - Whether the hole gets a thread - - - Threaded - - - - - - - - 0 - 0 - - - - Class - - - - - - - false - - - Cutoff inner - - - - - - - Taper angle for the hole -90 degree: straight hole -under 90: smaller hole radius at the bottom -over 90: larger hole radius at the bottom - - - deg - - - 0.000000000000000 - - - - - - - false - - - mm - - - 0.000000000000000 - - - - - - - <b>Hole cut</b> - - - - - - - false - - - deg - - - 0.000000000000000 - - - - - - - - 0 - 0 - - - - deg - - - 0.000000000000000 - - - - - - - - 0 - 0 - - - - Flat - - - drillPointButtonGroup - - - - - - - <b>Threading and size</b> - - - - - - - - 0 - 0 - - + + Diameter - - - - - 0 - 0 - - - - Type - - - - - - - <b>Misc</b> - - - - - - - - 0 - 0 - - - - mm - - - - - - - false - - - Model actual thread - - - - - - - - 0 - 0 - - - - - 140 - 16777215 - - - - - - - - - 0 - 0 - - - - Angled - - - drillPointButtonGroup - - - @@ -526,28 +490,15 @@ over 90: larger hole radius at the bottom - - - - - 0 - 0 - - + + Depth - - - - Diameter - - - - - + + 0 @@ -556,37 +507,63 @@ over 90: larger hole radius at the bottom - 110 + 16777215 16777215 - - Hole diameter - mm 0.000000000000000 + + 0.100000000000000 + - - + + + + Countersink angle + + + + + - + 0 0 - 140 + 16777215 16777215 - - Tolerance class for threaded holes according to hole profile + + deg + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + <b>Drill point</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -600,55 +577,78 @@ over 90: larger hole radius at the bottom - - - - false + + + + + 0 + 0 + + + + Flat + + + drillPointButtonGroup + + + + + + + + 0 + 0 + + + + Angled + + + drillPointButtonGroup + + + + + + + + 0 + 0 + - mm + deg 0.000000000000000 - - - - - 0 - 0 - - + + - Size + <b>Misc</b> - - - - Qt::Horizontal + + + + Tapered - - QSizePolicy::Fixed - - - - 13 - 20 - - - + - - - - false + + + + Taper angle for the hole +90 degree: straight hole +under 90: smaller hole radius at the bottom +over 90: larger hole radius at the bottom - mm + deg 0.000000000000000