finished all of over except app, which will need a lot more attention
This commit is contained in:
parent
45eb51550a
commit
a4665ae277
13 changed files with 529 additions and 1206 deletions
|
@ -3,16 +3,24 @@
|
|||
|
||||
import sys
|
||||
|
||||
#from . import app
|
||||
from . import aux
|
||||
from . import cmd
|
||||
from . import file
|
||||
from . import misc
|
||||
from . import text
|
||||
from . import version
|
||||
|
||||
print = text.Output("over.__init__", stream=sys.stderr)
|
||||
|
||||
try:
|
||||
from . import cython_types as types
|
||||
except:
|
||||
aux._print('unable to load C implementation, using python instead', text.prefix.warn)
|
||||
from . import python_types as types
|
||||
|
||||
print("unable to load C implementation, using python instead", print.tl.warn)
|
||||
|
||||
core = aux.DeprecationForwarder(sys.modules[__name__], 'over.core', 'over')
|
||||
textui = aux.DeprecationForwarder(text, 'over.core.textui', 'over.text')
|
||||
core = aux.DeprecationForwarder(sys.modules[__name__], "over.core", "over")
|
||||
textui = aux.DeprecationForwarder(text, "over.core.textui", "over.text")
|
||||
|
||||
del sys
|
||||
|
|
|
@ -6,9 +6,9 @@ import traceback
|
|||
|
||||
from . import text
|
||||
|
||||
_print = text.Output('over.core', stream=sys.stderr)
|
||||
|
||||
class DeprecationForwarder:
|
||||
print = text.Output("over.aux.DeprecationForwarder", stream=sys.stderr)
|
||||
|
||||
def __init__(self, target, old_name, new_name):
|
||||
self._target = target
|
||||
self._old_name = old_name
|
||||
|
@ -16,5 +16,6 @@ class DeprecationForwarder:
|
|||
|
||||
def __getattr__(self, name):
|
||||
caller = traceback.extract_stack()[-2]
|
||||
_print('%s is deprecated, please use %s instead (%s:%d)' %(self._old_name, self._new_name, caller[0], caller[1]), text.prefix.warn)
|
||||
self.print("%s is deprecated, please use %s instead (%s:%d)" %(self._old_name, self._new_name, caller[0], caller[1]), self.print.tl.warn)
|
||||
|
||||
return getattr(self._target, name)
|
||||
|
|
54
over/cmd.py
54
over/cmd.py
|
@ -27,27 +27,27 @@ def char_in_str(chars, string):
|
|||
return False
|
||||
|
||||
class Command:
|
||||
'''
|
||||
"""
|
||||
A shell command with argument substitution and output capture.
|
||||
|
||||
>>> c = Command('process.sh', '-x', 'ARGUMENT')
|
||||
>>> c.ARGUMENT = 'file.txt'
|
||||
>>> c = Command("process.sh", "-x", "ARGUMENT")
|
||||
>>> c.ARGUMENT = "file.txt"
|
||||
>>> c.dump()
|
||||
['process.sh', '-x', 'file.txt']
|
||||
["process.sh", "-x", "file.txt"]
|
||||
>>> c.run(stderr=False) # capture stdout
|
||||
>>> c.get_output()
|
||||
b'some output'
|
||||
b"some output"
|
||||
>>> c.get_output()
|
||||
b'' # there was no output since the last call
|
||||
b"" # there was no output since the last call
|
||||
>>> c.get_output()
|
||||
b'more of it\nand some more'
|
||||
b"more of it\nand some more"
|
||||
>>> c.get_output()
|
||||
None # indicates the process ended and there is no more output
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, *sequence):
|
||||
self.__dict__['sequence_original'] = list(sequence)
|
||||
self.__dict__["sequence_original"] = list(sequence)
|
||||
self.reset()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
|
@ -59,14 +59,14 @@ class Command:
|
|||
found = True
|
||||
|
||||
if not found:
|
||||
raise AttributeError('Command has no attribute \'%s\'' %(name))
|
||||
raise AttributeError('Command has no attribute "%s"' %(name))
|
||||
|
||||
def reset(self):
|
||||
self.__dict__['sequence'] = list(self.sequence_original)
|
||||
self.__dict__['thread'] = None
|
||||
self.__dict__['fifo'] = None
|
||||
self.__dict__['terminated'] = False
|
||||
self.__dict__['returncode'] = None
|
||||
self.__dict__["sequence"] = list(self.sequence_original)
|
||||
self.__dict__["thread"] = None
|
||||
self.__dict__["fifo"] = None
|
||||
self.__dict__["terminated"] = False
|
||||
self.__dict__["returncode"] = None
|
||||
|
||||
def dump(self, sequence=None, pretty=False):
|
||||
out = []
|
||||
|
@ -88,30 +88,30 @@ class Command:
|
|||
return out
|
||||
|
||||
def run(self, stderr=False):
|
||||
'''
|
||||
"""
|
||||
Executes the command in the current environment.
|
||||
|
||||
stderr capture stderr instead of stdout (can't do both yet)
|
||||
'''
|
||||
"""
|
||||
|
||||
self.__dict__['process'] = subprocess.Popen(self.dump(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1)
|
||||
self.__dict__['fifo'] = queue.Queue()
|
||||
self.__dict__['thread'] = threading.Thread(
|
||||
self.__dict__["process"] = subprocess.Popen(self.dump(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1)
|
||||
self.__dict__["fifo"] = queue.Queue()
|
||||
self.__dict__["thread"] = threading.Thread(
|
||||
target=capture_output,
|
||||
args=(self.process.stderr if stderr else self.process.stdout, self.fifo)
|
||||
)
|
||||
self.__dict__['terminated'] = False
|
||||
self.__dict__["terminated"] = False
|
||||
self.thread.daemon = True # thread dies with the program
|
||||
self.thread.start()
|
||||
|
||||
def get_output(self, blocking=False):
|
||||
'''
|
||||
"""
|
||||
Returns the output of a currently running process and clears the buffer.
|
||||
|
||||
Returns None if no process is running and no more output is available.
|
||||
|
||||
blocking block until some output is available or the process terminates
|
||||
'''
|
||||
"""
|
||||
|
||||
buffer = []
|
||||
|
||||
|
@ -125,8 +125,8 @@ class Command:
|
|||
buffer.append(self.fifo.get_nowait()) # FIXME nowait needed?
|
||||
|
||||
if None in buffer:
|
||||
self.__dict__['terminated'] = True
|
||||
self.__dict__['returncode'] = self.process.poll()
|
||||
self.__dict__["terminated"] = True
|
||||
self.__dict__["returncode"] = self.process.poll()
|
||||
|
||||
if len(buffer) == 1:
|
||||
return None
|
||||
|
@ -134,7 +134,7 @@ class Command:
|
|||
assert(buffer[-1] is None)
|
||||
del buffer[-1]
|
||||
|
||||
return b''.join(buffer)
|
||||
return b"".join(buffer)
|
||||
|
||||
def get_all_output(self):
|
||||
buffer = []
|
||||
|
@ -147,4 +147,4 @@ class Command:
|
|||
else:
|
||||
buffer.append(chunk)
|
||||
|
||||
return b''.join(buffer) if buffer else None
|
||||
return b"".join(buffer) if buffer else None
|
||||
|
|
|
@ -1,105 +1,32 @@
|
|||
#! /usr/bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
cdef class ndict(dict):
|
||||
'''
|
||||
A dictionary superclass whose keys are exposed as attributes.
|
||||
cdef class ndict(OrderedDict):
|
||||
"""
|
||||
An OrderedDict subclass whose keys are exposed as attributes.
|
||||
|
||||
>>> d = ndict()
|
||||
>>> d.alpha = 1
|
||||
>>> d['alpha']
|
||||
>>> d["alpha"]
|
||||
1
|
||||
>>> d['beta'] = 42
|
||||
>>> d["beta"] = 42
|
||||
>>> d.beta
|
||||
42
|
||||
>>> d
|
||||
{'alpha': 1, 'beta': 42}
|
||||
'''
|
||||
{"alpha": 1, "beta": 42}
|
||||
"""
|
||||
|
||||
def __getattr__(self, str name):
|
||||
if name in self:
|
||||
return self[name]
|
||||
elif name.replace('_', '-') in self:
|
||||
return self[name.replace('_', '-')]
|
||||
elif name.replace("_", "-") in self:
|
||||
return self[name.replace("_", "-")]
|
||||
else:
|
||||
raise AttributeError("'ndict' object has no attribute '%s'" %(name))
|
||||
raise AttributeError('"ndict" object has no attribute "%s"' %(name))
|
||||
|
||||
def __setattr__(self, str name, value):
|
||||
self[name] = value
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
class map:
|
||||
'''
|
||||
An ordered dict.
|
||||
'''
|
||||
|
||||
def __init__(self, source=None):
|
||||
'''
|
||||
Initialize the map.
|
||||
|
||||
The source is a sequence of (key, value) tuples.
|
||||
'''
|
||||
|
||||
if source:
|
||||
self.keys, self.vals = zip(*source)
|
||||
else:
|
||||
self.keys = []
|
||||
self.vals = []
|
||||
|
||||
def __get_index(self, key):
|
||||
if key in self.keys:
|
||||
return self.keys.index(key)
|
||||
else:
|
||||
return None
|
||||
|
||||
def __getitem__(self, key):
|
||||
i = self.__get_index(key)
|
||||
|
||||
if i is None:
|
||||
raise KeyError(key)
|
||||
else:
|
||||
return self.vals[i]
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
i = self.__get_index(key)
|
||||
|
||||
if i is None:
|
||||
self.keys.append(key)
|
||||
self.vals.append(val)
|
||||
else:
|
||||
self.vals[i] = val
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.keys
|
||||
|
||||
def index(self, item):
|
||||
return self.keys.index(item)
|
||||
|
||||
def sort(self, key=None):
|
||||
tmp_keys = self.keys
|
||||
tmp_vals = self.vals
|
||||
|
||||
self.keys = []
|
||||
self.vals = []
|
||||
|
||||
for K, V in sorted(zip(tmp_keys, tmp_vals), key=key):
|
||||
self.keys.append(K)
|
||||
self.vals.append(V)
|
||||
|
||||
@property
|
||||
def items(self):
|
||||
return zip(self.keys, self.vals)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.vals)
|
||||
|
||||
def __repr__(self):
|
||||
pairs = []
|
||||
|
||||
for i in range(len(self.keys)):
|
||||
pairs.append('%s: %s' %(repr(self.keys[i]), repr(self.vals[i])))
|
||||
|
||||
return '<{%s}>' %(', '.join(pairs))
|
||||
|
|
108
over/file.py
108
over/file.py
|
@ -3,101 +3,33 @@
|
|||
|
||||
import os
|
||||
|
||||
from .aux import _print
|
||||
from .text import prefix
|
||||
# --------------------------------------------------
|
||||
|
||||
def touch(path, times=None):
|
||||
"""
|
||||
Sets a `path`'s atime and mtime.
|
||||
|
||||
`times` is a tuple of (atime, mtime) and defaults to "now".
|
||||
"""
|
||||
|
||||
path = os.path.expanduser(path)
|
||||
|
||||
with open(path, "a"):
|
||||
os.utime(path, times)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
class File:
|
||||
'''
|
||||
A binary r/w file container that abstracts file descriptors away. You just read and write data.
|
||||
|
||||
Nonexistent files will be created, including any directories if necessary.
|
||||
'''
|
||||
|
||||
def __init__(self, path, encoding=None):
|
||||
'''
|
||||
encoding which encoding to use when opening files; None means binary (raw)
|
||||
'''
|
||||
|
||||
self.encoding = encoding
|
||||
|
||||
if path[0] == '~':
|
||||
self.path = os.path.join(os.getenv('HOME'), path[2:])
|
||||
else:
|
||||
self.path = path
|
||||
|
||||
if not os.path.isfile(self.path):
|
||||
if os.path.exists(self.path):
|
||||
_print('path §y%s§/ exists but §ris not a file§/' %(self.path), prefix.fail)
|
||||
raise RuntimeError
|
||||
|
||||
else:
|
||||
dirname = os.path.dirname(self.path)
|
||||
|
||||
if dirname and not os.path.isdir(dirname):
|
||||
_print('creating directory §B%s§/' %(dirname))
|
||||
os.makedirs(dirname)
|
||||
|
||||
# create the file
|
||||
touch(self.path)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
'''
|
||||
Reads the file and returns the contents.
|
||||
'''
|
||||
|
||||
if self.encoding:
|
||||
fd = open(self.path, encoding=self.encoding)
|
||||
else:
|
||||
fd = open(self.path, 'rb')
|
||||
|
||||
data = fd.read()
|
||||
fd.close()
|
||||
|
||||
return data
|
||||
|
||||
@data.setter
|
||||
def data(self, data):
|
||||
'''
|
||||
Writes data into the file.
|
||||
'''
|
||||
if self.encoding:
|
||||
fd = open(self.path, 'w', encoding=self.encoding)
|
||||
else:
|
||||
fd = open(self.path, 'wb')
|
||||
|
||||
fd.write(data)
|
||||
fd.close()
|
||||
|
||||
def __repr__(self):
|
||||
return 'over.core.File(%s %s)' %(self.path, self.encoding if self.encoding else 'raw')
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def touch(fname, times=None):
|
||||
'''
|
||||
Sets a filename's atime and mtime.
|
||||
|
||||
times is a tuple of (atime, mtime) and defaults to 'now'.
|
||||
'''
|
||||
|
||||
with open(fname, 'a'):
|
||||
os.utime(fname, times)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def count_lines(filename):
|
||||
'''
|
||||
def count_lines(path):
|
||||
"""
|
||||
A reasonably fast and memory-lean line counter.
|
||||
'''
|
||||
"""
|
||||
|
||||
lines = 0
|
||||
buffer = bytearray(2048)
|
||||
buffer = bytearray(int(2**20))
|
||||
path = os.path.expanduser(path)
|
||||
|
||||
with open(filename, 'rb') as f:
|
||||
with open(path, "rb") as f:
|
||||
while f.readinto(buffer):
|
||||
lines += buffer.count(b'\n')
|
||||
lines += buffer.count(b"\n")
|
||||
|
||||
return lines
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
#! /usr/bin/env python3
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Part of Project Overwatch
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from .cython_m import mat4, vec3
|
||||
except ImportError:
|
||||
print('!!! [%s] unable to load native implementation, using python instead' %(__name__), file=sys.stderr)
|
||||
from .python_m import mat4, vec3
|
||||
|
||||
del sys
|
||||
|
||||
class MathError(Exception):
|
||||
def __init__(self, description):
|
||||
self.description = description
|
||||
|
||||
def __str__(self):
|
||||
return self.description
|
||||
|
||||
class GeneralError(Exception):
|
||||
def __init__(self, description):
|
||||
self.description = description
|
||||
|
||||
def __str__(self):
|
||||
return self.description
|
|
@ -1,473 +0,0 @@
|
|||
#! /bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
'''
|
||||
Vector And Matrix Math
|
||||
|
||||
TODO some nice description
|
||||
'''
|
||||
|
||||
from libc.stdlib cimport malloc, realloc, free
|
||||
from libc.stdint cimport uint8_t, uint16_t, uint64_t
|
||||
from libc.math cimport sin, cos, sqrt
|
||||
|
||||
class MathError(Exception):
|
||||
def __init__(self, description):
|
||||
self.description = description
|
||||
|
||||
def __str__(self):
|
||||
return self.description
|
||||
|
||||
class GeneralError(Exception):
|
||||
def __init__(self, description):
|
||||
self.description = description
|
||||
|
||||
def __str__(self):
|
||||
return self.description
|
||||
|
||||
cdef class mat4:
|
||||
'''
|
||||
A float 4x4 matrix.
|
||||
|
||||
All arrays are column-major, i.e. OpenGL style:
|
||||
|
||||
0 4 8 12
|
||||
1 5 9 13
|
||||
2 6 10 14
|
||||
3 7 11 15
|
||||
|
||||
The matrix implements stacking useful for graphics.
|
||||
'''
|
||||
|
||||
def __cinit__(mat4 self):
|
||||
to_alloc = 16 * sizeof(float)
|
||||
self.stack = <float *>malloc(to_alloc)
|
||||
|
||||
if not self.stack:
|
||||
raise MemoryError('Unable to malloc %d B for mat4.' %(to_alloc))
|
||||
|
||||
self.m = self.stack
|
||||
self.size = 1
|
||||
|
||||
def _debug(mat4 self):
|
||||
print('--- self.stack = %d' %(<uint64_t>self.stack))
|
||||
print('--- self.m = %d (+%d)' %(<uint64_t>self.m, self.m - self.stack))
|
||||
print('--- self.size = %d' %(self.size))
|
||||
|
||||
def __init__(mat4 self, *args):
|
||||
'''
|
||||
Create a ma4t.
|
||||
|
||||
Accepts any number of parameters between 0 and 16 to fill the
|
||||
matrix from the upper left corner going down (column-wise).
|
||||
'''
|
||||
|
||||
length = len(args)
|
||||
|
||||
if length == 1 and isinstance(args[0], (list, tuple)):
|
||||
args = args[0]
|
||||
length = len(args)
|
||||
|
||||
if length > 16:
|
||||
raise MathError('Attempt to initialize a mat4 with %d arguments.' %(length))
|
||||
|
||||
self.load_from(args)
|
||||
|
||||
def __dealloc__(mat4 self):
|
||||
free(self.stack)
|
||||
|
||||
def __getstate__(mat4 self):
|
||||
state = []
|
||||
|
||||
for i in range(self.m - self.stack + 16):
|
||||
state.append(self.stack[i])
|
||||
|
||||
return state
|
||||
|
||||
def __setstate__(mat4 self, state):
|
||||
length = len(state)
|
||||
matrices = length//16
|
||||
|
||||
if not matrices*16 == length:
|
||||
raise GeneralError('mat4 __setstate__ got %d floats as a state' %(length))
|
||||
|
||||
self.m = self.stack
|
||||
|
||||
slot_full = False
|
||||
for start in range(0, length, 16):
|
||||
if slot_full:
|
||||
self.push()
|
||||
slot_full = False
|
||||
|
||||
self.load_from(state[start:start+16])
|
||||
slot_full = True
|
||||
|
||||
def __getitem__(mat4 self, int i):
|
||||
if i > 16 or i < 0:
|
||||
raise IndexError('element index out of range(16)')
|
||||
|
||||
return self.m[i]
|
||||
|
||||
def __setitem__(self, int i, value):
|
||||
if i > 16 or i < 0:
|
||||
raise IndexError('element index out of range(16)')
|
||||
|
||||
self.m[i] = value
|
||||
|
||||
def push(mat4 self):
|
||||
'''
|
||||
Push the current matrix into the stack and load up an empty one (a zero matrix)
|
||||
'''
|
||||
|
||||
# self.m points to the current matrix
|
||||
# self.stack points to the first matrix
|
||||
# self.size how many matrices are allocated
|
||||
|
||||
# ensure there's room for one more
|
||||
cdef unsigned int used = 1 + (self.m - self.stack) / 16
|
||||
cdef unsigned int empty = self.size - used
|
||||
cdef float *tmp
|
||||
|
||||
if not empty:
|
||||
self.size += 1
|
||||
to_alloc = self.size * 16 * sizeof(float)
|
||||
tmp = <float *>realloc(self.stack, to_alloc)
|
||||
|
||||
if tmp:
|
||||
self.stack = tmp
|
||||
else:
|
||||
raise MemoryError('Unable to malloc %d B for mat4.' %(to_alloc))
|
||||
|
||||
# advance the pointer to the new one
|
||||
self.m = self.stack + 16 * used
|
||||
|
||||
# at this point there's at least enough space for one matrix
|
||||
# copy the old matrix into the new one
|
||||
cdef uint8_t i
|
||||
cdef float *old_m = self.m - 16
|
||||
for i in range(16):
|
||||
self.m[i] = old_m[i]
|
||||
|
||||
def pop(mat4 self):
|
||||
'''
|
||||
Pop a matrix from the stack.
|
||||
'''
|
||||
|
||||
if self.m == self.stack:
|
||||
raise IndexError('pop from an empty stack')
|
||||
|
||||
self.m -= 16
|
||||
|
||||
def get_list(mat4 self):
|
||||
L = []
|
||||
|
||||
for i in range(16):
|
||||
L.append(self.m[i])
|
||||
|
||||
return L
|
||||
|
||||
def load_from(mat4 self, L):
|
||||
'''
|
||||
Fill the current matrix from a either a list of values, column-major,
|
||||
or another matrix. This method doesn't modify the stack, only the
|
||||
current matrix is read and modified.
|
||||
|
||||
If the number of values isn't 16, it will be padded to 16 by zeros.
|
||||
If it's larger, GeneralError will be raised.
|
||||
'''
|
||||
|
||||
if isinstance(L, mat4):
|
||||
L = L.get_list()
|
||||
length = 16
|
||||
else:
|
||||
length = len(L)
|
||||
|
||||
if length > 16:
|
||||
raise GeneralError('supplied list is longer than 16')
|
||||
|
||||
for i in range(16):
|
||||
if i < length:
|
||||
self.m[i] = L[i]
|
||||
else:
|
||||
self.m[i] = 0.0
|
||||
|
||||
def zero(mat4 self):
|
||||
'''Fill the matrix with zeroes.'''
|
||||
|
||||
for i in range(16):
|
||||
self.m[i] = 0.0
|
||||
|
||||
def identity(mat4 self):
|
||||
'''Make the matrix an identity.'''
|
||||
|
||||
self.zero()
|
||||
|
||||
self.m[0] = 1.0
|
||||
self.m[5] = 1.0
|
||||
self.m[10] = 1.0
|
||||
self.m[15] = 1.0
|
||||
|
||||
def transpose(mat4 self):
|
||||
'''Transpose the matrix.'''
|
||||
|
||||
cdef float tmp
|
||||
|
||||
tmp = self.m[1]
|
||||
self.m[1] = self.m[4]
|
||||
self.m[4] = tmp
|
||||
|
||||
tmp = self.m[2]
|
||||
self.m[2] = self.m[8]
|
||||
self.m[8] = tmp
|
||||
|
||||
tmp = self.m[3]
|
||||
self.m[3] = self.m[12]
|
||||
self.m[12] = tmp
|
||||
|
||||
tmp = self.m[7]
|
||||
self.m[7] = self.m[13]
|
||||
self.m[13] = tmp
|
||||
|
||||
tmp = self.m[11]
|
||||
self.m[11] = self.m[14]
|
||||
self.m[14] = tmp
|
||||
|
||||
tmp = self.m[6]
|
||||
self.m[6] = self.m[9]
|
||||
self.m[9] = tmp
|
||||
|
||||
def invert(mat4 self):
|
||||
'''Invert the matrix.'''
|
||||
|
||||
cdef float tmp[16]
|
||||
cdef float det
|
||||
|
||||
tmp[0] = self.m[5]*self.m[10]*self.m[15] - self.m[5]*self.m[11]*self.m[14] - self.m[9]*self.m[6]*self.m[15] + self.m[9]*self.m[7]*self.m[14] + self.m[13]*self.m[6]*self.m[11] - self.m[13]*self.m[7]*self.m[10]
|
||||
tmp[4] = -self.m[4]*self.m[10]*self.m[15] + self.m[4]*self.m[11]*self.m[14] + self.m[8]*self.m[6]*self.m[15] - self.m[8]*self.m[7]*self.m[14] - self.m[12]*self.m[6]*self.m[11] + self.m[12]*self.m[7]*self.m[10]
|
||||
tmp[8] = self.m[4]*self.m[9]*self.m[15] - self.m[4]*self.m[11]*self.m[13] - self.m[8]*self.m[5]*self.m[15] + self.m[8]*self.m[7]*self.m[13] + self.m[12]*self.m[5]*self.m[11] - self.m[12]*self.m[7]*self.m[9]
|
||||
tmp[12] = -self.m[4]*self.m[9]*self.m[14] + self.m[4]*self.m[10]*self.m[13] + self.m[8]*self.m[5]*self.m[14] - self.m[8]*self.m[6]*self.m[13] - self.m[12]*self.m[5]*self.m[10] + self.m[12]*self.m[6]*self.m[9]
|
||||
|
||||
det = self.m[0]*tmp[0] + self.m[1]*tmp[4] + self.m[2]*tmp[8] + self.m[3]*tmp[12]
|
||||
|
||||
# epsilon pulled straight out of Uranus
|
||||
if det < 0.00005 and det > -0.00005:
|
||||
print('det=%.1f' %(det))
|
||||
return
|
||||
|
||||
tmp[1] = -self.m[1]*self.m[10]*self.m[15] + self.m[1]*self.m[11]*self.m[14] + self.m[9]*self.m[2]*self.m[15] - self.m[9]*self.m[3]*self.m[14] - self.m[13]*self.m[2]*self.m[11] + self.m[13]*self.m[3]*self.m[10]
|
||||
tmp[5] = self.m[0]*self.m[10]*self.m[15] - self.m[0]*self.m[11]*self.m[14] - self.m[8]*self.m[2]*self.m[15] + self.m[8]*self.m[3]*self.m[14] + self.m[12]*self.m[2]*self.m[11] - self.m[12]*self.m[3]*self.m[10]
|
||||
tmp[9] = -self.m[0]*self.m[9]*self.m[15] + self.m[0]*self.m[11]*self.m[13] + self.m[8]*self.m[1]*self.m[15] - self.m[8]*self.m[3]*self.m[13] - self.m[12]*self.m[1]*self.m[11] + self.m[12]*self.m[3]*self.m[9]
|
||||
tmp[13] = self.m[0]*self.m[9]*self.m[14] - self.m[0]*self.m[10]*self.m[13] - self.m[8]*self.m[1]*self.m[14] + self.m[8]*self.m[2]*self.m[13] + self.m[12]*self.m[1]*self.m[10] - self.m[12]*self.m[2]*self.m[9]
|
||||
tmp[2] = self.m[1]*self.m[6]*self.m[15] - self.m[1]*self.m[7]*self.m[14] - self.m[5]*self.m[2]*self.m[15] + self.m[5]*self.m[3]*self.m[14] + self.m[13]*self.m[2]*self.m[7] - self.m[13]*self.m[3]*self.m[6]
|
||||
tmp[6] = -self.m[0]*self.m[6]*self.m[15] + self.m[0]*self.m[7]*self.m[14] + self.m[4]*self.m[2]*self.m[15] - self.m[4]*self.m[3]*self.m[14] - self.m[12]*self.m[2]*self.m[7] + self.m[12]*self.m[3]*self.m[6]
|
||||
tmp[10] = self.m[0]*self.m[5]*self.m[15] - self.m[0]*self.m[7]*self.m[13] - self.m[4]*self.m[1]*self.m[15] + self.m[4]*self.m[3]*self.m[13] + self.m[12]*self.m[1]*self.m[7] - self.m[12]*self.m[3]*self.m[5]
|
||||
tmp[14] = -self.m[0]*self.m[5]*self.m[14] + self.m[0]*self.m[6]*self.m[13] + self.m[4]*self.m[1]*self.m[14] - self.m[4]*self.m[2]*self.m[13] - self.m[12]*self.m[1]*self.m[6] + self.m[12]*self.m[2]*self.m[5]
|
||||
tmp[3] = -self.m[1]*self.m[6]*self.m[11] + self.m[1]*self.m[7]*self.m[10] + self.m[5]*self.m[2]*self.m[11] - self.m[5]*self.m[3]*self.m[10] - self.m[9]*self.m[2]*self.m[7] + self.m[9]*self.m[3]*self.m[6]
|
||||
tmp[7] = self.m[0]*self.m[6]*self.m[11] - self.m[0]*self.m[7]*self.m[10] - self.m[4]*self.m[2]*self.m[11] + self.m[4]*self.m[3]*self.m[10] + self.m[8]*self.m[2]*self.m[7] - self.m[8]*self.m[3]*self.m[6]
|
||||
tmp[11] = -self.m[0]*self.m[5]*self.m[11] + self.m[0]*self.m[7]*self.m[9] + self.m[4]*self.m[1]*self.m[11] - self.m[4]*self.m[3]*self.m[9] - self.m[8]*self.m[1]*self.m[7] + self.m[8]*self.m[3]*self.m[5]
|
||||
tmp[15] = self.m[0]*self.m[5]*self.m[10] - self.m[0]*self.m[6]*self.m[9] - self.m[4]*self.m[1]*self.m[10] + self.m[4]*self.m[2]*self.m[9] + self.m[8]*self.m[1]*self.m[6] - self.m[8]*self.m[2]*self.m[5]
|
||||
|
||||
det = 1.0 / det
|
||||
self.m[0] = tmp[0] * det
|
||||
self.m[1] = tmp[1] * det
|
||||
self.m[2] = tmp[2] * det
|
||||
self.m[3] = tmp[3] * det
|
||||
self.m[4] = tmp[4] * det
|
||||
self.m[5] = tmp[5] * det
|
||||
self.m[6] = tmp[6] * det
|
||||
self.m[7] = tmp[7] * det
|
||||
self.m[8] = tmp[8] * det
|
||||
self.m[9] = tmp[9] * det
|
||||
self.m[10] = tmp[10] * det
|
||||
self.m[11] = tmp[11] * det
|
||||
self.m[12] = tmp[12] * det
|
||||
self.m[13] = tmp[13] * det
|
||||
self.m[14] = tmp[14] * det
|
||||
self.m[15] = tmp[15] * det
|
||||
|
||||
def mulm(mat4 self, mat4 B, bint inplace=False):
|
||||
'''
|
||||
Return a matrix that is the result of multiplying this matrix by another.
|
||||
|
||||
M = self * mat4 B
|
||||
'''
|
||||
|
||||
cdef uint8_t i
|
||||
cdef mat4 tmp = mat4()
|
||||
|
||||
tmp.m[0] = self.m[0] * B.m[0] + self.m[4] * B.m[1] + self.m[8] * B.m[2] + self.m[12] * B.m[3]
|
||||
tmp.m[1] = self.m[1] * B.m[0] + self.m[5] * B.m[1] + self.m[9] * B.m[2] + self.m[13] * B.m[3]
|
||||
tmp.m[2] = self.m[2] * B.m[0] + self.m[6] * B.m[1] + self.m[10] * B.m[2] + self.m[14] * B.m[3]
|
||||
tmp.m[3] = self.m[3] * B.m[0] + self.m[7] * B.m[1] + self.m[11] * B.m[2] + self.m[15] * B.m[3]
|
||||
tmp.m[4] = self.m[0] * B.m[4] + self.m[4] * B.m[5] + self.m[8] * B.m[6] + self.m[12] * B.m[7]
|
||||
tmp.m[5] = self.m[1] * B.m[4] + self.m[5] * B.m[5] + self.m[9] * B.m[6] + self.m[13] * B.m[7]
|
||||
tmp.m[6] = self.m[2] * B.m[4] + self.m[6] * B.m[5] + self.m[10] * B.m[6] + self.m[14] * B.m[7]
|
||||
tmp.m[7] = self.m[3] * B.m[4] + self.m[7] * B.m[5] + self.m[11] * B.m[6] + self.m[15] * B.m[7]
|
||||
tmp.m[8] = self.m[0] * B.m[8] + self.m[4] * B.m[9] + self.m[8] * B.m[10] + self.m[12] * B.m[11]
|
||||
tmp.m[9] = self.m[1] * B.m[8] + self.m[5] * B.m[9] + self.m[9] * B.m[10] + self.m[13] * B.m[11]
|
||||
tmp.m[10] = self.m[2] * B.m[8] + self.m[6] * B.m[9] + self.m[10] * B.m[10] + self.m[14] * B.m[11]
|
||||
tmp.m[11] = self.m[3] * B.m[8] + self.m[7] * B.m[9] + self.m[11] * B.m[10] + self.m[15] * B.m[11]
|
||||
tmp.m[12] = self.m[0] * B.m[12] + self.m[4] * B.m[13] + self.m[8] * B.m[14] + self.m[12] * B.m[15]
|
||||
tmp.m[13] = self.m[1] * B.m[12] + self.m[5] * B.m[13] + self.m[9] * B.m[14] + self.m[13] * B.m[15]
|
||||
tmp.m[14] = self.m[2] * B.m[12] + self.m[6] * B.m[13] + self.m[10] * B.m[14] + self.m[14] * B.m[15]
|
||||
tmp.m[15] = self.m[3] * B.m[12] + self.m[7] * B.m[13] + self.m[11] * B.m[14] + self.m[15] * B.m[15]
|
||||
|
||||
if inplace:
|
||||
for i in range(16):
|
||||
self.m[i] = tmp.m[i]
|
||||
else:
|
||||
return tmp
|
||||
|
||||
def mulv(mat4 self, vec3 v):
|
||||
'''
|
||||
Return a vec3 that is the result of multiplying this matrix by a vec3.
|
||||
|
||||
u = self * v
|
||||
'''
|
||||
|
||||
cdef mat4 tmp = vec3()
|
||||
|
||||
tmp.v[0] = v.v[0]*self.m[0] + v.v[1]*self.m[4] + v.v[2]*self.m[8] + self.m[12]
|
||||
tmp.v[1] = v.v[0]*self.m[1] + v.v[1]*self.m[5] + v.v[2]*self.m[9] + self.m[13]
|
||||
tmp.v[2] = v.v[0]*self.m[2] + v.v[1]*self.m[6] + v.v[2]*self.m[10] + self.m[14]
|
||||
|
||||
return tmp
|
||||
|
||||
def mulf(mat4 self, f):
|
||||
'''
|
||||
Return a matrix that is the result of multiplying this matrix by a scalar.
|
||||
|
||||
M = self * f
|
||||
'''
|
||||
|
||||
cdef mat4 tmp = mat4()
|
||||
cdef int i
|
||||
|
||||
for i in range(16):
|
||||
tmp.m[i] = self.m[i] * f
|
||||
|
||||
return tmp
|
||||
|
||||
def __repr__(mat4 self):
|
||||
lines = []
|
||||
|
||||
lines.append('mat4(%.1f %.1f %.1f %.1f' %(self.m[0], self.m[4], self.m[8], self.m[12]))
|
||||
lines.append(' %.1f %.1f %.1f %.1f' %(self.m[1], self.m[5], self.m[9], self.m[13]))
|
||||
lines.append(' %.1f %.1f %.1f %.1f' %(self.m[2], self.m[6], self.m[10], self.m[14]))
|
||||
lines.append(' %.1f %.1f %.1f %.1f)' %(self.m[3], self.m[7], self.m[11], self.m[15]))
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
cdef class vec3:
|
||||
'''
|
||||
A float 3D vector.
|
||||
|
||||
>>> v = vec3(1, 1, 0)
|
||||
>>> w = vec3(0, 1, 1)
|
||||
>>> v.length
|
||||
1.4142135623730951
|
||||
>>> v.dot(w)
|
||||
1.0
|
||||
>>> v.cross(w)
|
||||
vec4(1.00, 1.00, 1.00)
|
||||
>>> v + w
|
||||
vec4(1.00, 2.00, 1.00)
|
||||
>>> w - v
|
||||
vec4(-1.00, 0.00, 1.00)
|
||||
|
||||
'''
|
||||
|
||||
def __init__(vec3 self, *args):
|
||||
'''
|
||||
Create a vec3.
|
||||
|
||||
Accepts any number of parameters between 0 and 3 to fill the vector from the left.
|
||||
'''
|
||||
|
||||
length = len(args)
|
||||
|
||||
if length == 1 and isinstance(args[0], (list, tuple)):
|
||||
args = args[0]
|
||||
length = len(args)
|
||||
|
||||
if length > 3:
|
||||
raise MathError('Attempt to initialize a vec3 with %d arguments.' %(length))
|
||||
|
||||
for i in range(3):
|
||||
if i < length:
|
||||
self.v[i] = args[i]
|
||||
else:
|
||||
self.v[i] = 0.0
|
||||
|
||||
def __getitem__(vec3 self, int i):
|
||||
if i >= 3 or i < 0:
|
||||
raise IndexError('element index out of range(3)')
|
||||
|
||||
return self.v[i]
|
||||
|
||||
def __setitem__(vec3 self, int i, float value):
|
||||
if i >= 3 or i < 0:
|
||||
raise IndexError('element index out of range(3)')
|
||||
|
||||
self.v[i] = value
|
||||
|
||||
def __repr__(vec3 self):
|
||||
return 'vec3(%.2f, %.2f, %.2f)' %(self.v[0], self.v[1], self.v[2])
|
||||
|
||||
def __getstate__(vec3 self):
|
||||
return (self.v[0], self.v[1], self.v[2])
|
||||
|
||||
def __setstate__(vec3 self, state):
|
||||
self.v[0] = state[0]
|
||||
self.v[1] = state[1]
|
||||
self.v[2] = state[2]
|
||||
|
||||
@property
|
||||
def length(vec3 self):
|
||||
'''Contains the geometric length of the vector.'''
|
||||
|
||||
return sqrt(self.v[0]**2 + self.v[1]**2 + self.v[2]**2)
|
||||
|
||||
def normalized(vec3 self):
|
||||
'''Returns this vector, normalized.'''
|
||||
|
||||
length = self.length
|
||||
|
||||
return vec3(self.v[0] / length, self.v[1] / length, self.v[2] / length)
|
||||
|
||||
def __add__(vec3 L, vec3 R):
|
||||
return vec3(L.v[0] + R.v[0], L.v[1] + R.v[1], L.v[2] + R.v[2])
|
||||
|
||||
def __sub__(vec3 L, vec3 R):
|
||||
return vec3(L.v[0] - R.v[0], L.v[1] - R.v[1], L.v[2] - R.v[2])
|
||||
|
||||
def __neg__(vec3 self):
|
||||
return vec3(-self.v[0], -self.v[1], -self.v[2])
|
||||
|
||||
def dot(vec3 L, vec3 R):
|
||||
'''
|
||||
Returns the dot product of the two vectors.
|
||||
|
||||
E.g. u.dot(v) -> u . v
|
||||
'''
|
||||
|
||||
return L.v[0] * R.v[0] + L.v[1] * R.v[1] + L.v[2] * R.v[2]
|
||||
|
||||
def cross(vec3 L, vec3 R):
|
||||
'''
|
||||
Returns the cross product of the two vectors.
|
||||
|
||||
E.g. u.cross(v) -> u x v
|
||||
|
||||
'''
|
||||
|
||||
return vec3(L.v[1]*R.v[2] - L.v[2]*R.v[1], L.v[0]*R.v[2] - L.v[2]*R.v[0], L.v[0]*R.v[1] - L.v[1]*R.v[0])
|
||||
|
||||
def __mul__(vec3 L, R):
|
||||
'''
|
||||
Multiplication of a vec3 by a float.
|
||||
|
||||
The float has to be on the right.
|
||||
'''
|
||||
|
||||
return vec3(L.v[0] * R, L.v[1] * R, L.v[2] * R)
|
|
@ -7,6 +7,10 @@ class compare_float:
|
|||
"""
|
||||
|
||||
def __init__(self, A, B, epsilon=1e-12):
|
||||
self.A = A
|
||||
self.B = B
|
||||
self.epsilon = epsilon
|
||||
|
||||
self.lt = False
|
||||
self.le = True
|
||||
self.eq = False
|
||||
|
@ -33,11 +37,14 @@ class compare_float:
|
|||
self.greater_or_equal = self.ge
|
||||
self.greater = self.gt
|
||||
self.not_equal = self.ne
|
||||
|
||||
def __repr__(self):
|
||||
return "compare_float(%.2f, %.2f, epsilon=%.03e)" %(self.A, self.B, self.epsilon)
|
||||
|
||||
if __name__ == '__main__':
|
||||
x = compare_float(1, 2, 0.5)
|
||||
assert x.lt and x.less and not (x.le or x.ge)
|
||||
assert x.lt and x.less and not (x.gt or x.ge)
|
||||
x = compare_float(2, 2, 0.5)
|
||||
assert x.eq and x.le and x.ge and not (x.lt or x.gt)
|
||||
x = compare_float(3, 2, 0.5)
|
||||
assert x.gt and not (x.ge or x.eq or x.lt)
|
||||
assert x.gt and x.ge and not (x.le or x.eq or x.lt)
|
|
@ -1,16 +1,16 @@
|
|||
#! /usr/bin/env python3
|
||||
#! /bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
'''
|
||||
Vector And Matrix Math
|
||||
"""
|
||||
Vector and Matrix Math
|
||||
"""
|
||||
|
||||
Pure Python implementation.
|
||||
'''
|
||||
from libc.stdlib cimport malloc, realloc, free
|
||||
from libc.stdint cimport uint8_t, uint16_t, uint64_t
|
||||
from libc.math cimport sin, cos, sqrt
|
||||
|
||||
from math import sin, cos, sqrt
|
||||
|
||||
class mat4:
|
||||
'''
|
||||
cdef class mat4:
|
||||
"""
|
||||
A float 4x4 matrix.
|
||||
|
||||
All arrays are column-major, i.e. OpenGL style:
|
||||
|
@ -21,32 +21,30 @@ class mat4:
|
|||
3 7 11 15
|
||||
|
||||
The matrix implements stacking useful for graphics.
|
||||
'''
|
||||
"""
|
||||
|
||||
def __cinit__(mat4 self):
|
||||
to_alloc = 16 * sizeof(float)
|
||||
self.stack = <float *>malloc(to_alloc)
|
||||
|
||||
if not self.stack:
|
||||
raise MemoryError('Unable to malloc %d B for mat4.' %(to_alloc))
|
||||
raise MemoryError("Unable to malloc %d B for mat4." %(to_alloc))
|
||||
|
||||
self.m = self.stack
|
||||
self.size = 1
|
||||
|
||||
def _debug(mat4 self):
|
||||
print('--- self.stack = %d' %(<uint64_t>self.stack))
|
||||
print('--- self.m = %d (+%d)' %(<uint64_t>self.m, self.m - self.stack))
|
||||
print('--- self.size = %d' %(self.size))
|
||||
print("--- self.stack = %d" %(<uint64_t>self.stack))
|
||||
print("--- self.m = %d (+%d)" %(<uint64_t>self.m, self.m - self.stack))
|
||||
print("--- self.size = %d" %(self.size))
|
||||
|
||||
def __init__(mat4 self, *args):
|
||||
'''
|
||||
"""
|
||||
Create a ma4t.
|
||||
|
||||
Accepts any number of parameters between 0 and 16 to fill the
|
||||
matrix from the upper left corner going down (column-wise).
|
||||
'''
|
||||
|
||||
self.m =
|
||||
"""
|
||||
|
||||
length = len(args)
|
||||
|
||||
|
@ -55,7 +53,7 @@ class mat4:
|
|||
length = len(args)
|
||||
|
||||
if length > 16:
|
||||
raise MathError('Attempt to initialize a mat4 with %d arguments.' %(length))
|
||||
raise ValueError("Attempt to initialize a mat4 with %d arguments." %(length))
|
||||
|
||||
self.load_from(args)
|
||||
|
||||
|
@ -75,7 +73,7 @@ class mat4:
|
|||
matrices = length//16
|
||||
|
||||
if not matrices*16 == length:
|
||||
raise GeneralError('mat4 __setstate__ got %d floats as a state' %(length))
|
||||
raise ValueError("mat4 __setstate__ got %d floats as a state" %(length))
|
||||
|
||||
self.m = self.stack
|
||||
|
||||
|
@ -90,20 +88,22 @@ class mat4:
|
|||
|
||||
def __getitem__(mat4 self, int i):
|
||||
if i > 16 or i < 0:
|
||||
raise IndexError('element index out of range(16)')
|
||||
raise IndexError("element index out of range(16)")
|
||||
|
||||
return self.m[i]
|
||||
|
||||
def __setitem__(self, int i, value):
|
||||
if i > 16 or i < 0:
|
||||
raise IndexError('element index out of range(16)')
|
||||
raise IndexError("element index out of range(16)")
|
||||
|
||||
self.m[i] = value
|
||||
|
||||
def push(mat4 self):
|
||||
'''
|
||||
Push the current matrix into the stack and load up an empty one (a zero matrix)
|
||||
'''
|
||||
"""
|
||||
Push the current matrix into the stack and load up an empty one (a zero matrix).
|
||||
|
||||
TODO consider copying the matrix instead?
|
||||
"""
|
||||
|
||||
# self.m points to the current matrix
|
||||
# self.stack points to the first matrix
|
||||
|
@ -122,12 +122,12 @@ class mat4:
|
|||
if tmp:
|
||||
self.stack = tmp
|
||||
else:
|
||||
raise MemoryError('Unable to malloc %d B for mat4.' %(to_alloc))
|
||||
raise MemoryError("Unable to malloc %d B for mat4." %(to_alloc))
|
||||
|
||||
# advance the pointer to the new one
|
||||
self.m = self.stack + 16 * used
|
||||
|
||||
# at this point there's at least enough space for one matrix
|
||||
# at this point there"s at least enough space for one matrix
|
||||
# copy the old matrix into the new one
|
||||
cdef uint8_t i
|
||||
cdef float *old_m = self.m - 16
|
||||
|
@ -135,12 +135,12 @@ class mat4:
|
|||
self.m[i] = old_m[i]
|
||||
|
||||
def pop(mat4 self):
|
||||
'''
|
||||
"""
|
||||
Pop a matrix from the stack.
|
||||
'''
|
||||
"""
|
||||
|
||||
if self.m == self.stack:
|
||||
raise IndexError('pop from an empty stack')
|
||||
raise IndexError("pop from an empty stack")
|
||||
|
||||
self.m -= 16
|
||||
|
||||
|
@ -153,14 +153,14 @@ class mat4:
|
|||
return L
|
||||
|
||||
def load_from(mat4 self, L):
|
||||
'''
|
||||
"""
|
||||
Fill the current matrix from a either a list of values, column-major,
|
||||
or another matrix. This method doesn't modify the stack, only the
|
||||
current matrix is read and modified.
|
||||
|
||||
If the number of values isn't 16, it will be padded to 16 by zeros.
|
||||
If the number of values isn"t 16, it will be padded to 16 by zeros.
|
||||
If it's larger, GeneralError will be raised.
|
||||
'''
|
||||
"""
|
||||
|
||||
if isinstance(L, mat4):
|
||||
L = L.get_list()
|
||||
|
@ -169,7 +169,7 @@ class mat4:
|
|||
length = len(L)
|
||||
|
||||
if length > 16:
|
||||
raise GeneralError('supplied list is longer than 16')
|
||||
raise ValueError("supplied list is longer than 16")
|
||||
|
||||
for i in range(16):
|
||||
if i < length:
|
||||
|
@ -178,13 +178,17 @@ class mat4:
|
|||
self.m[i] = 0.0
|
||||
|
||||
def zero(mat4 self):
|
||||
'''Fill the matrix with zeroes.'''
|
||||
"""
|
||||
Fill the matrix with zeroes.
|
||||
"""
|
||||
|
||||
for i in range(16):
|
||||
self.m[i] = 0.0
|
||||
|
||||
def identity(mat4 self):
|
||||
'''Make the matrix an identity.'''
|
||||
"""
|
||||
Make the matrix an identity.
|
||||
"""
|
||||
|
||||
self.zero()
|
||||
|
||||
|
@ -194,7 +198,9 @@ class mat4:
|
|||
self.m[15] = 1.0
|
||||
|
||||
def transpose(mat4 self):
|
||||
'''Transpose the matrix.'''
|
||||
"""
|
||||
Transpose the matrix.
|
||||
"""
|
||||
|
||||
cdef float tmp
|
||||
|
||||
|
@ -223,7 +229,9 @@ class mat4:
|
|||
self.m[9] = tmp
|
||||
|
||||
def invert(mat4 self):
|
||||
'''Invert the matrix.'''
|
||||
"""
|
||||
Invert the matrix.
|
||||
"""
|
||||
|
||||
cdef float tmp[16]
|
||||
cdef float det
|
||||
|
@ -237,7 +245,7 @@ class mat4:
|
|||
|
||||
# epsilon pulled straight out of Uranus
|
||||
if det < 0.00005 and det > -0.00005:
|
||||
print('det=%.1f' %(det))
|
||||
print("det=%.1f" %(det))
|
||||
return
|
||||
|
||||
tmp[1] = -self.m[1]*self.m[10]*self.m[15] + self.m[1]*self.m[11]*self.m[14] + self.m[9]*self.m[2]*self.m[15] - self.m[9]*self.m[3]*self.m[14] - self.m[13]*self.m[2]*self.m[11] + self.m[13]*self.m[3]*self.m[10]
|
||||
|
@ -272,11 +280,11 @@ class mat4:
|
|||
self.m[15] = tmp[15] * det
|
||||
|
||||
def mulm(mat4 self, mat4 B, bint inplace=False):
|
||||
'''
|
||||
"""
|
||||
Return a matrix that is the result of multiplying this matrix by another.
|
||||
|
||||
M = self * mat4 B
|
||||
'''
|
||||
"""
|
||||
|
||||
cdef uint8_t i
|
||||
cdef mat4 tmp = mat4()
|
||||
|
@ -305,11 +313,11 @@ class mat4:
|
|||
return tmp
|
||||
|
||||
def mulv(mat4 self, vec3 v):
|
||||
'''
|
||||
"""
|
||||
Return a vec3 that is the result of multiplying this matrix by a vec3.
|
||||
|
||||
u = self * v
|
||||
'''
|
||||
"""
|
||||
|
||||
cdef mat4 tmp = vec3()
|
||||
|
||||
|
@ -320,11 +328,11 @@ class mat4:
|
|||
return tmp
|
||||
|
||||
def mulf(mat4 self, f):
|
||||
'''
|
||||
"""
|
||||
Return a matrix that is the result of multiplying this matrix by a scalar.
|
||||
|
||||
M = self * f
|
||||
'''
|
||||
"""
|
||||
|
||||
cdef mat4 tmp = mat4()
|
||||
cdef int i
|
||||
|
@ -337,15 +345,15 @@ class mat4:
|
|||
def __repr__(mat4 self):
|
||||
lines = []
|
||||
|
||||
lines.append('mat4(%.1f %.1f %.1f %.1f' %(self.m[0], self.m[4], self.m[8], self.m[12]))
|
||||
lines.append(' %.1f %.1f %.1f %.1f' %(self.m[1], self.m[5], self.m[9], self.m[13]))
|
||||
lines.append(' %.1f %.1f %.1f %.1f' %(self.m[2], self.m[6], self.m[10], self.m[14]))
|
||||
lines.append(' %.1f %.1f %.1f %.1f)' %(self.m[3], self.m[7], self.m[11], self.m[15]))
|
||||
lines.append("mat4(%.1f %.1f %.1f %.1f" %(self.m[0], self.m[4], self.m[8], self.m[12]))
|
||||
lines.append(" %.1f %.1f %.1f %.1f" %(self.m[1], self.m[5], self.m[9], self.m[13]))
|
||||
lines.append(" %.1f %.1f %.1f %.1f" %(self.m[2], self.m[6], self.m[10], self.m[14]))
|
||||
lines.append(" %.1f %.1f %.1f %.1f)" %(self.m[3], self.m[7], self.m[11], self.m[15]))
|
||||
|
||||
return '\n'.join(lines)
|
||||
return "\n".join(lines)
|
||||
|
||||
cdef class vec3:
|
||||
'''
|
||||
"""
|
||||
A float 3D vector.
|
||||
|
||||
>>> v = vec3(1, 1, 0)
|
||||
|
@ -361,14 +369,14 @@ cdef class vec3:
|
|||
>>> w - v
|
||||
vec4(-1.00, 0.00, 1.00)
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(vec3 self, *args):
|
||||
'''
|
||||
"""
|
||||
Create a vec3.
|
||||
|
||||
Accepts any number of parameters between 0 and 3 to fill the vector from the left.
|
||||
'''
|
||||
"""
|
||||
|
||||
length = len(args)
|
||||
|
||||
|
@ -377,7 +385,7 @@ cdef class vec3:
|
|||
length = len(args)
|
||||
|
||||
if length > 3:
|
||||
raise MathError('Attempt to initialize a vec3 with %d arguments.' %(length))
|
||||
raise ValueError("Attempt to initialize a vec3 with %d arguments." %(length))
|
||||
|
||||
for i in range(3):
|
||||
if i < length:
|
||||
|
@ -387,18 +395,18 @@ cdef class vec3:
|
|||
|
||||
def __getitem__(vec3 self, int i):
|
||||
if i >= 3 or i < 0:
|
||||
raise IndexError('element index out of range(3)')
|
||||
raise IndexError("element index out of range(3)")
|
||||
|
||||
return self.v[i]
|
||||
|
||||
def __setitem__(vec3 self, int i, float value):
|
||||
if i >= 3 or i < 0:
|
||||
raise IndexError('element index out of range(3)')
|
||||
raise IndexError("element index out of range(3)")
|
||||
|
||||
self.v[i] = value
|
||||
|
||||
def __repr__(vec3 self):
|
||||
return 'vec3(%.2f, %.2f, %.2f)' %(self.v[0], self.v[1], self.v[2])
|
||||
return "vec3(%.2f, %.2f, %.2f)" %(self.v[0], self.v[1], self.v[2])
|
||||
|
||||
def __getstate__(vec3 self):
|
||||
return (self.v[0], self.v[1], self.v[2])
|
||||
|
@ -410,12 +418,16 @@ cdef class vec3:
|
|||
|
||||
@property
|
||||
def length(vec3 self):
|
||||
'''Contains the geometric length of the vector.'''
|
||||
"""
|
||||
Contains the geometric length of the vector.
|
||||
"""
|
||||
|
||||
return sqrt(self.v[0]**2 + self.v[1]**2 + self.v[2]**2)
|
||||
|
||||
def normalized(vec3 self):
|
||||
'''Returns this vector, normalized.'''
|
||||
"""
|
||||
Returns this vector, normalized.
|
||||
"""
|
||||
|
||||
length = self.length
|
||||
|
||||
|
@ -431,29 +443,28 @@ cdef class vec3:
|
|||
return vec3(-self.v[0], -self.v[1], -self.v[2])
|
||||
|
||||
def dot(vec3 L, vec3 R):
|
||||
'''
|
||||
"""
|
||||
Returns the dot product of the two vectors.
|
||||
|
||||
E.g. u.dot(v) -> u . v
|
||||
'''
|
||||
"""
|
||||
|
||||
return L.v[0] * R.v[0] + L.v[1] * R.v[1] + L.v[2] * R.v[2]
|
||||
|
||||
def cross(vec3 L, vec3 R):
|
||||
'''
|
||||
"""
|
||||
Returns the cross product of the two vectors.
|
||||
|
||||
E.g. u.cross(v) -> u x v
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
return vec3(L.v[1]*R.v[2] - L.v[2]*R.v[1], L.v[0]*R.v[2] - L.v[2]*R.v[0], L.v[0]*R.v[1] - L.v[1]*R.v[0])
|
||||
|
||||
def __mul__(vec3 L, R):
|
||||
'''
|
||||
"""
|
||||
Multiplication of a vec3 by a float.
|
||||
|
||||
The float has to be on the right.
|
||||
'''
|
||||
"""
|
||||
|
||||
return vec3(L.v[0] * R, L.v[1] * R, L.v[2] * R)
|
100
over/misc.py
100
over/misc.py
|
@ -1,38 +1,36 @@
|
|||
#! /usr/bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
import copy
|
||||
import imp
|
||||
import io
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
from . import text
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def import_module(path):
|
||||
'''
|
||||
Imports a python file as a module. The path can be relative or absolute.
|
||||
"""
|
||||
Imports a python file as a module. The `path` can be relative or absolute.
|
||||
|
||||
Based on the work of Yuval Greenfield released into the public domain.
|
||||
'''
|
||||
"""
|
||||
|
||||
import imp
|
||||
import os
|
||||
|
||||
path = os.path.expanduser(path)
|
||||
|
||||
# remove the .py suffix
|
||||
mod_dn = os.path.dirname(path)
|
||||
mod_fn = os.path.basename(path)
|
||||
mod_dirname = os.path.dirname(path)
|
||||
mod_filename = os.path.basename(path)
|
||||
|
||||
if mod_fn.endswith('.py'):
|
||||
mod_name = mod_fn[:-3]
|
||||
if mod_filename.endswith(".py"):
|
||||
mod_name = mod_filename[:-3]
|
||||
else:
|
||||
# packages for example
|
||||
mod_name = mod_fn
|
||||
mod_name = mod_filename
|
||||
|
||||
fd = None
|
||||
|
||||
try:
|
||||
data = imp.find_module(mod_name, [mod_dn])
|
||||
data = imp.find_module(mod_name, [mod_dirname])
|
||||
module = imp.load_module(mod_name, *data)
|
||||
fd = data[0]
|
||||
|
||||
|
@ -45,24 +43,24 @@ def import_module(path):
|
|||
# --------------------------------------------------
|
||||
|
||||
def batch_gen(data, batch_size):
|
||||
'''
|
||||
Split data (a sequence) into sequences batch_size elements long.
|
||||
'''
|
||||
"""
|
||||
Split `data` (a sequence) into sequences `batch_size` elements long.
|
||||
"""
|
||||
|
||||
for i in range(0, len(data), batch_size):
|
||||
yield data[i:i+batch_size]
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def console(locals, globals=None, tab_completion=False):
|
||||
'''
|
||||
def console(locals, globals=None, tab_completion=True):
|
||||
"""
|
||||
Opens up a Python console.
|
||||
"""
|
||||
|
||||
tab_completion enables completion of object names using TAB, however note
|
||||
that this will make pasting formatted snippets of code difficult
|
||||
'''
|
||||
|
||||
import code, readline, rlcompleter
|
||||
import code
|
||||
import copy
|
||||
import readline
|
||||
import rlcompleter
|
||||
|
||||
environment = copy.copy(locals)
|
||||
|
||||
|
@ -72,17 +70,17 @@ def console(locals, globals=None, tab_completion=False):
|
|||
environment[key] = value
|
||||
|
||||
if tab_completion:
|
||||
readline.parse_and_bind('tab: complete')
|
||||
readline.parse_and_bind("tab: complete")
|
||||
|
||||
c = code.InteractiveConsole(environment)
|
||||
c.interact(banner='')
|
||||
c.interact(banner="")
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def debugger():
|
||||
'''
|
||||
"""
|
||||
Drops into the Python Debugger where called.
|
||||
'''
|
||||
"""
|
||||
|
||||
import pdb
|
||||
|
||||
|
@ -92,42 +90,46 @@ def debugger():
|
|||
|
||||
def hexdump(data, indent=0, offset=16, show_header=True, show_offsets=True, show_ascii=True, use_colors=True, output=sys.stdout):
|
||||
"""
|
||||
Writes a hex dump of 'data' to 'output'.
|
||||
Writes a hex dump of `data` to `output`.
|
||||
|
||||
The output text is indented with spaces and contains 'offset' bytes per line.
|
||||
If show_header is True, a single line with byte numbers preceeds all output.
|
||||
If show_offsets is True, each line is prefixed with the address of the first byte of that line.
|
||||
If show_ascii is True, each line is suffixed with its ASCII representation. Unprintable characters
|
||||
The output text is indented with spaces and contains `offset` bytes per line.
|
||||
If `show_header` is True, a single line with byte numbers preceeds all output.
|
||||
If `show_offsets` is True, each line is prefixed with the address of the first byte of that line.
|
||||
If `show_ascii` is True, each line is suffixed with its ASCII representation. Unprintable characters
|
||||
are replaced with a dot.
|
||||
The 'output' must implement a .write(str) method. If 'output' is None, the string is returned instead.
|
||||
The `output` must implement a .write(str x) method. If `output` is None, the string is returned instead.
|
||||
"""
|
||||
|
||||
import io
|
||||
import math
|
||||
from . import text
|
||||
|
||||
output_io = io.StringIO() if not output else output
|
||||
|
||||
if type(data) is not bytes:
|
||||
raise ValueError('data must be bytes')
|
||||
raise ValueError("data must be bytes")
|
||||
|
||||
offset_figures = math.ceil(math.log2(len(data)) / 8) * 2 if data else 2
|
||||
format_str = '%%0%dx ' %(offset_figures)
|
||||
format_str = "%%0%dx " %(offset_figures)
|
||||
ptr = 0
|
||||
|
||||
if show_header:
|
||||
line = []
|
||||
|
||||
if show_offsets:
|
||||
line.append('offset'[:offset_figures].ljust(offset_figures + 2))
|
||||
line.append("offset"[:offset_figures].ljust(offset_figures + 2))
|
||||
|
||||
for i in range(offset):
|
||||
line.append('%2x' %(i))
|
||||
line.append("%2x" %(i))
|
||||
|
||||
if show_ascii:
|
||||
line.append(' *{0}*'.format("ASCII".center(offset, "-")))
|
||||
line.append(" *{0}*".format("ASCII".center(offset, "-")))
|
||||
|
||||
output_io.write(' '.join(line) + '\n')
|
||||
output_io.write(" ".join(line) + "\n")
|
||||
|
||||
while data[ptr:]:
|
||||
if indent:
|
||||
output_io.write(' ' * indent)
|
||||
output_io.write(" " * indent)
|
||||
|
||||
if show_offsets:
|
||||
output_io.write(format_str %(ptr))
|
||||
|
@ -138,25 +140,25 @@ def hexdump(data, indent=0, offset=16, show_header=True, show_offsets=True, show
|
|||
for local_i, i in enumerate(range(ptr, ptr+offset)):
|
||||
if i < len(data):
|
||||
c = data[i]
|
||||
hex_bytes.append('%02x' %(c))
|
||||
hex_bytes.append("%02x" %(c))
|
||||
|
||||
if 0x20 <= c <= 0x7e:
|
||||
ascii_bytes.append(chr(c))
|
||||
else:
|
||||
ascii_bytes.append('.')
|
||||
ascii_bytes.append(".")
|
||||
elif i == len(data):
|
||||
hex_bytes.extend([' '] * (offset - local_i))
|
||||
hex_bytes.extend([" "] * (offset - local_i))
|
||||
|
||||
if use_colors: output_io.write(text.render("§B"))
|
||||
output_io.write(' '.join(hex_bytes))
|
||||
output_io.write(" ".join(hex_bytes))
|
||||
if use_colors: output_io.write(text.render("§/"))
|
||||
|
||||
if show_ascii:
|
||||
output_io.write(text.render(" |§B") if use_colors else " |")
|
||||
output_io.write(''.join(ascii_bytes))
|
||||
output_io.write("".join(ascii_bytes))
|
||||
output_io.write(text.render("§/|") if use_colors else "|")
|
||||
|
||||
output_io.write('\n')
|
||||
output_io.write("\n")
|
||||
|
||||
ptr += offset
|
||||
|
||||
|
|
|
@ -1,105 +1,32 @@
|
|||
#! /usr/bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
class ndict(dict):
|
||||
'''
|
||||
A dictionary superclass whose keys are exposed as attributes.
|
||||
class ndict(OrderedDict):
|
||||
"""
|
||||
An OrderedDict subclass whose keys are exposed as attributes.
|
||||
|
||||
>>> d = ndict()
|
||||
>>> d.alpha = 1
|
||||
>>> d['alpha']
|
||||
>>> d["alpha"]
|
||||
1
|
||||
>>> d['beta'] = 42
|
||||
>>> d["beta"] = 42
|
||||
>>> d.beta
|
||||
42
|
||||
>>> d
|
||||
{'alpha': 1, 'beta': 42}
|
||||
'''
|
||||
{"alpha": 1, "beta": 42}
|
||||
"""
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in self:
|
||||
return self[name]
|
||||
elif name.replace('_', '-') in self:
|
||||
return self[name.replace('_', '-')]
|
||||
elif name.replace("_", "-") in self:
|
||||
return self[name.replace("_", "-")]
|
||||
else:
|
||||
raise AttributeError("'ndict' object has no attribute '%s'" %(name))
|
||||
raise AttributeError('"ndict" object has no attribute "%s"' %(name))
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
self[name] = value
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
class map:
|
||||
'''
|
||||
An ordered dict.
|
||||
'''
|
||||
|
||||
def __init__(self, source=None):
|
||||
'''
|
||||
Initialize the map.
|
||||
|
||||
The source is a sequence of (key, value) tuples.
|
||||
'''
|
||||
|
||||
if source:
|
||||
self.keys, self.vals = zip(*source)
|
||||
else:
|
||||
self.keys = []
|
||||
self.vals = []
|
||||
|
||||
def __get_index(self, key):
|
||||
if key in self.keys:
|
||||
return self.keys.index(key)
|
||||
else:
|
||||
return None
|
||||
|
||||
def __getitem__(self, key):
|
||||
i = self.__get_index(key)
|
||||
|
||||
if i is None:
|
||||
raise KeyError(key)
|
||||
else:
|
||||
return self.vals[i]
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
i = self.__get_index(key)
|
||||
|
||||
if i is None:
|
||||
self.keys.append(key)
|
||||
self.vals.append(val)
|
||||
else:
|
||||
self.vals[i] = val
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.keys
|
||||
|
||||
def index(self, item):
|
||||
return self.keys.index(item)
|
||||
|
||||
def sort(self, key=None):
|
||||
tmp_keys = self.keys
|
||||
tmp_vals = self.vals
|
||||
|
||||
self.keys = []
|
||||
self.vals = []
|
||||
|
||||
for K, V in sorted(zip(tmp_keys, tmp_vals), key=key):
|
||||
self.keys.append(K)
|
||||
self.vals.append(V)
|
||||
|
||||
@property
|
||||
def items(self):
|
||||
return zip(self.keys, self.vals)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.vals)
|
||||
|
||||
def __repr__(self):
|
||||
pairs = []
|
||||
|
||||
for i in range(len(self.keys)):
|
||||
pairs.append('%s: %s' %(repr(self.keys[i]), repr(self.vals[i])))
|
||||
|
||||
return '<{%s}>' %(', '.join(pairs))
|
||||
|
|
|
@ -4,55 +4,60 @@
|
|||
import time
|
||||
import serial
|
||||
|
||||
from . import et
|
||||
from .. import text
|
||||
|
||||
prefix = et.prefix
|
||||
"""
|
||||
OverTalk protocol spec
|
||||
======================
|
||||
|
||||
'''
|
||||
===== OverTalk protocol spec =====
|
||||
==== Transport layer ====
|
||||
Transport layer
|
||||
---------------
|
||||
TODO: Swap checksum and payload.
|
||||
|
||||
frame = [ 0x2a | destination (1) | source (1) | payload length (1) | payload (n) | checksum (1) ]
|
||||
|
||||
Any time an 0x2a is encountered the parser should flush its state and begin reading a new frame.
|
||||
Any time a 0x2a is encountered the parser should flush its state and begin reading a new frame.
|
||||
Bytes 0x2a, 0x2b, 0x11 and 0x13 are prefixed with 0x2b and inverted (XORed with 0xff) to avoid collisions.
|
||||
|
||||
Frames with destinations unknown to an OverShell are sent to the default interface, or dropped if
|
||||
that's the interface they came from.
|
||||
|
||||
==== Command layer ====
|
||||
Command layer
|
||||
-------------
|
||||
NOTE: This is going to change.
|
||||
|
||||
Payload consists of one or more commands:
|
||||
|
||||
get_command = [ 0x00 | register (1) | target register (1) ]
|
||||
set_command = [ 0x01 | register (1) | length (1) | value (n) ]
|
||||
|
||||
With a get_command the sender requests the receiver to read its own 'register' and issue
|
||||
a set_command that sets the sender's 'target register' to that value.
|
||||
With a get_command the sender requests the receiver to read its own `register` and issue
|
||||
a set_command that sets the sender's `target register` to that value.
|
||||
|
||||
A set_command does what is says on the box.
|
||||
'''
|
||||
"""
|
||||
|
||||
class Transport:
|
||||
'''
|
||||
"""
|
||||
OverTalk Transport layer implementation.
|
||||
|
||||
Reads data from multiple interfaces and either router frames or receives them.
|
||||
Reads data from multiple interfaces and either routes frames or receives them.
|
||||
A received frame is then made available via a public attribute.
|
||||
|
||||
Interfaces are objects that implement methods read() (returns one byte of data as int), write(buffer, len),
|
||||
and waiting property (contains number of bytes waiting).
|
||||
'''
|
||||
"""
|
||||
def __init__(self, my_id, interfaces, def_route):
|
||||
'''
|
||||
"""
|
||||
@param my_id OverTalk address of this Transport instance
|
||||
@param interfaces a dict of interfaces keyed by their IDs
|
||||
@param def_route ID of default interface for sending
|
||||
'''
|
||||
"""
|
||||
|
||||
assert my_id not in interfaces
|
||||
assert def_route in interfaces
|
||||
|
||||
self._print = et.Output('over.com.Transport', default_suffix='\n', timestamp=True)
|
||||
self.print = text.Output("over.serial.Transport")
|
||||
|
||||
self.my_id = my_id
|
||||
self.def_route = def_route
|
||||
|
@ -61,7 +66,7 @@ class Transport:
|
|||
self.destination_unknown = 0
|
||||
self.incoming = None
|
||||
|
||||
self._print('<Cg>on-line<C/>')
|
||||
self.print("on-line", text.Output.tl.done)
|
||||
|
||||
def update(self):
|
||||
self.incoming = None
|
||||
|
@ -95,7 +100,7 @@ class Transport:
|
|||
buffer_length = len(interface.rxbuffer)
|
||||
|
||||
if buffer_length >= 4:
|
||||
# at this point we can read frame's declared size
|
||||
# at this point we can read frame"s declared size
|
||||
if buffer_length >= interface.rxbuffer[3] + 5: # header (4) + payload + checksum (1)
|
||||
# a frame has been read, huzzah!
|
||||
interface.reading_frame = False
|
||||
|
@ -106,7 +111,7 @@ class Transport:
|
|||
|
||||
else:
|
||||
interface.malformed_frames += 1
|
||||
self._print('broken frame received: <Cr>%s<C/>' %(interface.rxbuffer))
|
||||
self.print("broken frame received: §r§%s§.§" %(interface.rxbuffer))
|
||||
interface.rxbuffer = []
|
||||
|
||||
return False
|
||||
|
@ -133,15 +138,15 @@ class Transport:
|
|||
self.incoming = (frame[2], payload)
|
||||
else:
|
||||
if destination in self.interfaces:
|
||||
self._print('routing frame to [%d]' %(destination))
|
||||
self.print("routing frame to [%d]" %(destination))
|
||||
self.interfaces[destination].write(self.escape_frame(frame))
|
||||
else:
|
||||
if source_interface_id == self.def_route:
|
||||
self.destination_unknown += 1
|
||||
self._print('unknown destination <Cr>%d<C/> for frame: <Cy>%s<C/>' %(destination,
|
||||
repr(frame)), prefix.fail)
|
||||
self.print("unknown destination <r>%d<.> for frame: <y>%s<.>" %(destination,
|
||||
repr(frame)), text.Output.tl.fail)
|
||||
else:
|
||||
self._print('routing frame to default route [%d]' %(self.def_route))
|
||||
self.print("routing frame to default route [%d]" %(self.def_route))
|
||||
self.interfaces[self.def_route].write(self.escape_frame(frame))
|
||||
|
||||
def send_data(self, destination, data):
|
||||
|
@ -157,7 +162,7 @@ class Transport:
|
|||
s.write(frame)
|
||||
|
||||
class TTL_Interface:
|
||||
def __init__(self, interface='/dev/ttyUSB0', baudrate=57600):
|
||||
def __init__(self, interface="/dev/ttyUSB0", baudrate=57600):
|
||||
try:
|
||||
self.s = serial.Serial(interface, baudrate, timeout=1)
|
||||
except serial.serialutil.SerialException:
|
||||
|
@ -184,7 +189,7 @@ class TTL_Interface:
|
|||
return 0
|
||||
|
||||
def write(self, data):
|
||||
print('Sending:', ''.join([hex(x)[2:].zfill(2) for x in data]))
|
||||
print("Sending:", "".join([hex(x)[2:].zfill(2) for x in data]))
|
||||
|
||||
if self.s:
|
||||
self.s.write(bytes(data))
|
||||
|
|
546
over/text.py
546
over/text.py
|
@ -1,13 +1,14 @@
|
|||
#! /usr/bin/env python3
|
||||
# encoding: utf-8
|
||||
|
||||
import datetime
|
||||
import fcntl
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
import struct
|
||||
import fcntl
|
||||
import termios
|
||||
import sys
|
||||
import time
|
||||
import termios
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
|
@ -41,7 +42,277 @@ def lexical_join(words, oxford=False):
|
|||
|
||||
# --------------------------------------------------
|
||||
|
||||
class Unit:
|
||||
"""
|
||||
A object that represents numbers and units in human-readable form.
|
||||
|
||||
TODO use significant digits instead of rigid order boundaries
|
||||
TODO float superclass?
|
||||
TODO base_2 prefixes (Ki, Mi, ...)
|
||||
"""
|
||||
|
||||
_prefixes = (
|
||||
("Y", 24), ("Z", 21), ("E", 18), ("P", 15), ("T", 12), ("G", 9), ("M", 6), ("k", 3),
|
||||
("h", 2), ("D", 1), ("", 0), ("d", -1), ("c", -2), ("m", -3), ("μ", -6), ("n", -9),
|
||||
("p", -12), ("f", -15), ("a", -18), ("z", -21), ("y", -24)
|
||||
)
|
||||
|
||||
def __init__(self,
|
||||
value, unit=None, dimension=1,
|
||||
use_prefixes="YZEPTGMkmμnpfazy", format="%.2f pU",
|
||||
logarithmic=False, log_base=10
|
||||
):
|
||||
"""
|
||||
value the numerical value of the variable (int or float)
|
||||
unit the symbol to use, if any (str or None)
|
||||
dimension the dimensionality of the value (1, 2, 3, ...)
|
||||
use_prefixes which multiplier prefixes to use
|
||||
- the default "YZEPTGMkmμnpfazy" omits "c" for centi- and "D" for deca-
|
||||
format use printf notation for value (e.g. %010.5f), p for prefix and U for unit
|
||||
logarithmic when True, log_10 prefixes are used
|
||||
log_base logarithm base value
|
||||
|
||||
note that deca- is correctly rendered as "da", the "D" is used in use_prefixes only
|
||||
"""
|
||||
|
||||
self.value = float(value)
|
||||
self.unit = unit if unit else ""
|
||||
self.dimension = dimension
|
||||
self.use_prefixes = use_prefixes
|
||||
self.format = format
|
||||
self.logarithmic = logarithmic
|
||||
self.log_base = log_base
|
||||
|
||||
if self.logarithmic and (self.value < 0 or self.log_base < 0):
|
||||
raise ValueError("math domain error (negative values can't be represented in dB)")
|
||||
|
||||
def __str__(self):
|
||||
if self.value == 0.0:
|
||||
e = 0
|
||||
else:
|
||||
e = round(math.log(abs(self.value), 10), 6)
|
||||
|
||||
if self.logarithmic:
|
||||
prefix = "dB"
|
||||
value = math.log(self.value, self.log_base) * 10
|
||||
|
||||
else:
|
||||
for prefix, mul in self._prefixes:
|
||||
if prefix in self.use_prefixes and mul*self.dimension <= e:
|
||||
break
|
||||
|
||||
value = self.value / 10**(mul*self.dimension)
|
||||
|
||||
output = self.format %(value)
|
||||
output = output.replace("p", prefix if prefix != "D" else "da") # deca- handler
|
||||
output = output.replace("U", self.unit)
|
||||
|
||||
return output
|
||||
|
||||
def __repr__(self):
|
||||
return "over.text.Unit(%s)" %(self)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
colortags = {
|
||||
"r": "\x1b[31;01m",
|
||||
"g": "\x1b[32;01m",
|
||||
"y": "\x1b[33;01m",
|
||||
"b": "\x1b[34;01m",
|
||||
"m": "\x1b[35;01m",
|
||||
"c": "\x1b[36;01m",
|
||||
"B": "\x1b[01m",
|
||||
".": "\x1b[39;49;00m" # reset
|
||||
}
|
||||
|
||||
def render(text, colors=True):
|
||||
"""
|
||||
Processes text with color tags and either
|
||||
removes them (with colors=False) or replaces
|
||||
them with terminal color codes.
|
||||
|
||||
Color tags are <x> where x is the color code from colortags.
|
||||
<.> resets the color. Use <<x> for a literal <x>.
|
||||
"""
|
||||
|
||||
text = str(text)
|
||||
output = []
|
||||
window = []
|
||||
|
||||
for c in text:
|
||||
window.append(c)
|
||||
|
||||
if len(window) < 3:
|
||||
continue
|
||||
elif len(window) == 4:
|
||||
output.append(window.pop(0))
|
||||
|
||||
# the window now contains three chars
|
||||
if window[0] == "<" and window[2] == ">":
|
||||
code = window[1]
|
||||
|
||||
if code in colortags.keys():
|
||||
if not output or output[-1] != "<":
|
||||
if colors:
|
||||
output.append(colortags[code])
|
||||
|
||||
window.clear()
|
||||
elif output and output[-1] == "<":
|
||||
window.pop(0) # remove the opening "<" so the rest of the escaped sequence slides into output
|
||||
|
||||
return "".join(output + window)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def rfind(text, C, start, length):
|
||||
"""
|
||||
Returns the highest index of C in text[start:] that is <= start+length.
|
||||
Color tags aren't counted into length.
|
||||
"""
|
||||
|
||||
# find the index of the last C in colorless text
|
||||
colorless = render(text[start:], False)
|
||||
indices_colorless = [i for i, c in enumerate(colorless) if c == C and i < length]
|
||||
|
||||
# get indices of all Cs in the original
|
||||
indices = [i for i, c in enumerate(text[start:]) if c == C]
|
||||
|
||||
# return the original index at the same position as the last in colorless
|
||||
return start + indices[len(indices_colorless) - 1]
|
||||
|
||||
def paragraph(text, width=0, indent=0, stamp=None):
|
||||
"""
|
||||
str text text to format
|
||||
int width required line length; if 0, current terminal width will be used
|
||||
int indent how many spaces to indent the text with
|
||||
str stamp placed into the first line's indent (whether it fits is your problem)
|
||||
|
||||
Formats text into an indented paragraph that fits the terminal and returns it.
|
||||
Correctly handles colors.
|
||||
"""
|
||||
|
||||
fit_into_width = (width or get_terminal_size()[1]) - indent
|
||||
|
||||
lines = []
|
||||
last = 0
|
||||
|
||||
# break into lines
|
||||
while True:
|
||||
idx = rfind(text, " ", last, fit_into_width)
|
||||
adj_last = last + 1 if last else 0
|
||||
|
||||
if len(text[adj_last:]) < fit_into_width or idx == -1:
|
||||
lines.append(text[adj_last:])
|
||||
break
|
||||
elif idx == last:
|
||||
return (stamp or "") + text
|
||||
else:
|
||||
line = text[adj_last:idx]
|
||||
last = idx
|
||||
lines.append(line)
|
||||
|
||||
# indent
|
||||
indent_str = " " * indent
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if stamp and i == 0:
|
||||
lines[i] = stamp.ljust(indent) + line
|
||||
else:
|
||||
lines[i] = indent_str + line
|
||||
|
||||
# join
|
||||
return "\n".join(lines)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def strlen(string):
|
||||
"""
|
||||
Returns the length of a string minus all color tags.
|
||||
"""
|
||||
|
||||
plain_string = render(string, colors=False)
|
||||
|
||||
return len(plain_string)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
class Output:
|
||||
class tag:
|
||||
class short:
|
||||
info = " "
|
||||
debug = " <b>?<.>"
|
||||
start = "<B>>>><.>"
|
||||
exec = start
|
||||
warn = " <y>#<.>"
|
||||
fail = "<r>!!!<.>"
|
||||
done = " <g>*<.>"
|
||||
class long:
|
||||
info = "INFO"
|
||||
debug = "<b>DEBG<.>"
|
||||
start = "<B>EXEC<.>"
|
||||
exec = start
|
||||
warn = "<y>WARN<.>"
|
||||
fail = "<r>FAIL<.>"
|
||||
done = "<g>DONE<.>"
|
||||
|
||||
ts = tag.short
|
||||
tl = tag.long
|
||||
|
||||
"""
|
||||
Text UI output renderer.
|
||||
|
||||
Prints messages to the stdout with optional eye candy
|
||||
like colors and timestamps.
|
||||
|
||||
Formatting
|
||||
==========
|
||||
You can use all formatting characters supported by strftime, as well as:
|
||||
<T> - tag
|
||||
<n> - name
|
||||
<t> - supplied text
|
||||
"""
|
||||
|
||||
def __init__(self, name, format="[%Y-%m-%d %H:%M:%S] <T> -- <n>, <t>", colors=True, end=".\n", stream=sys.stderr):
|
||||
self.name = name
|
||||
self.format = format
|
||||
self.colors = colors
|
||||
self.end = end
|
||||
self.stream = stream
|
||||
|
||||
def __call__(self, text, tag=None, format=None, colors=None, end=None):
|
||||
tag = tag or self.__class__.tag.long.info
|
||||
format = format or self.format
|
||||
colors = colors or self.colors
|
||||
end = end or self.end
|
||||
|
||||
output = datetime.datetime.now().strftime(format)
|
||||
output = output.replace("<T>", tag)
|
||||
output = output.replace("<n>", self.name)
|
||||
output = output.replace("<t>", text)
|
||||
output += end
|
||||
|
||||
self.stream.write(render(output, colors))
|
||||
self.stream.flush()
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def get_terminal_size():
|
||||
"""
|
||||
Returns current terminal's (rows, cols).
|
||||
"""
|
||||
|
||||
terminal = sys.stdout.fileno()
|
||||
|
||||
try:
|
||||
return struct.unpack("HHHH", fcntl.ioctl(terminal, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0)))
|
||||
except IOError:
|
||||
return (40, 80)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
class _ProgressBarChannel:
|
||||
print = Output("over.text._ProgressBarChannel")
|
||||
|
||||
def __init__(self, unit, top, precision, use_prefixes=True, prefix_base2=False, min_width_raw=0, min_width_rate=0, min_width_time=0, min_width_percent=7):
|
||||
self.unit = unit
|
||||
self.top = top
|
||||
|
@ -60,7 +331,7 @@ class _ProgressBarChannel:
|
|||
self.set()
|
||||
|
||||
if prefix_base2:
|
||||
_print("Unit does not yet support base2 prefixes (e.g. Gi, Mi), using decadic (G, M) instead", prefix.warn)
|
||||
self.print("Unit does not yet support base2 prefixes (e.g. Gi, Mi), using decadic (G, M) instead", self.print.tl.warn)
|
||||
|
||||
def set(self, value=None):
|
||||
if value is not None:
|
||||
|
@ -303,273 +574,6 @@ class ProgressBar:
|
|||
|
||||
# --------------------------------------------------
|
||||
|
||||
class Unit:
|
||||
"""
|
||||
A object that represents numbers and units in human-readable form.
|
||||
|
||||
TODO use significant digits instead of rigid order boundaries
|
||||
TODO float superclass?
|
||||
TODO base_2 prefixes (Ki, Mi, ...)
|
||||
"""
|
||||
|
||||
_prefixes = (
|
||||
("Y", 24), ("Z", 21), ("E", 18), ("P", 15), ("T", 12), ("G", 9), ("M", 6), ("k", 3),
|
||||
("h", 2), ("D", 1), ("", 0), ("d", -1), ("c", -2), ("m", -3), ("μ", -6), ("n", -9),
|
||||
("p", -12), ("f", -15), ("a", -18), ("z", -21), ("y", -24)
|
||||
)
|
||||
|
||||
def __init__(self,
|
||||
value, unit=None, dimension=1,
|
||||
use_prefixes="YZEPTGMkmμnpfazy", format="%.2f pU",
|
||||
logarithmic=False, log_base=10
|
||||
):
|
||||
"""
|
||||
value the numerical value of the variable (int or float)
|
||||
unit the symbol to use, if any (str or None)
|
||||
dimension the dimensionality of the value (1, 2, 3, ...)
|
||||
use_prefixes which multiplier prefixes to use
|
||||
- the default "YZEPTGMkmμnpfazy" omits "c" for centi- and "D" for deca-
|
||||
format use printf notation for value (e.g. %010.5f), p for prefix and U for unit
|
||||
logarithmic when True, log_10 prefixes are used
|
||||
log_base logarithm base value
|
||||
|
||||
note that deca- is correctly rendered as "da", the "D" is used in use_prefixes only
|
||||
"""
|
||||
|
||||
self.value = float(value)
|
||||
self.unit = unit if unit else ""
|
||||
self.dimension = dimension
|
||||
self.use_prefixes = use_prefixes
|
||||
self.format = format
|
||||
self.logarithmic = logarithmic
|
||||
self.log_base = log_base
|
||||
|
||||
if self.logarithmic and (self.value < 0 or self.log_base < 0):
|
||||
raise ValueError("math domain error (negative values can't be represented in dB)")
|
||||
|
||||
def __str__(self):
|
||||
if self.value == 0.0:
|
||||
e = 0
|
||||
else:
|
||||
e = round(math.log(abs(self.value), 10), 6)
|
||||
|
||||
if self.logarithmic:
|
||||
prefix = "dB"
|
||||
value = math.log(self.value, self.log_base) * 10
|
||||
|
||||
else:
|
||||
for prefix, mul in self._prefixes:
|
||||
if prefix in self.use_prefixes and mul*self.dimension <= e:
|
||||
break
|
||||
|
||||
value = self.value / 10**(mul*self.dimension)
|
||||
|
||||
output = self.format %(value)
|
||||
output = output.replace("p", prefix if prefix != "D" else "da") # deca- handler
|
||||
output = output.replace("U", self.unit)
|
||||
|
||||
return output
|
||||
|
||||
def __repr__(self):
|
||||
return "over.text.Unit(%s)" %(self)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
colortag_char = "§"
|
||||
|
||||
colortags = {
|
||||
"r": "\x1b[31;01m",
|
||||
"g": "\x1b[32;01m",
|
||||
"y": "\x1b[33;01m",
|
||||
"b": "\x1b[34;01m",
|
||||
"m": "\x1b[35;01m",
|
||||
"c": "\x1b[36;01m",
|
||||
"B": "\x1b[01m",
|
||||
".": "\x1b[39;49;00m" # reset
|
||||
}
|
||||
|
||||
def render(text, colors=True):
|
||||
"""
|
||||
Processes text with color tags and either
|
||||
removes them (with colors=False) or replaces
|
||||
them with terminal color codes.
|
||||
|
||||
Color tags are §x§ where x is the color code from colortags.
|
||||
§.§ resets the color. Use §§x§ for a literal §x§.
|
||||
"""
|
||||
|
||||
text = str(text)
|
||||
output = []
|
||||
window = []
|
||||
|
||||
for c in text:
|
||||
window.append(c)
|
||||
|
||||
if len(window) < 3:
|
||||
continue
|
||||
elif len(window) == 4:
|
||||
output.append(window.pop(0))
|
||||
|
||||
# the window now contains three chars
|
||||
if window[0] == window[2] == colortag_char:
|
||||
code = window[1]
|
||||
|
||||
if code in colortags.keys() and (not output or output[-1] != colortag_char):
|
||||
if colors:
|
||||
output.append(colortags[code])
|
||||
|
||||
window.clear()
|
||||
|
||||
return "".join(output + window)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def rfind(text, C, start, length):
|
||||
"""
|
||||
Returns the highest index of C in text[start:] that is <= start+length.
|
||||
Color tags aren't counted into length.
|
||||
"""
|
||||
|
||||
# find the index of the last C in colorless text
|
||||
colorless = render(text[start:], False)
|
||||
indices_colorless = [i for i, c in enumerate(colorless) if c == C and i < length]
|
||||
|
||||
# get indices of all Cs in the original
|
||||
indices = [i for i, c in enumerate(text[start:]) if c == C]
|
||||
|
||||
# return the original index at the same position as the last in colorless
|
||||
return start + indices[len(indices_colorless) - 1]
|
||||
|
||||
def paragraph(text, width=0, indent=0, stamp=None):
|
||||
"""
|
||||
str text text to format
|
||||
int width required line length; if 0, current terminal width will be used
|
||||
int indent how many spaces to indent the text with
|
||||
str stamp placed into the first line's indent (whether it fits is your problem)
|
||||
|
||||
Formats text into an indented paragraph that fits the terminal and returns it.
|
||||
Correctly handles colors.
|
||||
"""
|
||||
|
||||
fit_into_width = (width or get_terminal_size()[1]) - indent
|
||||
|
||||
lines = []
|
||||
last = 0
|
||||
|
||||
# break into lines
|
||||
while True:
|
||||
idx = rfind(text, " ", last, fit_into_width)
|
||||
adj_last = last + 1 if last else 0
|
||||
|
||||
if len(text[adj_last:]) < fit_into_width or idx == -1:
|
||||
lines.append(text[adj_last:])
|
||||
break
|
||||
elif idx == last:
|
||||
return (stamp or "") + text
|
||||
else:
|
||||
line = text[adj_last:idx]
|
||||
last = idx
|
||||
lines.append(line)
|
||||
|
||||
# indent
|
||||
indent_str = " " * indent
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if stamp and i == 0:
|
||||
lines[i] = stamp.ljust(indent) + line
|
||||
else:
|
||||
lines[i] = indent_str + line
|
||||
|
||||
# join
|
||||
return "\n".join(lines)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def strlen(string):
|
||||
"""
|
||||
Returns the length of a string minus all color tags.
|
||||
"""
|
||||
|
||||
plain_string = render(string, colors=False)
|
||||
|
||||
return len(plain_string)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
class Output:
|
||||
class tag:
|
||||
class short:
|
||||
info = " "
|
||||
debug = " §b§?§.§"
|
||||
start = "§B§>>>§.§"
|
||||
exec = start
|
||||
warn = " §y§#§.§"
|
||||
fail = "§r§!!!§.§"
|
||||
done = " §g§*§.§"
|
||||
class long:
|
||||
info = "INFO"
|
||||
debug = "§b§DEBG§.§"
|
||||
start = "§B§EXEC§.§"
|
||||
exec = start
|
||||
warn = "§y§WARN§.§"
|
||||
fail = "§r§FAIL§.§"
|
||||
done = "§g§DONE§.§"
|
||||
|
||||
ts = tag.short
|
||||
tl = tag.long
|
||||
|
||||
"""
|
||||
Text UI output renderer.
|
||||
|
||||
Prints messages to the stdout with optional eye candy
|
||||
like colors and timestamps.
|
||||
|
||||
Formatting
|
||||
==========
|
||||
You can use all formatting characters supported by strftime, as well as:
|
||||
§T§ - tag
|
||||
§n§ - name
|
||||
§t§ - supplied text
|
||||
"""
|
||||
|
||||
def __init__(self, name, format="[%Y-%m-%d %H:%M:%S] §T§ -- §n§, §t§", colors=True, end=".\n", stream=sys.stderr):
|
||||
self.name = name
|
||||
self.format = format
|
||||
self.colors = colors
|
||||
self.end = end
|
||||
self.stream = stream
|
||||
|
||||
def __call__(self, text, tag=None, format=None, colors=None, end=None):
|
||||
tag = tag or self.__class__.tag.long.info
|
||||
format = format or self.format
|
||||
colors = colors or self.colors
|
||||
end = end or self.end
|
||||
|
||||
output = datetime.datetime.now().strftime(format)
|
||||
output = output.replace("§T§", tag)
|
||||
output = output.replace("§n§", self.name)
|
||||
output = output.replace("§t§", text)
|
||||
output += end
|
||||
|
||||
self.stream.write(render(output, colors))
|
||||
self.stream.flush()
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
def get_terminal_size():
|
||||
"""
|
||||
Returns current terminal's (rows, cols).
|
||||
"""
|
||||
|
||||
terminal = sys.stdout.fileno()
|
||||
|
||||
try:
|
||||
return struct.unpack("HHHH", fcntl.ioctl(terminal, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0)))
|
||||
except IOError:
|
||||
return (40, 80)
|
||||
|
||||
# --------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
pb = ProgressBar(
|
||||
"§%a (§ra/§za) [§=a>§ A] §sa [§rb/§zb] (ETA §TA)",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue