mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-09 22:25:46 +01:00
For some reason this keeps disappearing
This commit is contained in:
@@ -5,8 +5,8 @@ Created on Sep 21, 2013
|
||||
'''
|
||||
import os
|
||||
|
||||
from .map import Map, Tile, MapRenderFlags
|
||||
from .objtree import ObjectTree
|
||||
from byond.map import Map, Tile, MapRenderFlags
|
||||
from byond.objtree import ObjectTree
|
||||
|
||||
def GetFilesFromDME(dmefile='baystation12.dme', ext='.dm'):
|
||||
filesInDME=[]
|
||||
@@ -30,7 +30,7 @@ def GetFilesFromDME(dmefile='baystation12.dme', ext='.dm'):
|
||||
if c == '"':
|
||||
inString = not inString
|
||||
if not inString:
|
||||
filepath = os.path.join(rootdir, filename)
|
||||
filepath = os.path.join(rootdir, filename.replace('\\',os.sep))
|
||||
if filepath.endswith(ext):
|
||||
filesInDME += [filepath]
|
||||
filename = ''
|
||||
|
||||
@@ -10,7 +10,48 @@ OBJ_LAYER = 3
|
||||
MOB_LAYER = 4
|
||||
FLY_LAYER = 5
|
||||
|
||||
import re
|
||||
# @formatter:off
|
||||
COLORS = {
|
||||
'aqua': '#00FFFF',
|
||||
'black': '#000000',
|
||||
'blue': '#0000FF',
|
||||
'brown': '#A52A2A',
|
||||
'cyan': '#00FFFF',
|
||||
'fuchsia': '#FF00FF',
|
||||
'gold': '#FFD700',
|
||||
'gray': '#808080',
|
||||
'green': '#008000',
|
||||
'grey': '#808080',
|
||||
'lime': '#00FF00',
|
||||
'magenta': '#FF00FF',
|
||||
'maroon': '#800000',
|
||||
'navy': '#000080',
|
||||
'olive': '#808000',
|
||||
'purple': '#800080',
|
||||
'red': '#FF0000',
|
||||
'silver': '#C0C0C0',
|
||||
'teal': '#008080',
|
||||
'white': '#FFFFFF',
|
||||
'yellow': '#FFFF00'
|
||||
}
|
||||
# @formatter:on
|
||||
|
||||
_COLOR_LOOKUP = {}
|
||||
|
||||
def BYOND2RGBA(colorstring, alpha=255):
|
||||
colorstring = colorstring.strip()
|
||||
if colorstring[0] == '#':
|
||||
colorstring = colorstring[1:]
|
||||
r, g, b = colorstring[:2], colorstring[2:4], colorstring[4:]
|
||||
r, g, b = [int(n, 16) for n in (r, g, b)]
|
||||
return (r, g, b, alpha)
|
||||
else:
|
||||
return _COLOR_LOOKUP[colorstring]
|
||||
|
||||
for name, color in COLORS.items():
|
||||
_COLOR_LOOKUP[name] = BYOND2RGBA(color)
|
||||
|
||||
import re, hashlib
|
||||
from .utils import eval_expr
|
||||
REGEX_TABS = re.compile('^(?P<tabs>\t*)')
|
||||
class BYONDValue:
|
||||
@@ -18,28 +59,28 @@ class BYONDValue:
|
||||
Handles numbers and unhandled types like lists.
|
||||
"""
|
||||
def __init__(self, string, filename='', line=0, typepath='/', **kwargs):
|
||||
#: The actual value.
|
||||
# : The actual value.
|
||||
self.value = string
|
||||
|
||||
#: Filename this was found in
|
||||
# : Filename this was found in
|
||||
self.filename = filename
|
||||
|
||||
#: Line of the originating file.
|
||||
# : Line of the originating file.
|
||||
self.line = line
|
||||
|
||||
#: Typepath of the value.
|
||||
# : Typepath of the value.
|
||||
self.type = typepath
|
||||
|
||||
#: Has this value been inherited?
|
||||
# : Has this value been inherited?
|
||||
self.inherited = kwargs.get('inherited', False)
|
||||
|
||||
#: Is this a declaration? (/var)
|
||||
# : Is this a declaration? (/var)
|
||||
self.declaration = kwargs.get('declaration', False)
|
||||
|
||||
#: Anything special? (global, const, etc.)
|
||||
# : Anything special? (global, const, etc.)
|
||||
self.special = kwargs.get('special', None)
|
||||
|
||||
#: If a list, what's the size?
|
||||
# : If a list, what's the size?
|
||||
self.size = kwargs.get('size', None)
|
||||
|
||||
def copy(self):
|
||||
@@ -47,6 +88,8 @@ class BYONDValue:
|
||||
return BYONDValue(self.value, self.filename, self.line, self.type, declaration=self.declaration, inherited=self.inherited, special=self.special)
|
||||
|
||||
def __str__(self):
|
||||
if self.value is None:
|
||||
return 'null'
|
||||
return '{0}'.format(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -104,16 +147,16 @@ class BYONDString(BYONDValue):
|
||||
class PropertyFlags:
|
||||
'''Collection of flags that affect :func:`Atom.setProperty` behavior.'''
|
||||
|
||||
#: Property being set should be saved to the map
|
||||
# : Property being set should be saved to the map
|
||||
MAP_SPECIFIED = 1
|
||||
|
||||
#: Property being set should be handled as a string
|
||||
# : Property being set should be handled as a string
|
||||
STRING = 2
|
||||
|
||||
#: Property being set should be handled as a file reference
|
||||
# : Property being set should be handled as a file reference
|
||||
FILEREF = 4
|
||||
|
||||
#: Property being set should be handled as a value
|
||||
# : Property being set should be handled as a value
|
||||
VALUE = 8
|
||||
|
||||
class Atom:
|
||||
@@ -128,61 +171,76 @@ class Atom:
|
||||
The line in the aforementioned file.
|
||||
'''
|
||||
|
||||
#: Prints all inherited properties, not just the ones that are mapSpecified.
|
||||
# : Prints all inherited properties, not just the ones that are mapSpecified.
|
||||
FLAG_INHERITED_PROPERTIES = 1
|
||||
|
||||
#: writeMap2 prints old_ids instead of the actual IID.
|
||||
# : writeMap2 prints old_ids instead of the actual IID.
|
||||
FLAG_USE_OLD_ID = 2
|
||||
|
||||
def __init__(self, path, filename='', line=0, **kwargs):
|
||||
global TURF_LAYER, AREA_LAYER, OBJ_LAYER, MOB_LAYER
|
||||
|
||||
#: Absolute path of this atom
|
||||
# : Absolute path of this atom
|
||||
self.path = path
|
||||
|
||||
#: Vars of this atom, including inherited vars.
|
||||
# : Vars of this atom, including inherited vars.
|
||||
self.properties = {}
|
||||
|
||||
#: List of var names that were specified by the map, if atom was loaded from a :class:`byond.map.Map`.
|
||||
# : List of var names that were specified by the map, if atom was loaded from a :class:`byond.map.Map`.
|
||||
self.mapSpecified = []
|
||||
|
||||
#: Child atoms and procs.
|
||||
# : Child atoms and procs.
|
||||
self.children = {}
|
||||
|
||||
#: The parent of this atom.
|
||||
# : The parent of this atom.
|
||||
self.parent = None
|
||||
|
||||
#: The file this atom originated from.
|
||||
# : The file this atom originated from.
|
||||
self.filename = filename
|
||||
|
||||
#: Line from the originating file.
|
||||
# : Line from the originating file.
|
||||
self.line = line
|
||||
|
||||
#: Instance ID (maps only). Used internally, do NOT change.
|
||||
# : Instance ID (maps only). Used internally, do NOT change.
|
||||
self.id = None
|
||||
|
||||
#: Instance ID that was read from the map.
|
||||
# : Instance ID that was read from the map.
|
||||
self.old_id = None
|
||||
|
||||
#: Used internally.
|
||||
self.ob_inherited=False
|
||||
# : Used internally.
|
||||
self.ob_inherited = False
|
||||
|
||||
#: Loaded from map, but missing in the code. (Maps only)
|
||||
self.missing=kwargs.get('missing',False)
|
||||
# : Loaded from map, but missing in the code. (Maps only)
|
||||
self.missing = kwargs.get('missing', False)
|
||||
|
||||
#if not self.missing and path == '/area/engine/engineering':
|
||||
# if not self.missing and path == '/area/engine/engineering':
|
||||
# raise Exception('God damnit')
|
||||
|
||||
self._hash = None
|
||||
|
||||
def UpdateHash(self):
|
||||
if self._hash is None:
|
||||
self._hash = hashlib.md5(str(self)).hexdigest()
|
||||
|
||||
def InvalidateHash(self):
|
||||
self._hash = None
|
||||
|
||||
def GetHash(self):
|
||||
self.UpdateHash()
|
||||
return self._hash
|
||||
|
||||
def copy(self):
|
||||
'''
|
||||
Make a copy of this atom, without dangling references.
|
||||
|
||||
:returns byond.basetypes.Atom
|
||||
'''
|
||||
new_node = Atom(self.path,self.filename,self.line,missing=self.missing)
|
||||
new_node = Atom(self.path, self.filename, self.line, missing=self.missing)
|
||||
new_node.properties = self.properties.copy()
|
||||
new_node.mapSpecified = self.mapSpecified
|
||||
new_node.id = self.id
|
||||
new_node.old_id = self.old_id
|
||||
new_node.UpdateHash()
|
||||
# new_node.parent = self.parent
|
||||
return new_node
|
||||
|
||||
@@ -250,10 +308,12 @@ class Atom:
|
||||
self.properties[index] = BYONDFileRef(value)
|
||||
else:
|
||||
self.properties[index] = BYONDValue(value)
|
||||
|
||||
self.UpdateHash()
|
||||
|
||||
def InheritProperties(self):
|
||||
if self.ob_inherited: return
|
||||
#debugInheritance=self.path in ('/area','/obj','/mob','/atom/movable','/atom')
|
||||
# debugInheritance=self.path in ('/area','/obj','/mob','/atom/movable','/atom')
|
||||
if self.parent:
|
||||
if not self.parent.ob_inherited:
|
||||
self.parent.InheritProperties()
|
||||
@@ -262,26 +322,26 @@ class Atom:
|
||||
if key not in self.properties:
|
||||
self.properties[key] = value
|
||||
self.properties[key].inherited = True
|
||||
#if debugInheritance:print(' {0}[{2}] -> {1}'.format(self.parent.path,self.path,key))
|
||||
#assert 'name' in self.properties
|
||||
self.ob_inherited=True
|
||||
# if debugInheritance:print(' {0}[{2}] -> {1}'.format(self.parent.path,self.path,key))
|
||||
# assert 'name' in self.properties
|
||||
self.ob_inherited = True
|
||||
for k in self.children.iterkeys():
|
||||
self.children[k].InheritProperties()
|
||||
|
||||
|
||||
def __ne__(self, atom):
|
||||
return not self.__eq__(atom)
|
||||
|
||||
def __eq__(self, atom):
|
||||
if atom == None:
|
||||
return False
|
||||
if self.mapSpecified != atom.mapSpecified:
|
||||
return False
|
||||
# if self.mapSpecified != atom.mapSpecified:
|
||||
# return False
|
||||
if self.path != atom.path:
|
||||
return False
|
||||
return self.properties == atom.properties
|
||||
|
||||
def handle_math(self,expr):
|
||||
if isinstance(expr,str):
|
||||
def handle_math(self, expr):
|
||||
if isinstance(expr, str):
|
||||
return eval_expr(expr)
|
||||
return expr
|
||||
|
||||
@@ -291,12 +351,12 @@ class Atom:
|
||||
myLayer = 0
|
||||
otherLayer = 0
|
||||
try:
|
||||
myLayer = self.handle_math(self.getProperty('layer',myLayer))
|
||||
myLayer = self.handle_math(self.getProperty('layer', myLayer))
|
||||
except ValueError:
|
||||
print('Failed to parse {0} as float.'.format(self.properties['layer'].value))
|
||||
pass
|
||||
try:
|
||||
otherLayer = self.handle_math(other.getProperty('layer',otherLayer))
|
||||
otherLayer = self.handle_math(other.getProperty('layer', otherLayer))
|
||||
except ValueError:
|
||||
print('Failed to parse {0} as float.'.format(other.properties['layer'].value))
|
||||
pass
|
||||
@@ -308,34 +368,24 @@ class Atom:
|
||||
myLayer = 0
|
||||
otherLayer = 0
|
||||
try:
|
||||
myLayer = self.handle_math(self.getProperty('layer',myLayer))
|
||||
myLayer = self.handle_math(self.getProperty('layer', myLayer))
|
||||
except ValueError:
|
||||
print('Failed to parse {0} as float.'.format(self.properties['layer'].value))
|
||||
pass
|
||||
try:
|
||||
otherLayer = self.handle_math(other.getProperty('layer',otherLayer))
|
||||
otherLayer = self.handle_math(other.getProperty('layer', otherLayer))
|
||||
except ValueError:
|
||||
print('Failed to parse {0} as float.'.format(other.properties['layer'].value))
|
||||
pass
|
||||
return myLayer < otherLayer
|
||||
|
||||
def MapSerialize(self, flags=0):
|
||||
atomContents = []
|
||||
# print(repr(self.mapSpecified))
|
||||
if (flags & Atom.FLAG_INHERITED_PROPERTIES):
|
||||
for key, val in self.properties.items():
|
||||
atomContents += ['{0} = {1}'.format(key, val)]
|
||||
else:
|
||||
for i in range(len(self.mapSpecified)):
|
||||
key = self.mapSpecified[i]
|
||||
if key in self.properties:
|
||||
val = self.properties[key]
|
||||
atomContents += ['{0} = {1}'.format(key, val)]
|
||||
if len(atomContents) > 0:
|
||||
return self.path + '{' + '; '.join(atomContents) + '}'
|
||||
else:
|
||||
return self.path
|
||||
|
||||
def __str__(self):
|
||||
|
||||
atomContents = []
|
||||
for key, val in self.properties.items():
|
||||
atomContents += ['{0}={1}'.format(key, val)]
|
||||
return '{}{{{}}}'.format(self.path, ';'.join(atomContents))
|
||||
|
||||
def dumpPropInfo(self, name):
|
||||
o = '{0}: '.format(name)
|
||||
if name not in self.properties:
|
||||
@@ -391,7 +441,7 @@ class Proc(Atom):
|
||||
self.definition = False
|
||||
self.origpath = ''
|
||||
|
||||
def figureOutName(self,path):
|
||||
def figureOutName(self, path):
|
||||
name = path.split('(')[0]
|
||||
return name.split('/')[-1]
|
||||
|
||||
|
||||
@@ -0,0 +1,791 @@
|
||||
"""
|
||||
Map Interface Module
|
||||
|
||||
Copyright 2013 Rob "N3X15" Nelson <nexis@7chan.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
"""
|
||||
import os, itertools, sys, numpy, logging, hashlib
|
||||
from byond.map.format import GetMapFormat, Load as LoadMapFormats
|
||||
from byond.DMI import DMI
|
||||
from byond.directions import SOUTH, IMAGE_INDICES
|
||||
from byond.basetypes import Atom, BYONDString, BYONDValue, BYONDFileRef, BYOND2RGBA
|
||||
# from byond.objtree import ObjectTree
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
# Cache
|
||||
_icons = {}
|
||||
_dmis = {}
|
||||
|
||||
LoadMapFormats()
|
||||
|
||||
# From StackOverflow
|
||||
def trim(im):
|
||||
bg = Image.new(im.mode, im.size, im.getpixel((0, 0)))
|
||||
diff = ImageChops.difference(im, bg)
|
||||
diff = ImageChops.add(diff, diff, 2.0, -100)
|
||||
bbox = diff.getbbox()
|
||||
if bbox:
|
||||
return im.crop(bbox)
|
||||
|
||||
# Bytes
|
||||
def tint_image(image, tint_color):
|
||||
return ImageChops.multiply(image, Image.new('RGBA', image.size, tint_color))
|
||||
|
||||
class TileIterator:
|
||||
def __init__(self, _map):
|
||||
self.map = _map
|
||||
self.x = -1
|
||||
self.y = 0
|
||||
self.z = 0
|
||||
|
||||
self.max_z = len(self.map.zLevels)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return self.next()
|
||||
|
||||
def next(self):
|
||||
self.x += 1
|
||||
|
||||
zLev = self.map.zLevels[self.z]
|
||||
|
||||
if self.x >= zLev.width:
|
||||
self.y += 1
|
||||
self.x = 0
|
||||
|
||||
if self.y >= zLev.height:
|
||||
self.z += 1
|
||||
self.y = 0
|
||||
|
||||
if self.z >= self.max_z:
|
||||
raise StopIteration
|
||||
|
||||
t = self.map.GetTileAt(self.x, self.y, self.z)
|
||||
#print('{} = {}'.format((self.x,self.y,self.z),str(t)))
|
||||
return t
|
||||
|
||||
class Tile(object):
|
||||
def __init__(self, _map):
|
||||
self.origID = ''
|
||||
self.ID = 0
|
||||
self.instances = []
|
||||
self.frame = None
|
||||
self.unselected_frame = None
|
||||
self.areaSelected = True
|
||||
self.log = logging.getLogger(__name__ + '.Tile')
|
||||
self.map = _map
|
||||
self._hash = None
|
||||
|
||||
def UpdateHash(self):
|
||||
if self._hash is None:
|
||||
self._hash = hashlib.md5(str(self)).hexdigest()
|
||||
|
||||
def InvalidateHash(self):
|
||||
self._hash = None
|
||||
|
||||
def GetHash(self):
|
||||
self.UpdateHash()
|
||||
return self._hash
|
||||
|
||||
def RemoveAtom(self, atom, hash=True):
|
||||
'''
|
||||
:param Atom atom:
|
||||
Atom to remove. Raises ValueError if not found.
|
||||
'''
|
||||
self.instances.remove(atom)
|
||||
self.InvalidateHash()
|
||||
if hash: self.UpdateHash()
|
||||
|
||||
def AppendAtom(self, atom, hash=True):
|
||||
'''
|
||||
:param Atom atom:
|
||||
Atom to add.
|
||||
'''
|
||||
self.instances.append(atom)
|
||||
self.InvalidateHash()
|
||||
if hash: self.UpdateHash()
|
||||
|
||||
def CountAtom(self, atom):
|
||||
'''
|
||||
:param Atom atom:
|
||||
Atom to count.
|
||||
:return int: Count of atoms
|
||||
'''
|
||||
return self.instances.count(atom)
|
||||
|
||||
def copy(self, origID=False):
|
||||
tile = self.map.CreateTile()
|
||||
tile.instances = self.instances
|
||||
if origID:
|
||||
tile.origID = self.origID
|
||||
|
||||
tile.UpdateHash()
|
||||
return tile
|
||||
|
||||
def GetAtoms(self):
|
||||
atoms = []
|
||||
for atom in self.instances:
|
||||
if atom is None: continue
|
||||
atoms += [atom]
|
||||
return atoms
|
||||
|
||||
def SortAtoms(self):
|
||||
return sorted(self.GetAtoms(), reverse=True)
|
||||
|
||||
def GetAtom(self, idx):
|
||||
return self.map.getInstance(self.instances[idx])
|
||||
|
||||
def GetInstances(self):
|
||||
return self.instances
|
||||
|
||||
def __str__(self):
|
||||
return self._serialize()
|
||||
|
||||
def __ne__(self, tile):
|
||||
return not self.__eq__(tile)
|
||||
|
||||
def __eq__(self, other):
|
||||
return other and ((other._hash and self._hash and self._hash == other._hash) or (len(self.instances) == len(other.instances) and self.instances == other.instances))
|
||||
# else:
|
||||
# return all(self.instances[i] == other.instances[i] for i in xrange(len(self.instances)))
|
||||
|
||||
def _serialize(self):
|
||||
return ','.join([str(i) for i in self.GetInstances()])
|
||||
|
||||
def RenderToMapTile(self, passnum, basedir, renderflags, **kwargs):
|
||||
img = Image.new('RGBA', (96, 96))
|
||||
self.offset = (32, 32)
|
||||
foundAPixelOffset = False
|
||||
render_types = kwargs.get('render_types', ())
|
||||
skip_alpha = kwargs.get('skip_alpha', False)
|
||||
# for atom in sorted(self.GetAtoms(), reverse=True):
|
||||
for atom in self.SortAtoms():
|
||||
if len(render_types) > 0:
|
||||
found = False
|
||||
for path in render_types:
|
||||
if atom.path.startswith(path):
|
||||
found = True
|
||||
if not found:
|
||||
continue
|
||||
|
||||
aid = atom.id
|
||||
# Ignore /areas. They look like ass.
|
||||
if atom.path.startswith('/area'):
|
||||
if not (renderflags & MapRenderFlags.RENDER_AREAS):
|
||||
continue
|
||||
|
||||
# We're going to turn space black for smaller images.
|
||||
if atom.path == '/turf/space':
|
||||
if not (renderflags & MapRenderFlags.RENDER_STARS):
|
||||
continue
|
||||
|
||||
if 'icon' not in atom.properties:
|
||||
logging.critical('UNKNOWN ICON IN {0} (atom #{1})'.format(self.origID, aid))
|
||||
logging.info(atom.MapSerialize())
|
||||
logging.info(atom.MapSerialize(Atom.FLAG_INHERITED_PROPERTIES))
|
||||
continue
|
||||
|
||||
dmi_file = atom.properties['icon'].value
|
||||
|
||||
if 'icon_state' not in atom.properties:
|
||||
# Grab default icon_state ('') if we can't find the one defined.
|
||||
atom.properties['icon_state'] = BYONDString("")
|
||||
|
||||
state = atom.properties['icon_state'].value
|
||||
|
||||
direction = SOUTH
|
||||
if 'dir' in atom.properties:
|
||||
try:
|
||||
direction = int(atom.properties['dir'].value)
|
||||
except ValueError:
|
||||
logging.critical('FAILED TO READ dir = ' + repr(atom.properties['dir'].value))
|
||||
continue
|
||||
|
||||
icon_key = '{0}|{1}|{2}'.format(dmi_file, state, direction)
|
||||
frame = None
|
||||
pixel_x = 0
|
||||
pixel_y = 0
|
||||
if icon_key in _icons:
|
||||
frame, pixel_x, pixel_y = _icons[icon_key]
|
||||
else:
|
||||
dmi_path = os.path.join(basedir, dmi_file)
|
||||
dmi = None
|
||||
if dmi_path in _dmis:
|
||||
dmi = _dmis[dmi_path]
|
||||
else:
|
||||
try:
|
||||
dmi = DMI(dmi_path)
|
||||
dmi.loadAll()
|
||||
_dmis[dmi_path] = dmi
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
for prop in ['icon', 'icon_state', 'dir']:
|
||||
print('\t{0}'.format(atom.dumpPropInfo(prop)))
|
||||
pass
|
||||
if dmi.img is None:
|
||||
logging.warning('Unable to open {0}!'.format(dmi_path))
|
||||
continue
|
||||
|
||||
if dmi.img.mode not in ('RGBA', 'P'):
|
||||
logging.warn('{} is mode {}!'.format(dmi_file, dmi.img.mode))
|
||||
|
||||
if direction not in IMAGE_INDICES:
|
||||
logging.warn('Unrecognized direction {} on atom {} in tile {}!'.format(direction, atom.MapSerialize(), self.origID))
|
||||
direction = SOUTH # DreamMaker property editor shows dir = 2. WTF?
|
||||
|
||||
frame = dmi.getFrame(state, direction, 0)
|
||||
if frame == None:
|
||||
# Get the error/default state.
|
||||
frame = dmi.getFrame("", direction, 0)
|
||||
|
||||
if frame == None:
|
||||
continue
|
||||
|
||||
if frame.mode != 'RGBA':
|
||||
frame = frame.convert("RGBA")
|
||||
|
||||
pixel_x = 0
|
||||
if 'pixel_x' in atom.properties:
|
||||
pixel_x = int(atom.properties['pixel_x'].value)
|
||||
|
||||
pixel_y = 0
|
||||
if 'pixel_y' in atom.properties:
|
||||
pixel_y = int(atom.properties['pixel_y'].value)
|
||||
|
||||
_icons[icon_key] = (frame, pixel_x, pixel_y)
|
||||
|
||||
# Handle BYOND alpha and coloring
|
||||
c_frame = frame
|
||||
alpha = int(atom.getProperty('alpha', 255))
|
||||
if skip_alpha:
|
||||
alpha = 255
|
||||
color = atom.getProperty('color', '#FFFFFF')
|
||||
if alpha != 255 or color != '#FFFFFF':
|
||||
c_frame = tint_image(frame, BYOND2RGBA(color, alpha))
|
||||
img.paste(c_frame, (32 + pixel_x, 32 - pixel_y), c_frame) # Add to the top of the stack.
|
||||
if pixel_x != 0 or pixel_y != 0:
|
||||
if passnum == 0: return # Wait for next pass
|
||||
foundAPixelOffset = True
|
||||
|
||||
if passnum == 1 and not foundAPixelOffset:
|
||||
return None
|
||||
if not self.areaSelected:
|
||||
# Fade out unselected tiles.
|
||||
bands = list(img.split())
|
||||
# Excluding alpha band
|
||||
for i in range(3):
|
||||
bands[i] = bands[i].point(lambda x: x * 0.4)
|
||||
img = Image.merge(img.mode, bands)
|
||||
|
||||
return img
|
||||
|
||||
vTile = numpy.vectorize(Tile)
|
||||
|
||||
class MapLayer:
|
||||
def __init__(self, _map, height=255, width=255):
|
||||
self.map = _map
|
||||
self.min = (1, 1)
|
||||
self.max = (height, width)
|
||||
self.height = height
|
||||
self.width = width
|
||||
self.tiles = numpy.empty((height, width), object)
|
||||
|
||||
self.tiles[:, :] = vTile(self)
|
||||
|
||||
def Resize(self,height,width):
|
||||
self.height=height
|
||||
self.width=width
|
||||
self.tiles.resize(height,width)
|
||||
|
||||
class MapRenderFlags:
|
||||
RENDER_STARS = 1
|
||||
RENDER_AREAS = 2
|
||||
|
||||
class Map:
|
||||
def __init__(self, tree=None, **kwargs):
|
||||
self.zLevels = []
|
||||
self.DMIs = {}
|
||||
self.tree = tree
|
||||
self.generatedTexAtlas = False
|
||||
self.selectedAreas = ()
|
||||
self.whitelistTypes = None
|
||||
self.forgiving_atom_lookups = kwargs.get('forgiving_atom_lookups', False)
|
||||
|
||||
self.log = logging.getLogger(__name__ + '.Map')
|
||||
|
||||
def CreateZLevel(self, height, width, z= -1):
|
||||
zLevel = MapLayer(self, height, width)
|
||||
if z >= 0:
|
||||
self.zLevels[z] = zLevel
|
||||
else:
|
||||
self.zLevels.append(zLevel)
|
||||
return zLevel
|
||||
|
||||
def Tiles(self):
|
||||
return TileIterator(self)
|
||||
|
||||
def Load(self, filename, **kwargs):
|
||||
_, ext = os.path.splitext(filename)
|
||||
fmt=kwargs.get('format','dmm2' if ext=='dmm2' else 'dmm')
|
||||
reader = GetMapFormat(self, fmt)
|
||||
reader.Load(filename, **kwargs)
|
||||
|
||||
def Save(self, filename, **kwargs):
|
||||
_, ext = os.path.splitext(filename)
|
||||
fmt=kwargs.get('format','dmm2' if ext=='dmm2' else 'dmm')
|
||||
reader = GetMapFormat(self, kwargs.get('format',fmt))
|
||||
reader.Save(filename, **kwargs)
|
||||
|
||||
def writeMap2(self, filename, flags=0):
|
||||
self.filename = filename
|
||||
tileFlags = 0
|
||||
atomFlags = 0
|
||||
if flags & Map.WRITE_OLD_IDS:
|
||||
tileFlags |= Tile.FLAG_USE_OLD_ID
|
||||
atomFlags |= Atom.FLAG_USE_OLD_ID
|
||||
padding = len(self.tileTypes[-1].ID2String())
|
||||
with open(filename, 'w') as f:
|
||||
f.write('// Atom Instances\n')
|
||||
for atom in self.instances:
|
||||
f.write('{0} = {1}\n'.format(atom.id, atom.MapSerialize(atomFlags)))
|
||||
f.write('// Tiles\n')
|
||||
for tile in self.tileTypes:
|
||||
f.write('{0}\n'.format(tile.MapSerialize2(tileFlags, padding)))
|
||||
f.write('// Layout\n')
|
||||
for z in self.zLevels.keys():
|
||||
f.write('\n(1,1,{0}) = {{"\n'.format(z))
|
||||
zlevel = self.zLevels[z]
|
||||
for y in xrange(zlevel.height):
|
||||
for x in xrange(zlevel.width):
|
||||
tile = zlevel.GetTileAt(x, y)
|
||||
if flags & Map.WRITE_OLD_IDS:
|
||||
f.write(tile.origID)
|
||||
else:
|
||||
f.write(tile.ID2String(padding))
|
||||
f.write("\n")
|
||||
f.write('"}\n')
|
||||
|
||||
def GetTileAt(self, x, y, z):
|
||||
'''
|
||||
:param int x:
|
||||
:param int y:
|
||||
:param int z:
|
||||
:rtype Tile:
|
||||
'''
|
||||
if z < len(self.zLevels):
|
||||
return self.zLevels[z].tiles[x, y]
|
||||
|
||||
def CopyTileAt(self, x, y, z):
|
||||
'''
|
||||
:param int x:
|
||||
:param int y:
|
||||
:param int z:
|
||||
:rtype Tile:
|
||||
'''
|
||||
if z < len(self.zLevels):
|
||||
return self.zLevels[z].tiles[x, y].copy()
|
||||
|
||||
def SetTileAt(self, x, y, z, tile):
|
||||
'''
|
||||
:param int x:
|
||||
:param int y:
|
||||
:param int z:
|
||||
'''
|
||||
if z < len(self.zLevels):
|
||||
self.zLevels[z].tiles[x, y] = tile
|
||||
|
||||
def CreateTile(self):
|
||||
'''
|
||||
:rtype Tile:
|
||||
'''
|
||||
return Tile(self)
|
||||
|
||||
def generateTexAtlas(self, basedir, renderflags=0):
|
||||
if self.generatedTexAtlas:
|
||||
return
|
||||
print('--- Generating texture atlas...')
|
||||
self._icons = {}
|
||||
self._dmis = {}
|
||||
self.generatedTexAtlas = True
|
||||
for tid in xrange(len(self.tileTypes)):
|
||||
tile = self.tileTypes[tid]
|
||||
img = Image.new('RGBA', (96, 96))
|
||||
tile.offset = (32, 32)
|
||||
tile.areaSelected = True
|
||||
tile.render_deferred = False
|
||||
for atom in sorted(tile.GetAtoms(), reverse=True):
|
||||
|
||||
aid = atom.id
|
||||
# Ignore /areas. They look like ass.
|
||||
if atom.path.startswith('/area'):
|
||||
if not (renderflags & MapRenderFlags.RENDER_AREAS):
|
||||
continue
|
||||
|
||||
# We're going to turn space black for smaller images.
|
||||
if atom.path == '/turf/space':
|
||||
if not (renderflags & MapRenderFlags.RENDER_STARS):
|
||||
continue
|
||||
|
||||
if 'icon' not in atom.properties:
|
||||
print('CRITICAL: UNKNOWN ICON IN {0} (atom #{1})'.format(tile.origID, aid))
|
||||
print(atom.MapSerialize())
|
||||
print(atom.MapSerialize(Atom.FLAG_INHERITED_PROPERTIES))
|
||||
continue
|
||||
|
||||
dmi_file = atom.properties['icon'].value
|
||||
|
||||
if 'icon_state' not in atom.properties:
|
||||
# Grab default icon_state ('') if we can't find the one defined.
|
||||
atom.properties['icon_state'] = BYONDString("")
|
||||
|
||||
state = atom.properties['icon_state'].value
|
||||
|
||||
direction = SOUTH
|
||||
if 'dir' in atom.properties:
|
||||
try:
|
||||
direction = int(atom.properties['dir'].value)
|
||||
except ValueError:
|
||||
print('FAILED TO READ dir = ' + repr(atom.properties['dir'].value))
|
||||
continue
|
||||
|
||||
icon_key = '{0}:{1}[{2}]'.format(dmi_file, state, direction)
|
||||
frame = None
|
||||
pixel_x = 0
|
||||
pixel_y = 0
|
||||
if icon_key in self._icons:
|
||||
frame, pixel_x, pixel_y = self._icons[icon_key]
|
||||
else:
|
||||
dmi_path = os.path.join(basedir, dmi_file)
|
||||
dmi = None
|
||||
if dmi_path in self._dmis:
|
||||
dmi = self._dmis[dmi_path]
|
||||
else:
|
||||
try:
|
||||
dmi = self.loadDMI(dmi_path)
|
||||
self._dmis[dmi_path] = dmi
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
for prop in ['icon', 'icon_state', 'dir']:
|
||||
print('\t{0}'.format(atom.dumpPropInfo(prop)))
|
||||
pass
|
||||
|
||||
if dmi.img is None:
|
||||
self.log.warn('Unable to open {0}!'.format(dmi_path))
|
||||
continue
|
||||
|
||||
if dmi.img.mode not in ('RGBA', 'P'):
|
||||
self.log.warn('{} is mode {}!'.format(dmi_file, dmi.img.mode))
|
||||
|
||||
if direction not in IMAGE_INDICES:
|
||||
self.log.warn('Unrecognized direction {} on atom {} in tile {}!'.format(direction, atom.MapSerialize(), tile.origID))
|
||||
direction = SOUTH # DreamMaker property editor shows dir = 2. WTF?
|
||||
|
||||
frame = dmi.getFrame(state, direction, 0)
|
||||
if frame == None:
|
||||
# Get the error/default state.
|
||||
frame = dmi.getFrame("", direction, 0)
|
||||
|
||||
if frame == None:
|
||||
continue
|
||||
|
||||
if frame.mode != 'RGBA':
|
||||
frame = frame.convert("RGBA")
|
||||
|
||||
pixel_x = 0
|
||||
if 'pixel_x' in atom.properties:
|
||||
pixel_x = int(atom.properties['pixel_x'].value)
|
||||
|
||||
pixel_y = 0
|
||||
if 'pixel_y' in atom.properties:
|
||||
pixel_y = int(atom.properties['pixel_y'].value)
|
||||
|
||||
self._icons[icon_key] = (frame, pixel_x, pixel_y)
|
||||
img.paste(frame, (32 + pixel_x, 32 - pixel_y), frame) # Add to the top of the stack.
|
||||
if pixel_x != 0 or pixel_y != 0:
|
||||
tile.render_deferred = True
|
||||
tile.frame = img
|
||||
|
||||
# Fade out unselected tiles.
|
||||
bands = list(img.split())
|
||||
# Excluding alpha band
|
||||
for i in range(3):
|
||||
bands[i] = bands[i].point(lambda x: x * 0.4)
|
||||
tile.unselected_frame = Image.merge(img.mode, bands)
|
||||
|
||||
self.tileTypes[tid] = tile
|
||||
|
||||
def renderAtom(self, atom, basedir, skip_alpha=False):
|
||||
if 'icon' not in atom.properties:
|
||||
logging.critical('UNKNOWN ICON IN ATOM #{0} ({1})'.format(atom.id, atom.path))
|
||||
logging.info(atom.MapSerialize())
|
||||
logging.info(atom.MapSerialize(Atom.FLAG_INHERITED_PROPERTIES))
|
||||
return None
|
||||
# else:
|
||||
# logging.info('Icon found for #{}.'.format(atom.id))
|
||||
|
||||
dmi_file = atom.properties['icon'].value
|
||||
|
||||
if dmi_file is None:
|
||||
return None
|
||||
|
||||
# Grab default icon_state ('') if we can't find the one defined.
|
||||
state = atom.getProperty('icon_state', '')
|
||||
|
||||
direction = SOUTH
|
||||
if 'dir' in atom.properties:
|
||||
try:
|
||||
direction = int(atom.properties['dir'].value)
|
||||
except ValueError:
|
||||
logging.critical('FAILED TO READ dir = ' + repr(atom.properties['dir'].value))
|
||||
return None
|
||||
|
||||
icon_key = '{0}|{1}|{2}'.format(dmi_file, state, direction)
|
||||
frame = None
|
||||
pixel_x = 0
|
||||
pixel_y = 0
|
||||
if icon_key in _icons:
|
||||
frame, pixel_x, pixel_y = _icons[icon_key]
|
||||
else:
|
||||
dmi_path = os.path.join(basedir, dmi_file)
|
||||
dmi = None
|
||||
if dmi_path in _dmis:
|
||||
dmi = _dmis[dmi_path]
|
||||
else:
|
||||
try:
|
||||
dmi = DMI(dmi_path)
|
||||
dmi.loadAll()
|
||||
_dmis[dmi_path] = dmi
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
for prop in ['icon', 'icon_state', 'dir']:
|
||||
print('\t{0}'.format(atom.dumpPropInfo(prop)))
|
||||
pass
|
||||
if dmi.img is None:
|
||||
logging.warning('Unable to open {0}!'.format(dmi_path))
|
||||
return None
|
||||
|
||||
if dmi.img.mode not in ('RGBA', 'P'):
|
||||
logging.warn('{} is mode {}!'.format(dmi_file, dmi.img.mode))
|
||||
|
||||
if direction not in IMAGE_INDICES:
|
||||
logging.warn('Unrecognized direction {} on atom {}!'.format(direction, atom.MapSerialize()))
|
||||
direction = SOUTH # DreamMaker property editor shows dir = 2. WTF?
|
||||
|
||||
frame = dmi.getFrame(state, direction, 0)
|
||||
if frame == None:
|
||||
# Get the error/default state.
|
||||
frame = dmi.getFrame("", direction, 0)
|
||||
|
||||
if frame == None:
|
||||
return None
|
||||
|
||||
if frame.mode != 'RGBA':
|
||||
frame = frame.convert("RGBA")
|
||||
|
||||
pixel_x = 0
|
||||
if 'pixel_x' in atom.properties:
|
||||
pixel_x = int(atom.properties['pixel_x'].value)
|
||||
|
||||
pixel_y = 0
|
||||
if 'pixel_y' in atom.properties:
|
||||
pixel_y = int(atom.properties['pixel_y'].value)
|
||||
|
||||
_icons[icon_key] = (frame, pixel_x, pixel_y)
|
||||
|
||||
# Handle BYOND alpha and coloring
|
||||
c_frame = frame
|
||||
alpha = int(atom.getProperty('alpha', 255))
|
||||
if skip_alpha:
|
||||
alpha = 255
|
||||
color = atom.getProperty('color', '#FFFFFF')
|
||||
if alpha != 255 or color != '#FFFFFF':
|
||||
c_frame = tint_image(frame, BYOND2RGBA(color, alpha))
|
||||
return c_frame
|
||||
|
||||
def generateImage(self, filename_tpl, basedir='.', renderflags=0, z=None, **kwargs):
|
||||
'''
|
||||
Instead of generating on a tile-by-tile basis, this creates a large canvas and places
|
||||
each atom on it after sorting layers. This resolves the pixel_(x,y) problem.
|
||||
'''
|
||||
if z is None:
|
||||
for z in self.zLevels.keys():
|
||||
self.generateImage(filename_tpl, basedir, renderflags, z, **kwargs)
|
||||
return
|
||||
self.selectedAreas = ()
|
||||
skip_alpha = False
|
||||
render_types = ()
|
||||
if 'area' in kwargs:
|
||||
self.selectedAreas = kwargs['area']
|
||||
if 'render_types' in kwargs:
|
||||
render_types = kwargs['render_types']
|
||||
if 'skip_alpha' in kwargs:
|
||||
skip_alpha = kwargs['skip_alpha']
|
||||
|
||||
print('Checking z-level {0}...'.format(z))
|
||||
instancePositions = {}
|
||||
for y in range(self.zLevels[z].height):
|
||||
for x in range(self.zLevels[z].width):
|
||||
t = self.zLevels[z].GetTileAt(x, y)
|
||||
# print('*** {},{}'.format(x,y))
|
||||
if t is None:
|
||||
continue
|
||||
if len(self.selectedAreas) > 0:
|
||||
renderThis = True
|
||||
for atom in t.GetAtoms():
|
||||
if atom.path.startswith('/area'):
|
||||
if atom.path not in self.selectedAreas:
|
||||
renderThis = False
|
||||
if not renderThis: continue
|
||||
for atom in t.GetAtoms():
|
||||
if atom is None: continue
|
||||
iid = atom.id
|
||||
if atom.path.startswith('/area'):
|
||||
if atom.path not in self.selectedAreas:
|
||||
continue
|
||||
|
||||
# Check for render restrictions
|
||||
if len(render_types) > 0:
|
||||
found = False
|
||||
for path in render_types:
|
||||
if atom.path.startswith(path):
|
||||
found = True
|
||||
if not found:
|
||||
continue
|
||||
|
||||
# Ignore /areas. They look like ass.
|
||||
if atom.path.startswith('/area'):
|
||||
if not (renderflags & MapRenderFlags.RENDER_AREAS):
|
||||
continue
|
||||
|
||||
# We're going to turn space black for smaller images.
|
||||
if atom.path == '/turf/space':
|
||||
if not (renderflags & MapRenderFlags.RENDER_STARS):
|
||||
continue
|
||||
|
||||
if iid not in instancePositions:
|
||||
instancePositions[iid] = []
|
||||
|
||||
# pixel offsets
|
||||
'''
|
||||
pixel_x = int(atom.getProperty('pixel_x', 0))
|
||||
pixel_y = int(atom.getProperty('pixel_y', 0))
|
||||
t_o_x = int(round(pixel_x / 32))
|
||||
t_o_y = int(round(pixel_y / 32))
|
||||
pos = (x + t_o_x, y + t_o_y)
|
||||
'''
|
||||
pos = (x, y)
|
||||
|
||||
instancePositions[iid].append(pos)
|
||||
|
||||
if len(instancePositions) == 0:
|
||||
return
|
||||
|
||||
levelAtoms = []
|
||||
for iid in instancePositions:
|
||||
levelAtoms += [self.getInstance(iid)]
|
||||
|
||||
pic = Image.new('RGBA', ((self.zLevels[z].width + 2) * 32, (self.zLevels[z].height + 2) * 32), "black")
|
||||
|
||||
# Bounding box, used for cropping.
|
||||
bbox = [99999, 99999, 0, 0]
|
||||
|
||||
# Replace {z} with current z-level.
|
||||
filename = filename_tpl.replace('{z}', str(z))
|
||||
|
||||
pastes = 0
|
||||
for atom in sorted(levelAtoms, reverse=True):
|
||||
if atom.id not in instancePositions:
|
||||
continue
|
||||
icon = self.renderAtom(atom, basedir, skip_alpha)
|
||||
if icon is None:
|
||||
continue
|
||||
for x, y in instancePositions[atom.id]:
|
||||
new_bb = self.getBBoxForAtom(x, y, atom, icon)
|
||||
# print('{0},{1} = {2}'.format(x, y, new_bb))
|
||||
# Adjust cropping bounds
|
||||
if new_bb[0] < bbox[0]:
|
||||
bbox[0] = new_bb[0]
|
||||
if new_bb[1] < bbox[1]:
|
||||
bbox[1] = new_bb[1]
|
||||
if new_bb[2] > bbox[2]:
|
||||
bbox[2] = new_bb[2]
|
||||
if new_bb[3] > bbox[3]:
|
||||
bbox[3] = new_bb[3]
|
||||
pic.paste(icon, new_bb, icon)
|
||||
pastes += 1
|
||||
|
||||
if len(self.selectedAreas) == 0:
|
||||
# Autocrop (only works if NOT rendering stars or areas)
|
||||
pic = trim(pic)
|
||||
else:
|
||||
# if nSelAreas == 0:
|
||||
# continue
|
||||
pic = pic.crop(bbox)
|
||||
|
||||
if pic is not None:
|
||||
# Saev
|
||||
filedir = os.path.dirname(filename)
|
||||
if not os.path.isdir(filedir):
|
||||
os.makedirs(filedir)
|
||||
print(' -> {} ({}x{}) - {} objects'.format(filename, pic.size[0], pic.size[1], pastes))
|
||||
pic.save(filename, 'PNG')
|
||||
|
||||
def getBBoxForAtom(self, x, y, atom, icon):
|
||||
icon_width, icon_height = icon.size
|
||||
pixel_x = int(atom.getProperty('pixel_x', 0))
|
||||
pixel_y = int(atom.getProperty('pixel_y', 0))
|
||||
|
||||
return self.tilePosToBBox(x, y, pixel_x, pixel_y, icon_height, icon_width)
|
||||
|
||||
def tilePosToBBox(self, tile_x, tile_y, pixel_x, pixel_y, icon_height, icon_width):
|
||||
# Tile Pos
|
||||
X = tile_x * 32
|
||||
Y = tile_y * 32
|
||||
|
||||
# pixel offsets
|
||||
X += pixel_x
|
||||
Y -= pixel_y
|
||||
|
||||
# BYOND coordinates -> PIL coords.
|
||||
# BYOND uses LOWER left.
|
||||
# PIL uses UPPER left
|
||||
X += 0
|
||||
Y += 32 - icon_height
|
||||
|
||||
return (
|
||||
X,
|
||||
Y,
|
||||
X + icon_width,
|
||||
Y + icon_height
|
||||
)
|
||||
|
||||
# So we can read a map without parsing the tree.
|
||||
def GetAtom(self, path):
|
||||
if self.tree is not None:
|
||||
atom = self.tree.GetAtom(path)
|
||||
if atom is None and self.forgiving_atom_lookups:
|
||||
self.missing_atoms.add(path)
|
||||
return Atom(path, self.filename, missing=True)
|
||||
return atom
|
||||
return Atom(path)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import glob, os
|
||||
from .base import *
|
||||
|
||||
def Load():
|
||||
print('Loading Map Format Modules...')
|
||||
for f in glob.glob(os.path.dirname(__file__) + "/*.py"):
|
||||
modName = 'byond.map.format.' + os.path.basename(f)[:-3]
|
||||
print(' Loading module ' + modName)
|
||||
mod = __import__(modName)
|
||||
for attr in dir(mod):
|
||||
if not attr.startswith('_'):
|
||||
#print(' {} = {}'.format(attr,getattr(mod, attr)))
|
||||
globals()[attr] = getattr(mod, attr)
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
# Decorator
|
||||
class MapFormat(object):
|
||||
all = {}
|
||||
def __init__(self, ext, _id=None):
|
||||
self.id = _id
|
||||
self.extension = ext
|
||||
def __call__(self, c):
|
||||
if self.id is None:
|
||||
fname_p = c.__name__
|
||||
self.id = fname_p
|
||||
print('Adding Map Format "{}" (.{})...'.format(self.id,self.extension))
|
||||
if self.extension in MapFormat.all:
|
||||
print('!!! *.{} FILES ARE ALREADY HANDLED BY {}!'.format(self.extension,self.id))
|
||||
MapFormat.all[self.extension] = c
|
||||
return c
|
||||
|
||||
def GetMapFormat(_map,ext):
|
||||
f = MapFormat.all.get(ext.lstrip('.'),None)
|
||||
if f is None:
|
||||
print('Unable to find MapFormat for {}.'.format(ext))
|
||||
return f(_map)
|
||||
|
||||
class BaseMapFormat:
|
||||
def __init__(self, _map):
|
||||
self.map = _map
|
||||
self.missing_atoms = set()
|
||||
|
||||
def Load(self, filename, **kwargs):
|
||||
return
|
||||
|
||||
def Save(self, filename, **kwargs):
|
||||
return
|
||||
@@ -0,0 +1,490 @@
|
||||
from byond.basetypes import *
|
||||
from byond.utils import getElapsed
|
||||
# from byond.map import Tile, MapLayer
|
||||
from byond.map.format.base import BaseMapFormat, MapFormat
|
||||
import os, sys, logging, itertools, shutil, collections, math
|
||||
from time import clock
|
||||
|
||||
ID_ENCODING_TABLE = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
IET_SIZE = len(ID_ENCODING_TABLE)
|
||||
|
||||
def chunker(iterable, chunksize):
|
||||
"""
|
||||
Return elements from the iterable in `chunksize`-ed lists. The last returned
|
||||
chunk may be smaller (if length of collection is not divisible by `chunksize`).
|
||||
|
||||
>>> print list(chunker(xrange(10), 3))
|
||||
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
|
||||
"""
|
||||
i = iter(iterable)
|
||||
while True:
|
||||
wrapped_chunk = [list(itertools.islice(i, int(chunksize)))]
|
||||
if not wrapped_chunk[0]:
|
||||
break
|
||||
yield wrapped_chunk.pop()
|
||||
|
||||
def DMMSortAlg(v):
|
||||
t = (v.upper(),)
|
||||
l = [c.isupper() for c in v]
|
||||
l.reverse()
|
||||
return t + tuple(l)
|
||||
|
||||
@MapFormat('dmm')
|
||||
class DMMFormat(BaseMapFormat):
|
||||
def __init__(self, map):
|
||||
BaseMapFormat.__init__(self, map)
|
||||
|
||||
self.tileTypes = []
|
||||
self.instances = []
|
||||
self.oldID2NewID = {}
|
||||
self.log = logging.getLogger('DMMFormat')
|
||||
|
||||
self.filename = 'BUILT-IN?'
|
||||
|
||||
# Number of duplicates found when loading.
|
||||
self.duplicates = 0
|
||||
|
||||
# Caches
|
||||
self.tileChunk2ID = {}
|
||||
self.atomCache = {}
|
||||
|
||||
self.atomBorders = {
|
||||
'{':'}',
|
||||
'"':'"',
|
||||
'(':')'
|
||||
}
|
||||
nit = self.atomBorders.copy()
|
||||
for start, stop in self.atomBorders.items():
|
||||
if start != stop:
|
||||
nit[stop] = None
|
||||
self.atomBorders = nit
|
||||
self.idlen = 0
|
||||
|
||||
self.dump_inherited = False
|
||||
|
||||
def Load(self, filename, **kwargs):
|
||||
if not os.path.isfile(filename):
|
||||
self.log.warn('File ' + filename + " does not exist.")
|
||||
self.filename = filename
|
||||
with open(filename, 'r') as f:
|
||||
print('Reading tile types from {0}...'.format(self.filename))
|
||||
self.consumeTiles(f)
|
||||
print('Reading tile positions...')
|
||||
self.consumeTileMap(f)
|
||||
|
||||
def consumeDataValue(self, value, lineNumber):
|
||||
data = None
|
||||
if value[0] in ('"', "'"):
|
||||
quote = value[0]
|
||||
if quote == '"':
|
||||
data = BYONDString(value[1:-1], self.filename, lineNumber)
|
||||
elif quote == "'":
|
||||
data = BYONDFileRef(value[1:-1], self.filename, lineNumber)
|
||||
elif value == 'null':
|
||||
data = BYONDValue(None, self.filename, lineNumber)
|
||||
else:
|
||||
data = BYONDValue(value, self.filename, lineNumber)
|
||||
return data
|
||||
def consumeTileMap(self, f):
|
||||
zLevel = None
|
||||
y = 0
|
||||
z = 0
|
||||
inZLevel = False
|
||||
width = 0
|
||||
height = 0
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line == '':
|
||||
return
|
||||
# (1,1,1) = {"
|
||||
if line.startswith('('):
|
||||
coordChunk = line[1:line.index(')')].split(',')
|
||||
# print(repr(coordChunk))
|
||||
z = int(coordChunk[2])
|
||||
inZLevel = True
|
||||
y = 0
|
||||
width = 0
|
||||
height = 0
|
||||
continue
|
||||
if line.strip() == '"}':
|
||||
inZLevel = False
|
||||
if height == 0:
|
||||
height = y
|
||||
# self.map.zLevels[z] = zLevel
|
||||
zLevel = None
|
||||
# self.log.info('Added map layer {0} ({1}x{2})'.format(z, height, width))
|
||||
print(' * Added map layer {0} ({1}x{2})'.format(z, height, width))
|
||||
continue
|
||||
if inZLevel:
|
||||
if zLevel is None:
|
||||
zLevel = self.map.CreateZLevel(height, width) # , z-1)
|
||||
if width == 0:
|
||||
width = len(line) / self.idlen
|
||||
# print('Width detected as {}.'.format(width))
|
||||
zLevel.Resize(width, width)
|
||||
if width > 255:
|
||||
logging.warn("Line is {} blocks long!".format(width))
|
||||
x = 0
|
||||
for chunk in chunker(line.strip(), self.idlen):
|
||||
chunk = self.String2ID(''.join(chunk))
|
||||
tid = self.oldID2NewID[chunk]
|
||||
zLevel.tiles[x, y] = self.tileTypes[tid].copy(origID=True)
|
||||
x += 1
|
||||
y += 1
|
||||
|
||||
def consumeTiles(self, f):
|
||||
index = 0
|
||||
self.duplicates = 0
|
||||
lineNumber = 0
|
||||
self.tileChunk2ID = {}
|
||||
while True:
|
||||
line = f.readline()
|
||||
lineNumber += 1
|
||||
if line.startswith('"'):
|
||||
t = self.consumeTile(line, lineNumber)
|
||||
t.ID = index
|
||||
t.UpdateHash()
|
||||
self.tileTypes += [t]
|
||||
self.idlen = max(self.idlen, len(self.ID2String(t.ID)))
|
||||
self.oldID2NewID[t.origID] = t.ID
|
||||
index += 1
|
||||
# No longer needed, 2fast.
|
||||
# if((index % 100) == 0):
|
||||
# print(index)
|
||||
else:
|
||||
self.log.info('-- {} tiles loaded, {} duplicates discarded'.format(index, self.duplicates))
|
||||
return
|
||||
|
||||
def consumeTileAtoms(self, line, lineNumber):
|
||||
instances = []
|
||||
atom_chunks = self.SplitAtoms(line)
|
||||
|
||||
for atom_chunk in atom_chunks:
|
||||
if atom_chunk in self.atomCache:
|
||||
instances += [self.atomCache[atom_chunk]]
|
||||
else:
|
||||
atom = self.consumeAtom(atom_chunk, lineNumber)
|
||||
self.atomCache[atom_chunk] = atom
|
||||
instances += [atom]
|
||||
|
||||
return instances
|
||||
|
||||
def SplitProperties(self, string):
|
||||
o = []
|
||||
buf = []
|
||||
inString = False
|
||||
for chunk in string.split(';'):
|
||||
if not inString:
|
||||
if '"' in chunk:
|
||||
inString = False
|
||||
pos = 0
|
||||
while(True):
|
||||
pos = chunk.find('"', pos)
|
||||
if pos == -1:
|
||||
break
|
||||
pc = ''
|
||||
if pos > 0:
|
||||
pc = chunk[pos - 1]
|
||||
if pc != '\\':
|
||||
inString = not inString
|
||||
pos += 1
|
||||
if not inString:
|
||||
o += [chunk]
|
||||
else:
|
||||
buf += [chunk]
|
||||
else:
|
||||
o += [chunk]
|
||||
else:
|
||||
if '"' in chunk:
|
||||
o += [';'.join(buf + [chunk])]
|
||||
inString = False
|
||||
buf = []
|
||||
else:
|
||||
buf += [chunk]
|
||||
return o
|
||||
|
||||
def SplitAtoms(self, string):
|
||||
ignoreLevel = []
|
||||
|
||||
o = []
|
||||
buf = ''
|
||||
|
||||
string = string.rstrip()
|
||||
line_len = len(string)
|
||||
for i in xrange(line_len):
|
||||
c = string[i]
|
||||
pc = ''
|
||||
if i > 0:
|
||||
pc = string[i - 1]
|
||||
|
||||
if c in self.atomBorders and pc != '\\':
|
||||
end = self.atomBorders[c]
|
||||
if end == c: # Just toggle.
|
||||
if len(ignoreLevel) > 0:
|
||||
if ignoreLevel[-1] == c:
|
||||
ignoreLevel.pop()
|
||||
else:
|
||||
ignoreLevel.append(c)
|
||||
else:
|
||||
if end == None:
|
||||
if len(ignoreLevel) > 0:
|
||||
if ignoreLevel[-1] == c:
|
||||
ignoreLevel.pop()
|
||||
else:
|
||||
ignoreLevel.append(end)
|
||||
if c == ',' and len(ignoreLevel) == 0:
|
||||
o += [buf]
|
||||
buf = ''
|
||||
else:
|
||||
buf += c
|
||||
|
||||
if len(ignoreLevel) > 0:
|
||||
print(repr(ignoreLevel))
|
||||
sys.exit()
|
||||
return o + [buf]
|
||||
|
||||
def consumeAtom(self, line, lineNumber=0):
|
||||
if '{' not in line:
|
||||
atom = line.strip()
|
||||
if atom.endswith('/'):
|
||||
self.log.warn('{file}:{line}: Malformed atom: {data} has ending slash. Stripping slashes from right side.'.format(file=self.filename, line=lineNumber, data=atom))
|
||||
atom = atom.rstrip('/')
|
||||
currentAtom = self.map.GetAtom(atom)
|
||||
if currentAtom is not None:
|
||||
return currentAtom
|
||||
else:
|
||||
self.log.critical('{file}:{line}: Failed to consumeAtom({data}): Unable to locate atom.'.format(file=self.filename, line=lineNumber, data=line))
|
||||
return None
|
||||
chunks = line.split('{')
|
||||
if len(chunks) < 2:
|
||||
self.log.error('{file}:{line}: Something went wrong in consumeAtom(). line={data}'.format(file=self.filename, line=lineNumber, data=line))
|
||||
atom = chunks[0].strip()
|
||||
if atom.endswith('/'):
|
||||
self.log.warn('{file}:{line}: Malformed atom: {data} has ending slash. Stripping slashes from right side.'.format(file=self.filename, line=lineNumber, data=atom))
|
||||
atom = atom.rstrip('/')
|
||||
currentAtom = self.map.GetAtom(atom)
|
||||
if currentAtom is not None:
|
||||
currentAtom = currentAtom.copy()
|
||||
else:
|
||||
return None
|
||||
if chunks[1].endswith('}'):
|
||||
chunks[1] = chunks[1][:-1]
|
||||
property_chunks = self.SplitProperties(chunks[1])
|
||||
mapSupplied = []
|
||||
for chunk in property_chunks:
|
||||
if chunk.endswith('}'):
|
||||
chunk = chunk[:-1]
|
||||
pparts = chunk.split('=', 1)
|
||||
key = pparts[0].strip()
|
||||
value = pparts[1].strip()
|
||||
if key == '':
|
||||
self.log.warn('Ignoring property with blank name. (given {0})'.format(chunk))
|
||||
continue
|
||||
data = self.consumeDataValue(value, lineNumber)
|
||||
if key not in currentAtom.mapSpecified:
|
||||
mapSupplied += [key]
|
||||
currentAtom.properties[key] = data
|
||||
|
||||
currentAtom.mapSpecified = mapSupplied
|
||||
|
||||
# Compare to base
|
||||
if True: # TODO: not (self.readFlags & Map.READ_NO_BASE_COMP):
|
||||
base_atom = self.map.GetAtom(currentAtom.path)
|
||||
assert base_atom != None
|
||||
# for key in base_atom.properties.keys():
|
||||
# val = base_atom.properties[key]
|
||||
# if key not in currentAtom.properties:
|
||||
# currentAtom.properties[key] = val
|
||||
for key in currentAtom.properties.iterkeys():
|
||||
val = currentAtom.properties[key].value
|
||||
if key in base_atom.properties and val == base_atom.properties[key].value:
|
||||
if key in currentAtom.mapSpecified:
|
||||
currentAtom.mapSpecified.remove(key)
|
||||
# print('Removed {0} from atom: Equivalent to base atom!')
|
||||
|
||||
return currentAtom
|
||||
|
||||
def consumeTile(self, line, lineNumber):
|
||||
t = self.map.CreateTile()
|
||||
t.origID = self.consumeTileID(line)
|
||||
tileChunk = line.strip()[line.index('(') + 1:-1]
|
||||
|
||||
if tileChunk in self.tileChunk2ID:
|
||||
tid = self.tileChunk2ID[tileChunk]
|
||||
print('{} duplicate of {}! Installing redirect...'.format(t.origID, tid))
|
||||
self.oldID2NewID[t.origID] = tid
|
||||
self.duplicates += 1
|
||||
return self.tileTypes[tid]
|
||||
self.tileChunk2ID[tileChunk]=t.origID
|
||||
t.instances = self.consumeTileAtoms(tileChunk, lineNumber)
|
||||
if t.origID == 0:
|
||||
print('{} -> {}'.format(t.origID,tileChunk))
|
||||
print('AKA {}'.format(str(t)))
|
||||
return t
|
||||
|
||||
def consumeTileID(self, line):
|
||||
e = line.index('"', 1)
|
||||
return self.String2ID(line[1:e])
|
||||
|
||||
def SerializeAtom(self, atom):
|
||||
atomContents = []
|
||||
# print(repr(self.mapSpecified))
|
||||
if self.dump_inherited:
|
||||
for key, val in atom.properties.items():
|
||||
atomContents += ['{0} = {1}'.format(key, val)]
|
||||
else:
|
||||
for i in range(len(atom.mapSpecified)):
|
||||
key = atom.mapSpecified[i]
|
||||
if key in atom.properties:
|
||||
val = atom.properties[key]
|
||||
atomContents += ['{0} = {1}'.format(key, val)]
|
||||
if len(atomContents) > 0:
|
||||
return atom.path + '{' + '; '.join(atomContents) + '}'
|
||||
else:
|
||||
return atom.path
|
||||
|
||||
def SerializeTile(self, tile):
|
||||
# "aat" = (/obj/structure/grille,/obj/structure/window/reinforced{dir = 8},/obj/structure/window/reinforced{dir = 1},/obj/structure/window/reinforced,/obj/structure/cable{d1 = 2; d2 = 4; icon_state = "2-4"; tag = ""},/turf/simulated/floor/plating,/area/security/prison)
|
||||
atoms = []
|
||||
if tile.origID == 0:
|
||||
print(str(tile))
|
||||
print('len = {}'.format(len(tile.instances)))
|
||||
for i in xrange(len(tile.instances)):
|
||||
atom = tile.instances[i]
|
||||
if atom.path != '':
|
||||
atoms += [self.SerializeAtom(atom)]
|
||||
|
||||
return '({atoms})'.format(atoms=','.join(atoms))
|
||||
|
||||
def ID2String(self, _id, pad=0):
|
||||
o = ''
|
||||
while(_id >= len(ID_ENCODING_TABLE)):
|
||||
i = _id % IET_SIZE
|
||||
o = ID_ENCODING_TABLE[i] + o
|
||||
_id -= i
|
||||
_id /= IET_SIZE
|
||||
o = ID_ENCODING_TABLE[_id] + o
|
||||
if pad > len(o):
|
||||
o = o.rjust(pad, ID_ENCODING_TABLE[0])
|
||||
return o
|
||||
|
||||
def String2ID(self, _id):
|
||||
o = 0
|
||||
for i, c in enumerate(reversed(_id)):
|
||||
o += ID_ENCODING_TABLE.index(c) * (IET_SIZE ** i)
|
||||
return o
|
||||
|
||||
def AssignTID(self, tile):
|
||||
'''
|
||||
:param tile Tile:
|
||||
Tile to assign ID to.
|
||||
'''
|
||||
if tile not in self.tileTypes:
|
||||
self.tileTypes.append(tile)
|
||||
tile.ID = self.tileTypes.index(tile)
|
||||
return self.ID2String(tile)
|
||||
|
||||
def GetTID(self, tile):
|
||||
# print('GetTID: origID: {}'.format(repr(tile.origID)))
|
||||
if self.serialize_cleanly and tile.origID != '':
|
||||
return tile.origID
|
||||
else:
|
||||
return tile.ID
|
||||
|
||||
def Save(self, filename, **kwargs):
|
||||
self.filename = filename
|
||||
|
||||
self.tileTypes = []
|
||||
examined = []
|
||||
hashMap = {}
|
||||
self.typeMap = {}
|
||||
self.type2TID = {}
|
||||
self.instances = []
|
||||
|
||||
self.serialize_cleanly = kwargs.get('clean', True)
|
||||
self.dump_inherited = kwargs.get('inherited', False)
|
||||
|
||||
#print('"aaa" = {}'.format(self.map.GetTileAt(0,0,0)))
|
||||
|
||||
# Preprocess and assign IDs.
|
||||
idlen = 0
|
||||
it = self.map.Tiles()
|
||||
lz = -1
|
||||
last_str = None
|
||||
start = None
|
||||
maxid=0
|
||||
for tile in it:
|
||||
#print(str(tile))
|
||||
if lz != it.z:
|
||||
if start:
|
||||
print(' -> Took {}'.format(getElapsed(start)))
|
||||
print(' * Consolidating level {}...'.format(it.z+1))
|
||||
start = clock()
|
||||
lz = it.z
|
||||
strt = tile.GetHash() # str(tile)
|
||||
dbg = False
|
||||
#if strt == 'e18826df83665059358c177f43f6ac72':
|
||||
# dbg=True
|
||||
if last_str == strt:
|
||||
#if dbg: print('Continuing: Same as last examined.')
|
||||
continue
|
||||
if strt in examined:
|
||||
#if dbg: print('Continuing: Hash already examined.')
|
||||
last_str = strt
|
||||
continue
|
||||
# tile.ID = len(examined)
|
||||
examined += [strt]
|
||||
tid = self.GetTID(tile)
|
||||
if tid in self.typeMap:
|
||||
if hashMap.get(strt, None) == tid:
|
||||
if dbg: print('Continuing: TID {} in typeMap, hashes match.'.format(tid))
|
||||
continue
|
||||
tile.origID = ''
|
||||
tid = len(self.typeMap)
|
||||
while tid in self.typeMap:
|
||||
tid+=1
|
||||
#print('{} assigned to new TID {}'.format(strt,tid))
|
||||
maxid=max(maxid,tid)
|
||||
|
||||
#if tid == 0: print('<{},{},{}> {}: {}'.format(it.x, it.y, it.z, tid, str(tile)))
|
||||
self.typeMap[tid] = (strt,self.SerializeTile(tile))
|
||||
last_str = strt
|
||||
hashMap[strt]=tid
|
||||
|
||||
############################################################################
|
||||
## HACK ALERT BECAUSE SOMETHING'S HORRIBLY WRONG AND I DON'T KNOW WHAT IT IS
|
||||
############################################################################
|
||||
self.typeMap[0]=(self.typeMap[0][0],'(/turf/space,/area)')
|
||||
### /hack
|
||||
|
||||
idlen=len(self.ID2String(maxid))
|
||||
tmpfile = filename + '.tmp'
|
||||
print('Opening {} for write...'.format(tmpfile))
|
||||
print('idlen = {}'.format(idlen))
|
||||
print('"aaa" = {}'.format(self.typeMap[0][1]))
|
||||
start = clock()
|
||||
with open(tmpfile, 'w') as f:
|
||||
for tid in sorted(self.typeMap.keys()):
|
||||
stid=self.ID2String(tid, idlen)
|
||||
strt,serdata=self.typeMap[tid]
|
||||
f.write('"{}" = {}\n'.format(stid, serdata))
|
||||
self.type2TID[strt] = stid
|
||||
print(' Wrote types in {}...'.format(getElapsed(start)))
|
||||
lap = clock()
|
||||
for z in xrange(len(self.map.zLevels)):
|
||||
print(' Writing z={}...'.format(z))
|
||||
f.write('\n(1,1,{0}) = {{"\n'.format(z+1))
|
||||
zlevel = self.map.zLevels[z]
|
||||
for y in xrange(zlevel.height):
|
||||
for x in xrange(zlevel.width):
|
||||
tile = self.map.GetTileAt(x, y, z)
|
||||
thash = tile.GetHash()
|
||||
f.write(self.type2TID[thash])
|
||||
f.write("\n")
|
||||
f.write('"}\n')
|
||||
print(' Wrote tiles in {}...'.format(getElapsed(lap)))
|
||||
if os.path.isfile(filename):
|
||||
os.remove(filename)
|
||||
os.rename(tmpfile, filename)
|
||||
print('-> {} in {}'.format(filename, getElapsed(start)))
|
||||
|
||||
@@ -22,7 +22,7 @@ def debug(filename, line, path, message):
|
||||
|
||||
class OTRCache:
|
||||
# : Only used for obliterating outdated data.
|
||||
VERSION = [28, 4, 2014]
|
||||
VERSION = [18, 6, 2014]
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
@@ -93,13 +93,13 @@ class ObjectTree:
|
||||
'atom_defaults.dm'
|
||||
)
|
||||
def __init__(self, **options):
|
||||
#: All atoms, in a list.
|
||||
# : All atoms, in a list.
|
||||
self.Atoms = {}
|
||||
|
||||
#: All atoms, in a tree-node structure.
|
||||
# : All atoms, in a tree-node structure.
|
||||
self.Tree = Atom('')
|
||||
|
||||
#: Skip loading from .OTR?
|
||||
# : Skip loading from .OTR?
|
||||
self.skip_otr = False
|
||||
|
||||
self.LoadedStdLib = False
|
||||
@@ -193,7 +193,7 @@ class ObjectTree:
|
||||
if c == '"':
|
||||
inString = not inString
|
||||
if not inString:
|
||||
filepath = os.path.join(rootdir, filename)
|
||||
filepath = os.path.join(rootdir, filename.replace('\\',os.sep))
|
||||
if filepath.endswith(ext):
|
||||
ToRead += [filepath]
|
||||
filename = ''
|
||||
@@ -583,9 +583,13 @@ class ObjectTree:
|
||||
line = decl = self.lineBeforePreprocessing.strip()
|
||||
else:
|
||||
decl = line.strip()
|
||||
if '[' in line:
|
||||
if '[' in line and not 'list(' in line:
|
||||
line_split, arr_decl = line.split('[', 1)
|
||||
str_size = arr_decl[:arr_decl.index(']')]
|
||||
try:
|
||||
str_size = arr_decl[:arr_decl.index(']')]
|
||||
except ValueError:
|
||||
logging.warn('{}:{}: MALFORMED CODE: Unable to find ]'.format(filename, ln))
|
||||
logging.info(line)
|
||||
size = -1
|
||||
if str_size != '':
|
||||
try:
|
||||
@@ -626,6 +630,8 @@ class ObjectTree:
|
||||
'size':size
|
||||
}
|
||||
if typepath != '/':
|
||||
if value == 'null':
|
||||
return (name, BYONDValue(None, filename, ln, typepath, **kwargs))
|
||||
return (name, BYONDValue(value, filename, ln, typepath, **kwargs))
|
||||
elif value and value[0] == '"':
|
||||
return (name, BYONDString(value[1:-1], filename, ln, **kwargs))
|
||||
@@ -636,6 +642,8 @@ class ObjectTree:
|
||||
return (name, BYONDValue(float(value), filename, ln, typepath, **kwargs))
|
||||
except ValueError:
|
||||
pass
|
||||
elif value == 'null':
|
||||
return (name, BYONDValue(None, filename, ln, typepath, **kwargs))
|
||||
return (name, BYONDValue(value, filename, ln, typepath, **kwargs))
|
||||
|
||||
def MakeTree(self):
|
||||
@@ -662,7 +670,7 @@ class ObjectTree:
|
||||
if '(' in path_item:
|
||||
cNode.children[path_item] = Proc('/'.join([''] + cpath), [])
|
||||
else:
|
||||
cNode.children[path_item] = Atom('/'.join([''] + cpath),atom.filename,atom.line)
|
||||
cNode.children[path_item] = Atom('/'.join([''] + cpath), atom.filename, atom.line)
|
||||
cNode.children[path_item].parent = cNode
|
||||
parent_type = cNode.children[path_item].getProperty('parent_type')
|
||||
if parent_type is not None:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import hashlib, ast, os
|
||||
from time import clock
|
||||
import operator as op
|
||||
|
||||
def md5sum(filename):
|
||||
@@ -26,6 +27,9 @@ def eval_expr(expr):
|
||||
"""
|
||||
return eval_(ast.parse(expr).body[0].value) # Module(body=[Expr(value=...)])
|
||||
|
||||
def getElapsed(start):
|
||||
return '%d:%02d:%02d.%03d' % reduce(lambda ll, b : divmod(ll[0], b) + ll[1:], [((clock() - start) * 1000,), 1000, 60, 60])
|
||||
|
||||
def eval_(node):
|
||||
if isinstance(node, ast.Num): # <number>
|
||||
return node.n
|
||||
|
||||
Reference in New Issue
Block a user