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
|
import sys
|
||||||
|
|
||||||
|
#from . import app
|
||||||
from . import aux
|
from . import aux
|
||||||
|
from . import cmd
|
||||||
|
from . import file
|
||||||
|
from . import misc
|
||||||
from . import text
|
from . import text
|
||||||
|
from . import version
|
||||||
|
|
||||||
|
print = text.Output("over.__init__", stream=sys.stderr)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from . import cython_types as types
|
from . import cython_types as types
|
||||||
except:
|
except:
|
||||||
aux._print('unable to load C implementation, using python instead', text.prefix.warn)
|
|
||||||
from . import python_types as types
|
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')
|
core = aux.DeprecationForwarder(sys.modules[__name__], "over.core", "over")
|
||||||
textui = aux.DeprecationForwarder(text, 'over.core.textui', 'over.text')
|
textui = aux.DeprecationForwarder(text, "over.core.textui", "over.text")
|
||||||
|
|
||||||
del sys
|
del sys
|
||||||
|
|
|
@ -6,9 +6,9 @@ import traceback
|
||||||
|
|
||||||
from . import text
|
from . import text
|
||||||
|
|
||||||
_print = text.Output('over.core', stream=sys.stderr)
|
|
||||||
|
|
||||||
class DeprecationForwarder:
|
class DeprecationForwarder:
|
||||||
|
print = text.Output("over.aux.DeprecationForwarder", stream=sys.stderr)
|
||||||
|
|
||||||
def __init__(self, target, old_name, new_name):
|
def __init__(self, target, old_name, new_name):
|
||||||
self._target = target
|
self._target = target
|
||||||
self._old_name = old_name
|
self._old_name = old_name
|
||||||
|
@ -16,5 +16,6 @@ class DeprecationForwarder:
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
caller = traceback.extract_stack()[-2]
|
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)
|
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
|
return False
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
'''
|
"""
|
||||||
A shell command with argument substitution and output capture.
|
A shell command with argument substitution and output capture.
|
||||||
|
|
||||||
>>> c = Command('process.sh', '-x', 'ARGUMENT')
|
>>> c = Command("process.sh", "-x", "ARGUMENT")
|
||||||
>>> c.ARGUMENT = 'file.txt'
|
>>> c.ARGUMENT = "file.txt"
|
||||||
>>> c.dump()
|
>>> c.dump()
|
||||||
['process.sh', '-x', 'file.txt']
|
["process.sh", "-x", "file.txt"]
|
||||||
>>> c.run(stderr=False) # capture stdout
|
>>> c.run(stderr=False) # capture stdout
|
||||||
>>> c.get_output()
|
>>> c.get_output()
|
||||||
b'some output'
|
b"some output"
|
||||||
>>> c.get_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()
|
>>> c.get_output()
|
||||||
b'more of it\nand some more'
|
b"more of it\nand some more"
|
||||||
>>> c.get_output()
|
>>> c.get_output()
|
||||||
None # indicates the process ended and there is no more output
|
None # indicates the process ended and there is no more output
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __init__(self, *sequence):
|
def __init__(self, *sequence):
|
||||||
self.__dict__['sequence_original'] = list(sequence)
|
self.__dict__["sequence_original"] = list(sequence)
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
|
@ -59,14 +59,14 @@ class Command:
|
||||||
found = True
|
found = True
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
raise AttributeError('Command has no attribute \'%s\'' %(name))
|
raise AttributeError('Command has no attribute "%s"' %(name))
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.__dict__['sequence'] = list(self.sequence_original)
|
self.__dict__["sequence"] = list(self.sequence_original)
|
||||||
self.__dict__['thread'] = None
|
self.__dict__["thread"] = None
|
||||||
self.__dict__['fifo'] = None
|
self.__dict__["fifo"] = None
|
||||||
self.__dict__['terminated'] = False
|
self.__dict__["terminated"] = False
|
||||||
self.__dict__['returncode'] = None
|
self.__dict__["returncode"] = None
|
||||||
|
|
||||||
def dump(self, sequence=None, pretty=False):
|
def dump(self, sequence=None, pretty=False):
|
||||||
out = []
|
out = []
|
||||||
|
@ -88,30 +88,30 @@ class Command:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def run(self, stderr=False):
|
def run(self, stderr=False):
|
||||||
'''
|
"""
|
||||||
Executes the command in the current environment.
|
Executes the command in the current environment.
|
||||||
|
|
||||||
stderr capture stderr instead of stdout (can't do both yet)
|
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__["process"] = subprocess.Popen(self.dump(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1)
|
||||||
self.__dict__['fifo'] = queue.Queue()
|
self.__dict__["fifo"] = queue.Queue()
|
||||||
self.__dict__['thread'] = threading.Thread(
|
self.__dict__["thread"] = threading.Thread(
|
||||||
target=capture_output,
|
target=capture_output,
|
||||||
args=(self.process.stderr if stderr else self.process.stdout, self.fifo)
|
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.daemon = True # thread dies with the program
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
def get_output(self, blocking=False):
|
def get_output(self, blocking=False):
|
||||||
'''
|
"""
|
||||||
Returns the output of a currently running process and clears the buffer.
|
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.
|
Returns None if no process is running and no more output is available.
|
||||||
|
|
||||||
blocking block until some output is available or the process terminates
|
blocking block until some output is available or the process terminates
|
||||||
'''
|
"""
|
||||||
|
|
||||||
buffer = []
|
buffer = []
|
||||||
|
|
||||||
|
@ -125,8 +125,8 @@ class Command:
|
||||||
buffer.append(self.fifo.get_nowait()) # FIXME nowait needed?
|
buffer.append(self.fifo.get_nowait()) # FIXME nowait needed?
|
||||||
|
|
||||||
if None in buffer:
|
if None in buffer:
|
||||||
self.__dict__['terminated'] = True
|
self.__dict__["terminated"] = True
|
||||||
self.__dict__['returncode'] = self.process.poll()
|
self.__dict__["returncode"] = self.process.poll()
|
||||||
|
|
||||||
if len(buffer) == 1:
|
if len(buffer) == 1:
|
||||||
return None
|
return None
|
||||||
|
@ -134,7 +134,7 @@ class Command:
|
||||||
assert(buffer[-1] is None)
|
assert(buffer[-1] is None)
|
||||||
del buffer[-1]
|
del buffer[-1]
|
||||||
|
|
||||||
return b''.join(buffer)
|
return b"".join(buffer)
|
||||||
|
|
||||||
def get_all_output(self):
|
def get_all_output(self):
|
||||||
buffer = []
|
buffer = []
|
||||||
|
@ -147,4 +147,4 @@ class Command:
|
||||||
else:
|
else:
|
||||||
buffer.append(chunk)
|
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
|
#! /usr/bin/env python3
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
cdef class ndict(dict):
|
cdef class ndict(OrderedDict):
|
||||||
'''
|
"""
|
||||||
A dictionary superclass whose keys are exposed as attributes.
|
An OrderedDict subclass whose keys are exposed as attributes.
|
||||||
|
|
||||||
>>> d = ndict()
|
>>> d = ndict()
|
||||||
>>> d.alpha = 1
|
>>> d.alpha = 1
|
||||||
>>> d['alpha']
|
>>> d["alpha"]
|
||||||
1
|
1
|
||||||
>>> d['beta'] = 42
|
>>> d["beta"] = 42
|
||||||
>>> d.beta
|
>>> d.beta
|
||||||
42
|
42
|
||||||
>>> d
|
>>> d
|
||||||
{'alpha': 1, 'beta': 42}
|
{"alpha": 1, "beta": 42}
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __getattr__(self, str name):
|
def __getattr__(self, str name):
|
||||||
if name in self:
|
if name in self:
|
||||||
return self[name]
|
return self[name]
|
||||||
elif name.replace('_', '-') in self:
|
elif name.replace("_", "-") in self:
|
||||||
return self[name.replace('_', '-')]
|
return self[name.replace("_", "-")]
|
||||||
else:
|
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):
|
def __setattr__(self, str name, value):
|
||||||
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))
|
|
||||||
|
|
108
over/file.py
108
over/file.py
|
@ -3,101 +3,33 @@
|
||||||
|
|
||||||
import os
|
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:
|
def count_lines(path):
|
||||||
'''
|
"""
|
||||||
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):
|
|
||||||
'''
|
|
||||||
A reasonably fast and memory-lean line counter.
|
A reasonably fast and memory-lean line counter.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
lines = 0
|
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):
|
while f.readinto(buffer):
|
||||||
lines += buffer.count(b'\n')
|
lines += buffer.count(b"\n")
|
||||||
|
|
||||||
return lines
|
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):
|
def __init__(self, A, B, epsilon=1e-12):
|
||||||
|
self.A = A
|
||||||
|
self.B = B
|
||||||
|
self.epsilon = epsilon
|
||||||
|
|
||||||
self.lt = False
|
self.lt = False
|
||||||
self.le = True
|
self.le = True
|
||||||
self.eq = False
|
self.eq = False
|
||||||
|
@ -33,11 +37,14 @@ class compare_float:
|
||||||
self.greater_or_equal = self.ge
|
self.greater_or_equal = self.ge
|
||||||
self.greater = self.gt
|
self.greater = self.gt
|
||||||
self.not_equal = self.ne
|
self.not_equal = self.ne
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "compare_float(%.2f, %.2f, epsilon=%.03e)" %(self.A, self.B, self.epsilon)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
x = compare_float(1, 2, 0.5)
|
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)
|
x = compare_float(2, 2, 0.5)
|
||||||
assert x.eq and x.le and x.ge and not (x.lt or x.gt)
|
assert x.eq and x.le and x.ge and not (x.lt or x.gt)
|
||||||
x = compare_float(3, 2, 0.5)
|
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
|
# 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
|
cdef class mat4:
|
||||||
|
"""
|
||||||
class mat4:
|
|
||||||
'''
|
|
||||||
A float 4x4 matrix.
|
A float 4x4 matrix.
|
||||||
|
|
||||||
All arrays are column-major, i.e. OpenGL style:
|
All arrays are column-major, i.e. OpenGL style:
|
||||||
|
@ -21,32 +21,30 @@ class mat4:
|
||||||
3 7 11 15
|
3 7 11 15
|
||||||
|
|
||||||
The matrix implements stacking useful for graphics.
|
The matrix implements stacking useful for graphics.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __cinit__(mat4 self):
|
def __cinit__(mat4 self):
|
||||||
to_alloc = 16 * sizeof(float)
|
to_alloc = 16 * sizeof(float)
|
||||||
self.stack = <float *>malloc(to_alloc)
|
self.stack = <float *>malloc(to_alloc)
|
||||||
|
|
||||||
if not self.stack:
|
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.m = self.stack
|
||||||
self.size = 1
|
self.size = 1
|
||||||
|
|
||||||
def _debug(mat4 self):
|
def _debug(mat4 self):
|
||||||
print('--- self.stack = %d' %(<uint64_t>self.stack))
|
print("--- self.stack = %d" %(<uint64_t>self.stack))
|
||||||
print('--- self.m = %d (+%d)' %(<uint64_t>self.m, self.m - self.stack))
|
print("--- self.m = %d (+%d)" %(<uint64_t>self.m, self.m - self.stack))
|
||||||
print('--- self.size = %d' %(self.size))
|
print("--- self.size = %d" %(self.size))
|
||||||
|
|
||||||
def __init__(mat4 self, *args):
|
def __init__(mat4 self, *args):
|
||||||
'''
|
"""
|
||||||
Create a ma4t.
|
Create a ma4t.
|
||||||
|
|
||||||
Accepts any number of parameters between 0 and 16 to fill the
|
Accepts any number of parameters between 0 and 16 to fill the
|
||||||
matrix from the upper left corner going down (column-wise).
|
matrix from the upper left corner going down (column-wise).
|
||||||
'''
|
"""
|
||||||
|
|
||||||
self.m =
|
|
||||||
|
|
||||||
length = len(args)
|
length = len(args)
|
||||||
|
|
||||||
|
@ -55,7 +53,7 @@ class mat4:
|
||||||
length = len(args)
|
length = len(args)
|
||||||
|
|
||||||
if length > 16:
|
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)
|
self.load_from(args)
|
||||||
|
|
||||||
|
@ -75,7 +73,7 @@ class mat4:
|
||||||
matrices = length//16
|
matrices = length//16
|
||||||
|
|
||||||
if not matrices*16 == length:
|
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
|
self.m = self.stack
|
||||||
|
|
||||||
|
@ -90,20 +88,22 @@ class mat4:
|
||||||
|
|
||||||
def __getitem__(mat4 self, int i):
|
def __getitem__(mat4 self, int i):
|
||||||
if i > 16 or i < 0:
|
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]
|
return self.m[i]
|
||||||
|
|
||||||
def __setitem__(self, int i, value):
|
def __setitem__(self, int i, value):
|
||||||
if i > 16 or i < 0:
|
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
|
self.m[i] = value
|
||||||
|
|
||||||
def push(mat4 self):
|
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.m points to the current matrix
|
||||||
# self.stack points to the first matrix
|
# self.stack points to the first matrix
|
||||||
|
@ -122,12 +122,12 @@ class mat4:
|
||||||
if tmp:
|
if tmp:
|
||||||
self.stack = tmp
|
self.stack = tmp
|
||||||
else:
|
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
|
# advance the pointer to the new one
|
||||||
self.m = self.stack + 16 * used
|
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
|
# copy the old matrix into the new one
|
||||||
cdef uint8_t i
|
cdef uint8_t i
|
||||||
cdef float *old_m = self.m - 16
|
cdef float *old_m = self.m - 16
|
||||||
|
@ -135,12 +135,12 @@ class mat4:
|
||||||
self.m[i] = old_m[i]
|
self.m[i] = old_m[i]
|
||||||
|
|
||||||
def pop(mat4 self):
|
def pop(mat4 self):
|
||||||
'''
|
"""
|
||||||
Pop a matrix from the stack.
|
Pop a matrix from the stack.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if self.m == self.stack:
|
if self.m == self.stack:
|
||||||
raise IndexError('pop from an empty stack')
|
raise IndexError("pop from an empty stack")
|
||||||
|
|
||||||
self.m -= 16
|
self.m -= 16
|
||||||
|
|
||||||
|
@ -153,14 +153,14 @@ class mat4:
|
||||||
return L
|
return L
|
||||||
|
|
||||||
def load_from(mat4 self, L):
|
def load_from(mat4 self, L):
|
||||||
'''
|
"""
|
||||||
Fill the current matrix from a either a list of values, column-major,
|
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
|
or another matrix. This method doesn't modify the stack, only the
|
||||||
current matrix is read and modified.
|
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 it's larger, GeneralError will be raised.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if isinstance(L, mat4):
|
if isinstance(L, mat4):
|
||||||
L = L.get_list()
|
L = L.get_list()
|
||||||
|
@ -169,7 +169,7 @@ class mat4:
|
||||||
length = len(L)
|
length = len(L)
|
||||||
|
|
||||||
if length > 16:
|
if length > 16:
|
||||||
raise GeneralError('supplied list is longer than 16')
|
raise ValueError("supplied list is longer than 16")
|
||||||
|
|
||||||
for i in range(16):
|
for i in range(16):
|
||||||
if i < length:
|
if i < length:
|
||||||
|
@ -178,13 +178,17 @@ class mat4:
|
||||||
self.m[i] = 0.0
|
self.m[i] = 0.0
|
||||||
|
|
||||||
def zero(mat4 self):
|
def zero(mat4 self):
|
||||||
'''Fill the matrix with zeroes.'''
|
"""
|
||||||
|
Fill the matrix with zeroes.
|
||||||
|
"""
|
||||||
|
|
||||||
for i in range(16):
|
for i in range(16):
|
||||||
self.m[i] = 0.0
|
self.m[i] = 0.0
|
||||||
|
|
||||||
def identity(mat4 self):
|
def identity(mat4 self):
|
||||||
'''Make the matrix an identity.'''
|
"""
|
||||||
|
Make the matrix an identity.
|
||||||
|
"""
|
||||||
|
|
||||||
self.zero()
|
self.zero()
|
||||||
|
|
||||||
|
@ -194,7 +198,9 @@ class mat4:
|
||||||
self.m[15] = 1.0
|
self.m[15] = 1.0
|
||||||
|
|
||||||
def transpose(mat4 self):
|
def transpose(mat4 self):
|
||||||
'''Transpose the matrix.'''
|
"""
|
||||||
|
Transpose the matrix.
|
||||||
|
"""
|
||||||
|
|
||||||
cdef float tmp
|
cdef float tmp
|
||||||
|
|
||||||
|
@ -223,7 +229,9 @@ class mat4:
|
||||||
self.m[9] = tmp
|
self.m[9] = tmp
|
||||||
|
|
||||||
def invert(mat4 self):
|
def invert(mat4 self):
|
||||||
'''Invert the matrix.'''
|
"""
|
||||||
|
Invert the matrix.
|
||||||
|
"""
|
||||||
|
|
||||||
cdef float tmp[16]
|
cdef float tmp[16]
|
||||||
cdef float det
|
cdef float det
|
||||||
|
@ -237,7 +245,7 @@ class mat4:
|
||||||
|
|
||||||
# epsilon pulled straight out of Uranus
|
# epsilon pulled straight out of Uranus
|
||||||
if det < 0.00005 and det > -0.00005:
|
if det < 0.00005 and det > -0.00005:
|
||||||
print('det=%.1f' %(det))
|
print("det=%.1f" %(det))
|
||||||
return
|
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[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
|
self.m[15] = tmp[15] * det
|
||||||
|
|
||||||
def mulm(mat4 self, mat4 B, bint inplace=False):
|
def mulm(mat4 self, mat4 B, bint inplace=False):
|
||||||
'''
|
"""
|
||||||
Return a matrix that is the result of multiplying this matrix by another.
|
Return a matrix that is the result of multiplying this matrix by another.
|
||||||
|
|
||||||
M = self * mat4 B
|
M = self * mat4 B
|
||||||
'''
|
"""
|
||||||
|
|
||||||
cdef uint8_t i
|
cdef uint8_t i
|
||||||
cdef mat4 tmp = mat4()
|
cdef mat4 tmp = mat4()
|
||||||
|
@ -305,11 +313,11 @@ class mat4:
|
||||||
return tmp
|
return tmp
|
||||||
|
|
||||||
def mulv(mat4 self, vec3 v):
|
def mulv(mat4 self, vec3 v):
|
||||||
'''
|
"""
|
||||||
Return a vec3 that is the result of multiplying this matrix by a vec3.
|
Return a vec3 that is the result of multiplying this matrix by a vec3.
|
||||||
|
|
||||||
u = self * v
|
u = self * v
|
||||||
'''
|
"""
|
||||||
|
|
||||||
cdef mat4 tmp = vec3()
|
cdef mat4 tmp = vec3()
|
||||||
|
|
||||||
|
@ -320,11 +328,11 @@ class mat4:
|
||||||
return tmp
|
return tmp
|
||||||
|
|
||||||
def mulf(mat4 self, f):
|
def mulf(mat4 self, f):
|
||||||
'''
|
"""
|
||||||
Return a matrix that is the result of multiplying this matrix by a scalar.
|
Return a matrix that is the result of multiplying this matrix by a scalar.
|
||||||
|
|
||||||
M = self * f
|
M = self * f
|
||||||
'''
|
"""
|
||||||
|
|
||||||
cdef mat4 tmp = mat4()
|
cdef mat4 tmp = mat4()
|
||||||
cdef int i
|
cdef int i
|
||||||
|
@ -337,15 +345,15 @@ class mat4:
|
||||||
def __repr__(mat4 self):
|
def __repr__(mat4 self):
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
lines.append('mat4(%.1f %.1f %.1f %.1f' %(self.m[0], self.m[4], self.m[8], self.m[12]))
|
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[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[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(" %.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:
|
cdef class vec3:
|
||||||
'''
|
"""
|
||||||
A float 3D vector.
|
A float 3D vector.
|
||||||
|
|
||||||
>>> v = vec3(1, 1, 0)
|
>>> v = vec3(1, 1, 0)
|
||||||
|
@ -361,14 +369,14 @@ cdef class vec3:
|
||||||
>>> w - v
|
>>> w - v
|
||||||
vec4(-1.00, 0.00, 1.00)
|
vec4(-1.00, 0.00, 1.00)
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __init__(vec3 self, *args):
|
def __init__(vec3 self, *args):
|
||||||
'''
|
"""
|
||||||
Create a vec3.
|
Create a vec3.
|
||||||
|
|
||||||
Accepts any number of parameters between 0 and 3 to fill the vector from the left.
|
Accepts any number of parameters between 0 and 3 to fill the vector from the left.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
length = len(args)
|
length = len(args)
|
||||||
|
|
||||||
|
@ -377,7 +385,7 @@ cdef class vec3:
|
||||||
length = len(args)
|
length = len(args)
|
||||||
|
|
||||||
if length > 3:
|
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):
|
for i in range(3):
|
||||||
if i < length:
|
if i < length:
|
||||||
|
@ -387,18 +395,18 @@ cdef class vec3:
|
||||||
|
|
||||||
def __getitem__(vec3 self, int i):
|
def __getitem__(vec3 self, int i):
|
||||||
if i >= 3 or i < 0:
|
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]
|
return self.v[i]
|
||||||
|
|
||||||
def __setitem__(vec3 self, int i, float value):
|
def __setitem__(vec3 self, int i, float value):
|
||||||
if i >= 3 or i < 0:
|
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
|
self.v[i] = value
|
||||||
|
|
||||||
def __repr__(vec3 self):
|
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):
|
def __getstate__(vec3 self):
|
||||||
return (self.v[0], self.v[1], self.v[2])
|
return (self.v[0], self.v[1], self.v[2])
|
||||||
|
@ -410,12 +418,16 @@ cdef class vec3:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def length(vec3 self):
|
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)
|
return sqrt(self.v[0]**2 + self.v[1]**2 + self.v[2]**2)
|
||||||
|
|
||||||
def normalized(vec3 self):
|
def normalized(vec3 self):
|
||||||
'''Returns this vector, normalized.'''
|
"""
|
||||||
|
Returns this vector, normalized.
|
||||||
|
"""
|
||||||
|
|
||||||
length = self.length
|
length = self.length
|
||||||
|
|
||||||
|
@ -431,29 +443,28 @@ cdef class vec3:
|
||||||
return vec3(-self.v[0], -self.v[1], -self.v[2])
|
return vec3(-self.v[0], -self.v[1], -self.v[2])
|
||||||
|
|
||||||
def dot(vec3 L, vec3 R):
|
def dot(vec3 L, vec3 R):
|
||||||
'''
|
"""
|
||||||
Returns the dot product of the two vectors.
|
Returns the dot product of the two vectors.
|
||||||
|
|
||||||
E.g. u.dot(v) -> u . v
|
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]
|
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):
|
def cross(vec3 L, vec3 R):
|
||||||
'''
|
"""
|
||||||
Returns the cross product of the two vectors.
|
Returns the cross product of the two vectors.
|
||||||
|
|
||||||
E.g. u.cross(v) -> u x v
|
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])
|
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):
|
def __mul__(vec3 L, R):
|
||||||
'''
|
"""
|
||||||
Multiplication of a vec3 by a float.
|
Multiplication of a vec3 by a float.
|
||||||
|
|
||||||
The float has to be on the right.
|
The float has to be on the right.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
return vec3(L.v[0] * R, L.v[1] * R, L.v[2] * R)
|
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
|
#! /usr/bin/env python3
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
import copy
|
|
||||||
import imp
|
|
||||||
import io
|
|
||||||
import math
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from . import text
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
def import_module(path):
|
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.
|
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
|
# remove the .py suffix
|
||||||
mod_dn = os.path.dirname(path)
|
mod_dirname = os.path.dirname(path)
|
||||||
mod_fn = os.path.basename(path)
|
mod_filename = os.path.basename(path)
|
||||||
|
|
||||||
if mod_fn.endswith('.py'):
|
if mod_filename.endswith(".py"):
|
||||||
mod_name = mod_fn[:-3]
|
mod_name = mod_filename[:-3]
|
||||||
else:
|
else:
|
||||||
# packages for example
|
# packages for example
|
||||||
mod_name = mod_fn
|
mod_name = mod_filename
|
||||||
|
|
||||||
fd = None
|
fd = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = imp.find_module(mod_name, [mod_dn])
|
data = imp.find_module(mod_name, [mod_dirname])
|
||||||
module = imp.load_module(mod_name, *data)
|
module = imp.load_module(mod_name, *data)
|
||||||
fd = data[0]
|
fd = data[0]
|
||||||
|
|
||||||
|
@ -45,24 +43,24 @@ def import_module(path):
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
def batch_gen(data, batch_size):
|
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):
|
for i in range(0, len(data), batch_size):
|
||||||
yield data[i:i+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.
|
Opens up a Python console.
|
||||||
|
"""
|
||||||
|
|
||||||
tab_completion enables completion of object names using TAB, however note
|
import code
|
||||||
that this will make pasting formatted snippets of code difficult
|
import copy
|
||||||
'''
|
import readline
|
||||||
|
import rlcompleter
|
||||||
import code, readline, rlcompleter
|
|
||||||
|
|
||||||
environment = copy.copy(locals)
|
environment = copy.copy(locals)
|
||||||
|
|
||||||
|
@ -72,17 +70,17 @@ def console(locals, globals=None, tab_completion=False):
|
||||||
environment[key] = value
|
environment[key] = value
|
||||||
|
|
||||||
if tab_completion:
|
if tab_completion:
|
||||||
readline.parse_and_bind('tab: complete')
|
readline.parse_and_bind("tab: complete")
|
||||||
|
|
||||||
c = code.InteractiveConsole(environment)
|
c = code.InteractiveConsole(environment)
|
||||||
c.interact(banner='')
|
c.interact(banner="")
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
def debugger():
|
def debugger():
|
||||||
'''
|
"""
|
||||||
Drops into the Python Debugger where called.
|
Drops into the Python Debugger where called.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
import pdb
|
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):
|
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.
|
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_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_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
|
If `show_ascii` is True, each line is suffixed with its ASCII representation. Unprintable characters
|
||||||
are replaced with a dot.
|
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
|
output_io = io.StringIO() if not output else output
|
||||||
|
|
||||||
if type(data) is not bytes:
|
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
|
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
|
ptr = 0
|
||||||
|
|
||||||
if show_header:
|
if show_header:
|
||||||
line = []
|
line = []
|
||||||
|
|
||||||
if show_offsets:
|
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):
|
for i in range(offset):
|
||||||
line.append('%2x' %(i))
|
line.append("%2x" %(i))
|
||||||
|
|
||||||
if show_ascii:
|
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:]:
|
while data[ptr:]:
|
||||||
if indent:
|
if indent:
|
||||||
output_io.write(' ' * indent)
|
output_io.write(" " * indent)
|
||||||
|
|
||||||
if show_offsets:
|
if show_offsets:
|
||||||
output_io.write(format_str %(ptr))
|
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)):
|
for local_i, i in enumerate(range(ptr, ptr+offset)):
|
||||||
if i < len(data):
|
if i < len(data):
|
||||||
c = data[i]
|
c = data[i]
|
||||||
hex_bytes.append('%02x' %(c))
|
hex_bytes.append("%02x" %(c))
|
||||||
|
|
||||||
if 0x20 <= c <= 0x7e:
|
if 0x20 <= c <= 0x7e:
|
||||||
ascii_bytes.append(chr(c))
|
ascii_bytes.append(chr(c))
|
||||||
else:
|
else:
|
||||||
ascii_bytes.append('.')
|
ascii_bytes.append(".")
|
||||||
elif i == len(data):
|
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"))
|
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 use_colors: output_io.write(text.render("§/"))
|
||||||
|
|
||||||
if show_ascii:
|
if show_ascii:
|
||||||
output_io.write(text.render(" |§B") if use_colors else " |")
|
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(text.render("§/|") if use_colors else "|")
|
||||||
|
|
||||||
output_io.write('\n')
|
output_io.write("\n")
|
||||||
|
|
||||||
ptr += offset
|
ptr += offset
|
||||||
|
|
||||||
|
|
|
@ -1,105 +1,32 @@
|
||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
class ndict(dict):
|
class ndict(OrderedDict):
|
||||||
'''
|
"""
|
||||||
A dictionary superclass whose keys are exposed as attributes.
|
An OrderedDict subclass whose keys are exposed as attributes.
|
||||||
|
|
||||||
>>> d = ndict()
|
>>> d = ndict()
|
||||||
>>> d.alpha = 1
|
>>> d.alpha = 1
|
||||||
>>> d['alpha']
|
>>> d["alpha"]
|
||||||
1
|
1
|
||||||
>>> d['beta'] = 42
|
>>> d["beta"] = 42
|
||||||
>>> d.beta
|
>>> d.beta
|
||||||
42
|
42
|
||||||
>>> d
|
>>> d
|
||||||
{'alpha': 1, 'beta': 42}
|
{"alpha": 1, "beta": 42}
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name in self:
|
if name in self:
|
||||||
return self[name]
|
return self[name]
|
||||||
elif name.replace('_', '-') in self:
|
elif name.replace("_", "-") in self:
|
||||||
return self[name.replace('_', '-')]
|
return self[name.replace("_", "-")]
|
||||||
else:
|
else:
|
||||||
raise AttributeError("'ndict' object has no attribute '%s'" %(name))
|
raise AttributeError('"ndict" object has no attribute "%s"' %(name))
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
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 time
|
||||||
import serial
|
import serial
|
||||||
|
|
||||||
from . import et
|
from .. import text
|
||||||
|
|
||||||
prefix = et.prefix
|
"""
|
||||||
|
OverTalk protocol spec
|
||||||
|
======================
|
||||||
|
|
||||||
'''
|
Transport layer
|
||||||
===== OverTalk protocol spec =====
|
---------------
|
||||||
==== Transport layer ====
|
TODO: Swap checksum and payload.
|
||||||
|
|
||||||
frame = [ 0x2a | destination (1) | source (1) | payload length (1) | payload (n) | checksum (1) ]
|
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.
|
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
|
Frames with destinations unknown to an OverShell are sent to the default interface, or dropped if
|
||||||
that's the interface they came from.
|
that's the interface they came from.
|
||||||
|
|
||||||
==== Command layer ====
|
Command layer
|
||||||
|
-------------
|
||||||
|
NOTE: This is going to change.
|
||||||
|
|
||||||
Payload consists of one or more commands:
|
Payload consists of one or more commands:
|
||||||
|
|
||||||
get_command = [ 0x00 | register (1) | target register (1) ]
|
get_command = [ 0x00 | register (1) | target register (1) ]
|
||||||
set_command = [ 0x01 | register (1) | length (1) | value (n) ]
|
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
|
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 that sets the sender's `target register` to that value.
|
||||||
|
|
||||||
A set_command does what is says on the box.
|
A set_command does what is says on the box.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
class Transport:
|
class Transport:
|
||||||
'''
|
"""
|
||||||
OverTalk Transport layer implementation.
|
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.
|
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),
|
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).
|
and waiting property (contains number of bytes waiting).
|
||||||
'''
|
"""
|
||||||
def __init__(self, my_id, interfaces, def_route):
|
def __init__(self, my_id, interfaces, def_route):
|
||||||
'''
|
"""
|
||||||
@param my_id OverTalk address of this Transport instance
|
@param my_id OverTalk address of this Transport instance
|
||||||
@param interfaces a dict of interfaces keyed by their IDs
|
@param interfaces a dict of interfaces keyed by their IDs
|
||||||
@param def_route ID of default interface for sending
|
@param def_route ID of default interface for sending
|
||||||
'''
|
"""
|
||||||
|
|
||||||
assert my_id not in interfaces
|
assert my_id not in interfaces
|
||||||
assert def_route 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.my_id = my_id
|
||||||
self.def_route = def_route
|
self.def_route = def_route
|
||||||
|
@ -61,7 +66,7 @@ class Transport:
|
||||||
self.destination_unknown = 0
|
self.destination_unknown = 0
|
||||||
self.incoming = None
|
self.incoming = None
|
||||||
|
|
||||||
self._print('<Cg>on-line<C/>')
|
self.print("on-line", text.Output.tl.done)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.incoming = None
|
self.incoming = None
|
||||||
|
@ -95,7 +100,7 @@ class Transport:
|
||||||
buffer_length = len(interface.rxbuffer)
|
buffer_length = len(interface.rxbuffer)
|
||||||
|
|
||||||
if buffer_length >= 4:
|
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)
|
if buffer_length >= interface.rxbuffer[3] + 5: # header (4) + payload + checksum (1)
|
||||||
# a frame has been read, huzzah!
|
# a frame has been read, huzzah!
|
||||||
interface.reading_frame = False
|
interface.reading_frame = False
|
||||||
|
@ -106,7 +111,7 @@ class Transport:
|
||||||
|
|
||||||
else:
|
else:
|
||||||
interface.malformed_frames += 1
|
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 = []
|
interface.rxbuffer = []
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -133,15 +138,15 @@ class Transport:
|
||||||
self.incoming = (frame[2], payload)
|
self.incoming = (frame[2], payload)
|
||||||
else:
|
else:
|
||||||
if destination in self.interfaces:
|
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))
|
self.interfaces[destination].write(self.escape_frame(frame))
|
||||||
else:
|
else:
|
||||||
if source_interface_id == self.def_route:
|
if source_interface_id == self.def_route:
|
||||||
self.destination_unknown += 1
|
self.destination_unknown += 1
|
||||||
self._print('unknown destination <Cr>%d<C/> for frame: <Cy>%s<C/>' %(destination,
|
self.print("unknown destination <r>%d<.> for frame: <y>%s<.>" %(destination,
|
||||||
repr(frame)), prefix.fail)
|
repr(frame)), text.Output.tl.fail)
|
||||||
else:
|
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))
|
self.interfaces[self.def_route].write(self.escape_frame(frame))
|
||||||
|
|
||||||
def send_data(self, destination, data):
|
def send_data(self, destination, data):
|
||||||
|
@ -157,7 +162,7 @@ class Transport:
|
||||||
s.write(frame)
|
s.write(frame)
|
||||||
|
|
||||||
class TTL_Interface:
|
class TTL_Interface:
|
||||||
def __init__(self, interface='/dev/ttyUSB0', baudrate=57600):
|
def __init__(self, interface="/dev/ttyUSB0", baudrate=57600):
|
||||||
try:
|
try:
|
||||||
self.s = serial.Serial(interface, baudrate, timeout=1)
|
self.s = serial.Serial(interface, baudrate, timeout=1)
|
||||||
except serial.serialutil.SerialException:
|
except serial.serialutil.SerialException:
|
||||||
|
@ -184,7 +189,7 @@ class TTL_Interface:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def write(self, data):
|
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:
|
if self.s:
|
||||||
self.s.write(bytes(data))
|
self.s.write(bytes(data))
|
||||||
|
|
546
over/text.py
546
over/text.py
|
@ -1,13 +1,14 @@
|
||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import fcntl
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import struct
|
import struct
|
||||||
import fcntl
|
import sys
|
||||||
import termios
|
|
||||||
import time
|
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:
|
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):
|
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.unit = unit
|
||||||
self.top = top
|
self.top = top
|
||||||
|
@ -60,7 +331,7 @@ class _ProgressBarChannel:
|
||||||
self.set()
|
self.set()
|
||||||
|
|
||||||
if prefix_base2:
|
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):
|
def set(self, value=None):
|
||||||
if value is not 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__":
|
if __name__ == "__main__":
|
||||||
pb = ProgressBar(
|
pb = ProgressBar(
|
||||||
"§%a (§ra/§za) [§=a>§ A] §sa [§rb/§zb] (ETA §TA)",
|
"§%a (§ra/§za) [§=a>§ A] §sa [§rb/§zb] (ETA §TA)",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue