406 lines
12 KiB
Python
406 lines
12 KiB
Python
|
#!/usr/bin/python3
|
||
|
# -*- coding: utf-8 -*-
|
||
|
# -*- mode: Python -*-
|
||
|
|
||
|
'''
|
||
|
|
||
|
fromGML
|
||
|
v0.1.0
|
||
|
|
||
|
Display a GML file
|
||
|
|
||
|
See GML specs at the end.
|
||
|
Support the gml spec="1.0 (minimum)"
|
||
|
and header/client/name
|
||
|
and maybe one day drawing/brush/color
|
||
|
|
||
|
LICENCE : CC
|
||
|
by cocoa and Sam Neurohack
|
||
|
|
||
|
Heavy use of : https://github.com/kgn/pygml
|
||
|
'''
|
||
|
from __future__ import print_function
|
||
|
import time
|
||
|
import struct
|
||
|
import argparse
|
||
|
import sys
|
||
|
import xml.etree.ElementTree as etree
|
||
|
#import urllib
|
||
|
from datetime import datetime
|
||
|
import math, random
|
||
|
import ast
|
||
|
|
||
|
name="generator::fromgml"
|
||
|
|
||
|
|
||
|
def debug(*args, **kwargs):
|
||
|
if( verbose == False ):
|
||
|
return
|
||
|
print(*args, file=sys.stderr, **kwargs)
|
||
|
|
||
|
argsparser = argparse.ArgumentParser(description="GML file frame generator")
|
||
|
argsparser.add_argument("-f","--fps",help="Frame Per Second",default=30,type=int)
|
||
|
argsparser.add_argument("-v","--verbose",action="store_true",help="Verbose output")
|
||
|
argsparser.add_argument("-g","--gml",help=".gml file",default="147.gml",type=str)
|
||
|
argsparser.add_argument("-t","--total",help="Total time",default=32,type=int)
|
||
|
argsparser.add_argument("-m","--mode",help="once or anim mode",default="anim",type=str)
|
||
|
argsparser.add_argument("-s","--skip",help="% of points to skip",default="0.4",type=float)
|
||
|
argsparser.add_argument("-r","--rot",help="(angleX, angleY, angleZ) in degree",default="(0,0,270)",type=str)
|
||
|
args = argsparser.parse_args()
|
||
|
|
||
|
fps=args.fps
|
||
|
verbose=args.verbose
|
||
|
mode = args.mode
|
||
|
optimal_looptime = 1 / fps
|
||
|
angles = ast.literal_eval(args.rot)
|
||
|
debug(name+" optimal frame time "+str(optimal_looptime))
|
||
|
|
||
|
|
||
|
TOTAL_TIME=float(args.total)
|
||
|
TIME_STRETCH = 1
|
||
|
ZOOM=1.0
|
||
|
DELTA = 7
|
||
|
width = 500
|
||
|
height = 500
|
||
|
centerX = width / 2
|
||
|
centerY = height / 2
|
||
|
# 3D to 2D projection parameters
|
||
|
fov = 200
|
||
|
viewer_distance = 2.2
|
||
|
|
||
|
skip = args.skip
|
||
|
#skip is the percentage of points that we ignore in order to render
|
||
|
# faster in the laser display. Unfortunately we are not able to render too
|
||
|
# complex content in our display without resulting in a lot of blinking.
|
||
|
|
||
|
# return a list with all points
|
||
|
def readGML(filename):
|
||
|
|
||
|
outputData = []
|
||
|
tree = etree.parse(filename)
|
||
|
root = tree.getroot()
|
||
|
'''
|
||
|
if (root.tag.lower() != "gml"):
|
||
|
print("Not a GML file.")
|
||
|
return
|
||
|
'''
|
||
|
#~
|
||
|
tag = root.find("tag")
|
||
|
header = tag.find("header")
|
||
|
if header != None:
|
||
|
client = header.find("client")
|
||
|
if client != None:
|
||
|
debug("Graffiti name :", client.find("name").text)
|
||
|
drawing = tag.find("drawing")
|
||
|
environment = header.find("environment")
|
||
|
if not environment:
|
||
|
environment = tag.find("environment")
|
||
|
#screenBounds = environment.find("screenBounds")
|
||
|
#globalScale = (1.0,1.0,1.0)
|
||
|
#dim = (float(screenBounds.find("x").text) * globalScale[0], float(screenBounds.find("y").text) * globalScale[1], float(screenBounds.find("z").text) * globalScale[2])
|
||
|
#dim = (40.0,40.0,40.0)
|
||
|
#~
|
||
|
strokes = drawing.findall("stroke")
|
||
|
for stroke in strokes:
|
||
|
pointsEl = stroke.findall("pt")
|
||
|
for pointEl in pointsEl:
|
||
|
|
||
|
x = float(pointEl.find("x").text) - 0.5
|
||
|
y = float(pointEl.find("y").text) - 0.5
|
||
|
z = float(pointEl.find("z").text) - 0.5
|
||
|
transpoint = Rot(x,y,z,angles[0],angles[1],angles[2])
|
||
|
x = (transpoint[0]*ZOOM*width/2) + (width/2)
|
||
|
y = (transpoint[1]*ZOOM*height/2) + (height/2)
|
||
|
z = transpoint[2]
|
||
|
# WIDTH/2 + ZOOM*point[0]*WIDTH/2, HEIGHT/2 + ZOOM*point[1]*HEIGHT/2
|
||
|
time = float(pointEl.find("time").text)
|
||
|
outputData.append([x,y,z,time])
|
||
|
#print(outputData)
|
||
|
return outputData
|
||
|
|
||
|
def Rot(x, y, z, angleX, angleY, angleZ):
|
||
|
|
||
|
rad = angleX * math.pi / 180
|
||
|
cosa = math.cos(rad)
|
||
|
sina = math.sin(rad)
|
||
|
y2 = y
|
||
|
y = y2 * cosa - z * sina
|
||
|
z = y2 * sina + z * cosa
|
||
|
|
||
|
rad = angleY * math.pi / 180
|
||
|
cosa = math.cos(rad)
|
||
|
sina = math.sin(rad)
|
||
|
z2 = z
|
||
|
z = z2 * cosa - x * sina
|
||
|
x = z2 * sina + x * cosa
|
||
|
|
||
|
rad = angleZ * math.pi / 180
|
||
|
cosa = math.cos(rad)
|
||
|
sina = math.sin(rad)
|
||
|
x2 = x
|
||
|
x = x2 * cosa - y * sina
|
||
|
y = x2 * sina + y * cosa
|
||
|
|
||
|
return (x,y,z)
|
||
|
|
||
|
|
||
|
#[x,y,z,time]
|
||
|
def iterPoints():
|
||
|
|
||
|
for point in gml:
|
||
|
yield point
|
||
|
|
||
|
|
||
|
# Play once during total time arg
|
||
|
def Once():
|
||
|
|
||
|
debug(name,"play once mode")
|
||
|
shape = []
|
||
|
for point in gml:
|
||
|
shape.append([point[0],point[1], 65535])
|
||
|
debug(name + str(shape))
|
||
|
|
||
|
t0=datetime.now()
|
||
|
deltat=0
|
||
|
|
||
|
while deltat<TOTAL_TIME:
|
||
|
|
||
|
start = time.time()
|
||
|
print(shape, flush=True);
|
||
|
looptime = time.time() - start
|
||
|
|
||
|
if( looptime < optimal_looptime ):
|
||
|
time.sleep( optimal_looptime - looptime)
|
||
|
debug(name+" micro sleep:"+str( optimal_looptime - looptime))
|
||
|
|
||
|
delta = datetime.now() - t0
|
||
|
deltat = delta.seconds + delta.microseconds/1000000.0
|
||
|
deltat = float(deltat)/TIME_STRETCH
|
||
|
|
||
|
|
||
|
# Anim
|
||
|
def Anim():
|
||
|
|
||
|
debug(name+" anim mode")
|
||
|
t0=datetime.now()
|
||
|
deltat = 0
|
||
|
|
||
|
while deltat<TOTAL_TIME:
|
||
|
|
||
|
delta = datetime.now() - t0
|
||
|
deltat = delta.seconds + delta.microseconds/1000000.0
|
||
|
deltat = float(deltat)/TIME_STRETCH
|
||
|
|
||
|
if deltat > TOTAL_TIME:
|
||
|
t0=datetime.now()
|
||
|
|
||
|
first=True
|
||
|
shape = []
|
||
|
for point in iterPoints():
|
||
|
if point[3] <= deltat and deltat <= point[3]+DELTA and random.random()<(1-skip):
|
||
|
if first:
|
||
|
first=False
|
||
|
else:
|
||
|
#LD.draw_point(WIDTH/2 + ZOOM*point.x*WIDTH/2, HEIGHT/2 + ZOOM*point.y*HEIGHT/2)
|
||
|
shape.append([point[0], point[1], 65535])
|
||
|
print(shape, flush=True);
|
||
|
|
||
|
|
||
|
debug(name + " Reading : "+args.gml+" in "+mode+" mode.")
|
||
|
gml = readGML(args.gml)
|
||
|
|
||
|
debug(name + " total points : "+ str(len(gml)))
|
||
|
|
||
|
if mode =="once":
|
||
|
Once()
|
||
|
else:
|
||
|
Anim()
|
||
|
|
||
|
debug(name + " ends.")
|
||
|
exit()
|
||
|
'''
|
||
|
|
||
|
<gml spec="1.0 (minimum)">
|
||
|
|
||
|
<tag>
|
||
|
<drawing>
|
||
|
<stroke>
|
||
|
<pt>
|
||
|
<x>0.0</x>
|
||
|
<y>0.0</y>
|
||
|
</pt>
|
||
|
</stroke>
|
||
|
</drawing>
|
||
|
</tag>
|
||
|
|
||
|
</gml>
|
||
|
|
||
|
|
||
|
|
||
|
<gml spec="1.0">
|
||
|
|
||
|
<tag>
|
||
|
|
||
|
<header>
|
||
|
|
||
|
<client> <!-- how, who, what and where -->
|
||
|
<name>Laser Tag</name> <!-- application name -->
|
||
|
<version>2.0</version> <!-- application version -->
|
||
|
<username>MyUserName</username> <!-- user name on 000000book.com, optional -->
|
||
|
<permalink>http://000000book.com/data/156/</permalink> <!-- URL to .gml data on 000000book.com, optional -->
|
||
|
<keywords>katsu,paris,2010</keywords> <!-- comma-separated -->
|
||
|
<uniquekey>28sks922ks992</uniquekey> <!-- iPhone uuid, MAC address, etc -->
|
||
|
<ip>192.168.1.1</ip>
|
||
|
<time>1928372722</time> <!-- unixtime -->
|
||
|
<location>
|
||
|
<lon>-39.392922</lon>
|
||
|
<lat>53.29292</lat>
|
||
|
</location>
|
||
|
</client>
|
||
|
|
||
|
<!-- This is all stuff that relates to the orientation and dimensions of the client -->
|
||
|
<!-- So that we know how to re-map the 0.0-1.0 coordinates that come in for each point -->
|
||
|
<!-- Also for figuring out the down vector for devices with accelerometers and how that effects drips -->
|
||
|
<!-- All numbers should be between 0.0 - 1.0 -->
|
||
|
<environment>
|
||
|
<offset>
|
||
|
<x>0.0</x>
|
||
|
<y>0.0</y>
|
||
|
<z>0.0</z>
|
||
|
</offset>
|
||
|
<rotation>
|
||
|
<x>0.0</x>
|
||
|
<y>0.0</y>
|
||
|
<z>0.0</z>
|
||
|
</rotation>
|
||
|
<up>
|
||
|
<x>0.0</x> <!-- commonly up for iphone apps -->
|
||
|
<y>-1.0</y> <!-- most common -->
|
||
|
<z>0.0</z>
|
||
|
</up>
|
||
|
<screenbounds> <!-- use this as your multipler to get 0.0 to 1.0 back to right size - pts should never go off 0.0 to 1.0 -->
|
||
|
<x>1024</x>
|
||
|
<y>768</y>
|
||
|
<z>0</z>
|
||
|
</screenbounds>
|
||
|
<origin>
|
||
|
<x>0</x>
|
||
|
<y>0</y>
|
||
|
<z>0</z>
|
||
|
</origin>
|
||
|
<realscale> <!-- how these units relate to real world units - good for laser tag -->
|
||
|
<x>1000</x>
|
||
|
<y>600</y>
|
||
|
<z>0</z>
|
||
|
<unit>cm</unit>
|
||
|
</realscale>
|
||
|
<audio>youraudio.mp3</audio> <!-- path to audio file -->
|
||
|
<background>yourimage.jpg</background> <!-- path to image file -->
|
||
|
</environment>
|
||
|
|
||
|
</header>
|
||
|
|
||
|
<drawing>
|
||
|
<!-- for all stroke and movement stuff it helps to have everything inside the stroke tag -->
|
||
|
<!-- this way it is easy to get a sense of order to events -->
|
||
|
|
||
|
<stroke isdrawing="false"> <!-- for non drawing mouse movements -->
|
||
|
<pt>
|
||
|
<x>0.0</x>
|
||
|
<y>0.0</y>
|
||
|
<z>0.0</z> <!--this is optional -->
|
||
|
<t>0.013</t> <!-- time is optional too -->
|
||
|
<!-- NOTE: older versions of GML use <time> instead of <t> -->
|
||
|
</pt>
|
||
|
</stroke>
|
||
|
|
||
|
<stroke> <!-- by default stroke drawing is true -->
|
||
|
|
||
|
<!-- each stroke could be drawn with a different brush -->
|
||
|
<!-- if no brush tag is found for a stroke then it inherits the previous settings -->
|
||
|
<brush>
|
||
|
<mode>0</mode> <!-- same as uniqueStyleID but an internal reference -->
|
||
|
<uniquestyleid>LaserTagArrowLetters</uniquestyleid> <!-- unique blackbook string for your style -->
|
||
|
<!-- see note about spec at the bottom - like unique style but with extra info -->
|
||
|
<spec>http://aurltodescribethebrushspec.com/someSpec.xml</spec>
|
||
|
<width>10</width>
|
||
|
<speedtowidthratio>1.5</speedtowidthratio> <!-- put 0 for fixed width -->
|
||
|
<dripamnt>1.0</dripamnt>
|
||
|
<dripspeed>1.0</dripspeed>
|
||
|
<layerabsolute>0</layerabsolute> <!--Think photoshop layers-->
|
||
|
<color>
|
||
|
<r>255</r>
|
||
|
<g>255</g>
|
||
|
<b>255</b>
|
||
|
<a>255</a> <!-- optional -->
|
||
|
</color>
|
||
|
<dripvecrelativetoup> <!-- what angle do our drips go in relation to our up vector -->
|
||
|
<x>0</x>
|
||
|
<y>1</y>
|
||
|
<z>0</z>
|
||
|
</dripvecrelativetoup>
|
||
|
</brush>
|
||
|
|
||
|
<pt>
|
||
|
<x>0.0</x>
|
||
|
<y>0.0</y>
|
||
|
<z>0.0</z> <!--this is optional -->
|
||
|
<t>0.013</t> <!-- time is optional too -->
|
||
|
</pt>
|
||
|
|
||
|
<pt>
|
||
|
<x>0.0</x>
|
||
|
<y>0.0</y>
|
||
|
<z>0.0</z> <!--this is optional -->
|
||
|
<t>0.023</t> <!-- time is optional too -->
|
||
|
</pt>
|
||
|
|
||
|
|
||
|
</stroke>
|
||
|
|
||
|
<!-- this stroke inherits the previous stroke properties -->
|
||
|
<!-- but changes color and draws on the layer below -->
|
||
|
<stroke>
|
||
|
<info> <!-- optional info - more stuff soon-->
|
||
|
<curved>true</curved>
|
||
|
</info>
|
||
|
<brush>
|
||
|
<color>
|
||
|
<r>255</r>
|
||
|
<g>255</g>
|
||
|
<b>0</b>
|
||
|
</color>
|
||
|
<layerrelative> <!-- this means one layer bellow the previous layer -->
|
||
|
-1
|
||
|
</layerrelative>
|
||
|
</brush>
|
||
|
|
||
|
<pt>
|
||
|
<x>0.0</x>
|
||
|
<y>0.0</y>
|
||
|
</pt>
|
||
|
|
||
|
<pt>
|
||
|
<x>0.0</x>
|
||
|
<y>0.0</y>
|
||
|
</pt>
|
||
|
|
||
|
</stroke>
|
||
|
|
||
|
<stroke>
|
||
|
<pt>
|
||
|
<pres>0.5</pres> <!-- Optional. Preasure range from 0 to 1 -->
|
||
|
<rot>0.5</rot> <!-- Optional. Rotation range from 0 to 1 for 0 to 2*PI -->
|
||
|
<dir> <!-- Optional Direction -->
|
||
|
<x></x> <!-- range from 0 to 1 -->
|
||
|
<y></y> <!-- range from 0 to 1 -->
|
||
|
<z></z> <!-- Optional inside direction. Range from 0 to 1 -->
|
||
|
</dir>
|
||
|
</pt>
|
||
|
</stroke>
|
||
|
</drawing>
|
||
|
|
||
|
</tag>
|
||
|
|
||
|
</gml>
|
||
|
'''
|