# -*- coding: utf-8 -*- # Copyright 2023 by brothermechanic. All Rights Reserved. # Based on ARBench by github/mahaarbo # This library is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation, either # version 3 of the License, or (at your option) any later version. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . import FreeCAD import Tools from scenarios.robossembler_freecad_export_scenario import RobossemblerFreeCadExportScenario if FreeCAD.GuiUp: import FreeCADGui from pivy import coin from PySide import QtCore, QtGui, QtSvg import Part import os __title__ = 'Frames' __author__ = 'brothermechanic' __workbenchname__ = 'Frames' __version__ = '0.1' __url__ = ["https://robossembler.org"] __doc__ = """""" ############################################################ # Frame Objects ############################################################ class Frame(object): """Basic freestanding frame""" def __init__(self, obj): obj.addProperty("App::PropertyPlacement", "Placement", "Base", "Placement of the frame") obj.Placement = FreeCAD.Placement() obj.Proxy = self self.obj = obj self.additional_data = {} def onChanged(self, fp, prop): pass def execute(self, obj): pass def __getstate__(self): return None def __setstate__(self, state): return None def getDict(self): d = {} d["label"] = str(self.obj.Label) d["placement"] = Tools.placement2axisvec(self.obj.Placement) d.update(self.additional_data) return d class PartFrame(Frame): """Frame rigidly attached to a part frame. Inherits the base placement from the part's frame, and placement is relative to the part frame. """ def __init__(self, obj, partobj): Frame.__init__(self, obj) obj.addProperty("App::PropertyPlacementLink", "Part", "Parent", "The part to attach to.") obj.Part = partobj obj.setEditorMode("Part", 1) def execute(self, obj): if FreeCAD.GuiUp: obj.ViewObject.Proxy.updateData(obj, "Placement") def getDict(self): d = Frame.getDict(self) d["part"] = str(self.obj.Part.Label) return d class FeatureFrame(PartFrame): """Frame rigidly attached to a feature. The feature frame is attached to a feature on a part. It gives both the placement of the feature w.r.t. the part, and the placement from the feature.""" def __init__(self, obj, partobj, featurePlacement): PartFrame.__init__(self, obj, partobj) obj.addProperty("App::PropertyPlacement", "FeaturePlacement", "Feature", "The frame attached to the feature.") obj.addProperty("App::PropertyString", "PrimitiveType", "Feature", "The primitive type of the feature.") obj.addProperty("App::PropertyString", "ShapeType", "Feature", "The shape type of the feature.") obj.addProperty("App::PropertyString", "Positioning", "Feature", "The type of positioning used during creation.") obj.FeaturePlacement = featurePlacement def getDict(self): d = PartFrame.getDict(self) d["featureplacement"] = Tools.placement2axisvec(self.obj.FeaturePlacement) d["shapetype"] = str(self.obj.ShapeType) d["positioning"] = str(self.obj.Positioning) return d ############################################################ # ViewProvider to the frames ############################################################ class ViewProviderFrame(object): """ViewProvider for the basic frame. Uses the SOAxiscrosskit to create axises with constant length regardless of zoom. Updates position when placement is changed. """ def __init__(self, vobj): vobj.addProperty("App::PropertyFloat", "Scale") vobj.Scale = 0.12 vobj.addProperty("App::PropertyFloat", "HeadSize") vobj.HeadSize = 3.0 vobj.addProperty("App::PropertyFloat", "LineWidth") vobj.LineWidth = 2.0 vobj.Proxy = self def attach(self, vobj): # We only have a shaded visual group self.shaded = coin.SoGroup() # Takes heavily from SoAxisCrosskit.h, # and Toggle_DH_Frames by galou_breizh on the forums self.vframe = coin.SoType.fromName("SoShapeScale").createInstance() self.vframe.setPart("shape", coin.SoType.fromName("SoAxisCrossKit").createInstance()) self.vframe.scaleFactor.setValue(0.2) ax = self.vframe.getPart("shape", 0) cone = ax.getPart("xHead.shape", 0) cone.bottomRadius.setValue(vobj.HeadSize) cone = ax.getPart("yHead.shape", 0) cone.bottomRadius.setValue(vobj.HeadSize) cone = ax.getPart("zHead.shape", 0) cone.bottomRadius.setValue(vobj.HeadSize) lwstring = "lineWidth {0}".format(vobj.LineWidth) ax.set("xAxis.appearance.drawStyle", lwstring) ax.set("yAxis.appearance.drawStyle", lwstring) ax.set("zAxis.appearance.drawStyle", lwstring) ax.set("xAxis.pickStyle", "style SHAPE") ax.set("yAxis.pickStyle", "style SHAPE") ax.set("zAxis.pickStyle", "style SHAPE") # Then remember to make it selectable in the viewer selectionNode = coin.SoType.fromName("SoFCSelection").createInstance() selectionNode.documentName.setValue(FreeCAD.ActiveDocument.Name) selectionNode.objectName.setValue(vobj.Object.Name) selectionNode.subElementName.setValue("Frame") selectionNode.addChild(self.vframe) # We would like to place it where we want self.transform = coin.SoTransform() self.shaded.addChild(self.transform) self.shaded.addChild(self.vframe) self.shaded.addChild(selectionNode) vobj.addDisplayMode(self.shaded, "Shaded") def updateData(self, fp, prop): if prop == "Placement": pl = fp.getPropertyByName("Placement") self.transform.translation = (pl.Base.x, pl.Base.y, pl.Base.z) self.transform.rotation = pl.Rotation.Q def getDisplayModes(self, vobj): modes = ["Shaded"] return modes def getDefaultDisplayMode(self): return "Shaded" def getIcon(self): icondir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", __workbenchname__, "UI", "icons") return str(os.path.join(icondir, "frame.svg")) def onChanged(self, vp, prop): if prop == "Scale": s = vp.getPropertyByName("Scale") self.vframe.scaleFactor.setValue(float(s)) elif prop == "HeadSize": hs = vp.getPropertyByName("HeadSize") xcone = self.vframe.getPart("shape", 0).getPart("xHead.shape", 0) xcone.bottomRadius.setValue(float(hs)) ycone = self.vframe.getPart("shape", 0).getPart("yHead.shape", 0) ycone.bottomRadius.setValue(float(hs)) zcone = self.vframe.getPart("shape", 0).getPart("zHead.shape", 0) zcone.bottomRadius.setValue(float(hs)) elif prop == "LineWidth": lw = vp.getPropertyByName("LineWidth") lwstring = "lineWidth {0}".format(lw) ax = self.vframe.getPart("shape", 0) ax.set("xAxis.appearance.drawStyle", lwstring) ax.set("yAxis.appearance.drawStyle", lwstring) ax.set("zAxis.appearance.drawStyle", lwstring) def __getstate__(self): return None def __setstate__(self, state): pass class ViewProviderPartFrame(ViewProviderFrame): """View provider to the part frame.""" def updateData(self, fp, prop): if prop == "Placement": parentpl = fp.getPropertyByName("Part").Placement localpl = fp.Placement pl = parentpl.multiply(localpl) self.transform.translation = (pl.Base.x, pl.Base.y, pl.Base.z) self.transform.rotation = pl.Rotation.Q class ViewProviderFeatureFrame(ViewProviderFrame): """View provider to the feature frames.""" def updateData(self, fp, prop): if prop == "Placement": parentpl = fp.getPropertyByName("Part").Placement featurepl = fp.getPropertyByName("FeaturePlacement") localpl = fp.Placement pl = parentpl.multiply(featurepl.multiply(localpl)) self.transform.translation = (pl.Base.x, pl.Base.y, pl.Base.z) self.transform.rotation = pl.Rotation.Q ################################################################### # Base functions ################################################################### def makeFrame(placement=FreeCAD.Placement()): obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", "Frame") Frame(obj) if FreeCAD.GuiUp: ViewProviderFrame(obj.ViewObject) return obj def makePartFrame(part): obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", "PartFrame") PartFrame(obj, part) #if int(FreeCAD.Version()[1]) > 16: # geo_feature_group = part.getParentGeoFeatureGroup() # geo_feature_group.addObject(obj) if FreeCAD.GuiUp: ViewProviderPartFrame(obj.ViewObject) return obj def makeFeatureFrame(part, featurepl): obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", "FeatureFrame") FeatureFrame(obj, part, featurepl) # If we're >0.16, add the feature frame to the assembly #if int(FreeCAD.Version()[1]) > 16: # geo_feature_group = part.getParentGeoFeatureGroup() # geo_feature_group.addObject(obj) if FreeCAD.GuiUp: ViewProviderFeatureFrame(obj.ViewObject) return obj def makeSelectedPartFrames(): for part in FreeCADGui.Selection.getSelection(): if isinstance(part, Part.Feature): pf = makePartFrame(part) pf.Label = "Frame"+str(part.Label) def makeAllPartFrames(): dc = FreeCAD.activeDocument() for part in dc.Objects: if isinstance(part, Part.Feature): pf = makePartFrame(part) pf.Label = "Frame"+str(part.Label) def spawnFeatureFrameCreator(): ffpanel = FeatureFramePanel() FreeCADGui.Control.showDialog(ffpanel) ################################################################### # GUI Related ################################################################### uidir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", __workbenchname__, "UI") icondir = os.path.join(uidir, "icons") Tools.spawnClassCommand("FrameCommand", makeFrame, {"Pixmap": str(os.path.join(icondir, "frame.svg")), "MenuText": "Make a free frame", "ToolTip": "Make a freestanding reference frame."}) Tools.spawnClassCommand("ASM4StructureParsing", RobossemblerFreeCadExportScenario().call, {"Pixmap": str(os.path.join(icondir, "assembly4.svg")), "MenuText": "Make a ASM4 parsing", "ToolTip": "Make a ASM4 1"}) Tools.spawnClassCommand("SelectedPartFrameCommand", makeSelectedPartFrames, {"Pixmap": str(os.path.join(icondir, "partframe.svg")), "MenuText": "selected parts frames", "ToolTip": "Make selected parts frames."}) Tools.spawnClassCommand("AllPartFramesCommand", makeAllPartFrames, {"Pixmap": str(os.path.join(icondir, "allpartframes.svg")), "MenuText": "All parts frames", "ToolTip": "Make all parts frames."}) Tools.spawnClassCommand("FeatureFrameCommand", spawnFeatureFrameCreator, {"Pixmap": str(os.path.join(icondir, "featureframecreator.svg")), "MenuText": "frame on selected primitive", "ToolTip": "Create a frame on selected primitive."}) ################################################################### # GUI buttons ################################################################### class FeatureFramePanel: """Spawn panel choices for a feature.""" def __init__(self): selected = FreeCADGui.Selection.getSelectionEx() # Check selection if len(selected) == 1: selected = selected[0] self.selected = selected else: FreeCAD.Console.PrintError("Multipart selection not available.") self.reject() if not selected.HasSubObjects: FreeCAD.Console.PrintError("Part selected not feature.") self.reject() elif not len(selected.SubObjects) == 1: FreeCAD.Console.PrintError("Multifeature selection not available") self.reject() # Choices related to selection so_desc = Tools.describeSubObject(selected.SubObjects[0]) self.so_desc = so_desc shape_choices = { "Vertex": [], "Edge": ["PointOnEdge"], "Face": ["PointOnSurface"] } prim_choices = { "ArcOfCircle": ["Center"], "ArcOfEllipse": ["Center"], "ArcOfHyperBola": ["Center"], "ArcOfParabola": ["Center"], "BSplineCurve": ["Center"], "BezierCurve": ["Center"], "Circle": ["Center"], "Ellipse": ["Center"], "Hyperbola": ["Center"], "Parabola": ["Center"], "Line": [], "BSplineSurface": ["Center"], "BezierSurface": ["Center"], "Cylinder": ["PointOnCenterline"], "Plane": ["Center"], "Sphere": ["Center"], "Toroid": ["Center"], "Cone": ["PointOnCenterline"] } self.choices = ["PickedPoint"] self.choices = self.choices + shape_choices[so_desc[1]] self.choices = self.choices + prim_choices[so_desc[0]] # Setting up QT form uiform_path = os.path.join(uidir, "FeatureFrameCreator.ui") self.form = FreeCADGui.PySideUic.loadUi(uiform_path) self.form.ChoicesBox.addItems(self.choices) self.form.PickedTypeLabel.setText(so_desc[0]) QtCore.QObject.connect(self.form.ChoicesBox, QtCore.SIGNAL("currentIndexChanged(QString)"), self.choiceChanged) self.scenes = {} for choice in self.choices: sc = QtGui.QGraphicsScene() icon = str(os.path.join(icondir, choice+".svg")) sc.addItem(QtSvg.QGraphicsSvgItem(icon)) self.scenes[choice] = sc self.choiceChanged(self.form.ChoicesBox.currentText()) def choiceChanged(self, choice): if choice in self.scenes.keys(): self.form.Preview.setScene(self.scenes[choice]) def accept(self): sel_choice = self.form.ChoicesBox.currentText() paneldict = {"PickedPoint": PickedPointPanel, "PointOnEdge": PointOnEdgePanel, "PointOnSurface": PointOnSurfacePanel, "Center": CenterPanel, "PointOnCenterline": PointOnCenterlinePanel} pan = paneldict[sel_choice](self.selected, self.so_desc) FreeCADGui.Control.closeDialog() # The dialog is actually closed after the accept function has # completed. So we need to use a delayed task to open the new dialog: QtCore.QTimer.singleShot(0, lambda: FreeCADGui.Control.showDialog(pan)) def reject(self): FreeCADGui.Control.closeDialog() class BaseFeaturePanel(object): """Base feature panel to be inherited from.""" def __init__(self, selected, so_desc): # Handle selected and FF placement self.selected = selected self.so_desc = so_desc # Connect offset to spinboxes QtCore.QObject.connect(self.form.XBox, QtCore.SIGNAL("valueChanged(double)"), self.offsetChanged) QtCore.QObject.connect(self.form.YBox, QtCore.SIGNAL("valueChanged(double)"), self.offsetChanged) QtCore.QObject.connect(self.form.ZBox, QtCore.SIGNAL("valueChanged(double)"), self.offsetChanged) QtCore.QObject.connect(self.form.RollBox, QtCore.SIGNAL("valueChanged(double)"), self.offsetChanged) QtCore.QObject.connect(self.form.PitchBox, QtCore.SIGNAL("valueChanged(double)"), self.offsetChanged) QtCore.QObject.connect(self.form.YawBox, QtCore.SIGNAL("valueChanged(double)"), self.offsetChanged) QtCore.QObject.connect(self.form.ScaleBox, QtCore.SIGNAL("valueChanged(double)"), self.scaleChanged) def createFrame(self): self.fframe = makeFeatureFrame(self.selected.Object, self.local_ffpl) self.fframe.PrimitiveType = self.so_desc[0] self.fframe.ShapeType = self.so_desc[1] ad = Tools.getPrimitiveInfo(self.so_desc[0], self.selected.SubObjects[0]) self.fframe.Proxy.additional_data.update(ad) def scaleChanged(self): scale = self.form.ScaleBox.value() self.fframe.ViewObject.Scale = scale def offsetChanged(self): disp = FreeCAD.Vector(self.form.XBox.value(), self.form.YBox.value(), self.form.ZBox.value()) rot = FreeCAD.Rotation(self.form.YawBox.value(), self.form.PitchBox.value(), self.form.RollBox.value()) offset = FreeCAD.Placement(disp, rot) self.fframe.Placement = offset def accept(self): framelabel = self.form.FrameLabelField.toPlainText() if not len(framelabel) == 0: self.fframe.Label = framelabel FreeCADGui.Control.closeDialog() def reject(self): FreeCAD.activeDocument().removeObject(self.fframe.Name) FreeCADGui.Control.closeDialog() class PickedPointPanel(BaseFeaturePanel): """Create a feature frame at the picked point.""" # Not very clever. It just places the frame with default rotation. def __init__(self, selected, so_desc): uiform_path = os.path.join(uidir, "FramePlacer.ui") self.form = FreeCADGui.PySideUic.loadUi(uiform_path) BaseFeaturePanel.__init__(self, selected, so_desc) parent_pl = selected.Object.Placement abs_pl = FreeCAD.Placement(selected.PickedPoints[0], FreeCAD.Rotation()) self.local_ffpl = parent_pl.inverse().multiply(abs_pl) self.createFrame() self.fframe.Positioning = "PickedPoint" class PointOnEdgePanel(BaseFeaturePanel): """Create a feature frame on an edge.""" def __init__(self, selected, so_desc): uiform_path = os.path.join(uidir, "FramePlacer.ui") self.form = FreeCADGui.PySideUic.loadUi(uiform_path) # Enable the first parameter self.form.VLabel.setEnabled(True) self.form.VLabel.setVisible(True) self.form.VLabel.setText("u") self.form.VBox.setEnabled(True) self.form.VBox.setVisible(True) QtCore.QObject.connect(self.form.VBox, QtCore.SIGNAL("valueChanged(double)"), self.parameterChanged) # Enable percentage or param selection self.form.OptionsLabel.setEnabled(True) self.form.OptionsLabel.setVisible(True) self.form.OptionsLabel.setText("Arc param.") self.form.OptionsBox.setEnabled(True) self.form.OptionsBox.setVisible(True) self.form.OptionsBox.addItems(["mm", "%"]) QtCore.QObject.connect(self.form.OptionsBox, QtCore.SIGNAL("currentIndexChanged(QString)"), self.choiceChanged) BaseFeaturePanel.__init__(self, selected, so_desc) # Place the frame wherever the values are atm self.local_ffpl = FreeCAD.Placement() self.createFrame() self.fframe.Positioning = "PointOnEdge" self.choiceChanged(self.form.OptionsBox.currentText()) self.parameterChanged() def parameterChanged(self): value = self.form.VBox.value() if self.form.OptionsBox.currentText() == "%": value = self.p2mm(value) edge = self.selected.SubObjects[0] point = edge.valueAt(value) tangentdir = edge.tangentAt(value) rot = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), tangentdir) abs_ffpl = FreeCAD.Placement(point, rot) parent_pl = self.selected.Object.Placement self.local_ffpl = parent_pl.inverse().multiply(abs_ffpl) self.fframe.FeaturePlacement = self.local_ffpl # force recompute of placement? self.fframe.Placement = self.fframe.Placement def choiceChanged(self, choice): value = self.form.VBox.value() if choice == "mm": value = self.p2mm(value) self.form.VBox.setSuffix("mm") parameter_range = self.selected.SubObjects[0].ParameterRange self.form.VBox.setRange(*parameter_range) self.form.VBox.setSingleStep(0.1) elif choice == "%": value = self.mm2p(value) self.form.VBox.setSuffix("%") self.form.VBox.setRange(0, 100.0) self.form.VBox.setSingleStep(1.0) self.form.VBox.setValue(value) def p2mm(self, value): parameter_range = self.selected.SubObjects[0].ParameterRange delta = parameter_range[1] - parameter_range[0] return 0.01*value*delta + parameter_range[0] def mm2p(self, value): parameter_range = self.selected.SubObjects[0].ParameterRange delta = parameter_range[1] - parameter_range[0] return 100.0*(value - parameter_range[0])/delta class PointOnSurfacePanel(BaseFeaturePanel): """Create a feature on a surface.""" def __init__(self, selected, so_desc): uiform_path = os.path.join(uidir, "FramePlacer.ui") self.form = FreeCADGui.PySideUic.loadUi(uiform_path) # Enable both parameters self.form.ULabel.setVisible(True) self.form.VLabel.setVisible(True) self.form.UBox.setVisible(True) self.form.VBox.setVisible(True) QtCore.QObject.connect(self.form.VBox, QtCore.SIGNAL("valueChanged(double)"), self.parameterChanged) QtCore.QObject.connect(self.form.UBox, QtCore.SIGNAL("valueChanged(double)"), self.parameterChanged) # Enable percentage or param selection self.form.OptionsLabel.setEnabled(True) self.form.OptionsLabel.setVisible(True) self.form.OptionsLabel.setText("Surf. param.") self.form.OptionsBox.setEnabled(True) self.form.OptionsBox.setVisible(True) self.form.OptionsBox.addItems(["mm", "%"]) QtCore.QObject.connect(self.form.OptionsBox, QtCore.SIGNAL("currentIndexChanged(QString)"), self.choiceChanged) BaseFeaturePanel.__init__(self, selected, so_desc) # Place the frame wherever the values are atm self.local_ffpl = FreeCAD.Placement() self.createFrame() self.fframe.Positioning = "PointOnSurface" self.choiceChanged(self.form.OptionsBox.currentText()) self.parameterChanged() def parameterChanged(self): value = (self.form.UBox.value(), self.form.VBox.value()) if self.form.OptionsBox.currentText() == "%": value = self.p2mm(value) face = self.selected.SubObjects[0] point = face.valueAt(*value) normaldir = face.normalAt(*value) rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), normaldir) abs_ffpl = FreeCAD.Placement(point, rotation) parent_pl = self.selected.Object.Placement self.local_ffpl = parent_pl.inverse().multiply(abs_ffpl) self.fframe.FeaturePlacement = self.local_ffpl # Force recompute of placement self.fframe.Placement = self.fframe.Placement def choiceChanged(self, choice): value = (self.form.UBox.value(), self.form.VBox.value()) if choice == "mm": value = self.p2mm(value) parameter_range = self.selected.SubObjects[0].ParameterRange self.form.UBox.setRange(parameter_range[0], parameter_range[1]) self.form.UBox.setSuffix("mm") self.form.UBox.setSingleStep(0.1) self.form.VBox.setRange(parameter_range[2], parameter_range[3]) self.form.VBox.setSuffix("mm") self.form.VBox.setSingleStep(0.1) elif choice == "%": value = self.mm2p(value) self.form.UBox.setRange(0.0, 100.0) self.form.UBox.setSuffix("%") self.form.VBox.setRange(0.0, 100.0) self.form.UBox.setSingleStep(1.0) self.form.VBox.setSuffix("%") self.form.VBox.setSingleStep(1.0) self.form.UBox.setValue(value[0]) self.form.VBox.setValue(value[1]) def p2mm(self, value): parameter_range = self.selected.SubObjects[0].ParameterRange delta = [parameter_range[1] - parameter_range[0], parameter_range[3] - parameter_range[2]] u = 0.01*value[0]*delta[0] + parameter_range[0] v = 0.01*value[1]*delta[1] + parameter_range[2] return (u, v) def mm2p(self, value): parameter_range = self.selected.SubObjects[0].ParameterRange delta = [parameter_range[1] - parameter_range[0], parameter_range[3] - parameter_range[2]] u = 100.0*(value[0] - parameter_range[0])/delta[0] v = 100.0*(value[1] - parameter_range[2])/delta[1] return (u, v) class CenterPanel(BaseFeaturePanel): """Create a feature frame on center.""" def __init__(self, selected, so_desc): uiform_path = os.path.join(uidir, "FramePlacer.ui") self.form = FreeCADGui.PySideUic.loadUi(uiform_path) BaseFeaturePanel.__init__(self, selected, so_desc) edge_curve_list = ["ArcOfCircle", "ArcOfEllipse", "ArcOfHyperbola", "ArcOfParabola", "Circle", "Ellipse", "Hyperbola", "Parabola"] face_surf_list = ["Sphere", "Toroid"] if so_desc[0] in edge_curve_list: edge = selected.SubObjects[0] axis = edge.Curve.Axis rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), axis) center_point = edge.Curve.Center elif so_desc[0] in face_surf_list: face = selected.SubObjects[0] axis = face.Surface.Axis rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), axis) center_point = face.Surface.Center else: rotation = FreeCAD.Rotation() center_point = selected.SubObjects[0].CenterOfMass parent_pl = selected.Object.Placement abs_pl = FreeCAD.Placement(center_point, rotation) self.local_ffpl = parent_pl.inverse().multiply(abs_pl) self.createFrame() self.fframe.Positioning = "Center" class PointOnCenterlinePanel(BaseFeaturePanel): """Create a point on centerline of primitive.""" def __init__(self, selected, so_desc): uiform_path = os.path.join(uidir, "FramePlacer.ui") self.form = FreeCADGui.PySideUic.loadUi(uiform_path) BaseFeaturePanel.__init__(self, selected, so_desc) # Enable the along line parameter self.form.VLabel.setVisible(True) self.form.VLabel.setText("u") self.form.VBox.setVisible(True) QtCore.QObject.connect(self.form.VBox, QtCore.SIGNAL("valueChanged(double)"), self.parameterChanged) # Enable percentage of param selection self.form.OptionsLabel.setVisible(True) self.form.OptionsLabel.setText("Line param.") self.form.OptionsBox.setVisible(True) self.form.OptionsBox.addItems(["mm", "%"]) QtCore.QObject.connect(self.form.OptionsBox, QtCore.SIGNAL("currentIndexChanged(QString)"), self.choiceChanged) # Place the frame wherever the values are atm self.local_ffpl = FreeCAD.Placement() self.createFrame() self.fframe.Positioning = "PointOnCenterline" self.parameterChanged() def parameterChanged(self): value = self.form.VBox.value() if self.form.OptionsBox.currentText() == "%": value = self.p2mm(value) displacement_pl = FreeCAD.Placement(FreeCAD.Vector(0, 0, value), FreeCAD.Rotation()) # Find the center axis = self.selected.SubObjects[0].Surface.Axis rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), axis) center_point = self.selected.SubObjects[0].Surface.Center center_pl = FreeCAD.Placement(center_point, rotation) abs_ffpl = center_pl.multiply(displacement_pl) parent_pl = self.selected.Object.Placement self.local_ffpl = parent_pl.inverse().multiply(abs_ffpl) self.fframe.FeaturePlacement = self.local_ffpl # force recompute of placement self.fframe.Placement = self.fframe.Placement def choiceChanged(self, choice): FreeCAD.Console.PrintMessage("choiceChanged\n") value = self.form.VBox.value() FreeCAD.Console.PrintMessage("preval:"+str(value)+"\n") if choice == "mm": value = self.p2mm(value) self.form.VBox.setSuffix("mm") parameter_range = self.selected.SubObjects[0].ParameterRange[2:] self.form.VBox.setRange(*parameter_range) self.form.VBox.setSingleStep(0.1) elif choice == "%": value = self.mm2p(value) self.form.VBox.setSuffix("%") self.form.VBox.setRange(0, 100) self.form.VBox.setSingleStep(1.0) self.form.VBox.setValue(value) FreeCAD.Console.PrintMessage("postval:"+str(value)+"\n") def p2mm(self, value): parameter_range = self.selected.SubObjects[0].ParameterRange[2:] delta = parameter_range[1] - parameter_range[0] return 0.01*value*delta + parameter_range[0] def mm2p(self, value): parameter_range = self.selected.SubObjects[0].ParameterRange[2:] delta = parameter_range[1] - parameter_range[0] return 100.0*(value - parameter_range[0])/delta