Start new implementation of holding tags, just called Tag.

This commit is contained in:
Markus Lampert
2017-06-25 19:01:12 -07:00
committed by wmayer
parent 92efb41844
commit 900e9e0dcf
5 changed files with 443 additions and 18 deletions

View File

@@ -29,6 +29,8 @@ SET(PathScripts_SRCS
PathScripts/PathDressupDragknife.py
PathScripts/PathDressupHoldingTags.py
PathScripts/PathDressupRampEntry.py
PathScripts/PathDressupTag.py
PathScripts/PathDressupTagGui.py
PathScripts/PathDressupTagPreferences.py
PathScripts/PathDrilling.py
PathScripts/PathEngrave.py

View File

@@ -53,6 +53,7 @@ class PathWorkbench (Workbench):
from PathScripts import PathDressupDragknife
from PathScripts import PathDressupHoldingTags
from PathScripts import PathDressupRampEntry
from PathScripts import PathDressupTagGui
from PathScripts import PathDrilling
from PathScripts import PathEngrave
from PathScripts import PathFacePocket
@@ -86,7 +87,7 @@ class PathWorkbench (Workbench):
twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace", "Path_Helix"]
threedopcmdlist = ["Path_Surfacing"]
modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ]
dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags", "PathDressup_RampEntry"]
dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags", "PathDressup_Tag", "PathDressup_RampEntry"]
extracmdlist = ["Path_SelectLoop", "Path_Shape", "Path_Area", "Path_Area_Workplane", "Path_Stock"]
#modcmdmore = ["Path_Hop",]
#remotecmdlist = ["Path_Remote"]

View File

@@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * *
# * 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
import Part
import Path
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferencesPathDressup as PathPreferencesPathDressup
import math
import sys
from PathScripts.PathDressupTagPreferences import HoldingTagPreferences
from PathScripts.PathGeom import PathGeom
from PySide import QtCore
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule()
# Qt tanslation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class TagSolid:
def __init__(self, proxy, z, R):
self.proxy = proxy
self.z = z
self.toolRadius = R
self.angle = math.fabs(proxy.obj.Angle)
self.width = math.fabs(proxy.obj.Width)
self.height = math.fabs(proxy.obj.Height)
self.radius = math.fabs(proxy.obj.Radius)
self.actualHeight = self.height
self.fullWidth = 2 * self.toolRadius + self.width
r1 = self.fullWidth / 2
self.r1 = r1
self.r2 = r1
height = self.actualHeight * 1.01
radius = 0
if self.angle == 90 and height > 0:
# cylinder
self.solid = Part.makeCylinder(r1, height)
radius = min(min(self.radius, r1), self.height)
PathLog.debug("Part.makeCylinder(%f, %f)" % (r1, height))
elif self.angle > 0.0 and height > 0.0:
# cone
rad = math.radians(self.angle)
tangens = math.tan(rad)
dr = height / tangens
if dr < r1:
# with top
r2 = r1 - dr
s = height / math.sin(rad)
radius = min(r2, s) * math.tan((math.pi - rad)/2) * 0.95
else:
# triangular
r2 = 0
height = r1 * tangens * 1.01
self.actualHeight = height
self.r2 = r2
PathLog.debug("Part.makeCone(r1=%.2f, r2=%.2f, h=%.2f)" % (r1, r2, height))
self.solid = Part.makeCone(r1, r2, height)
else:
# degenerated case - no tag
PathLog.debug("Part.makeSphere(%.2f)" % (r1 / 10000))
self.solid = Part.makeSphere(r1 / 10000)
radius = min(self.radius, radius)
self.realRadius = radius
if radius != 0:
PathLog.debug("makeFillet(%.4f)" % radius)
self.solid = self.solid.makeFillet(radius, [self.solid.Edges[0]])
#lastly determine the center of the model, we want to make sure the seam of
# the tag solid points away (in the hopes it doesn't coincide with a path)
self.baseCenter = FreeCAD.Vector((proxy.ptMin.x+proxy.ptMax.x)/2, (proxy.ptMin.y+proxy.ptMax.y)/2, 0)
def cloneAt(self, pos):
clone = self.solid.copy()
pos.z = 0
angle = -PathGeom.getAngle(pos - self.baseCenter) * 180 / math.pi
clone.rotate(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), angle)
pos.z = self.z - self.actualHeight * 0.01
clone.translate(pos)
return clone
class ObjectDressup(QtCore.QObject):
changed = QtCore.Signal()
def __init__(self, obj):
obj.Proxy = self
self.obj = obj
obj.addProperty('App::PropertyLink', 'Base','Base', QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'The base path to modify'))
obj.addProperty('App::PropertyLength', 'Width', 'Tag', QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'Width of tags.'))
obj.addProperty('App::PropertyLength', 'Height', 'Tag', QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'Height of tags.'))
obj.addProperty('App::PropertyAngle', 'Angle', 'Tag', QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'Angle of tag plunge and ascent.'))
obj.addProperty('App::PropertyLength', 'Radius', 'Tag', QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'Radius of the fillet for the tag.'))
obj.addProperty('App::PropertyVectorList', 'Positions', 'Tag', QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'Locations of insterted holding tags'))
obj.addProperty('App::PropertyIntegerList', 'Disabled', 'Tag', QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'Ids of disabled holding tags'))
obj.addProperty('App::PropertyInteger', 'SegmentationFactor', 'Tag', QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'Factor determining the # segments used to approximate rounded tags.'))
obj.addProperty('App::PropertyLink', 'Debug', 'Debug', QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'Some elements for debugging'))
if PathLog.getLevel(PathLog.thisModule()) != PathLog.Level.DEBUG:
obj.setEditorMode('Debug', 2) # hide
dbg = obj.Document.addObject('App::DocumentObjectGroup', 'TagDebug')
obj.Debug = dbg
self.solids = []
super(ObjectDressup, self).__init__()
def __getstate__(self):
return None
def __setstate__(self, state):
return None
def assignDefaultValues(self):
self.obj.Width = HoldingTagPreferences.defaultWidth(self.toolRadius() * 2)
self.obj.Height = HoldingTagPreferences.defaultHeight(self.toolRadius())
self.obj.Angle = HoldingTagPreferences.defaultAngle()
self.obj.Radius = HoldingTagPreferences.defaultRadius()
def execute(self, obj):
PathLog.track()
if not obj.Base:
PathLog.error(translate('PathDressup_Tag', 'No Base object found.'))
return
if not obj.Base.isDerivedFrom('Path::Feature'):
PathLog.error(translate('PathDressup_Tag', 'Base is not a Path::Feature object.'))
return
if not obj.Base.Path:
PathLog.error(translate('PathDressup_Tag', 'Base doesn\'t have a Path to dress-up.'))
return
if not obj.Base.Path.Commands:
PathLog.error(translate('PathDressup_Tag', 'Base Path is empty.'))
return
self.obj = obj;
minZ = sys.maxint
minX = minZ
minY = minZ
maxZ = -sys.maxint
maxX = maxZ
maxY = maxZ
# the assumption is that all helixes are in the xy-plane - in other words there is no
# intermittent point of a command that has a lower/higer Z-position than the start and
# and end positions of a command.
lastPt = FreeCAD.Vector(0, 0, 0)
for cmd in obj.Base.Path.Commands:
pt = PathGeom.commandEndPoint(cmd, lastPt)
if lastPt.x != pt.x:
maxX = max(pt.x, maxX)
minX = min(pt.x, minX)
if lastPt.y != pt.y:
maxY = max(pt.y, maxY)
minY = min(pt.y, minY)
if lastPt.z != pt.z:
maxZ = max(pt.z, maxZ)
minZ = min(pt.z, minZ)
lastPt = pt
PathLog.debug("bb = (%.2f, %.2f, %.2f) ... (%.2f, %.2f, %.2f)" % (minX, minY, minZ, maxX, maxY, maxZ))
self.ptMin = FreeCAD.Vector(minX, minY, minZ)
self.ptMax = FreeCAD.Vector(maxX, maxY, maxZ)
self.masterSolid = TagSolid(self, minZ, self.toolRadius())
self.solids = [self.masterSolid.cloneAt(pos) for pos in self.obj.Positions]
self.changed.emit()
PathLog.track()
def toolRadius(self):
return self.obj.Base.ToolController.Tool.Diameter / 2.0
def addTagsToDocuemnt(self):
for i, solid in enumerate(self.solids):
obj = FreeCAD.ActiveDocument.addObject('Part::Compound', "tag_%02d" % i)
obj.Shape = solid
PathLog.notice('Loading PathDressupTag... done\n')

View File

@@ -0,0 +1,232 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * *
# * 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
import FreeCADGui
import PathScripts.PathLog as PathLog
import PathScripts.PathUtils as PathUtils
from PathScripts.PathPreferences import PathPreferences
from PySide import QtCore
from pivy import coin
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule()
# Qt tanslation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class HoldingTagMarker:
def __init__(self, point, colors):
self.point = point
self.color = colors
self.sep = coin.SoSeparator()
self.pos = coin.SoTranslation()
self.pos.translation = (point.x, point.y, point.z)
self.sphere = coin.SoSphere()
self.scale = coin.SoType.fromName('SoShapeScale').createInstance()
self.scale.setPart('shape', self.sphere)
self.scale.scaleFactor.setValue(7)
self.material = coin.SoMaterial()
self.sep.addChild(self.pos)
self.sep.addChild(self.material)
self.sep.addChild(self.scale)
self.enabled = True
self.selected = False
def setSelected(self, select):
self.selected = select
self.sphere.radius = 1.5 if select else 1.0
self.setEnabled(self.enabled)
def setEnabled(self, enabled):
self.enabled = enabled
if enabled:
self.material.diffuseColor = self.color[0] if not self.selected else self.color[2]
self.material.transparency = 0.0
else:
self.material.diffuseColor = self.color[1] if not self.selected else self.color[2]
self.material.transparency = 0.6
class PathDressupTagViewProvider:
def __init__(self, vobj):
PathLog.track()
vobj.Proxy = self
self.vobj = vobj
self.panel = None
def setupColors(self):
def colorForColorValue(val):
v = [((val >> n) & 0xff) / 255. for n in [24, 16, 8, 0]]
return coin.SbColor(v[0], v[1], v[2])
pref = PathPreferences.preferences()
# R G B A
npc = pref.GetUnsigned('DefaultPathMarkerColor', (( 85*256 + 255)*256 + 0)*256 + 255)
hpc = pref.GetUnsigned('DefaultHighlightPathColor', ((255*256 + 125)*256 + 0)*256 + 255)
dpc = pref.GetUnsigned('DefaultDisabledPathColor', ((205*256 + 205)*256 + 205)*256 + 154)
self.colors = [colorForColorValue(npc), colorForColorValue(dpc), colorForColorValue(hpc)]
def attach(self, vobj):
PathLog.track()
self.setupColors()
self.obj = vobj.Object
self.tags = []
self.switch = coin.SoSwitch()
vobj.RootNode.addChild(self.switch)
self.turnMarkerDisplayOn(False)
if self.obj and self.obj.Base:
for i in self.obj.Base.InList:
if hasattr(i, 'Group') and self.obj.Base.Name in [o.Name for o in i.Group]:
i.Group = [o for o in i.Group if o.Name != self.obj.Base.Name]
if self.obj.Base.ViewObject:
PathLog.info("Setting visibility for %s" % (self.obj.Base.Name))
self.obj.Base.ViewObject.Visibility = False
else:
PathLog.info("Ignoring visibility")
if PathLog.getLevel(PathLog.thisModule()) != PathLog.Level.DEBUG and self.obj.Debug.ViewObject:
self.obj.Debug.ViewObject.Visibility = False
self.obj.Proxy.changed.connect(self.onModelChanged)
def turnMarkerDisplayOn(self, display):
sw = coin.SO_SWITCH_ALL if display else coin.SO_SWITCH_NONE
self.switch.whichChild = sw
def claimChildren(self):
PathLog.track()
return [self.obj.Base, self.obj.Debug]
def onDelete(self, arg1=None, arg2=None):
PathLog.track()
'''this makes sure that the base operation is added back to the project and visible'''
if self.obj.Base.ViewObject:
self.obj.Base.ViewObject.Visibility = True
PathUtils.addToJob(arg1.Object.Base)
self.obj.Debug.removeObjectsFromDocument()
self.obj.Debug.Document.removeObject(self.obj.Debug.Name)
self.obj.Debug = None
return True
def updateData(self, obj, propName):
PathLog.track(propName)
if 'Disabled' == propName:
for tag in self.tags:
self.switch.removeChild(tag.sep)
tags = []
for i, p in enumerate(obj.Positions):
tag = HoldingTagMarker(p, self.colors)
tag.setEnabled(not i in obj.Disabled)
tags.append(tag)
self.switch.addChild(tag.sep)
self.tags = tags
def onModelChanged(self):
PathLog.track()
self.obj.Debug.removeObjectsFromDocument()
for solid in self.obj.Proxy.solids:
tag = self.obj.Document.addObject('Part::Feature', 'tag')
tag.Shape = solid
if tag.ViewObject and self.obj.Debug.ViewObject:
tag.ViewObject.Visibility = self.obj.Debug.ViewObject.Visibility
tag.ViewObject.Transparency = 80
self.obj.Debug.addObject(tag)
tag.purgeTouched()
def selectTag(self, index):
PathLog.track(index)
for i, tag in enumerate(self.tags):
tag.setSelected(i == index)
def tagAtPoint(self, point):
p = FreeCAD.Vector(point[0], point[1], point[2])
for i, tag in enumerate(self.tags):
if PathGeom.pointsCoincide(p, tag.point, tag.sphere.radius.getValue() * 1.1):
return i
return -1
# SelectionObserver interface
def allow(self, doc, obj, sub):
if obj == self.obj:
return True
return False
def addSelection(self, doc, obj, sub, point):
i = self.tagAtPoint(point)
if self.panel:
self.panel.selectTagWithId(i)
FreeCADGui.updateGui()
class CommandPathDressupTag:
def GetResources(self):
return {'Pixmap': 'Path-Dressup',
'MenuText': QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'Tag Dress-up'),
'ToolTip': QtCore.QT_TRANSLATE_NOOP('PathDressup_Tag', 'Creates a Tag Dress-up object from a selected path')}
def IsActive(self):
if FreeCAD.ActiveDocument is not None:
for o in FreeCAD.ActiveDocument.Objects:
if o.Name[:3] == 'Job':
return True
return False
def Activated(self):
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1:
PathLog.error(translate('PathDressup_Tag', 'Please select one path object\n'))
return
baseObject = selection[0]
if not baseObject.isDerivedFrom('Path::Feature'):
PathLog.error(translate('PathDressup_Tag', 'The selected object is not a path\n'))
return
if baseObject.isDerivedFrom('Path::FeatureCompoundPython'):
PathLog.error(translate('PathDressup_Tag', 'Please select a Profile object'))
return
# everything ok!
FreeCAD.ActiveDocument.openTransaction(translate('PathDressup_Tag', 'Create Tag Dress-up'))
FreeCADGui.addModule('PathScripts.PathDressupTag')
FreeCADGui.addModule('PathScripts.PathDressupTagGui')
FreeCADGui.addModule('PathScripts.PathUtils')
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "TagDressup")')
FreeCADGui.doCommand('dbo = PathScripts.PathDressupTag.ObjectDressup(obj)')
FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name)
FreeCADGui.doCommand('PathScripts.PathDressupTagGui.PathDressupTagViewProvider(obj.ViewObject)')
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
FreeCADGui.doCommand('dbo.assignDefaultValues()')
FreeCADGui.doCommand('obj.Positions = [App.Vector(-10, -10, 0), App.Vector(10, 10, 0)]')
FreeCADGui.doCommand('dbo.execute(obj)')
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('PathDressup_Tag', CommandPathDressupTag())
PathLog.notice('Loading PathDressupTagGui... done\n')

View File

@@ -24,15 +24,16 @@
'''PathUtils -common functions used in PathScripts for filterig, sorting, and generating gcode toolpath data '''
import FreeCAD
import FreeCADGui
import Part
import math
from DraftGeomUtils import geomType
import PathScripts
from PathScripts import PathJob
import numpy
from PathScripts import PathLog
from FreeCAD import Vector
import Part
import Path
import PathScripts
from DraftGeomUtils import geomType
from FreeCAD import Vector
from PathScripts import PathJob
from PathScripts import PathLog
from PySide import QtCore
from PySide import QtGui
@@ -455,13 +456,12 @@ def addToJob(obj, jobname=None):
if len(jobs) == 1:
job = jobs[0]
else:
FreeCAD.Console.PrintError("Didn't find the job")
PathLog.error("Job %s does not exist" % jobname)
return None
else:
jobs = GetJobs()
if len(jobs) == 0:
job = PathJob.CommandJob.Create()
elif len(jobs) == 1:
job = jobs[0]
else:
@@ -736,15 +736,6 @@ def guessDepths(objshape, subs=None):
return depth_params(clearance, safe, start, 1.0, 0.0, final, user_depths=None, equalstep=False)
def drillTipLength(tool):
"""returns the length of the drillbit tip.
"""
if tool.CuttingEdgeAngle == 0.0 or tool.Diameter == 0.0:
return 0.0
else:
theta = math.radians(tool.CuttingEdgeAngle)
return (tool.Diameter/2) / math.tan(theta)
class depth_params:
'''calculates the intermediate depth values for various operations given the starting, ending, and stepdown parameters