diff --git a/.travis.yml b/.travis.yml
index 2074c5cf0d..2ea5676d4c 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -186,7 +186,7 @@ before_install:
libmetis-dev \
libspnav-dev
# Runtime deps
- sudo apt-get install -y --no-install-recommends freecad-daily-python3 python-ply python3-ply
+ sudo apt-get install -y --no-install-recommends freecad-daily-python3 python-pivy python3-pivy python-ply python3-ply
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
diff --git a/src/App/Link.cpp b/src/App/Link.cpp
index a07c967c7c..22869ad949 100644
--- a/src/App/Link.cpp
+++ b/src/App/Link.cpp
@@ -207,22 +207,24 @@ App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) {
const char *method = getLinkExecuteValue();
if(!method || !method[0])
method = "appLinkExecute";
- Py::Object attr = proxyValue.getAttr(method);
- if(attr.ptr() && attr.isCallable()) {
- Py::Tuple args(4);
- args.setItem(0, Py::asObject(linked->getPyObject()));
- args.setItem(1, Py::asObject(container->getPyObject()));
- if(!_getElementCountValue()) {
- Py::Callable(attr).apply(args);
- } else {
- const auto &elements = _getElementListValue();
- for(int i=0; i<_getElementCountValue(); ++i) {
- args.setItem(2, Py::Int(i));
- if(i < (int)elements.size())
- args.setItem(3, Py::asObject(elements[i]->getPyObject()));
- else
- args.setItem(3, Py::Object());
+ if(proxyValue.hasAttr(method)) {
+ Py::Object attr = proxyValue.getAttr(method);
+ if(attr.ptr() && attr.isCallable()) {
+ Py::Tuple args(4);
+ args.setItem(0, Py::asObject(linked->getPyObject()));
+ args.setItem(1, Py::asObject(container->getPyObject()));
+ if(!_getElementCountValue()) {
Py::Callable(attr).apply(args);
+ } else {
+ const auto &elements = _getElementListValue();
+ for(int i=0; i<_getElementCountValue(); ++i) {
+ args.setItem(2, Py::Int(i));
+ if(i < (int)elements.size())
+ args.setItem(3, Py::asObject(elements[i]->getPyObject()));
+ else
+ args.setItem(3, Py::Object());
+ Py::Callable(attr).apply(args);
+ }
}
}
}
diff --git a/src/Gui/DlgSettings3DView.ui b/src/Gui/DlgSettings3DView.ui
index a4bd3e0b5f..5ac9ed0186 100644
--- a/src/Gui/DlgSettings3DView.ui
+++ b/src/Gui/DlgSettings3DView.ui
@@ -40,6 +40,25 @@ lower right corner within opened files
+ -
+
+
+ If checked, application will remember which workbench is active for each tab of the viewport
+
+
+ Remember active workbench by tab
+
+
+ false
+
+
+ SaveWBbyTab
+
+
+ View
+
+
+
-
diff --git a/src/Gui/DlgSettings3DViewImp.cpp b/src/Gui/DlgSettings3DViewImp.cpp
index 7b79f8adae..7c4872014c 100644
--- a/src/Gui/DlgSettings3DViewImp.cpp
+++ b/src/Gui/DlgSettings3DViewImp.cpp
@@ -102,6 +102,7 @@ void DlgSettings3DViewImp::saveSettings()
ui->spinBoxZoomStep->onSave();
ui->checkBoxDragAtCursor->onSave();
ui->CheckBox_CornerCoordSystem->onSave();
+ ui->CheckBox_WbByTab->onSave();
ui->CheckBox_ShowFPS->onSave();
ui->CheckBox_useVBO->onSave();
ui->CheckBox_NaviCube->onSave();
@@ -134,6 +135,7 @@ void DlgSettings3DViewImp::loadSettings()
ui->spinBoxZoomStep->onRestore();
ui->checkBoxDragAtCursor->onRestore();
ui->CheckBox_CornerCoordSystem->onRestore();
+ ui->CheckBox_WbByTab->onRestore();
ui->CheckBox_ShowFPS->onRestore();
ui->CheckBox_useVBO->onRestore();
ui->CheckBox_NaviCube->onRestore();
diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp
index c58d20f9eb..f7f60d55f6 100644
--- a/src/Gui/MainWindow.cpp
+++ b/src/Gui/MainWindow.cpp
@@ -700,6 +700,15 @@ void MainWindow::activatePreviousWindow ()
void MainWindow::activateWorkbench(const QString& name)
{
+ ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
+ bool saveWB = hGrp->GetBool("SaveWBbyTab", false);
+ QMdiSubWindow* subWin = d->mdiArea->activeSubWindow();
+ if (subWin /*!= nullptr*/ && saveWB) {
+ QString currWb = subWin->property("ownWB").toString();
+ if (currWb.isEmpty() || currWb != name) {
+ subWin->setProperty("ownWB", name);
+ }
+ }
// emit this signal
workbenchActivated(name);
updateActions(true);
@@ -1020,6 +1029,18 @@ void MainWindow::onWindowActivated(QMdiSubWindow* w)
if (!w) return;
MDIView* view = dynamic_cast(w->widget());
+ ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
+ bool saveWB = hGrp->GetBool("SaveWBbyTab", false);
+ if (saveWB) {
+ QString currWb = w->property("ownWB").toString();
+ if (! currWb.isEmpty()) {
+ this->activateWorkbench(currWb);
+ }
+ else {
+ w->setProperty("ownWB", QString::fromStdString(WorkbenchManager::instance()->active()->name()));
+ }
+ }
+
// Even if windowActivated() signal is emitted mdi doesn't need to be a top-level window.
// This happens e.g. if two windows are top-level and one of them gets docked again.
// QWorkspace emits the signal then even though the other window is in front.
diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py
index efc7d92d02..f798171dbb 100644
--- a/src/Mod/Arch/ArchComponent.py
+++ b/src/Mod/Arch/ArchComponent.py
@@ -317,8 +317,7 @@ class Component(ArchIFC.IfcProduct):
return siblings
def getExtrusionData(self,obj):
-
- "returns (shape,extrusion vector,placement) or None"
+ """returns (shape,extrusion vector or path,placement) or None"""
if hasattr(obj,"CloneOf"):
if obj.CloneOf:
if hasattr(obj.CloneOf,"Proxy"):
diff --git a/src/Mod/Arch/ArchStructure.py b/src/Mod/Arch/ArchStructure.py
index 4a5f09e954..dc78284e7a 100644
--- a/src/Mod/Arch/ArchStructure.py
+++ b/src/Mod/Arch/ArchStructure.py
@@ -669,14 +669,14 @@ class _Structure(ArchComponent.Component):
else:
pli = pla[-1].copy()
shi.Placement = pli.multiply(shi.Placement)
- extv = pla[0].Rotation.multVec(evi)
- if obj.Tool:
+ if not isinstance(evi, FreeCAD.Vector):
try:
- shi = obj.Tool.Shape.copy().makePipe(obj.Base.Shape.copy())
+ shi = evi.makePipe(shi)
except Part.OCCError:
FreeCAD.Console.PrintError(translate("Arch","Error: The base shape couldn't be extruded along this tool object")+"\n")
return
else:
+ extv = pla[0].Rotation.multVec(evi)
shi = shi.extrude(extv)
base.append(shi)
if len(base) == 1:
@@ -807,23 +807,31 @@ class _Structure(ArchComponent.Component):
baseface = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1]))
base,placement = self.rebase(baseface)
if base and placement:
- if obj.Normal.Length:
- normal = Vector(obj.Normal)
- if isinstance(placement,list):
- normal = placement[0].inverse().Rotation.multVec(normal)
- else:
- normal = placement.inverse().Rotation.multVec(normal)
- if not normal:
- normal = Vector(0,0,1)
- if not normal.Length:
- normal = Vector(0,0,1)
- extrusion = normal
- if (length > height) and (IfcType != "Slab"):
- if length:
- extrusion = normal.multiply(length)
+ if obj.Tool:
+ if obj.Tool.Shape:
+ edges = obj.Tool.Shape.Edges
+ if len(edges) == 1 and DraftGeomUtils.geomType(edges[0]) == "Line":
+ extrusion = DraftGeomUtils.vec(edges[0])
+ else:
+ extrusion = obj.Tool.Shape.copy()
else:
- if height:
- extrusion = normal.multiply(height)
+ if obj.Normal.Length:
+ normal = Vector(obj.Normal)
+ if isinstance(placement,list):
+ normal = placement[0].inverse().Rotation.multVec(normal)
+ else:
+ normal = placement.inverse().Rotation.multVec(normal)
+ if not normal:
+ normal = Vector(0,0,1)
+ if not normal.Length:
+ normal = Vector(0,0,1)
+ extrusion = normal
+ if (length > height) and (IfcType != "Slab"):
+ if length:
+ extrusion = normal.multiply(length)
+ else:
+ if height:
+ extrusion = normal.multiply(height)
return (base,extrusion,placement)
return None
diff --git a/src/Mod/Arch/InitGui.py b/src/Mod/Arch/InitGui.py
index cf178844da..53d154c1b9 100644
--- a/src/Mod/Arch/InitGui.py
+++ b/src/Mod/Arch/InitGui.py
@@ -51,6 +51,8 @@ class ArchWorkbench(FreeCADGui.Workbench):
import DraftGui
from draftguitools import gui_circulararray
from draftguitools import gui_polararray
+ from draftguitools import gui_orthoarray
+ from draftguitools import gui_arrays
import Arch_rc
import Arch
diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py
index c5f2717310..89cc676afa 100644
--- a/src/Mod/Arch/exportIFC.py
+++ b/src/Mod/Arch/exportIFC.py
@@ -1812,44 +1812,49 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
pl = extdata[2]
if not isinstance(pl,list):
pl = [pl]
- for i in range(len(p)):
- pi = p[i]
- pi.scale(preferences['SCALE_FACTOR'])
- if i < len(ev):
- evi = FreeCAD.Vector(ev[i])
- else:
- evi = FreeCAD.Vector(ev[-1])
- evi.multiply(preferences['SCALE_FACTOR'])
- if i < len(pl):
- pli = pl[i].copy()
- else:
- pli = pl[-1].copy()
- pli.Base = pli.Base.multiply(preferences['SCALE_FACTOR'])
- pstr = str([v.Point for v in p[i].Vertexes])
- if pstr in profiledefs:
- profile = profiledefs[pstr]
- shapetype = "reusing profile"
- else:
- profile = getProfile(ifcfile,pi)
- if profile:
- profiledefs[pstr] = profile
- if profile and not(DraftVecUtils.isNull(evi)):
- #ev = pl.Rotation.inverted().multVec(evi)
- #print("evi:",evi)
- if not tostore:
- # add the object placement to the profile placement. Otherwise it'll be done later at map insert
- pl2 = obj.getGlobalPlacement()
- pl2.Base = pl2.Base.multiply(preferences['SCALE_FACTOR'])
- pli = pl2.multiply(pli)
- xvc = ifcbin.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(1,0,0))))
- zvc = ifcbin.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(0,0,1))))
- ovc = ifcbin.createIfcCartesianPoint(tuple(pli.Base))
- lpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
- edir = ifcbin.createIfcDirection(tuple(FreeCAD.Vector(evi).normalize()))
- shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,evi.Length)
- shapes.append(shape)
- solidType = "SweptSolid"
- shapetype = "extrusion"
+ simpleExtrusion = True
+ for evi in ev:
+ if not isinstance(evi, FreeCAD.Vector):
+ simpleExtrusion = False
+ if simpleExtrusion:
+ for i in range(len(p)):
+ pi = p[i]
+ pi.scale(preferences['SCALE_FACTOR'])
+ if i < len(ev):
+ evi = FreeCAD.Vector(ev[i])
+ else:
+ evi = FreeCAD.Vector(ev[-1])
+ evi.multiply(preferences['SCALE_FACTOR'])
+ if i < len(pl):
+ pli = pl[i].copy()
+ else:
+ pli = pl[-1].copy()
+ pli.Base = pli.Base.multiply(preferences['SCALE_FACTOR'])
+ pstr = str([v.Point for v in p[i].Vertexes])
+ if pstr in profiledefs:
+ profile = profiledefs[pstr]
+ shapetype = "reusing profile"
+ else:
+ profile = getProfile(ifcfile,pi)
+ if profile:
+ profiledefs[pstr] = profile
+ if profile and not(DraftVecUtils.isNull(evi)):
+ #ev = pl.Rotation.inverted().multVec(evi)
+ #print("evi:",evi)
+ if not tostore:
+ # add the object placement to the profile placement. Otherwise it'll be done later at map insert
+ pl2 = obj.getGlobalPlacement()
+ pl2.Base = pl2.Base.multiply(preferences['SCALE_FACTOR'])
+ pli = pl2.multiply(pli)
+ xvc = ifcbin.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(1,0,0))))
+ zvc = ifcbin.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(0,0,1))))
+ ovc = ifcbin.createIfcCartesianPoint(tuple(pli.Base))
+ lpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
+ edir = ifcbin.createIfcDirection(tuple(FreeCAD.Vector(evi).normalize()))
+ shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,evi.Length)
+ shapes.append(shape)
+ solidType = "SweptSolid"
+ shapetype = "extrusion"
if not shapes:
diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt
index 3ed3af7aa0..ae6a91f934 100644
--- a/src/Mod/Draft/CMakeLists.txt
+++ b/src/Mod/Draft/CMakeLists.txt
@@ -60,12 +60,14 @@ SET(Draft_utilities
SET(Draft_objects
draftobjects/__init__.py
draftobjects/circulararray.py
+ draftobjects/orthoarray.py
draftobjects/polararray.py
)
SET(Draft_view_providers
draftviewproviders/__init__.py
draftviewproviders/view_circulararray.py
+ draftviewproviders/view_orthoarray.py
draftviewproviders/view_polararray.py
)
@@ -73,12 +75,15 @@ SET(Draft_GUI_tools
draftguitools/__init__.py
draftguitools/gui_base.py
draftguitools/gui_circulararray.py
+ draftguitools/gui_orthoarray.py
draftguitools/gui_polararray.py
+ draftguitools/gui_arrays.py
)
SET(Draft_task_panels
drafttaskpanels/__init__.py
drafttaskpanels/task_circulararray.py
+ drafttaskpanels/task_orthoarray.py
drafttaskpanels/task_polararray.py
)
diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py
index febc7082d6..47d2cb5345 100644
--- a/src/Mod/Draft/Draft.py
+++ b/src/Mod/Draft/Draft.py
@@ -361,7 +361,7 @@ def makeAngularDimension(center,angles,p3,normal=None):
return obj
-def makeWire(pointslist,closed=False,placement=None,face=None,support=None):
+def makeWire(pointslist,closed=False,placement=None,face=None,support=None,bs2wire=False):
"""makeWire(pointslist,[closed],[placement]): Creates a Wire object
from the given list of vectors. If closed is True or first
and last points are identical, the wire is closed. If face is
@@ -387,7 +387,8 @@ def makeWire(pointslist,closed=False,placement=None,face=None,support=None):
if placement:
typecheck([(placement,FreeCAD.Placement)], "makeWire")
ipl = placement.inverse()
- pointslist = [ipl.multVec(p) for p in pointslist]
+ if not bs2wire:
+ pointslist = [ipl.multVec(p) for p in pointslist]
if len(pointslist) == 2: fname = "Line"
else: fname = "Wire"
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",fname)
diff --git a/src/Mod/Draft/DraftEdit.py b/src/Mod/Draft/DraftEdit.py
index 49ebd89997..80e9a373e4 100644
--- a/src/Mod/Draft/DraftEdit.py
+++ b/src/Mod/Draft/DraftEdit.py
@@ -2,6 +2,7 @@
#***************************************************************************
#* Copyright (c) 2009, 2010 Yorik van Havre *
#* Copyright (c) 2009, 2010 Ken Cline *
+#* Copyright (c) 2019, 2020 Carlo Pavan *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
@@ -1118,17 +1119,19 @@ class Edit():
return
if Draft.getType(obj) in ["BezCurve"]:
pts = self.recomputePointsBezier(obj,pts,nodeIndex,v,obj.Degree,moveTrackers=False)
- # check that the new point lies on the plane of the wire
- import DraftGeomUtils, DraftVecUtils
+
if obj.Closed:
- n = DraftGeomUtils.getNormal(obj.Shape)
- dv = editPnt.sub(pts[nodeIndex])
- rn = DraftVecUtils.project(dv,n)
- if dv.Length:
- editPnt = editPnt.add(rn.negative())
+ # check that the new point lies on the plane of the wire
+ if hasattr(obj.Shape,"normalAt"):
+ normal = obj.Shape.normalAt(0,0)
+ point_on_plane = obj.Shape.Vertexes[0].Point
+ print(v)
+ v.projectToPlane(point_on_plane, normal)
+ print(v)
+ editPnt = obj.getGlobalPlacement().inverse().multVec(v)
pts[nodeIndex] = editPnt
obj.Points = pts
- #self.trackers[obj.Name][nodeIndex].set(v)
+ self.trackers[obj.Name][nodeIndex].set(v)
def recomputePointsBezier(self,obj,pts,idx,v,degree,moveTrackers=True):
diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py
index 9e8fc429f9..76726b5353 100644
--- a/src/Mod/Draft/DraftTools.py
+++ b/src/Mod/Draft/DraftTools.py
@@ -4489,7 +4489,8 @@ class WireToBSpline(Modifier):
if (Draft.getType(self.obj) == 'Wire'):
n = Draft.makeBSpline(self.Points, self.closed, self.pl)
elif (Draft.getType(self.obj) == 'BSpline'):
- n = Draft.makeWire(self.Points, self.closed, self.pl)
+ self.bs2wire = True
+ n = Draft.makeWire(self.Points, self.closed, self.pl, None, None, self.bs2wire)
if n:
Draft.formatObject(n,self.obj)
self.doc.recompute()
diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py
index 069ed07263..d864f31af4 100644
--- a/src/Mod/Draft/InitGui.py
+++ b/src/Mod/Draft/InitGui.py
@@ -82,6 +82,8 @@ class DraftWorkbench(FreeCADGui.Workbench):
import DraftFillet
from draftguitools import gui_circulararray
from draftguitools import gui_polararray
+ from draftguitools import gui_orthoarray
+ from draftguitools import gui_arrays
FreeCADGui.addLanguagePath(":/translations")
FreeCADGui.addIconPath(":/icons")
except Exception as exc:
diff --git a/src/Mod/Draft/Resources/Draft.qrc b/src/Mod/Draft/Resources/Draft.qrc
index 0156b55822..0eef2df32e 100644
--- a/src/Mod/Draft/Resources/Draft.qrc
+++ b/src/Mod/Draft/Resources/Draft.qrc
@@ -152,6 +152,7 @@
ui/preferences-oca.ui
ui/preferences-svg.ui
ui/TaskPanel_CircularArray.ui
+ ui/TaskPanel_OrthoArray.ui
ui/TaskPanel_PolarArray.ui
ui/TaskSelectPlane.ui
ui/TaskShapeString.ui
diff --git a/src/Mod/Draft/Resources/icons/DraftWorkbench.svg b/src/Mod/Draft/Resources/icons/DraftWorkbench.svg
index 4e69418dd2..50019a76a8 100644
--- a/src/Mod/Draft/Resources/icons/DraftWorkbench.svg
+++ b/src/Mod/Draft/Resources/icons/DraftWorkbench.svg
@@ -7,237 +7,237 @@
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
- version="1.1"
- id="svg2980"
+ width="64px"
height="64px"
- width="64px">
+ id="svg2980"
+ version="1.1">
+ id="stop3892" />
+ id="stop3894" />
+ id="stop3831" />
+ id="stop3833" />
+ id="stop3805" />
+ id="stop3807" />
+ id="stop3857" />
+ id="stop3859" />
+ id="linearGradient3786"
+ osb:paint="solid">
+ id="stop3788" />
+ style="stop-color:#71b2f8;stop-opacity:1;" />
+ style="stop-color:#002795;stop-opacity:1;" />
+ id="stop3379" />
+ id="stop3381" />
-
+
+ id="linearGradient3861-4"
+ x1="3.9825215"
+ y1="31.552309"
+ x2="60.769054"
+ y2="51.094166"
+ gradientUnits="userSpaceOnUse" />
+ id="stop3857-1" />
-
-
-
-
-
+ id="stop3859-6" />
+ gradientUnits="userSpaceOnUse" />
+
+
+
+
+
+ x1="40"
+ y1="59"
+ x2="26"
+ y2="5"
+ gradientUnits="userSpaceOnUse" />
+ x1="21"
+ y1="52"
+ x2="19"
+ y2="41"
+ gradientUnits="userSpaceOnUse" />
+ x1="5.1754909"
+ y1="28.663757"
+ x2="9.3772163"
+ y2="63.578461"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.95198975,0,0,0.91651928,0.07298588,1.7291139)" />
+ id="stop3857-3" />
+ id="stop3859-5" />
+ x1="37"
+ y1="38"
+ x2="32"
+ y2="14"
+ gradientUnits="userSpaceOnUse" />
+ width="48"
+ height="58"
+ x="13"
+ y="3" />
+ id="path3010" />
+ width="44"
+ height="54"
+ x="15"
+ y="5" />
+ id="path3782" />
+ id="path3010-6" />
+ id="path3012" />
+ id="path3012-6" />
+ id="path3782-2" />
diff --git a/src/Mod/Draft/Resources/icons/Draft_2DShapeView.svg b/src/Mod/Draft/Resources/icons/Draft_2DShapeView.svg
index 2886227e5e..fb1b0be2cb 100644
--- a/src/Mod/Draft/Resources/icons/Draft_2DShapeView.svg
+++ b/src/Mod/Draft/Resources/icons/Draft_2DShapeView.svg
@@ -1,6 +1,4 @@
-
-