Files
create/src/Mod/PartDesign/InvoluteGearFeature.py
Jonas Bähr e89fb4a92c PD: Fix error "duplicate command PartDesign_InvoluteGear"
Every time the InvoluteGearFeature python module was imported, and we're
in GUI context, the command "PartDesign_InvoluteGear" was registered. On
the 2nd (3rd, ...) time, this was reported as an error in the console,
like "error: Command.cpp(1841): duplicate command PartDesign_InvoluteGear"

The first import happens when the PartDesign FreeCAD Module is loaded,
via `InitGui.py`. Subsequent imports may happen when e.g. executing the
involute gear command or when running it's tests via FC's Test WB.

This change now registers the command only then the PartDesign WB is set
up in InitGui, not when importing the python module. The same fix is
applied for sprocket, where the same pattern for command registration
was used.

In addition, the import error catch was removed, which seems to be a copy
paste left-over from the ShaftWizard. In contrast to ShaftWizard,
involute gear and sprocket only use modules from the Python standard lib,
so I think this precaution is not worth the additional complexity, just
for hiding actual errors.
2024-12-09 11:53:23 -05:00

280 lines
13 KiB
Python

#***************************************************************************
#* Copyright (c) 2014 Juergen Riegel <FreeCAD@juergen-riegel.net> *
#* *
#* 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 pathlib
import FreeCAD, Part
from PySide import QtCore
from fcgear import involute
from fcgear import fcgear
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtGui
__title__="PartDesign InvoluteGearObject management"
__author__ = "Juergen Riegel"
__url__ = "https://www.freecad.org"
def makeInvoluteGear(name):
'''makeInvoluteGear(name): makes an InvoluteGear'''
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",name)
_InvoluteGear(obj)
if FreeCAD.GuiUp:
_ViewProviderInvoluteGear(obj.ViewObject)
#FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
body=FreeCADGui.ActiveDocument.ActiveView.getActiveObject("pdbody")
part=FreeCADGui.ActiveDocument.ActiveView.getActiveObject("part")
if body:
body.Group=body.Group+[obj]
elif part:
part.Group=part.Group+[obj]
return obj
class CommandInvoluteGear:
"GUI command to create an InvoluteGear"
def GetResources(self):
return {'Pixmap' : 'PartDesign_InternalExternalGear',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PartDesign_InvoluteGear","Involute gear..."),
'Accel': "",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PartDesign_InvoluteGear","Creates or edit the involute gear definition.")}
def Activated(self):
FreeCAD.ActiveDocument.openTransaction("Create involute gear")
FreeCADGui.addModule("InvoluteGearFeature")
FreeCADGui.doCommand("InvoluteGearFeature.makeInvoluteGear('InvoluteGear')")
FreeCADGui.doCommand("Gui.activeDocument().setEdit(App.ActiveDocument.ActiveObject.Name,0)")
def IsActive(self):
if FreeCAD.ActiveDocument:
return True
else:
return False
class _InvoluteGear:
"The InvoluteGear object"
def __init__(self,obj):
self.Type = "InvoluteGear"
self._ensure_properties(obj, is_restore=False)
obj.Proxy = self
def onDocumentRestored(self, obj):
"""hook used to migrate older versions of this object"""
self._ensure_properties(obj, is_restore=True)
def _ensure_properties(self, obj, is_restore):
def ensure_property(type_, name, doc, default):
if not hasattr(obj, name):
obj.addProperty(type_, name, "Gear", doc)
if callable(default):
setattr(obj, name, default())
else:
setattr(obj, name, default)
# for details about the property's docstring translation,
# see https://tracker.freecad.org/view.php?id=2524
ensure_property("App::PropertyInteger", "NumberOfTeeth",
doc=QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of gear teeth"),
default=26)
ensure_property("App::PropertyLength", "Modules",
doc=QtCore.QT_TRANSLATE_NOOP("App::Property", "Module of the gear"),
default="2.5 mm")
ensure_property("App::PropertyAngle", "PressureAngle",
doc=QtCore.QT_TRANSLATE_NOOP("App::Property", "Pressure angle of gear teeth"),
default="20 deg")
ensure_property("App::PropertyBool", "HighPrecision",
doc=QtCore.QT_TRANSLATE_NOOP("App::Property",
"True=2 curves with each 3 control points, False=1 curve with 4 control points."),
default=True)
ensure_property("App::PropertyBool", "ExternalGear",
doc=QtCore.QT_TRANSLATE_NOOP("App::Property", "True=external Gear, False=internal Gear"),
default=True)
ensure_property("App::PropertyFloat", "AddendumCoefficient",
doc=QtCore.QT_TRANSLATE_NOOP("App::Property",
"The height of the tooth from the pitch circle up to its tip, normalized by the module."),
default=lambda: 1.0 if obj.ExternalGear else 0.6)
ensure_property("App::PropertyFloat","DedendumCoefficient",
doc=QtCore.QT_TRANSLATE_NOOP("App::Property",
"The height of the tooth from the pitch circle down to its root, normalized by the module."),
default=1.25)
ensure_property("App::PropertyFloat","RootFilletCoefficient",
doc=QtCore.QT_TRANSLATE_NOOP("App::Property",
"The radius of the fillet at the root of the tooth, normalized by the module."),
default=lambda: 0.375 if is_restore else 0.38)
ensure_property("App::PropertyFloat","ProfileShiftCoefficient",
doc=QtCore.QT_TRANSLATE_NOOP("App::Property",
"The distance by which the reference profile is shifted outwards, normalized by the module."),
default=0.0)
def execute(self,obj):
w = fcgear.FCWireBuilder()
generator_func = involute.CreateExternalGear if obj.ExternalGear else involute.CreateInternalGear
generator_func(w, obj.Modules.Value, obj.NumberOfTeeth, obj.PressureAngle.Value,
split=obj.HighPrecision, addCoeff=obj.AddendumCoefficient, dedCoeff=obj.DedendumCoefficient,
filletCoeff=obj.RootFilletCoefficient, shiftCoeff=obj.ProfileShiftCoefficient)
gearw = Part.Wire([o.toShape() for o in w.wire])
obj.Shape = gearw
obj.positionBySupport()
return
class _ViewProviderInvoluteGear:
"A View Provider for the InvoluteGear object"
def __init__(self,vobj):
vobj.Proxy = self
def getIcon(self):
return ":/icons/PartDesign_InternalExternalGear.svg"
def attach(self, vobj):
self.ViewObject = vobj
self.Object = vobj.Object
def setEdit(self,vobj,mode):
taskd = _InvoluteGearTaskPanel(self.Object,mode)
taskd.obj = vobj.Object
taskd.update()
FreeCADGui.Control.showDialog(taskd)
return True
def unsetEdit(self,vobj,mode):
FreeCADGui.Control.closeDialog()
return
def dumps(self):
return None
def loads(self,state):
return None
class _InvoluteGearTaskPanel:
'''The editmode TaskPanel for InvoluteGear objects'''
def __init__(self,obj,mode):
self.obj = obj
self.form=FreeCADGui.PySideUic.loadUi(str(pathlib.Path(__file__).with_suffix(".ui")))
self.form.setWindowIcon(QtGui.QIcon(":/icons/PartDesign_InternalExternalGear.svg"))
self.assignToolTipsFromPropertyDocs()
def assignValue(property_name, fitView=False):
"""Returns a function that takes a single value and assigns it to the given property"""
def assigner(value):
setattr(self.obj, property_name, value)
self.obj.Proxy.execute(self.obj)
if fitView:
FreeCAD.Gui.SendMsgToActiveView("ViewFit")
return assigner
def assignIndexAsBool(property_name):
"""Variant of assignValue that transforms the index of a Yes/No Combobox to a bool."""
assigner = assignValue(property_name)
def transformingAssigner(value):
assigner(True if value == 0 else False)
return transformingAssigner
self.form.Quantity_Modules.valueChanged.connect(assignValue("Modules", fitView=True))
self.form.Quantity_PressureAngle.valueChanged.connect(assignValue("PressureAngle"))
self.form.spinBox_NumberOfTeeth.valueChanged.connect(assignValue("NumberOfTeeth", fitView=True))
self.form.comboBox_HighPrecision.currentIndexChanged.connect(assignIndexAsBool("HighPrecision"))
self.form.comboBox_ExternalGear.currentIndexChanged.connect(assignIndexAsBool("ExternalGear"))
self.form.doubleSpinBox_Addendum.valueChanged.connect(assignValue("AddendumCoefficient"))
self.form.doubleSpinBox_Dedendum.valueChanged.connect(assignValue("DedendumCoefficient"))
self.form.doubleSpinBox_RootFillet.valueChanged.connect(assignValue("RootFilletCoefficient"))
self.form.doubleSpinBox_ProfileShift.valueChanged.connect(assignValue("ProfileShiftCoefficient"))
self.update()
if mode == 0: # fresh created
self.obj.Proxy.execute(self.obj) # calculate once
FreeCAD.Gui.SendMsgToActiveView("ViewFit")
def assignToolTipsFromPropertyDocs(self):
def assign(property_name, *widgets):
doc = self.obj.getDocumentationOfProperty(property_name)
translated_doc = QtGui.QApplication.translate("App::Property", doc)
for w in widgets:
w.setToolTip(translated_doc)
# we assign the tool tip to both, the label and the input field, for user convenience
assign("Modules", self.form.Quantity_Modules, self.form.label_Modules)
assign("PressureAngle", self.form.Quantity_PressureAngle, self.form.label_PressureAngle)
assign("NumberOfTeeth", self.form.spinBox_NumberOfTeeth, self.form.label_NumberOfTeeth)
assign("HighPrecision", self.form.comboBox_HighPrecision, self.form.label_HighPrecision)
assign("ExternalGear", self.form.comboBox_ExternalGear, self.form.label_ExternalGear)
assign("AddendumCoefficient", self.form.doubleSpinBox_Addendum, self.form.label_Addendum)
assign("DedendumCoefficient", self.form.doubleSpinBox_Dedendum, self.form.label_Dedendum)
assign("RootFilletCoefficient", self.form.doubleSpinBox_RootFillet, self.form.label_RootFillet)
assign("ProfileShiftCoefficient", self.form.doubleSpinBox_ProfileShift, self.form.label_ProfileShift)
def changeEvent(self, event):
if event == QtCore.QEvent.LanguageChange:
self.assignToolTipsFromPropertyDocs()
def transferTo(self):
"Transfer from the dialog to the object"
self.obj.NumberOfTeeth = self.form.spinBox_NumberOfTeeth.value()
self.obj.Modules = self.form.Quantity_Modules.text()
self.obj.PressureAngle = self.form.Quantity_PressureAngle.text()
self.obj.HighPrecision = True if self.form.comboBox_HighPrecision.currentIndex() == 0 else False
self.obj.ExternalGear = True if self.form.comboBox_ExternalGear.currentIndex() == 0 else False
self.obj.AddendumCoefficient = self.form.doubleSpinBox_Addendum.value()
self.obj.DedendumCoefficient = self.form.doubleSpinBox_Dedendum.value()
self.obj.RootFilletCoefficient = self.form.doubleSpinBox_RootFillet.value()
self.obj.ProfileShiftCoefficient = self.form.doubleSpinBox_ProfileShift.value()
def transferFrom(self):
"Transfer from the object to the dialog"
self.form.spinBox_NumberOfTeeth.setValue(self.obj.NumberOfTeeth)
self.form.Quantity_Modules.setText(self.obj.Modules.UserString)
self.form.Quantity_PressureAngle.setText(self.obj.PressureAngle.UserString)
self.form.comboBox_HighPrecision.setCurrentIndex(0 if self.obj.HighPrecision else 1)
self.form.comboBox_ExternalGear.setCurrentIndex(0 if self.obj.ExternalGear else 1)
self.form.doubleSpinBox_Addendum.setValue(self.obj.AddendumCoefficient)
self.form.doubleSpinBox_Dedendum.setValue(self.obj.DedendumCoefficient)
self.form.doubleSpinBox_RootFillet.setValue(self.obj.RootFilletCoefficient)
self.form.doubleSpinBox_ProfileShift.setValue(self.obj.ProfileShiftCoefficient)
def getStandardButtons(self):
return QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Apply
def clicked(self,button):
if button == QtGui.QDialogButtonBox.Apply:
self.transferTo()
self.obj.Proxy.execute(self.obj)
def update(self):
'fills the widgets'
self.transferFrom()
def accept(self):
self.transferTo()
FreeCAD.ActiveDocument.recompute()
FreeCADGui.ActiveDocument.resetEdit()
def reject(self):
FreeCADGui.ActiveDocument.resetEdit()
FreeCAD.ActiveDocument.abortTransaction()