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

474
lib/python/glnav.py Normal file
View File

@@ -0,0 +1,474 @@
import math
import numpy as np
def use_pango_font(font, start, count, will_call_prepost=False):
"""Build a glyph-atlas font for the OpenGL 3.3 core overlay renderer.
Signature preserved for callers (axis.py, gremlin.py, qt5_graphics.py):
returns ``(handle, char_width, line_space)``. The handle is now an opaque
``rs274.glcanon_gl.GlyphAtlas`` (a texture atlas + overlay shader) instead
of a per-glyph display-list base; consumers pass it through get_font_info()
to the shared overlay pass, which draws text as textured quads.
"""
from rs274 import glcanon_gl
atlas = glcanon_gl.build_atlas(font, start, count)
return atlas, atlas.char_width, atlas.line_space
def identity_matrix():
return np.identity(4, dtype=np.float64)
def multiply(*matrices):
"""Multiply 4x4 matrices using OpenGL's column-vector convention."""
result = identity_matrix()
for matrix in matrices:
result = result @ np.asarray(matrix, dtype=np.float64)
return result
def translation_matrix(x, y, z):
result = identity_matrix()
result[:3, 3] = (x, y, z)
return result
def rotation_matrix(angle, x, y, z):
axis = np.asarray((x, y, z), dtype=np.float64)
length = np.linalg.norm(axis)
if not length:
return identity_matrix()
x, y, z = axis / length
c = math.cos(math.radians(angle))
s = math.sin(math.radians(angle))
d = 1.0 - c
return np.array(((x*x*d+c, x*y*d-z*s, x*z*d+y*s, 0.0),
(y*x*d+z*s, y*y*d+c, y*z*d-x*s, 0.0),
(z*x*d-y*s, z*y*d+x*s, z*z*d+c, 0.0),
(0.0, 0.0, 0.0, 1.0)), dtype=np.float64)
def perspective_matrix(fovy, aspect, near, far):
f = 1.0 / math.tan(math.radians(fovy) / 2.0)
return np.array(((f / aspect, 0.0, 0.0, 0.0),
(0.0, f, 0.0, 0.0),
(0.0, 0.0, (far + near) / (near - far), 2*far*near / (near - far)),
(0.0, 0.0, -1.0, 0.0)), dtype=np.float64)
def ortho_matrix(left, right, bottom, top, near, far):
return np.array(((2.0 / (right - left), 0.0, 0.0, -(right + left) / (right - left)),
(0.0, 2.0 / (top - bottom), 0.0, -(top + bottom) / (top - bottom)),
(0.0, 0.0, -2.0 / (far - near), -(far + near) / (far - near)),
(0.0, 0.0, 0.0, 1.0)), dtype=np.float64)
def project(point, modelview, projection, viewport):
clip = np.asarray(projection) @ np.asarray(modelview) @ np.append(point, 1.0)
ndc = clip[:3] / clip[3]
x, y, width, height = viewport
return np.array((x + (ndc[0] + 1.0) * width / 2.0,
y + (ndc[1] + 1.0) * height / 2.0,
(ndc[2] + 1.0) / 2.0))
def unproject(point, modelview, projection, viewport):
x, y, width, height = viewport
ndc = np.array(((point[0] - x) * 2.0 / width - 1.0,
(point[1] - y) * 2.0 / height - 1.0,
point[2] * 2.0 - 1.0, 1.0))
obj = np.linalg.inv(np.asarray(projection) @ np.asarray(modelview)) @ ndc
return obj[:3] / obj[3]
def glTranslateScene(w, s, x, y, mousex, mousey):
zoom_boost = max(1.0, (5.0 / w.distance) ** 0.6)
s *= zoom_boost
s = max(0.005, s)
w.modelview = multiply(translation_matrix(s * (x - mousex),
s * (mousey - y), 0.0),
w.modelview)
def glRotateScene(w, s, xcenter, ycenter, zcenter, x, y, mousex, mousey):
def snap(a):
m = a%90
if m < 3:
return a-m
elif m > 87:
return a-m+90
else:
return a
lat = min(w.maxlat, max(w.minlat, w.lat + (y - mousey) * .5))
lon = (w.lon + (x - mousex) * .5) % 360
# The legacy sequence preserved the translated origin while replacing the
# rotational part of the modelview matrix. Express that sequence directly.
translated = multiply(w.modelview, translation_matrix(xcenter, ycenter, zcenter))
tx, ty, tz = translated[:3, 3]
w.modelview = multiply(translation_matrix(tx, ty, tz),
rotation_matrix(snap(lat), *w.rotation_vectors[0]),
rotation_matrix(snap(lon), *w.rotation_vectors[1]),
translation_matrix(-xcenter, -ycenter, -zcenter))
w.lat = lat
w.lon = lon
def sub(x, y):
return list(map(lambda a, b: a-b, x, y))
def dot(x, y):
t = 0
for i in range(len(x)):
t = t + x[i]*y[i]
return t
def glDistFromLine(x, p1, p2):
f = list(map(lambda x, y: x-y, p2, p1))
g = list(map(lambda x, y: x-y, x, p1))
return dot(g, g) - dot(f, g)**2/dot(f, f)
def v3distsq(a,b):
d = ( a[0] - b[0], a[1] - b[1], a[2] - b[2] )
return d[0]*d[0] + d[1]*d[1] + d[2]*d[2]
class GlNavBase:
rotation_vectors = [(1.,0.,0.), (0., 0., 1.)]
def __init__(self):
# Current coordinates of the mouse.
self.xmouse = 0
self.ymouse = 0
# Where we are centering.
self.xcenter = 0.0
self.ycenter = 0.0
self.zcenter = 0.0
# The _back color
self.r_back = 1.
self.g_back = 0.
self.b_back = 1.
# Where the eye is
self.distance = 10.0
# Field of view in y direction
self.fovy = 30.0
# Position of clipping planes.
self.near = 0.1
self.far = 1000.0
# View settings
self.perspective = 0
self.lat = 0
self.lon = 0
self.minlat = -90
self.maxlat = 90
# keep track of total translations
# since last view reset
self._totalx = 0.0
self._totaly = 0.0
self.modelview = identity_matrix()
def basic_lighting(self):
"""\
Reset the camera for the first expose.
Widgets that own a fixed-function context (rs274.OpenGLTk.Opengl,
vismach) override this to set their lighting and depth state up first;
the camera itself has no GL state to configure."""
self.modelview = identity_matrix()
def set_background(self, r, g, b):
"""Change the background colour of the widget."""
self.r_back = r
self.g_back = g
self.b_back = b
self._redraw()
def set_centerpoint(self, x, y, z):
"""Set the new center point for the model.
This is where we are looking."""
self.xcenter = x
self.ycenter = y
self.zcenter = z
self._redraw()
def set_latitudelimits(self, minlat, maxlat):
"""Set the new "latitude" limits for rotations."""
if maxlat > 180:
return
if minlat < -180:
return
if maxlat <= minlat:
return
self.maxlat = maxlat
self.minlat = minlat
self._redraw()
def set_eyepoint(self, distance):
"""Set how far the eye is from the position we are looking."""
self.distance = distance
self._redraw()
def set_eyepoint_from_extents(self, e1, e2):
"""Set how far the eye is from the position we are looking
based on the screen width and height of a subject."""
w = self.winfo_width()
h = self.winfo_height()
ztran = max(2.0, e1, e2 * w/h) ** 2
self.set_eyepoint(ztran - self.zcenter)
def reset(self):
"""Reset rotation matrix for this widget."""
self.modelview = identity_matrix()
self._redraw()
# zero the translations - we will be recentering
self._totalx = 0.0
self._totaly = 0.0
def recordMouse(self, x, y):
self.xmouse = x
self.ymouse = y
def startRotate(self, x, y):
self.recordMouse(x, y)
def scale(self, x, y):
"""Scale the scene. Achieved by moving the eye position.
Dragging up zooms in, while dragging down zooms out
"""
scale = 1 - 0.01 * (y - self.ymouse)
# do some sanity checks, scale no more than
# 1:1000 on any given click+drag
if scale < 0.001:
scale = 0.001
elif scale > 1000:
scale = 1000
newdistance = self.distance * scale
if newdistance < 1e-30 or newdistance > 1e30:
return
self.distance = newdistance
self._redraw()
self.recordMouse(x, y)
def rotate(self, x, y):
"""Perform rotation of scene."""
self.perspective = True
glRotateScene(self, 0.5, self.xcenter, self.ycenter, self.zcenter, x, y, self.xmouse, self.ymouse)
if self.is_lathe():
self.modelview = multiply(self.modelview,
rotation_matrix(90, 1, 0, 0),
rotation_matrix(90, 0, 1, 0))
self._redraw()
self.recordMouse(x, y)
def translate(self, x, y):
"""Perform translation of scene."""
# Scale mouse translations to object viewplane so object tracks with mouse
win_width = max(1, self.winfo_width())
win_height = max(1, self.winfo_height())
obj_c = ( self.xcenter, self.ycenter, self.zcenter )
projection = self.get_projection_matrix(win_width, win_height)
win = project(obj_c, self.modelview, projection, (0, 0, win_width, win_height))
obj = unproject((win[0], win[1] + 0.5 * win_height, win[2]),
self.modelview, projection, (0, 0, win_width, win_height))
dist = math.sqrt( v3distsq( obj, obj_c ) )
scale = abs( dist / ( 0.5 * win_height ) )
glTranslateScene(self, scale, x, y, self.xmouse, self.ymouse)
# keep track of all translations since view reset
self._totalx = self._totalx + (x - self.xmouse)
self._totaly = self._totaly - (self.ymouse - y)
self._redraw()
self.recordMouse(x, y)
def set_viewangle(self, lat, lon, forcerotate=0):
self.lat = lat
self.lon = lon
if forcerotate or self.perspective:
glRotateScene(self, 0.5, self.xcenter, self.ycenter, self.zcenter, 0, 0, 0, 0)
self._redraw()
def get_viewangle(self):
return self.lat, self.lon
def get_zoom_distance(self):
data = self.distance
return data
def set_zoom_distance(self,data):
self.distance = data
self._redraw()
def zoomin(self):
self.distance = self.distance / 1.1 - 0.2
if self.distance < 0.1:
self.distance = 0.1
self._redraw()
def zoomout(self):
self.distance = self.distance * 1.1 + 0.2
if self.distance > 6000:
self.distance = 6000
self._redraw()
def startZoom(self, y):
self.y0 = y
self.original_zoom = self.distance
def continueZoom(self, y):
dy = y - self.y0
self.distance = self.original_zoom * pow(1.25, dy / 16.)
self._redraw()
def getRotateMode(self): return False
def translateOrRotate(self, x, y):
if self.getRotateMode():
self.rotate(x, y)
else:
self.translate(x, y)
def rotateOrTranslate(self, x, y):
if not self.getRotateMode():
self.rotate(x, y)
else:
self.translate(x, y)
# can be used to get current view position
def get_total_translation(self):
return self._totalx, self._totaly
def get_projection_matrix(self, width, height):
"""Return the complete legacy projection, including its eye translation."""
width = max(1, width)
height = max(1, height)
if self.perspective:
return multiply(perspective_matrix(self.fovy, float(width) / height,
self.near, self.far + self.distance),
translation_matrix(0.0, 0.0, -self.distance))
k = abs(self.distance or 1.0) ** .55555
return multiply(ortho_matrix(-k, k, -k * height / width, k * height / width,
-1000.0, 1000.0),
translation_matrix(0.0, 0.0, -1.0))
def get_modelview_matrix(self):
return self.modelview.copy()
def translate_modelview(self, x, y, z):
"""Compose a translation onto the camera's modelview matrix.
This is the supported replacement for the legacy ``glTranslatef()``
against the fixed-function matrix stack: both camera consumers reload
the GL modelview from this matrix every frame, so a translation issued
outside the camera is discarded. The translation is post-multiplied,
matching what ``glTranslatef()`` did to the current matrix, so call
sites port with no numeric change.
Deliberately does not redraw. This is a composition primitive rather
than a settled camera state, and callers follow it with
``set_eyepoint*()`` or an explicit refresh, which redraws anyway.
"""
self.modelview = multiply(self.modelview, translation_matrix(x, y, z))
def set_view_x(self):
self.reset()
self.modelview = multiply(self.modelview, rotation_matrix(-90, 0, 1, 0), rotation_matrix(-90, 1, 0, 0))
mid, size = self.extents_info()
self.modelview = multiply(self.modelview, translation_matrix(-mid[0], -mid[1], -mid[2]))
self.set_eyepoint_from_extents(size[1], size[2])
self.perspective = False
self.lat = -90
self.lon = 270
self._redraw()
def set_view_y(self):
self.reset()
self.modelview = multiply(self.modelview, rotation_matrix(-90, 1, 0, 0))
if self.is_lathe():
self.modelview = multiply(self.modelview, rotation_matrix(90, 0, 1, 0))
mid, size = self.extents_info()
self.modelview = multiply(self.modelview, translation_matrix(-mid[0], -mid[1], -mid[2]))
self.set_eyepoint_from_extents(size[0], size[2])
self.perspective = False
self.lat = -90
self.lon = 0
self._redraw()
# lathe backtool display
def set_view_y2(self):
self.reset()
self.modelview = multiply(self.modelview, rotation_matrix(90, 1, 0, 0), rotation_matrix(90, 0, 1, 0))
mid, size = self.extents_info()
self.modelview = multiply(self.modelview, translation_matrix(-mid[0], -mid[1], -mid[2]))
self.set_eyepoint_from_extents(size[0], size[2])
self.perspective = False
self.lat = -90
self.lon = 0
self._redraw()
def set_view_z(self):
self.reset()
mid, size = self.extents_info()
self.modelview = multiply(self.modelview, translation_matrix(-mid[0], -mid[1], -mid[2]))
self.set_eyepoint_from_extents(size[0], size[1])
self.perspective = False
self.lat = self.lon = 0
self._redraw()
def set_view_z2(self):
self.reset()
self.modelview = multiply(self.modelview, rotation_matrix(-90, 0, 0, 1))
mid, size = self.extents_info()
self.modelview = multiply(self.modelview, translation_matrix(-mid[0], -mid[1], -mid[2]))
self.set_eyepoint_from_extents(size[1], size[0])
self.perspective = False
self.lat = 0
self.lon = 270
self._redraw()
def set_view_p(self):
self.reset()
self.perspective = True
mid, size = self.extents_info()
self.modelview = multiply(self.modelview, translation_matrix(-mid[0], -mid[1], -mid[2]))
size = (size[0] ** 2 + size[1] ** 2 + size[2] ** 2) ** .5
if size > 1e99: size = 5. # in case there are no moves in the preview
w = self.winfo_width()
h = self.winfo_height()
fovx = self.fovy if h == 0 else self.fovy * w / h
fov = min(fovx, self.fovy)
self.set_eyepoint((size * 1.1 + 1.0) / 2 / math.sin ( fov * math.pi / 180 / 2))
self.lat = -60
self.lon = 335
glRotateScene(self, 1.0, mid[0], mid[1], mid[2], 0, 0, 0, 0)
if self.is_lathe():
self.modelview = multiply(self.modelview,
rotation_matrix(90, 1, 0, 0),
rotation_matrix(90, 0, 1, 0))
self._redraw()
# vim:ts=8:sts=4:sw=4:et: