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 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(); diff --git a/src/Mod/Arch/importWebGL.py b/src/Mod/Arch/importWebGL.py index 3bc1f75c94..f8d2e27cea 100644 --- a/src/Mod/Arch/importWebGL.py +++ b/src/Mod/Arch/importWebGL.py @@ -1,5 +1,6 @@ #*************************************************************************** #* 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 +19,792 @@ #* 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("YourDocName");importWebGL.export([o.getObject("YourBodyName")],u"C:/path/to/your/file.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 + +if open.__module__ in ['__builtin__','io']: pythonopen = open ## @package importWebGL # \ingroup ARCH -# \brief WebGL file format exporter +# \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. -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 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") - -def getHTML(objectsList,colors=None,camera=None): - "returns the complete HTML code of a viewer for the given 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 - -def getCameraData(camera=None): - "returns the position and direction of the camera as three.js snippet" - - result = "" +def export( exportList, filename, colors = None, camera = None ): + """Exports objects to an html file""" + + global disableCompression, base, baseFloat + + data = { 'camera':{}, 'file':{}, 'objects':[] } + + if not FreeCADGui and not camera: + camera = OfflineRenderingUtils.getCamera(FreeCAD.ActiveDocument.FileName) + 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 - -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""" - - 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 + 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 + + # Take the objects out of groups + objectslist = Draft.get_group_contents(exportList, walls=True, addgroups=False) + # objectslist = Arch.pruneIncluded(objectslist) + + 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" + for i in f.PointIndices: + facets.append( vIndex[i] ) + objdata['facets'] = baseEncode(facets) + + # 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 + + 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]) + + # 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" ) - 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" - - 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 - - return result +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) \ No newline at end of file diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui index 33def9abea..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 @@ -509,7 +509,7 @@ Only available for holes without thread - + @@ -519,7 +519,7 @@ Only available for holes without thread - 140 + 16777215 16777215 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; diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 970341f1ea..acf6ec759b 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; @@ -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")); 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)); 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(); 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: