Import LinuxCNC source tree

This commit is contained in:
Wang Dequan
2026-08-20 06:17:39 +08:00
commit 2c814634c3
9429 changed files with 10556013 additions and 0 deletions

422
lib/python/rs274/OpenGLTk.py Executable file
View File

@@ -0,0 +1,422 @@
#!/usr/bin/env python3
# This file is from PyOpenGL-2.0.1.07. That distribution's license is
# """License :: OSI Approved :: BSD License""",
# A class that creates an opengl widget.
# Mike Hartshorn
# Department of Chemistry
# University of York, UK
#
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import os
import _togl
import glnav
import itertools
from tkinter import _default_root
from tkinter import *
# Keith Junius <junius@chem.rug.nl> provided many changes to Togl
TOGL_NORMAL = 1
TOGL_OVERLAY = 2
class Togl(Widget):
"""
Togl Widget
Keith Junius
Department of Biophysical Chemistry
University of Groningen, The Netherlands
Very basic widget which provides access to Togl functions.
N.B. this requires a modified version of Togl 1.5 to gain access to the
extra functionality. This support should be included in Togl 1.6, I hope.
"""
def __init__(self, master=None, cnf={}, **kw):
_togl.install(master.tk)
Widget.__init__(self, master, 'togl', cnf, kw)
def render(self):
return
def swapbuffers(self):
self.tk.call(self._w, 'swapbuffers')
def makecurrent(self):
self.tk.call(self._w, 'makecurrent')
def alloccolor(self, red, green, blue):
return self.tk.getint(self.tk.call(self._w, 'alloccolor', red, green, blue))
def freecolor(self, index):
self.tk.call(self._w, 'freecolor', index)
def setcolor(self, index, red, green, blue):
self.tk.call(self._w, 'setcolor', index, red, green, blue)
def loadbitmapfont(self, fontname):
return self.tk.getint(self.tk.call(self._w, 'loadbitmapfont', fontname))
def unloadbitmapfont(self, fontbase):
self.tk.call(self._w, 'unloadbitmapfont', fontbase)
def uselayer(self, layer):
self.tk.call(self._w, 'uselayer', layer)
def showoverlay(self):
self.tk.call(self._w, 'showoverlay')
def hideoverlay(self):
self.tk.call(self._w, 'hideoverlay')
def existsoverlay(self):
return self.tk.getboolean(self.tk.call(self._w, 'existsoverlay'))
def getoverlaytransparentvalue(self):
return self.tk.getint(self.tk.call(self._w, 'getoverlaytransparentvalue'))
def ismappedoverlay(self):
return self.tk.getboolean(self.tk.call(self._w, 'ismappedoverlay'))
def alloccoloroverlay(self, red, green, blue):
return self.tk.getint(self.tk.call(self._w, 'alloccoloroverlay', red, green, blue))
def freecoloroverlay(self, index):
self.tk.call(self._w, 'freecoloroverlay', index)
class RawOpengl(Togl, Misc):
"""Widget without any sophisticated bindings\
by Tom Schwaller"""
def __init__(self, master=None, cnf={}, **kw):
Togl.__init__(self, master, cnf, **kw)
self.bind('<Map>', self.tkMap)
self.bind('<Expose>', self.tkExpose)
self.bind('<Configure>', self.tkExpose)
def tkRedraw(self, *dummy):
# This must be outside of a pushmatrix, since a resize event
# will call redraw recursively.
self.update_idletasks()
self.tk.call(self._w, 'makecurrent')
_mode = glGetInteger(GL_MATRIX_MODE)
try:
glMatrixMode(GL_PROJECTION)
glPushMatrix()
try:
self.redraw()
glFlush()
finally:
glPopMatrix()
finally:
glMatrixMode(_mode)
self.swapbuffers()
def tkMap(self, *dummy):
self.tkExpose()
def tkExpose(self, *dummy):
self.tkRedraw()
class Opengl(RawOpengl, glnav.GlNavBase):
"""\
Tkinter bindings for an Opengl widget.
Mike Hartshorn
Department of Chemistry
University of York, UK
http://www.yorvic.york.ac.uk/~mjh/
"""
rotation_vectors = [(1.,0.,0.), (0., 0., 1.)]
def __init__(self, master=None, cnf={}, **kw):
"""\
Create an opengl widget.
Arrange for redraws when the window is exposed or when
it changes size."""
#Widget.__init__(self, master, 'togl', cnf, kw)
RawOpengl.__init__(*(self, master, cnf), **kw)
glnav.GlNavBase.__init__(self)
# Basic bindings for the virtual trackball
self.bind('<Map>', self.tkMap)
self.bind('<Expose>', self.tkExpose)
self.bind('<Configure>', self.tkExpose)
self.bind('<Shift-Button-1>', self.tkHandlePick)
self.bind('<Button-2>', self.tkStartRotate)
self.bind("<B2-Motion>", self.tkRotateOrTranslate)
self.bind('<ButtonRelease-2>', self.tkAutoSpin)
self.bind('<Button-3>', self.tkRecordMouse)
self.bind('<B3-Motion>', self.tkScale)
self.bind('<Button-4>', self.tkZoomin)
self.bind('<Button-5>', self.tkZoomout)
self.bind('<MouseWheel>', self.zoomwheel)
self.bind('<Button-1>', self.tkStartRotate)
self.bind('<Button1-Motion>', self.tkTranslateOrRotate)
self.bind("<Shift-Button-1>", self.tkStartRotate)
self.bind("<Shift-B1-Motion>", self.tkRotateOrTranslate)
self.bind("<Control-Button-1>", self.tkStartZoom)
self.bind("<Control-B1-Motion>", self.tkContinueZoom)
self.bind("<Button-3>", self.tkStartZoom)
self.bind("<B3-Motion>", self.tkContinueZoom)
self.lat = 0
self.minlat = -90
self.maxlat = 0
self.lon = 0
# Is the widget allowed to autospin?
self.autospin_allowed = 0
# Is the widget currently autospinning?
self.autospin = 0
self.initialised = 0
def help(self):
"""Help for the widget."""
import tkinter.dialog
d = tkinter.dialog.Dialog(None, {'title': 'Viewer help',
'text': 'Button-1: Translate\n'
'Button-2: Rotate\n'
'Button-3: Zoom\n'
'Reset: Resets transformation to identity\n',
'bitmap': 'questhead',
'default': 0,
'strings': ('Done', 'Ok')})
def deactivate(self):
pass
def activate(self):
"""Cause this Opengl widget to be the current destination for drawing."""
self.tk.call(self._w, 'makecurrent')
def basic_lighting(self):
"""\
Set up some basic lighting (single infinite light source).
Also switch on the depth buffer.
The fixed-function calls live here rather than in glnav because this
widget is the one that owns a compatibility context; glnav's base
method is the GL-free camera reset, invoked after the GL setup to keep
the legacy order (activate, GL state, camera reset)."""
self.activate()
glLightfv(GL_LIGHT0, GL_POSITION, (1, -1, 1, 0))
glLightfv(GL_LIGHT0, GL_AMBIENT, (.4, .4, .4, 1))
glLightfv(GL_LIGHT0, GL_DIFFUSE, (.6, .6, .6, 1))
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glDepthFunc(GL_LESS)
glEnable(GL_DEPTH_TEST)
super().basic_lighting()
def tkHandlePick(self, event):
"""Handle a pick on the scene."""
if hasattr(self, 'pick'):
# here we need to use glu.UnProject
# Tk and X have their origin top left,
# while Opengl has its origin bottom left.
# So we need to subtract y from the window height to get
# the proper pick position for Opengl
realy = self.winfo_height() - event.y
p1 = gluUnProject(event.x, realy, 0.)
p2 = gluUnProject(event.x, realy, 1.)
if self.pick(self, p1, p2):
"""If the pick method returns true we redraw the scene."""
self.tkRedraw()
def tkRecordMouse(self, event):
"""Record the current mouse position."""
self.recordMouse(event.x, event.y)
def tkStartRotate(self, event):
# Switch off any autospinning if it was happening
self.autospin = 0
self.tkRecordMouse(event)
def tkScale(self, event):
self.scale(event.x, event.y)
def do_AutoSpin(self):
s = 0.5
self.activate()
glnav.glRotateScene(self, 0.5, self.xcenter, self.ycenter, self.zcenter, self.yspin, self.xspin, 0, 0)
self.tkRedraw()
if self.autospin:
self.after(10, self.do_AutoSpin)
def tkAutoSpin(self, event):
"""Perform autospin of scene."""
self.after(4)
self.update_idletasks()
# This could be done with one call to pointerxy but I'm not sure
# it would any quicker as we would have to split up the resulting
# string and then conv
x = self.tk.getint(self.tk.call('winfo', 'pointerx', self._w))
y = self.tk.getint(self.tk.call('winfo', 'pointery', self._w))
if self.autospin_allowed:
if x != event.x_root and y != event.y_root:
self.autospin = 1
self.yspin = x - event.x_root
self.xspin = y - event.y_root
self.after(10, self.do_AutoSpin)
def tkRotate(self, event):
self.rotate(event.x, event.y)
def tkTranslate(self, event):
self.translate(event.x, event.y)
def tkTranslateOrRotate(self, event):
self.translateOrRotate(event.x, event.y)
def tkRotateOrTranslate(self, event):
self.rotateOrTranslate(event.x, event.y)
def _redraw(self):
self.tkRedraw()
def tkRedraw(self, *dummy):
"""Cause the opengl widget to redraw itself."""
if not self.initialised: return
self.activate()
glPushMatrix() # Protect our matrix
self.update_idletasks()
self.activate()
w = self.winfo_width()
h = self.winfo_height()
glViewport(0, 0, w, h)
# Clear the background and depth buffer.
glClearColor(self.r_back, self.g_back, self.b_back, 0.)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(self.fovy, float(w)/float(h), self.near, self.far)
if 0:
# Now translate the scene origin away from the world origin
glMatrixMode(GL_MODELVIEW)
mat = glGetDoublev(GL_MODELVIEW_MATRIX)
glLoadIdentity()
glTranslatef(-self.xcenter, -self.ycenter, -(self.zcenter+self.distance))
glMultMatrixd(mat)
else:
gluLookAt(self.xcenter, self.ycenter, self.zcenter + self.distance,
self.xcenter, self.ycenter, self.zcenter,
0., 1., 0.)
glMatrixMode(GL_MODELVIEW)
glLoadMatrixd(self.get_modelview_matrix().T)
# Call objects redraw method.
self.redraw()
glFlush() # Tidy up
glPopMatrix() # Restore the matrix
self.swapbuffers()
def tkMap(self, *dummy):
"""Cause the opengl widget to redraw itself."""
self.tkExpose()
def tkExpose(self, *dummy):
"""Redraw the widget.
Make it active, update tk events, call redraw procedure and
swap the buffers. Note: swapbuffers is clever enough to
only swap double buffered visuals."""
self.activate()
if not self.initialised:
self.basic_lighting()
self.initialised = 1
self.tkRedraw()
def tkPrint(self, file):
"""Turn the current scene into PostScript via the feedback buffer."""
self.activate()
def zoomwheel(self, event):
if event.delta > 0: self.zoomin()
else: self.zoomout()
def tkStartZoom(self, event):
self.startZoom(event.y)
def tkContinueZoom(self, event):
self.continueZoom(event.y)
def tkZoomin(self, event):
self.zoomin()
def tkZoomout(self, event):
self.zoomout()
# vim:ts=8:sts=4:et:

