Import LinuxCNC source tree
This commit is contained in:
422
lib/python/rs274/OpenGLTk.py
Executable file
422
lib/python/rs274/OpenGLTk.py
Executable 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:
|
||||
18
lib/python/rs274/__init__.py
Normal file
18
lib/python/rs274/__init__.py
Normal 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
406
lib/python/rs274/author.py
Normal 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
1494
lib/python/rs274/glcanon.py
Normal file
File diff suppressed because it is too large
Load Diff
1588
lib/python/rs274/glcanon_bake.py
Normal file
1588
lib/python/rs274/glcanon_bake.py
Normal file
File diff suppressed because it is too large
Load Diff
315
lib/python/rs274/glcanon_batch.py
Normal file
315
lib/python/rs274/glcanon_batch.py
Normal 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.
|
||||
"""
|
||||
3100
lib/python/rs274/glcanon_gl.py
Normal file
3100
lib/python/rs274/glcanon_gl.py
Normal file
File diff suppressed because it is too large
Load Diff
2112
lib/python/rs274/glcanon_scene.py
Normal file
2112
lib/python/rs274/glcanon_scene.py
Normal file
File diff suppressed because it is too large
Load Diff
183
lib/python/rs274/interpret.py
Normal file
183
lib/python/rs274/interpret.py
Normal 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
175
lib/python/rs274/options.py
Normal 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:
|
||||
Reference in New Issue
Block a user