# *************************************************************************** # * Copyright (c) 2014 Juergen Riegel * # * * # * 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 edits 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, locked=True) 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()