View File

@@ -0,0 +1,18 @@
# This is a component of AXIS, a front-end for emc
# Copyright 2004, 2005, 2006 Jeff Epler <jepler@unpythonic.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .interpret import Translated, ArcsToSegmentsMixin

406
lib/python/rs274/author.py Normal file
View File

@@ -0,0 +1,406 @@
# This is a component of emc2
# Copyright 2007 Jeff Epler <jepler@unpythonic.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import sys, math
def dist_lseg(l1, l2, p):
"Compute the 3D distance from the line segment l1..l2 to the point p."
x0, y0, z0 = l1
xa, ya, za = l2
xi, yi, zi = p
dx = xa-x0
dy = ya-y0
dz = za-z0
d2 = dx*dx + dy*dy + dz*dz
if d2 == 0: return 0
t = (dx * (xi-x0) + dy * (yi-y0) + dz * (zi-z0)) / d2
if t < 0: t = 0
if t > 1: t = 1
dist2 = (xi - x0 - t*dx)**2 + (yi - y0 - t*dy)**2 + (zi - z0 - t*dz)**2
return dist2 ** .5
def rad1(x1,y1,x2,y2,x3,y3):
x12 = x1-x2
y12 = y1-y2
x23 = x2-x3
y23 = y2-y3
x31 = x3-x1
y31 = y3-y1
den = abs(x12 * y23 - x23 * y12)
if abs(den) < 1e-5: return sys.maxsize
#print "rad1", x1, y1, x2, y2, x3, y3
math.hypot(x12, y12) * math.hypot(x23, y23) * math.hypot(x31, y31) / 2 / den
return math.hypot(x12, y12) * math.hypot(x23, y23) * math.hypot(x31, y31) / 2 / den
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self): return "<%f,%f>" % (self.x, self.y)
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __mul__(self, other):
return Point(self.x * other, self.y * other)
__rmul__ = __mul__
def cross(self, other):
return self.x * other.y - self.y * other.x
def dot(self, other):
return self.x * other.x + self.y * other.y
def mag(self):
return hypot(self.x, self.y)
def mag2(self):
return self.x**2 + self.y**2
def cent1(x1,y1,x2,y2,x3,y3):
P1 = Point(x1,y1)
P2 = Point(x2,y2)
P3 = Point(x3,y3)
den = abs((P1-P2).cross(P2-P3))
if abs(den) < 1e-5: return sys.maxsize, sys.maxsize
alpha = (P2-P3).mag2() * (P1-P2).dot(P1-P3) / 2 / den / den
beta = (P1-P3).mag2() * (P2-P1).dot(P2-P3) / 2 / den / den
gamma = (P1-P2).mag2() * (P3-P1).dot(P3-P2) / 2 / den / den
Pc = alpha * P1 + beta * P2 + gamma * P3
#print >>sys.stderr, "cent1", P1, P2, P3, Pc
#print >>sys.stderr, "\t", alpha, beta, gamma
return Pc.x, Pc.y
def arc_center(plane, p1, p2, p3):
x1, y1, z1 = p1
x2, y2, z2 = p2
x3, y3, z3 = p3
if plane == 17: return cent1(x1,y1,x2,y2,x3,y3)
if plane == 18: return cent1(x1,z1,x2,z2,x3,z3)
if plane == 19: return cent1(y1,z1,y2,z2,y3,z3)
def arc_rad(plane, P1, P2, P3):
if plane is None: return sys.maxsize
x1, y1, z1 = P1
x2, y2, z2 = P2
x3, y3, z3 = P3
if plane == 17: return rad1(x1,y1,x2,y2,x3,y3)
if plane == 18: return rad1(x1,z1,x2,z2,x3,z3)
if plane == 19: return rad1(y1,z1,y2,z2,y3,z3)
return None, 0
def get_pts(plane, xxx_todo_changeme):
(x,y,z) = xxx_todo_changeme
if plane == 17: return x,y
if plane == 18: return x,z
if plane == 19: return y,z
def one_quadrant(plane, c, p1, p2, p3):
xc, yc = c
x1, y1 = get_pts(plane, p1)
x2, y2 = get_pts(plane, p2)
x3, y3 = get_pts(plane, p3)
def sign(x):
if abs(x) < 1e-5: return 0
if x < 0: return -1
return 1
signs = set((
(sign(x1-xc),sign(y1-yc)),
(sign(x2-xc),sign(y2-yc)),
(sign(x3-xc),sign(y3-yc))
))
if len(signs) == 1: return True
if (1,1) in signs:
signs.discard((1,0))
signs.discard((0,1))
if (1,-1) in signs:
signs.discard((1,0))
signs.discard((0,-1))
if (-1,1) in signs:
signs.discard((-1,0))
signs.discard((0,1))
if (-1,-1) in signs:
signs.discard((-1,0))
signs.discard((0,-1))
if len(signs) == 1: return True
def arc_dir(plane, c, p1, p2, p3):
xc, yc = c
x1, y1 = get_pts(plane, p1)
x2, y2 = get_pts(plane, p2)
x3, y3 = get_pts(plane, p3)
theta_start = math.atan2(y1-yc, x1-xc)
theta_mid = math.atan2(y2-yc, x2-xc)
theta_end = math.atan2(y3-yc, x3-xc)
if theta_mid < theta_start:
theta_mid = theta_mid + 2 * math.pi
while theta_end < theta_mid:
theta_end = theta_end + 2 * math.pi
return theta_end < 2 * math.pi
def arc_fmt(plane, c1, c2, p1):
x, y, z = p1
if plane == 17: return "I%.4f J%.4f" % (c1-x, c2-y)
if plane == 18: return "I%.4f K%.4f" % (c1-x, c2-z)
if plane == 19: return "J%.4f K%.4f" % (c1-y, c2-z)
def douglas(st, tolerance=.001, plane=None, _first=True):
"""\
Perform Douglas-Peucker simplification on the path 'st' with the specified
tolerance. The '_first' argument is for internal use only.
The Douglas-Peucker simplification algorithm finds a subset of the input points
whose path is never more than 'tolerance' away from the original input path.
If 'plane' is specified as 17, 18, or 19, it may find helical arcs in the given
plane in addition to lines. Note that if there is movement in the plane
perpendicular to the arc, it will be distorted, so 'plane' should usually
be specified only when there is only movement on 2 axes
"""
if len(st) == 1:
yield "G1", st[0], None
return
l1 = st[0]
l2 = st[-1]
worst_dist = 0
worst = 0
min_rad = sys.maxsize
max_arc = -1
ps = st[0]
pe = st[-1]
for i, p in enumerate(st):
if p is l1 or p is l2: continue
dist = dist_lseg(l1, l2, p)
if dist > worst_dist:
worst = i
worst_dist = dist
rad = arc_rad(plane, ps, p, pe)
#print >>sys.stderr, "rad", rad, max_arc, min_rad
if rad < min_rad:
max_arc = i
min_rad = rad
worst_arc_dist = 0
if min_rad != sys.maxsize:
c1, c2 = arc_center(plane, ps, st[max_arc], pe)
lx, ly, lz = st[0]
if one_quadrant(plane, (c1, c2), ps, st[max_arc], pe):
for i, (x,y,z) in enumerate(st):
if plane == 17: dist = abs(math.hypot(c1-x, c2-y) - min_rad)
elif plane == 18: dist = abs(math.hypot(c1-x, c2-z) - min_rad)
elif plane == 19: dist = abs(math.hypot(c1-y, c2-z) - min_rad)
else: dist = sys.maxsize
#print >>sys.stderr, "wad", dist, worst_arc_dist
if dist > worst_arc_dist: worst_arc_dist = dist
mx = (x+lx)/2
my = (y+ly)/2
mz = (z+lz)/2
if plane == 17: dist = abs(math.hypot(c1-mx, c2-my) - min_rad)
elif plane == 18: dist = abs(math.hypot(c1-mx, c2-mz) - min_rad)
elif plane == 19: dist = abs(math.hypot(c1-my, c2-mz) - min_rad)
else: dist = sys.maxsize
#if dist > worst_arc_dist: worst_arc_dist = dist
lx, ly, lz = x, y, z
else:
worst_arc_dist = sys.maxsize
else:
worst_arc_dist = sys.maxsize
#if worst_arc_dist != sys.maxint:
#print >>sys.stderr, "douglas", len(st), "\n\t", st[0], "\n\t", st[max_arc], "\n\t", st[-1]
#print >>sys.stderr, "\t", worst_arc_dist, worst_dist
#print >>sys.stderr, "\t", c1, c2
if worst_arc_dist < tolerance and worst_arc_dist < worst_dist:
ccw = arc_dir(plane, (c1, c2), ps, st[max_arc], pe)
if plane == 18: ccw = not ccw # wtf?
yield "G1", ps, None
if ccw:
yield "G3", st[-1], arc_fmt(plane, c1, c2, ps)
else:
yield "G2", st[-1], arc_fmt(plane, c1, c2, ps)
elif worst_dist > tolerance:
if _first: yield "G1", st[0], None
for i in douglas(st[:worst+1], tolerance, plane, False):
yield i
yield "G1", st[worst], None
for i in douglas(st[worst:], tolerance, plane, False):
yield i
if _first: yield "G1", st[-1], None
else:
if _first: yield "G1", st[0], None
if _first: yield "G1", st[-1], None
class Gcode:
"For creating rs274ngc files"
def __init__(self, homeheight = 1.5, safetyheight = 0.04, tolerance=0.001,
spindle_speed=1000, units="G20",
target=lambda s: sys.stdout.write(s + "\n")):
self.lastx = self.lasty = self.lastz = self.lasta = None
self.lastgcode = self.lastfeed = None
self.homeheight = homeheight
self.safetyheight = self.lastz = safetyheight
self.tolerance = tolerance
self.units = units
self.cuts = []
self.write = target
self.time = 0
self.spindle_speed = spindle_speed
self.plane = None
def set_plane(self, p):
assert p in (17,18,19)
if p != self.plane:
self.plane = p
self.write("G%d" % p)
def begin(self):
"""\
This function moves to the safety height, sets many modal codes to default
values, turns the spindle on at 1000RPM, and waits for it to come up to
speed."""
self.write(self.units)
self.write("G0 Z%.4f" % (self.safetyheight))
self.write("G17 G40")
self.write("G80 G90 G94")
self.write("S%d M3" % (self.spindle_speed))
self.write("G04 P3")
def flush(self):
"""\
If any 'cut' moves are stored up, send them to the simplification algorithm
and actually output them.
This function is usually used internally (e.g., when changing from a cut
to a rapid) but can be called manually as well. For instance, when
a contouring program reaches the end of a row, it may be desirable to enforce
that the last 'cut' coordinate is actually in the output file, and it may
give better performance because this means that the simplification algorithm
will examine fewer points per run."""
if not self.cuts: return
for move, (x, y, z), cent in douglas(self.cuts, self.tolerance, self.plane):
if cent:
self.write("%s X%.4f Y%.4f Z%.4f %s" % (move, x, y, z, cent))
self.lastgcode = None
self.lastx = x
self.lasty = y
self.lastz = z
else:
self.move_common(x, y, z, gcode="G1")
self.cuts = []
def end(self):
"""End the program"""
self.flush()
self.safety()
self.write("M2")
def exactpath(self):
"""\
Set exact path mode. Note that unless self.tolerance is set to zero,
the simplification algorithm may still skip over specified points."""
self.write("G61")
def continuous(self, tolerance=0.0):
"Set continuous mode."
if tolerance > 0.0:
self.write("G64 P%.4f" % tolerance)
else:
self.write("G64")
def rapid(self, x=None, y=None, z=None, a=None):
"Perform a rapid move to the specified coordinates"
self.flush()
self.move_common(x, y, z, a, "G0")
def move_common(self, x=None, y=None, z=None, a=None, gcode="G0"):
"An internal function used for G0 and G1 moves"
gcodestring = xstring = ystring = zstring = astring = ""
if x == None: x = self.lastx
if y == None: y = self.lasty
if z == None: z = self.lastz
if a == None: a = self.lasta
if x != self.lastx:
xstring = " X%.4f" % (x)
self.lastx = x
if y != self.lasty:
ystring = " Y%.4f" % (y)
self.lasty = y
if z != self.lastz:
zstring = " Z%.4f" % (z)
self.lastz = z
if a != self.lasta:
astring = " A%.4f" % (a)
self.lasta = a
if xstring == ystring == zstring == astring == "":
return
if gcode != self.lastgcode:
gcodestring = gcode
self.lastgcode = gcode
cmd = "".join([gcodestring, xstring, ystring, zstring, astring])
if cmd:
self.write(cmd)
def set_feed(self, feed):
"Set the feed rate to the given value"
self.flush()
self.write("F%.4f" % feed)
def cut(self, x=None, y=None, z=None):
"Perform a cutting move at the specified feed rate to the specified coordinates"
if self.cuts:
lastx, lasty, lastz = self.cuts[-1]
else:
lastx, lasty, lastz = self.lastx, self.lasty, self.lastz
if x is None: x = lastx
if y is None: y = lasty
if z is None: z = lastz
self.cuts.append([x,y,z])
def home(self):
"Go to the 'home' height at rapid speed"
self.flush()
self.rapid(z=self.homeheight)
def safety(self):
"Go to the 'safety' height at rapid speed"
self.flush()
self.rapid(z=self.safetyheight)

1494
lib/python/rs274/glcanon.py Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,315 @@
# This is a component of AXIS, a front-end for emc
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""The reference consumer of ``gcode.parse``'s move-batch protocol.
The protocol is opt-in and lives in ``src/emc/rs274ngc/gcodemodule.cc`` (class
``MoveBatch``): a canon that sets ``use_move_batches = True`` (the bool itself -
anything else, including a merely truthy value, stays on the legacy protocol)
and provides a callable ``move_batch`` stops receiving ``straight_feed``/``straight_traverse``/
``straight_probe``/``rigid_tap``/``dwell``/``user_defined_function``/
``change_tool``/``tool_offset`` as one Python call per event, and receives them
instead as blocks of fixed-width float64 rows. On a million-move file that is
the difference between a million Python frames and about sixteen.
Each row is 13 float64s::
[kind, line_number, x, y, z, a, b, c, u, v, w, feedrate, spindle_speed]
``kind`` is one of the ``KIND_*`` constants below. Rows of kinds 0-3 are moves
and carry exactly the axis values the legacy per-event call would have passed -
after the interpreter's position update and after the metric conversion - which
is what lets this module reproduce ``GLCanon``'s geometry bit for bit rather
than merely closely. Rows of kinds 4-7 are the numeric non-move events, which
are batched rather than forwarded so that a canned drilling cycle's one dwell
per hole does not shred the moves around it into four-row batches; they carry
their payload in the axis slots (dwell seconds in x; M1xx index/P/Q in x/y/z;
tool number in x; the nine tool offsets in x..w) and zeros elsewhere.
``feedrate`` is the last ``SET_FEED_RATE`` value, so no correlation with a
separate rate callback is needed. ``spindle_speed`` is the last S word for
spindle 0 - data the legacy protocol never delivered at all, since that canon
call is an empty stub. This consumer ignores it; it is there for the renderer
rewrite to read without another C change.
**The view is on loan.** ``move_batch`` is handed a read-only memoryview over a
buffer the C module reuses. Copy or fully consume it before returning; a
retained view sees whatever the next batch overwrote it with.
Ordering is guaranteed by the C side: rows are delivered before any callback
that is still forwarded (comments, offsets, rotation, plane, messages, arcs,
``next_line``), when the buffer fills, once a second, and at end of parse. So
within one batch the suppression state, the g92/g5x offsets, the XY rotation and
the plane are all constant - which is what lets a whole run of moves be
transformed in one vectorized pass. The feed rate is the deliberate exception:
``set_feed_rate`` does *not* flush, because the rate is in the row, so
``self.feedrate`` may already have moved on by the time a batch holding rows
made at the old rate arrives. **Read the column, never the attribute.** It is
also forwarded only when the rate actually changes, since the interpreter
reports an ``F`` word whether or not it changed anything; a consumer that
counted ``set_feed_rate`` calls would see fewer of them than in legacy mode.
Rows are staged into the canon's own staging chunk rather than filled from here.
That is not about splitting a batch up - a whole batch is filled in one call,
and ``ProgramGeometry.add_moves_raw`` costs the same per row anywhere above
about a thousand of them. It is about putting a *floor* under the piece size:
filled straight from here, a fill would be exactly as big as whatever the C
side happened to deliver, and a batch can be a handful of rows. At 8 rows a
fill costs about 9x per row what it costs at 4096. Staging accumulates until
``FILL_BATCH`` instead, and as a bonus arc segments staged between batches keep
their place without a flush.
No GUI opts in yet; doing that is a separate change.
"""
import numpy as np
import rs274.glcanon
from rs274.glcanon import GLCanon
from rs274 import glcanon_bake
from rs274.glcanon_bake import CAT_TRAVERSE, CAT_FEED
#: Row width and columns, matching ``MoveBatch::ROW`` and the layout above.
ROW_WIDTH = 13
COL_KIND = 0
COL_LINE = 1
COL_AXES = slice(2, 11)
COL_FEEDRATE = 11
COL_SPINDLE = 12
# Column 0. Must match ``MoveBatch::Kind`` in gcodemodule.cc.
KIND_TRAVERSE = 0
KIND_FEED = 1
KIND_PROBE = 2
KIND_RIGID_TAP = 3
KIND_DWELL = 4
KIND_M1XX = 5
KIND_CHANGE_TOOL = 6
KIND_TOOL_OFFSET = 7
#: Kinds at or above this are events, not moves: they end a vectorized run.
FIRST_EVENT_KIND = KIND_DWELL
#: The nine axis letters, in row order.
_AXES = "xyzabcuvw"
#: ``CANON_PLANE`` (canon.hh) to the 0/1/2 index ``GLCanon._record_dwell``
#: derives from ``self.state.plane``: XY/UV -> 0, XZ/UW -> 1, YZ/VW -> 2.
_PLANE_INDEX = {1: 0, 2: 2, 3: 1, 4: 0, 5: 2, 6: 1}
class MoveBatchMixin:
"""Turns delivered batches into exactly what per-event ``GLCanon`` builds.
Mix in *before* ``GLCanon`` so the ``_record_dwell`` override below wins.
"""
#: What the C side reads to choose the protocol, once, at parse start.
#: Must be the bool ``True``; the C side ignores any other value.
use_move_batches = True
def batch_progress(self, lineno):
"""Called once per non-empty batch with the last line number in it.
The hook a GUI overrides to drive a progress bar. The C side flushes
before its once-a-second abort check, so this fires at least about that
often on a long parse - which is what replaces the per-line ``next_line``
a GUI used to count.
"""
# -- the protocol ------------------------------------------------------
def move_batch(self, view):
rows = np.frombuffer(view, dtype=np.float64).reshape(-1, ROW_WIDTH)
if len(rows) == 0:
return
# No flush here. Whatever the per-call path staged since the last batch
# - arc segments, which are deliberately not batched - is sitting in the
# canon's staging chunk, and _batch_run appends to that same chunk, so
# emission order is kept by construction. Flushing first would be worse
# than redundant: it would fill once per batch, and a file with an F
# word every few moves delivers batches of a few rows.
kinds = rows[:, COL_KIND].astype(np.int64)
events = np.nonzero(kinds >= FIRST_EVENT_KIND)[0]
start = 0
for at in events:
if at > start:
self._batch_run(rows[start:at], kinds[start:at])
self._batch_event(rows[at], int(kinds[at]))
start = int(at) + 1
if start < len(rows):
self._batch_run(rows[start:], kinds[start:])
self.batch_progress(int(rows[-1, COL_LINE]))
# -- a run of move rows ------------------------------------------------
def _batch_run(self, rows, kinds):
"""Fill one uninterrupted run of move rows, vectorized.
Everything a move depends on - suppression, the offsets, the rotation,
the tool-offset triple - is constant across a run, because every event
that could change one either flushes the batch (comments, offsets,
rotation) or ends the run (the tool rows). That is the whole reason the
run is the unit of work.
"""
if self.suppress > 0:
# Legacy straight_traverse/feed/probe/rigid_tap all return before
# touching any state while hidden, so a suppressed run leaves even
# `lo` and `first_move` alone.
return
n = len(rows)
# rotate_and_translate (rs274.interpret.Translated), op for op, on the
# whole run at once. The order matters and is not incidental: the same
# sequence of IEEE adds in the same order is what makes these results
# bit-identical to the per-move canon's, so an "equivalent" rearrangement
# here would turn an exact-equality test into an approximate one.
pts = rows[:, COL_AXES].copy()
pts += self._offsets("g92_offset_")
if self.rotation_xy:
rotx = pts[:, 0] * self.rotation_cos - pts[:, 1] * self.rotation_sin
pts[:, 1] = pts[:, 0] * self.rotation_sin + pts[:, 1] * self.rotation_cos
pts[:, 0] = rotx
pts += self._offsets("g5x_offset_")
# Where each move starts. Rigid taps do not advance the chain (the
# legacy rigid_tap never assigns self.lo), so consecutive taps all hang
# off the same point.
advances = kinds != KIND_RIGID_TAP
before = np.cumsum(advances) - advances # advancing rows before i
chain = np.empty((n, 9), dtype=np.float64)
chained = before > 0
chain[~chained] = self.lo
if chained.any():
source = np.nonzero(advances)[0]
chain[chained] = pts[source[before[chained] - 1]]
# A tap's endpoint is its transformed x,y,z joined to the chain point's
# a..w - the legacy rigid_tap transforms x,y,z with zeros and then
# splices self.lo's rotary and UVW components back in.
ends = pts.copy()
taps = ~advances
if taps.any():
ends[taps, 3:] = chain[taps, 3:]
# Leading traverses while first_move is set move the tool without
# drawing; the first move that is not a traverse clears the flag.
keep_from = 0
if self.first_move:
drawn = np.nonzero(kinds != KIND_TRAVERSE)[0]
if len(drawn) == 0:
keep_from = n
else:
keep_from = int(drawn[0])
self.first_move = False
# Before the early return: a dropped leading traverse still moves the
# tool, and the move after it starts where it left off.
if advances.any():
self.lo = tuple(pts[np.nonzero(advances)[0][-1]].tolist())
if keep_from >= n:
return
# Each surviving row becomes one staging row, except a tap, which
# becomes two - down, and back up the way it came.
index = np.arange(keep_from, n)
repeats = np.where(kinds[keep_from:] == KIND_RIGID_TAP, 2, 1)
index = np.repeat(index, repeats)
total = len(index)
offset_in_move = (np.arange(total)
- np.repeat(np.cumsum(repeats) - repeats, repeats))
returning = (offset_in_move == 1)[:, np.newaxis]
# Written straight into the canon's own staging chunk, in bulk, rather
# than filled from here. Not to break the run up - a run that is already
# big is filled in one call, and add_moves_raw costs the same per row
# anywhere above ~1024 - but to stop a *small* one reaching the fill on
# its own. Filled from here, the piece size would be the batch size, and
# a file with an F word every few moves arrives a few rows at a time:
# measured, a fill of 8 rows costs ~9x per row what a fill of 4096 does,
# which is enough to make batch mode slower than the per-move canon it
# replaces. Staging leaves the flush policy exactly where GLCanon has
# it, and reuses one buffer instead of allocating a chunk per run.
traversing = kinds[index] == KIND_TRAVERSE
self._reserve_staging(total)
at = self._staged
chunk = self._staging[at:at + total]
chunk[:, glcanon_bake.STAGE_LINE] = rows[index, COL_LINE]
chunk[:, glcanon_bake.STAGE_P1] = np.where(returning, ends[index],
chain[index])
chunk[:, glcanon_bake.STAGE_P2] = np.where(returning, chain[index],
ends[index])
chunk[:, glcanon_bake.STAGE_FEEDRATE] = np.where(
traversing, 0.0, rows[index, COL_FEEDRATE] / 60.)
chunk[:, glcanon_bake.STAGE_OFFSET] = (self.xo, self.yo, self.zo)
self._pending_kinds.extend(
np.where(traversing, CAT_TRAVERSE, CAT_FEED).astype(np.uint8)
.tobytes())
self._staged = at + total
if self._staged >= rs274.glcanon.FILL_BATCH:
self._flush_moves()
def _offsets(self, prefix):
return np.array([getattr(self, prefix + axis) for axis in _AXES],
dtype=np.float64)
# -- one non-move row --------------------------------------------------
def _batch_event(self, row, kind):
"""Replay one batched event exactly where it sits in the stream.
Delegates to the canon's own methods rather than reimplementing them:
these are the same calls the C module would have made, and going through
them keeps the suppression rules (dwell and M1xx are dropped while
hidden, tool changes and tool offsets are not) as the one statement of
those rules rather than two. ``lineno`` is taken from the row, since a
batched event delivers no ``next_line``.
"""
self.lineno = int(row[COL_LINE])
if kind == KIND_DWELL:
self.dwell(float(row[2]))
elif kind == KIND_M1XX:
self.user_defined_function(int(row[2]), float(row[3]),
float(row[4]))
elif kind == KIND_CHANGE_TOOL:
self.change_tool(int(row[2]))
elif kind == KIND_TOOL_OFFSET:
self.tool_offset(*row[COL_AXES].tolist())
else:
raise ValueError("move_batch: unknown row kind %d" % kind)
def _record_dwell(self, color):
"""As ``GLCanon._record_dwell``, but the plane comes from ``set_plane``.
The per-move canon reads ``self.state.plane`` - the snapshot the
``next_line`` for the dwell's own line carried. A batched dwell delivers
no ``next_line``, so that snapshot can predate the dwell by several
lines and hold the wrong plane. ``self.plane`` is what ``set_plane``
last set, and ``set_plane`` is still forwarded (and flushes first), so
it is current for every row in the batch.
"""
plane = _PLANE_INDEX.get(self.plane, 0)
self.dwells.append((self.lineno, color, self.lo[0], self.lo[1],
self.lo[2], plane))
self._flush_moves()
self._program_geometry.mark_dwell(self.lineno, self.lo, color, plane)
class BatchGLCanon(MoveBatchMixin, GLCanon):
"""``GLCanon`` that opts into the move-batch protocol.
Produces the same program record, dwell table, tool list and final state as
``GLCanon`` on the same file - see the equality test - so a GUI can swap one
for the other wherever numpy is importable.
"""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,183 @@
# This is a component of AXIS, a front-end for emc
# Copyright 2004, 2005, 2006 Jeff Epler <jepler@unpythonic.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import math, gcode
class Translated:
g92_offset_x = g92_offset_y = g92_offset_z = 0
g92_offset_a = g92_offset_b = g92_offset_c = 0
g92_offset_u = g92_offset_v = g92_offset_w = 0
g5x_offset_x = g5x_offset_y = g5x_offset_z = 0
g5x_offset_a = g5x_offset_b = g5x_offset_c = 0
g5x_offset_u = g5x_offset_v = g5x_offset_w = 0
rotation_xy = 0
def rotate_and_translate(self, x,y,z,a,b,c,u,v,w):
x += self.g92_offset_x
y += self.g92_offset_y
z += self.g92_offset_z
a += self.g92_offset_a
b += self.g92_offset_b
c += self.g92_offset_c
u += self.g92_offset_u
v += self.g92_offset_v
w += self.g92_offset_w
if self.rotation_xy:
rotx = x * self.rotation_cos - y * self.rotation_sin
y = x * self.rotation_sin + y * self.rotation_cos
x = rotx
x += self.g5x_offset_x
y += self.g5x_offset_y
z += self.g5x_offset_z
a += self.g5x_offset_a
b += self.g5x_offset_b
c += self.g5x_offset_c
u += self.g5x_offset_u
v += self.g5x_offset_v
w += self.g5x_offset_w
return (x, y, z, a, b, c, u, v, w)
def straight_traverse(self, *args):
self.straight_traverse_translated(*self.rotate_and_translate(*args))
def straight_feed(self, *args):
self.straight_feed_translated(*self.rotate_and_translate(*args))
def set_g5x_offset(self, index, x, y, z, a, b, c, u=None, v=None, w=None):
self.g5x_index = index
self.g5x_offset_x = x
self.g5x_offset_y = y
self.g5x_offset_z = z
self.g5x_offset_a = a
self.g5x_offset_b = b
self.g5x_offset_c = c
self.g5x_offset_u = u
self.g5x_offset_v = v
self.g5x_offset_w = w
def set_g92_offset(self, x, y, z, a, b, c, u=None, v=None, w=None):
self.g92_offset_x = x
self.g92_offset_y = y
self.g92_offset_z = z
self.g92_offset_a = a
self.g92_offset_b = b
self.g92_offset_c = c
self.g92_offset_u = u
self.g92_offset_v = v
self.g92_offset_w = w
def set_xy_rotation(self, theta):
self.rotation_xy = theta
t = math.radians(theta)
self.rotation_sin = math.sin(t)
self.rotation_cos = math.cos(t)
class ArcsToSegmentsMixin:
plane = 1
arcdivision = 64
def set_plane(self, plane):
self.plane = plane
def arc_feed(self, x1, y1, cx, cy, rot, z1, a, b, c, u, v, w):
segs = gcode.arc_to_segments(self, x1, y1, cx, cy, rot, z1, a, b, c, u, v, w, self.arcdivision)
self.straight_arcsegments(segs)
class PrintCanon:
def set_g5x_offset(self, *args):
print("set_g5x_offset", args)
def set_g92_offset(self, *args):
print("set_g92_offset", args)
def next_line(self, state):
print("next_line", state.sequence_number)
self.state = state
def set_plane(self, plane):
print("set plane", plane)
def set_feed_rate(self, arg):
print("set feed rate", arg)
def comment(self, arg):
print("#", arg)
def straight_traverse(self, *args):
print("straight_traverse %.4g %.4g %.4g %.4g %.4g %.4g" % args)
def straight_feed(self, *args):
print("straight_feed %.4g %.4g %.4g %.4g %.4g %.4g" % args)
def dwell(self, arg):
if arg < .1:
print("dwell %f ms" % (1000 * arg))
else:
print("dwell %f seconds" % arg)
def arc_feed(self, *args):
print("arc_feed %.4g %.4g %.4g %.4g %.4g %.4g %.4g %.4g %.4g" % args)
global tool_in_spindle, empty_spindle_data
tool_in_spindle = -1
empty_spindle_data = -1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0
class StatMixin:
def __init__(self, s, r):
self.s = s
self.tools = list(s.tool_table)
self.random = r
def change_tool(self, tool_nr):
global tool_in_spindle
idx = self.get_index(tool_nr)
if self.random:
self.tools[0], self.tools[idx] = self.tools[idx], self.tools[0]
tool_in_spindle = idx
elif idx==0:
self.tools[0] = empty_spindle_data
else:
self.tools[0] = self.tools[idx]
def get_tool(self, idx):
global tool_in_spindle
if idx >= 0 and idx < len(self.tools):
if (idx == tool_in_spindle):
return tuple(self.tools[0])
return tuple(self.tools[idx])
return empty_spindle_data
def get_index(self, tool_nr):
index = 1
for tool in self.tools[1:]:
if tool.id == tool_nr:
return index
index = index + 1
return 0
def get_external_angular_units(self):
return self.s.angular_units or 1.0
def get_external_length_units(self):
return self.s.linear_units or 1.0
def get_axis_mask(self):
return self.s.axis_mask
def get_block_delete(self):
return self.s.block_delete
# vim:ts=8:sts=4:et:

175
lib/python/rs274/options.py Normal file
View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python3
# This is a component of AXIS, a front-end for emc
# Copyright 2004, 2005, 2006 Jeff Epler <jepler@unpythonic.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import nf, os
# system Tcl path for installed emc
# tcl for run-in-place emc
rip = os.path.join(nf.PREFIX, 'tcl', 'linuxcnc.tcl')
LINUXCNC_TCL = rip if os.path.exists(rip) else os.path.join(nf.EMC2_TCL_DIR, 'linuxcnc.tcl')
options = '''
. configure -bg #d9d9d9
set BASE_FONT [linuxcnc::standard_font]
set FIXED_FONT [linuxcnc::standard_fixed_font]
option add *highlightBackground #d9d9d9 $OPTIONLEVEL
option add *background #d9d9d9 $OPTIONLEVEL
option add *foreground black $OPTIONLEVEL
option add *Button.borderWidth 2 $OPTIONLEVEL
option add *Button.font $BASE_FONT $OPTIONLEVEL
option add *Checkbutton.borderWidth 1 $OPTIONLEVEL
option add *Checkbutton.font $BASE_FONT $OPTIONLEVEL
option add *Entry.background white $OPTIONLEVEL
option add *Entry.borderWidth 2 $OPTIONLEVEL
option add *Entry.font $BASE_FONT $OPTIONLEVEL
option add *Entry.selectBackground #08246b $OPTIONLEVEL
option add *Entry.selectForeground white $OPTIONLEVEL
option add *Frame.borderWidth 0 $OPTIONLEVEL
option add *Hierbox.borderWidth 2 $OPTIONLEVEL
option add *Hierbox.selectBackground #08246b $OPTIONLEVEL
option add *Hierbox.selectForeground white $OPTIONLEVEL
option add *Label.font $BASE_FONT $OPTIONLEVEL
option add *Label.borderWidth 1 $OPTIONLEVEL
option add *Listbox.background white $OPTIONLEVEL
option add *Listbox.font $BASE_FONT $OPTIONLEVEL
option add *Listbox.borderWidth 2 $OPTIONLEVEL
option add *Listbox.selectBackground #08246b $OPTIONLEVEL
option add *Listbox.selectForeground white $OPTIONLEVEL
option add *Menu.activeBorderWidth 0 $OPTIONLEVEL
option add *Menu.borderWidth 1 $OPTIONLEVEL
option add *Menu.font $BASE_FONT $OPTIONLEVEL
option add *Menu.activeBackground #08246b $OPTIONLEVEL
option add *Menu.activeForeground white $OPTIONLEVEL
option add *Menubutton.borderWidth 1 $OPTIONLEVEL
option add *Menubutton.font $BASE_FONT $OPTIONLEVEL
option add *Menubutton.indicatorOn 1 $OPTIONLEVEL
option add *Menubutton.relief raised $OPTIONLEVEL
option add *Message.borderWidth 1 $OPTIONLEVEL
option add *Message.font $BASE_FONT $OPTIONLEVEL
option add *Radiobutton.borderWidth 1 $OPTIONLEVEL
option add *Radiobutton.font $BASE_FONT $OPTIONLEVEL
option add *Scrollbar.borderWidth 2 $OPTIONLEVEL
option add *Scrollbar.takeFocus 0 $OPTIONLEVEL
option add *Scrollbar.troughColor #d9d9d9 $OPTIONLEVEL
option add *Scrollbar.elementBorderWidth 2 $OPTIONLEVEL
option add *Text.background white $OPTIONLEVEL
option add *Text.borderWidth 2 $OPTIONLEVEL
option add *Text.font $FIXED_FONT $OPTIONLEVEL
option add *Text.selectBackground #08246b $OPTIONLEVEL
option add *Text.selectForeground white $OPTIONLEVEL
option add *Labelframe.borderWidth 2 $OPTIONLEVEL
option add *Labelframe.relief groove $OPTIONLEVEL
option add *Labelframe.font $BASE_FONT $OPTIONLEVEL
option add *work.borderWidth 3 $OPTIONLEVEL
option add *buttons*Button.default normal $OPTIONLEVEL
option add *Vspace.height 6 $OPTIONLEVEL
option add *Hspace.width 20 $OPTIONLEVEL
option add *Vrule.borderWidth 1 $OPTIONLEVEL
option add *Vrule.relief sunken $OPTIONLEVEL
option add *Vrule.width 2 $OPTIONLEVEL
option add *Hrule.borderWidth 1 $OPTIONLEVEL
option add *Hrule.relief sunken $OPTIONLEVEL
option add *Hrule.height 2 $OPTIONLEVEL
option add *Togl.back #000 startupFile
option add *Togl.dwell #ff8080 startupFile
option add *Togl.m1xx #8080ff startupFile
option add *Togl.straight_feed #ffffff startupFile
option add *Togl.straight_feed_xy #40ff40 startupFile
option add *Togl.straight_feed_uv #4040ff startupFile
option add *Togl.arc_feed #ffffff startupFile
option add *Togl.arc_feed_xy #40ff40 startupFile
option add *Togl.arc_feed_uv #4040ff startupFile
option add *Togl.cone #ffffff startupFile
option add *Togl.cone_xy #00ff00 startupFile
option add *Togl.cone_uv #0000ff startupFile
option add *Togl.traverse #4c8080 startupFile
option add *Togl.traverse_xy #4c8080 startupFile
option add *Togl.traverse_uv #4c8080 startupFile
option add *Togl.backplotjog yellow startupFile
option add *Togl.backplotfeed #c04040 startupFile
option add *Togl.backplotarc #c04080 startupFile
option add *Togl.backplottraverse #4c8080 startupFile
option add *Togl.backplottoolchange orange startupFile
option add *Togl.backplotprobing purple startupFile
option add *Togl.backplotjog_alpha .75 startupFile
option add *Togl.backplotfeed_alpha .75 startupFile
option add *Togl.backplotarc_alpha .75 startupFile
option add *Togl.backplottraverse_alpha .25 startupFile
option add *Togl.backplottoolchange_alpha .25 startupFile
option add *Togl.backplotprobing_alpha .75 startupFile
option add *Togl.selected #00ffff startupFile
option add *Togl.overlay_foreground #ffffff startupFile
option add *Togl.overlay_alpha .75 startupFile
option add *Togl.overlay_background #000000 startupFile
option add *Togl.label_limit #ff353a startupFile
option add *Togl.label_ok #ff8287 startupFile
option add *Togl.small_origin #00ffff startupFile
option add *Togl.axis_x #33ff33 startupFile
option add *Togl.axis_y #ff3333 startupFile
option add *Togl.axis_z #3333ff startupFile
option add *Togl.tool_diffuse #999999 startupFile
option add *Togl.tool_ambient #666666 startupFile
option add *Togl.tool_light_x 1 startupFile
option add *Togl.tool_light_y -1 startupFile
option add *Togl.tool_light_z 1 startupFile
option add *Togl.tool_alpha .2 startupFile
option add *Togl.lathetool #cccccc startupFile
option add *Togl.lathetool_alpha .1 startupFile
'''
import subprocess, sys
def install(root = None):
if root is None: root = Tkinter._default_root
o = root.option_get("optionLevel", "Level") or "interactive"
if hasattr(root, 'tk'): root = root.tk
root.call('source', LINUXCNC_TCL)
root.call('set', 'OPTIONLEVEL', o)
root.call('eval', options)
# vim:sw=4:sts=4:et:ts=8: