diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt
index d7c1b8b1d..c9d9b1def 100644
--- a/src/Mod/Part/App/CMakeLists.txt
+++ b/src/Mod/Part/App/CMakeLists.txt
@@ -265,6 +265,7 @@ SET(Part_Scripts
Init.py
TestPartApp.py
MakeBottle.py
+ JoinFeatures.py
)
add_library(Part SHARED ${Part_SRCS})
diff --git a/src/Mod/Part/CMakeLists.txt b/src/Mod/Part/CMakeLists.txt
index 958502f5a..b08e551f5 100644
--- a/src/Mod/Part/CMakeLists.txt
+++ b/src/Mod/Part/CMakeLists.txt
@@ -11,6 +11,7 @@ INSTALL(
MakeBottle.py
TestPartApp.py
TestPartGui.py
+ JoinFeatures.py
DESTINATION
Mod/Part
)
diff --git a/src/Mod/Part/Gui/Command.cpp b/src/Mod/Part/Gui/Command.cpp
index 5b900aa50..e52344649 100644
--- a/src/Mod/Part/Gui/Command.cpp
+++ b/src/Mod/Part/Gui/Command.cpp
@@ -39,6 +39,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -490,6 +491,101 @@ bool CmdPartFuse::isActive(void)
return getSelection().countObjectsOfType(Part::Feature::getClassTypeId())>=2;
}
+//===========================================================================
+// Part_CompJoinFeatures (dropdown toolbar button for Connect, Embed and Cutout)
+//===========================================================================
+
+DEF_STD_CMD_ACL(CmdPartCompJoinFeatures);
+
+CmdPartCompJoinFeatures::CmdPartCompJoinFeatures()
+ : Command("Part_CompJoinFeatures")
+{
+ sAppModule = "Part";
+ sGroup = QT_TR_NOOP("Part");
+ sMenuText = QT_TR_NOOP("Join objects...");
+ sToolTipText = QT_TR_NOOP("Join walled objects");
+ sWhatsThis = "Part_CompJoinFeatures";
+ sStatusTip = sToolTipText;
+}
+
+void CmdPartCompJoinFeatures::activated(int iMsg)
+{
+ Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager();
+ if (iMsg==0)
+ rcCmdMgr.runCommandByName("Part_JoinConnect");
+ else if (iMsg==1)
+ rcCmdMgr.runCommandByName("Part_JoinEmbed");
+ else if (iMsg==2)
+ rcCmdMgr.runCommandByName("Part_JoinCutout");
+ else
+ return;
+
+ // Since the default icon is reset when enabing/disabling the command we have
+ // to explicitly set the icon of the used command.
+ Gui::ActionGroup* pcAction = qobject_cast(_pcAction);
+ QList a = pcAction->actions();
+
+ assert(iMsg < a.size());
+ pcAction->setIcon(a[iMsg]->icon());
+}
+
+Gui::Action * CmdPartCompJoinFeatures::createAction(void)
+{
+ Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
+ pcAction->setDropDownMenu(true);
+ applyCommandData(this->className(), pcAction);
+
+ QAction* cmd0 = pcAction->addAction(QString());
+ cmd0->setIcon(Gui::BitmapFactory().pixmap("Part_JoinConnect"));
+ QAction* cmd1 = pcAction->addAction(QString());
+ cmd1->setIcon(Gui::BitmapFactory().pixmap("Part_JoinEmbed"));
+ QAction* cmd2 = pcAction->addAction(QString());
+ cmd2->setIcon(Gui::BitmapFactory().pixmap("Part_JoinCutout"));
+
+ _pcAction = pcAction;
+ languageChange();
+
+ pcAction->setIcon(cmd0->icon());
+ int defaultId = 0;
+ pcAction->setProperty("defaultAction", QVariant(defaultId));
+
+ return pcAction;
+}
+
+void CmdPartCompJoinFeatures::languageChange()
+{
+ Command::languageChange();
+
+ if (!_pcAction)
+ return;
+
+ Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager();
+
+ Gui::ActionGroup* pcAction = qobject_cast(_pcAction);
+ QList a = pcAction->actions();
+
+ QAction* cmd0 = a[0];
+ cmd0->setText(QApplication::translate("PartCompJoinFeatures", rcCmdMgr.getCommandByName("Part_JoinConnect")->getMenuText()));
+ cmd0->setToolTip(QApplication::translate("Part_JoinConnect", rcCmdMgr.getCommandByName("Part_JoinConnect")->getToolTipText()));
+ cmd0->setStatusTip(QApplication::translate("Part_JoinConnect", rcCmdMgr.getCommandByName("Part_JoinConnect")->getStatusTip()));
+ QAction* cmd1 = a[1];
+ cmd1->setText(QApplication::translate("PartCompJoinFeatures", rcCmdMgr.getCommandByName("Part_JoinEmbed")->getMenuText()));
+ cmd1->setToolTip(QApplication::translate("Part_JoinEmbed", rcCmdMgr.getCommandByName("Part_JoinEmbed")->getToolTipText()));
+ cmd1->setStatusTip(QApplication::translate("Part_JoinEmbed", rcCmdMgr.getCommandByName("Part_JoinEmbed")->getStatusTip()));
+ QAction* cmd2 = a[2];
+ cmd2->setText(QApplication::translate("PartCompJoinFeatures", rcCmdMgr.getCommandByName("Part_JoinCutout")->getMenuText()));
+ cmd2->setToolTip(QApplication::translate("Part_JoinCutout", rcCmdMgr.getCommandByName("Part_JoinCutout")->getToolTipText()));
+ cmd2->setStatusTip(QApplication::translate("Part_JoinCutout", rcCmdMgr.getCommandByName("Part_JoinCutout")->getStatusTip()));
+}
+
+bool CmdPartCompJoinFeatures::isActive(void)
+{
+ if (getActiveGuiDocument())
+ return true;
+ else
+ return false;
+}
+
//===========================================================================
// Part_Compound
//===========================================================================
@@ -1747,6 +1843,7 @@ void CreatePartCommands(void)
rcCmdMgr.addCommand(new CmdPartCommon());
rcCmdMgr.addCommand(new CmdPartCut());
rcCmdMgr.addCommand(new CmdPartFuse());
+ rcCmdMgr.addCommand(new CmdPartCompJoinFeatures());
rcCmdMgr.addCommand(new CmdPartCompound());
rcCmdMgr.addCommand(new CmdPartSection());
//rcCmdMgr.addCommand(new CmdPartBox2());
diff --git a/src/Mod/Part/Gui/Resources/Part.qrc b/src/Mod/Part/Gui/Resources/Part.qrc
index 2d9c68ac0..762ecba26 100644
--- a/src/Mod/Part/Gui/Resources/Part.qrc
+++ b/src/Mod/Part/Gui/Resources/Part.qrc
@@ -59,6 +59,10 @@
icons/Tree_Part_Prism.svg
icons/Tree_Part_Wedge.svg
icons/Part_Shape_from_Mesh.svg
+ icons/Part_JoinBypass.svg
+ icons/Part_JoinConnect.svg
+ icons/Part_JoinCutout.svg
+ icons/Part_JoinEmbed.svg
translations/Part_af.qm
translations/Part_de.qm
translations/Part_fi.qm
diff --git a/src/Mod/Part/Gui/Resources/icons/Part_JoinBypass.svg b/src/Mod/Part/Gui/Resources/icons/Part_JoinBypass.svg
new file mode 100644
index 000000000..650bf2ec0
--- /dev/null
+++ b/src/Mod/Part/Gui/Resources/icons/Part_JoinBypass.svg
@@ -0,0 +1,203 @@
+
+
+
+
diff --git a/src/Mod/Part/Gui/Resources/icons/Part_JoinConnect.svg b/src/Mod/Part/Gui/Resources/icons/Part_JoinConnect.svg
new file mode 100644
index 000000000..9a4b0ffa8
--- /dev/null
+++ b/src/Mod/Part/Gui/Resources/icons/Part_JoinConnect.svg
@@ -0,0 +1,194 @@
+
+
diff --git a/src/Mod/Part/Gui/Resources/icons/Part_JoinCutout.svg b/src/Mod/Part/Gui/Resources/icons/Part_JoinCutout.svg
new file mode 100644
index 000000000..8cc066dd8
--- /dev/null
+++ b/src/Mod/Part/Gui/Resources/icons/Part_JoinCutout.svg
@@ -0,0 +1,185 @@
+
+
diff --git a/src/Mod/Part/Gui/Resources/icons/Part_JoinEmbed.svg b/src/Mod/Part/Gui/Resources/icons/Part_JoinEmbed.svg
new file mode 100644
index 000000000..237a006c2
--- /dev/null
+++ b/src/Mod/Part/Gui/Resources/icons/Part_JoinEmbed.svg
@@ -0,0 +1,198 @@
+
+
diff --git a/src/Mod/Part/Gui/Workbench.cpp b/src/Mod/Part/Gui/Workbench.cpp
index 123927cd3..0736719e7 100644
--- a/src/Mod/Part/Gui/Workbench.cpp
+++ b/src/Mod/Part/Gui/Workbench.cpp
@@ -66,7 +66,10 @@ Gui::MenuItem* Workbench::setupMenuBar() const
Gui::MenuItem* bop = new Gui::MenuItem;
bop->setCommand("Boolean");
*bop << "Part_Boolean" << "Part_Cut" << "Part_Fuse" << "Part_Common";
-
+
+ Gui::MenuItem* join = new Gui::MenuItem;
+ join->setCommand("Join");
+ *join << "Part_JoinConnect" << "Part_JoinEmbed" << "Part_JoinCutout";
Gui::MenuItem* part = new Gui::MenuItem;
root->insertItem(item, part);
@@ -75,7 +78,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const
*part << prim << "Part_Primitives" << "Part_Builder" << "Separator"
<< "Part_ShapeFromMesh" << "Part_MakeSolid" << "Part_ReverseShape"
<< "Part_SimpleCopy" << "Part_RefineShape" << "Part_CheckGeometry"
- << "Separator" << bop << "Separator"
+ << "Separator" << bop << join << "Separator"
<< "Part_CrossSections" << "Part_Compound" << "Part_Extrude"
<< "Part_Revolve" << "Part_Mirror" << "Part_Fillet" << "Part_Chamfer"
<< "Part_RuledSurface" << "Part_Loft" << "Part_Sweep"
@@ -120,7 +123,8 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
Gui::ToolBarItem* boolop = new Gui::ToolBarItem(root);
boolop->setCommand("Boolean");
*boolop << "Part_Boolean" << "Part_Cut" << "Part_Fuse" << "Part_Common"
- << "Part_CheckGeometry" << "Part_Section" << "Part_CrossSections";
+ << "Part_CompJoinFeatures" << "Part_CheckGeometry" << "Part_Section"
+ << "Part_CrossSections";
Gui::ToolBarItem* measure = new Gui::ToolBarItem(root);
measure->setCommand("Measure");
diff --git a/src/Mod/Part/InitGui.py b/src/Mod/Part/InitGui.py
index 159909f8f..1a905eb81 100644
--- a/src/Mod/Part/InitGui.py
+++ b/src/Mod/Part/InitGui.py
@@ -32,8 +32,8 @@
class PartWorkbench ( Workbench ):
- "Part workbench object"
- Icon = """
+ "Part workbench object"
+ Icon = """
/* XPM */
static char * part_xpm[] = {
"16 16 9 1",
@@ -62,15 +62,20 @@ class PartWorkbench ( Workbench ):
".@$%&****%.%.. ",
" ......@##.. ",
" ... "};
- """
- MenuText = "Part"
- ToolTip = "Part workbench"
+ """
+ MenuText = "Part"
+ ToolTip = "Part workbench"
- def Initialize(self):
- # load the module
- import PartGui
- import Part
- def GetClassName(self):
- return "PartGui::Workbench"
+ def Initialize(self):
+ # load the module
+ import PartGui
+ import Part
+ try:
+ import JoinFeatures
+ except ImportError:
+ print "JoinFeatures module cannot be loaded"
+
+ def GetClassName(self):
+ return "PartGui::Workbench"
Gui.addWorkbench(PartWorkbench())
diff --git a/src/Mod/Part/JoinFeatures.py b/src/Mod/Part/JoinFeatures.py
new file mode 100644
index 000000000..84c655a5f
--- /dev/null
+++ b/src/Mod/Part/JoinFeatures.py
@@ -0,0 +1,260 @@
+#***************************************************************************
+#* *
+#* Copyright (c) 2015 - Victor Titov (DeepSOIC) *
+#* *
+#* *
+#* This program is free software; you can redistribute it and/or modify *
+#* it under the terms of the GNU Lesser General Public License (LGPL) *
+#* as published by the Free Software Foundation; either version 2 of *
+#* the License, or (at your option) any later version. *
+#* for detail see the LICENCE text file. *
+#* *
+#* This program is distributed in the hope that it will be useful, *
+#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+#* GNU Library General Public License for more details. *
+#* *
+#* You should have received a copy of the GNU Library General Public *
+#* License along with this program; if not, write to the Free Software *
+#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
+#* USA *
+#* *
+#***************************************************************************
+
+import FreeCAD, Part
+
+if FreeCAD.GuiUp:
+ import FreeCADGui
+ from PySide import QtCore, QtGui
+
+__title__="JoinFeatures module"
+__author__ = "DeepSOIC"
+__url__ = "http://www.freecadweb.org"
+
+#-------------------------- translation-related code ----------------------------------------
+#Thanks, yorik! (see forum thread "A new Part tool is being born... JoinFeatures!"
+#http://forum.freecadweb.org/viewtopic.php?f=22&t=11112&start=30#p90239 )
+try:
+ _fromUtf8 = QtCore.QString.fromUtf8
+except AttributeError:
+ def _fromUtf8(s):
+ return s
+try:
+ _encoding = QtGui.QApplication.UnicodeUTF8
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig, _encoding)
+except AttributeError:
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig)
+#--------------------------/translation-related code ----------------------------------------
+
+# -------------------------- common stuff --------------------------------------------------
+def getParamRefine():
+ return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Part/Boolean").GetBool("RefineModel")
+
+def shapeOfMaxVol(compound):
+ if compound.ShapeType == 'Compound':
+ maxVol = 0
+ cntEq = 0
+ shMax = None
+ for sh in compound.childShapes():
+ v = sh.Volume
+ if v > maxVol + 1e-8 :
+ maxVol = v
+ shMax = sh
+ cntEq = 1
+ elif abs(v - maxVol) <= 1e-8 :
+ cntEq = cntEq + 1
+ if cntEq > 1 :
+ raise ValueError("Equal volumes, can't figure out what to cut off!")
+ return shMax
+ else:
+ return compound
+
+def makePartJoinFeature(name, mode = 'bypass'):
+ '''makePartJoinFeature(name, mode = 'bypass'): makes an PartJoinFeature object.'''
+ obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name)
+ _PartJoinFeature(obj)
+ obj.Mode = mode
+ obj.Refine = getParamRefine()
+ _ViewProviderPartJoinFeature(obj.ViewObject)
+ return obj
+
+class _PartJoinFeature:
+ "The PartJoinFeature object"
+ def __init__(self,obj):
+ self.Type = "PartJoinFeature"
+ obj.addProperty("App::PropertyEnumeration","Mode","Join","The mode of operation. bypass = make compound (fast)")
+ obj.Mode = ['bypass','Connect','Embed','Cutout']
+ obj.addProperty("App::PropertyLink","Base","Join","First object")
+ obj.addProperty("App::PropertyLink","Tool","Join","Second object")
+ obj.addProperty("App::PropertyBool","Refine","Join","True = refine resulting shape. False = output as is.")
+
+ obj.Proxy = self
+
+
+ def execute(self,obj):
+ rst = None
+ if obj.Mode == 'bypass':
+ rst = Part.makeCompound([obj.Base.Shape, obj.Tool.Shape])
+ else:
+ cut1 = obj.Base.Shape.cut(obj.Tool.Shape)
+ cut1 = shapeOfMaxVol(cut1)
+ if obj.Mode == 'Connect':
+ cut2 = obj.Tool.Shape.cut(obj.Base.Shape)
+ cut2 = shapeOfMaxVol(cut2)
+ rst = cut1.multiFuse([cut2, obj.Tool.Shape.common(obj.Base.Shape)])
+ elif obj.Mode == 'Embed':
+ rst = cut1.fuse(obj.Tool.Shape)
+ elif obj.Mode == 'Cutout':
+ rst = cut1
+ if obj.Refine:
+ rst = rst.removeSplitter()
+ obj.Shape = rst
+ return
+
+
+class _ViewProviderPartJoinFeature:
+ "A View Provider for the PartJoinFeature object"
+
+ def __init__(self,vobj):
+ vobj.Proxy = self
+
+ def getIcon(self):
+ if self.Object == None:
+ return getIconPath("Part_JoinConnect.svg")
+ else:
+ return getIconPath( {
+ 'bypass':"Part_JoinBypass.svg",
+ 'Connect':"Part_JoinConnect.svg",
+ 'Embed':"Part_JoinEmbed.svg",
+ 'Cutout':"Part_JoinCutout.svg",
+ }[self.Object.Mode] )
+
+ def attach(self, vobj):
+ self.ViewObject = vobj
+ self.Object = vobj.Object
+
+
+ def setEdit(self,vobj,mode):
+ return False
+
+ def unsetEdit(self,vobj,mode):
+ return
+
+ def __getstate__(self):
+ return None
+
+ def __setstate__(self,state):
+ return None
+
+def CreateJoinFeature(name, mode):
+ FreeCAD.ActiveDocument.openTransaction("Create "+mode+"ObjectsFeature")
+ FreeCADGui.addModule("JoinFeatures")
+ FreeCADGui.doCommand("j = JoinFeatures.makePartJoinFeature(name = '"+name+"', mode = '"+mode+"' )")
+ FreeCADGui.doCommand("j.Base = FreeCADGui.Selection.getSelection()[0]")
+ FreeCADGui.doCommand("j.Tool = FreeCADGui.Selection.getSelection()[1]")
+ FreeCADGui.doCommand("j.Proxy.execute(j)")
+ FreeCADGui.doCommand("j.purgeTouched()")
+ FreeCADGui.doCommand("j.Base.ViewObject.hide()")
+ FreeCADGui.doCommand("j.Tool.ViewObject.hide()")
+ FreeCAD.ActiveDocument.commitTransaction()
+
+def getIconPath(icon_dot_svg):
+ return ":/icons/" + icon_dot_svg
+
+# -------------------------- /common stuff --------------------------------------------------
+
+# -------------------------- ConnectObjectsFeature --------------------------------------------------
+
+class _CommandConnectFeature:
+ "Command to create PartJoinFeature in Connect mode"
+ def GetResources(self):
+ return {'Pixmap' : getIconPath("Part_JoinConnect.svg"),
+ 'MenuText': QtCore.QT_TRANSLATE_NOOP("Part_ConnectFeature","Connect objects"),
+ 'Accel': "",
+ 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Part_ConnectFeature","Fuses objects, taking care to preserve voids.")}
+
+ def Activated(self):
+ if len(FreeCADGui.Selection.getSelection()) == 2 :
+ CreateJoinFeature(name = "Connect", mode = "Connect")
+ else:
+ mb = QtGui.QMessageBox()
+ mb.setIcon(mb.Icon.Warning)
+ mb.setText(_translate("Part_JoinFeatures", "Two solids need to be selected, first!", None))
+ mb.setWindowTitle(_translate("Part_JoinFeatures","Bad selection", None))
+ mb.exec_()
+
+ def IsActive(self):
+ if FreeCAD.ActiveDocument:
+ return True
+ else:
+ return False
+
+FreeCADGui.addCommand('Part_JoinConnect',_CommandConnectFeature())
+
+# -------------------------- /ConnectObjectsFeature --------------------------------------------------
+
+
+# -------------------------- EmbedFeature --------------------------------------------------
+
+class _CommandEmbedFeature:
+ "Command to create PartJoinFeature in Embed mode"
+ def GetResources(self):
+ return {'Pixmap' : getIconPath("Part_JoinEmbed.svg"),
+ 'MenuText': QtCore.QT_TRANSLATE_NOOP("Part_EmbedFeature","Embed object"),
+ 'Accel': "",
+ 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Part_EmbedFeature","Fuses one object into another, taking care to preserve voids.")}
+
+ def Activated(self):
+ if len(FreeCADGui.Selection.getSelection()) == 2 :
+ CreateJoinFeature(name = "Embed", mode = "Embed")
+ else:
+ mb = QtGui.QMessageBox()
+ mb.setIcon(mb.Icon.Warning)
+ mb.setText(_translate("Part_JoinFeatures","Select base object, then the object to embed, and invoke this tool.", None))
+ mb.setWindowTitle(_translate("Part_JoinFeatures","Bad selection", None))
+ mb.exec_()
+
+
+ def IsActive(self):
+ if FreeCAD.ActiveDocument:
+ return True
+ else:
+ return False
+
+FreeCADGui.addCommand('Part_JoinEmbed',_CommandEmbedFeature())
+
+# -------------------------- /EmbedFeature --------------------------------------------------
+
+
+
+# -------------------------- CutoutFeature --------------------------------------------------
+
+class _CommandCutoutFeature:
+ "Command to create PartJoinFeature in Cutout mode"
+ def GetResources(self):
+ return {'Pixmap' : getIconPath("Part_JoinCutout.svg"),
+ 'MenuText': QtCore.QT_TRANSLATE_NOOP("Part_CutoutFeature","Cutout for object"),
+ 'Accel': "",
+ 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Part_CutoutFeature","Makes a cutout in one object to fit another object.")}
+
+ def Activated(self):
+ if len(FreeCADGui.Selection.getSelection()) == 2 :
+ CreateJoinFeature(name = "Cutout", mode = "Cutout")
+ else:
+ mb = QtGui.QMessageBox()
+ mb.setIcon(mb.Icon.Warning)
+ mb.setText(_translate("Part_JoinFeatures","Select the object to make a cutout in, then the object that should fit into the cutout, and invoke this tool.", None))
+ mb.setWindowTitle(_translate("Part_JoinFeatures","Bad selection", None))
+ mb.exec_()
+
+ def IsActive(self):
+ if FreeCAD.ActiveDocument:
+ return True
+ else:
+ return False
+
+FreeCADGui.addCommand('Part_JoinCutout',_CommandCutoutFeature())
+
+# -------------------------- /CutoutFeature --------------------------------------------------