syntax/linting fixes

This commit is contained in:
Aaron Kimbre
2022-03-12 20:09:35 -06:00
parent 70742549b9
commit 5ce9ac85bc
18 changed files with 504 additions and 375 deletions

View File

@@ -1,33 +1,12 @@
#!/usr/bin/env python
# pylddlib version 0.4.9.7
# based on pyldd2obj version 0.4.8 - Copyright (c) 2019 by jonnysp
#
# Updates:
# 0.4.9.8 Make work with LEGO Universe brickdb
# 0.4.9.7 corrected bug of incorrectly parsing the primitive xml file, specifically with comments. Add support LDDLIFTREE envirnment variable to set location of db.lif.
# 0.4.9.6 preliminary Linux support
# 0.4.9.5 corrected bug of incorrectly Bounding / GeometryBounding parsing the primitive xml file.
# 0.4.9.4 improved lif.db checking for crucial files (because of the infamous botched 4.3.12 LDD Windows update).
# 0.4.9.3 improved Windows and Python 3 compatibility
# 0.4.9.2 changed handling of material = 0 for a part. Now a 0 will choose the 1st material (the base material of a part) and not the previous material of the subpart before. This will fix "Chicken Helmet Part 11262". It may break other parts and this change needs further regression.
# 0.4.9.1 improved custom2DField handling, fixed decorations bug, improved material assignments handling
# 0.4.9 updates to support reading extracted db.lif from db folder
#
# License: MIT License
#
import os
import platform
import sys
import math
import struct
import zipfile
from xml.dom import minidom
import time
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf-8')
PRIMITIVEPATH = '/Primitives/'
GEOMETRIEPATH = PRIMITIVEPATH
@@ -36,8 +15,9 @@ MATERIALNAMESPATH = '/MaterialNames/'
LOGOONSTUDSCONNTYPE = {"0:4", "0:4:1", "0:4:2", "0:4:33", "2:4:1", "2:4:34"}
class Matrix3D:
def __init__(self, n11=1,n12=0,n13=0,n14=0,n21=0,n22=1,n23=0,n24=0,n31=0,n32=0,n33=1,n34=0,n41=0,n42=0,n43=0,n44=1):
def __init__(self, n11=1, n12=0, n13=0, n14=0, n21=0, n22=1, n23=0, n24=0, n31=0, n32=0, n33=1, n34=0, n41=0, n42=0, n43=0, n44=1):
self.n11 = n11
self.n12 = n12
self.n13 = n13
@@ -56,9 +36,26 @@ class Matrix3D:
self.n44 = n44
def __str__(self):
return '[{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15}]'.format(self.n11, self.n12, self.n13,self.n14,self.n21, self.n22, self.n23,self.n24,self.n31, self.n32, self.n33,self.n34,self.n41, self.n42, self.n43,self.n44)
return '[{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15}]'.format(
self.n11,
self.n12,
self.n13,
self.n14,
self.n21,
self.n22,
self.n23,
self.n24,
self.n31,
self.n32,
self.n33,
self.n34,
self.n41,
self.n42,
self.n43,
self.n44
)
def rotate(self,angle=0,axis=0):
def rotate(self, angle=0, axis=0):
c = math.cos(angle)
s = math.sin(angle)
t = 1 - c
@@ -109,21 +106,22 @@ class Matrix3D:
self.n12 * other.n41 + self.n22 * other.n42 + self.n32 * other.n43 + self.n42 * other.n44,
self.n13 * other.n41 + self.n23 * other.n42 + self.n33 * other.n43 + self.n43 * other.n44,
self.n14 * other.n41 + self.n24 * other.n42 + self.n34 * other.n43 + self.n44 * other.n44
)
)
class Point3D:
def __init__(self, x=0,y=0,z=0):
def __init__(self, x=0, y=0, z=0):
self.x = x
self.y = y
self.z = z
def __str__(self):
return '[{0},{1},{2}]'.format(self.x, self.y,self.z)
return '[{0},{1},{2}]'.format(self.x, self.y, self.z)
def string(self,prefix = "v"):
return '{0} {1:f} {2:f} {3:f}\n'.format(prefix ,self.x , self.y, self.z)
def string(self, prefix="v"):
return '{0} {1:f} {2:f} {3:f}\n'.format(prefix, self.x, self.y, self.z)
def transformW(self,matrix):
def transformW(self, matrix):
x = matrix.n11 * self.x + matrix.n21 * self.y + matrix.n31 * self.z
y = matrix.n12 * self.x + matrix.n22 * self.y + matrix.n32 * self.z
z = matrix.n13 * self.x + matrix.n23 * self.y + matrix.n33 * self.z
@@ -131,7 +129,7 @@ class Point3D:
self.y = y
self.z = z
def transform(self,matrix):
def transform(self, matrix):
x = matrix.n11 * self.x + matrix.n21 * self.y + matrix.n31 * self.z + matrix.n41
y = matrix.n12 * self.x + matrix.n22 * self.y + matrix.n32 * self.z + matrix.n42
z = matrix.n13 * self.x + matrix.n23 * self.y + matrix.n33 * self.z + matrix.n43
@@ -140,41 +138,58 @@ class Point3D:
self.z = z
def copy(self):
return Point3D(x=self.x,y=self.y,z=self.z)
return Point3D(x=self.x, y=self.y, z=self.z)
class Point2D:
def __init__(self, x=0,y=0):
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return '[{0},{1}]'.format(self.x, self.y * -1)
def string(self,prefix="t"):
return '{0} {1:f} {2:f}\n'.format(prefix , self.x, self.y * -1 )
def string(self, prefix="t"):
return '{0} {1:f} {2:f}\n'.format(prefix, self.x, self.y * -1)
def copy(self):
return Point2D(x=self.x,y=self.y)
return Point2D(x=self.x, y=self.y)
class Face:
def __init__(self,a=0,b=0,c=0):
def __init__(self, a=0, b=0, c=0):
self.a = a
self.b = b
self.c = c
def string(self,prefix="f", indexOffset=0 ,textureoffset=0):
def string(self, prefix="f", indexOffset=0, textureoffset=0):
if textureoffset == 0:
return prefix + ' {0}//{0} {1}//{1} {2}//{2}\n'.format(self.a + indexOffset, self.b + indexOffset, self.c + indexOffset)
else:
return prefix + ' {0}/{3}/{0} {1}/{4}/{1} {2}/{5}/{2}\n'.format(self.a + indexOffset, self.b + indexOffset, self.c + indexOffset,self.a + textureoffset, self.b + textureoffset, self.c + textureoffset)
return prefix + ' {0}/{3}/{0} {1}/{4}/{1} {2}/{5}/{2}\n'.format(
self.a + indexOffset,
self.b + indexOffset,
self.c + indexOffset,
self.a + textureoffset,
self.b + textureoffset,
self.c + textureoffset
)
def __str__(self):
return '[{0},{1},{2}]'.format(self.a, self.b, self.c)
class Group:
def __init__(self, node):
self.partRefs = node.getAttribute('partRefs').split(',')
class Bone:
def __init__(self, node):
self.refID = node.getAttribute('refID')
(a, b, c, d, e, f, g, h, i, x, y, z) = map(float, node.getAttribute('transformation').split(','))
self.matrix = Matrix3D(n11=a,n12=b,n13=c,n14=0,n21=d,n22=e,n23=f,n24=0,n31=g,n32=h,n33=i,n34=0,n41=x,n42=y,n43=z,n44=1)
self.matrix = Matrix3D(n11=a, n12=b, n13=c, n14=0, n21=d, n22=e, n23=f, n24=0, n31=g, n32=h, n33=i, n34=0, n41=x, n42=y, n43=z, n44=1)
class Part:
def __init__(self, node):
@@ -185,19 +200,17 @@ class Part:
self.designID = node.getAttribute('designID')
self.materials = list(map(str, node.getAttribute('materials').split(',')))
lastm = '0'
for i, m in enumerate(self.materials):
if (m == '0'):
# self.materials[i] = lastm
self.materials[i] = self.materials[0] #in case of 0 choose the 'base' material
else:
lastm = m
self.materials[i] = self.materials[0] # in case of 0 choose the 'base' material
if node.hasAttribute('decoration'):
self.decoration = list(map(str,node.getAttribute('decoration').split(',')))
self.decoration = list(map(str, node.getAttribute('decoration').split(',')))
for childnode in node.childNodes:
if childnode.nodeName == 'Bone':
self.Bones.append(Bone(node=childnode))
class Brick:
def __init__(self, node):
self.refID = node.getAttribute('refID')
@@ -207,14 +220,16 @@ class Brick:
if childnode.nodeName == 'Part':
self.Parts.append(Part(node=childnode))
class SceneCamera:
def __init__(self, node):
self.refID = node.getAttribute('refID')
(a, b, c, d, e, f, g, h, i, x, y, z) = map(float, node.getAttribute('transformation').split(','))
self.matrix = Matrix3D(n11=a,n12=b,n13=c,n14=0,n21=d,n22=e,n23=f,n24=0,n31=g,n32=h,n33=i,n34=0,n41=x,n42=y,n43=z,n44=1)
self.matrix = Matrix3D(n11=a, n12=b, n13=c, n14=0, n21=d, n22=e, n23=f, n24=0, n31=g, n32=h, n33=i, n34=0, n41=x, n42=y, n43=z, n44=1)
self.fieldOfView = float(node.getAttribute('fieldOfView'))
self.distance = float(node.getAttribute('distance'))
class Scene:
def __init__(self, file):
self.Bricks = []
@@ -262,6 +277,7 @@ class Scene:
# print('Scene "'+ self.Name + '" Brickversion: ' + str(self.Version))
class GeometryReader:
def __init__(self, data):
self.offset = 0
@@ -282,10 +298,10 @@ class GeometryReader:
options = self.readInt()
for i in range(0, self.valueCount):
self.positions.append(Point3D(x=self.readFloat(),y= self.readFloat(),z=self.readFloat()))
self.positions.append(Point3D(x=self.readFloat(), y=self.readFloat(), z=self.readFloat()))
for i in range(0, self.valueCount):
self.normals.append(Point3D(x=self.readFloat(),y= self.readFloat(),z=self.readFloat()))
self.normals.append(Point3D(x=self.readFloat(), y=self.readFloat(), z=self.readFloat()))
if (options & 3) == 3:
self.texCount = self.valueCount
@@ -293,7 +309,7 @@ class GeometryReader:
self.textures.append(Point2D(x=self.readFloat(), y=self.readFloat()))
for i in range(0, self.faceCount):
self.faces.append(Face(a=self.readInt(),b=self.readInt(),c=self.readInt()))
self.faces.append(Face(a=self.readInt(), b=self.readInt(), c=self.readInt()))
if (options & 48) == 48:
num = self.readInt()
@@ -311,7 +327,7 @@ class GeometryReader:
boneoffset = self.readInt() + 4
self.bonemap[i] = self.read_Int(datastart + boneoffset)
def read_Int(self,_offset):
def read_Int(self, _offset):
if sys.version_info < (3, 0):
return int(struct.unpack_from('i', self.data, _offset)[0])
else:
@@ -330,6 +346,7 @@ class GeometryReader:
self.offset += 4
return ret
class Geometry:
def __init__(self, designID, database):
self.designID = designID
@@ -337,21 +354,25 @@ class Geometry:
self.maxGeoBounding = -1
self.studsFields2D = []
GeometryLocation = '{0}{1}{2}'.format(GEOMETRIEPATH, designID,'.g')
GeometryLocation = '{0}{1}{2}'.format(GEOMETRIEPATH, designID, '.g')
GeometryCount = 0
while str(GeometryLocation) in database.filelist:
self.Parts[GeometryCount] = GeometryReader(data=database.filelist[GeometryLocation].read())
GeometryCount += 1
GeometryLocation = '{0}{1}{2}{3}'.format(GEOMETRIEPATH, designID,'.g',GeometryCount)
GeometryLocation = '{0}{1}{2}{3}'.format(GEOMETRIEPATH, designID, '.g', GeometryCount)
primitive = Primitive(data = database.filelist[PRIMITIVEPATH + designID + '.xml'].read())
primitive = Primitive(data=database.filelist[PRIMITIVEPATH + designID + '.xml'].read())
self.Partname = primitive.Designname
self.studsFields2D = primitive.Fields2D
try:
geoBoundingList = [abs(float(primitive.Bounding['minX']) - float(primitive.Bounding['maxX'])), abs(float(primitive.Bounding['minY']) - float(primitive.Bounding['maxY'])), abs(float(primitive.Bounding['minZ']) - float(primitive.Bounding['maxZ']))]
geoBoundingList = [
abs(float(primitive.Bounding['minX']) - float(primitive.Bounding['maxX'])),
abs(float(primitive.Bounding['minY']) - float(primitive.Bounding['maxY'])),
abs(float(primitive.Bounding['minZ']) - float(primitive.Bounding['maxZ']))
]
geoBoundingList.sort()
self.maxGeoBounding = geoBoundingList[-1]
except KeyError as e:
except KeyError:
# print('\nBounding errror in part {0}: {1}\n'.format(designID, e))
pass
@@ -386,25 +407,33 @@ class Geometry:
count += self.Parts[part].texCount
return count
class Bone2:
def __init__(self,boneId=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0):
def __init__(self, boneId=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0):
self.boneId = boneId
rotationMatrix = Matrix3D()
rotationMatrix.rotate(angle = -angle * math.pi / 180.0,axis = Point3D(x=ax,y=ay,z=az))
p = Point3D(x=tx,y=ty,z=tz)
rotationMatrix.rotate(
angle=(-angle * math.pi / 180.0),
axis=Point3D(x=ax, y=ay, z=az)
)
p = Point3D(x=tx, y=ty, z=tz)
p.transformW(rotationMatrix)
rotationMatrix.n41 -= p.x
rotationMatrix.n42 -= p.y
rotationMatrix.n43 -= p.z
self.matrix = rotationMatrix
class Field2D:
def __init__(self, type=0, width=0, height=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0, field2DRawData='none'):
self.type = type
self.field2DRawData = field2DRawData
rotationMatrix = Matrix3D()
rotationMatrix.rotate(angle = -angle * math.pi / 180.0, axis = Point3D(x=ax,y=ay,z=az))
p = Point3D(x=tx,y=ty,z=tz)
rotationMatrix.rotate(
angle=(-angle * math.pi / 180.0),
axis=Point3D(x=ax, y=ay, z=az)
)
p = Point3D(x=tx, y=ty, z=tz)
p.transformW(rotationMatrix)
rotationMatrix.n41 -= p.x
rotationMatrix.n42 -= p.y
@@ -413,7 +442,7 @@ class Field2D:
self.matrix = rotationMatrix
self.custom2DField = []
#The height and width are always double the number of studs. The contained text is a 2D array that is always height + 1 and width + 1.
# The height and width are always double the number of studs. The contained text is a 2D array that is always height + 1 and width + 1.
rows_count = height + 1
cols_count = width + 1
# creation looks reverse
@@ -432,18 +461,23 @@ class Field2D:
def __str__(self):
return '[type="{0}" transform="{1}" custom2DField="{2}"]'.format(self.type, self.matrix, self.custom2DField)
class CollisionBox:
def __init__(self, sX=0, sY=0, sZ=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0):
rotationMatrix = Matrix3D()
rotationMatrix.rotate(angle = -angle * math.pi / 180.0, axis = Point3D(x=ax,y=ay,z=az))
p = Point3D(x=tx,y=ty,z=tz)
rotationMatrix.rotate(
angle=(-angle * math.pi / 180.0),
axis=Point3D(x=ax, y=ay, z=az)
)
p = Point3D(x=tx, y=ty,
z=tz)
p.transformW(rotationMatrix)
rotationMatrix.n41 -= p.x
rotationMatrix.n42 -= p.y
rotationMatrix.n43 -= p.z
self.matrix = rotationMatrix
self.corner = Point3D(x=sX,y=sY,z=sZ)
self.corner = Point3D(x=sX, y=sY, z=sZ)
self.positions = []
self.positions.append(Point3D(x=0, y=0, z=0))
@@ -452,11 +486,14 @@ class CollisionBox:
self.positions.append(Point3D(x=sX, y=sY, z=0))
self.positions.append(Point3D(x=0, y=0, z=sZ))
self.positions.append(Point3D(x=0, y=sY, z=sZ))
self.positions.append(Point3D(x=sX ,y=0, z=sZ))
self.positions.append(Point3D(x=sX ,y=sY, z=sZ))
self.positions.append(Point3D(x=sX, y=0, z=sZ))
self.positions.append(Point3D(x=sX, y=sY, z=sZ))
def __str__(self):
return '[0,0,0] [{0},0,0] [0,{1},0] [{0},{1},0] [0,0,{2}] [0,{1},{2}] [{0},0,{2}] [{0},{1},{2}]'.format(self.corner.x, self.corner.y, self.corner.z)
return '[0,0,0] [{0},0,0] [0,{1},0] [{0},{1},0] [0,0,{2}] [0,{1},{2}] [{0},0,{2}] [{0},{1},{2}]'.format(
self.corner.x, self.corner.y, self.corner.z
)
class Primitive:
def __init__(self, data):
@@ -475,7 +512,18 @@ class Primitive:
if node.nodeName == 'Flex':
for node in node.childNodes:
if node.nodeName == 'Bone':
self.Bones.append(Bone2(boneId=int(node.getAttribute('boneId')), angle=float(node.getAttribute('angle')), ax=float(node.getAttribute('ax')), ay=float(node.getAttribute('ay')), az=float(node.getAttribute('az')), tx=float(node.getAttribute('tx')), ty=float(node.getAttribute('ty')), tz=float(node.getAttribute('tz'))))
self.Bones.append(
Bone2(
boneId=int(node.getAttribute('boneId')),
angle=float(node.getAttribute('angle')),
ax=float(node.getAttribute('ax')),
ay=float(node.getAttribute('ay')),
az=float(node.getAttribute('az')),
tx=float(node.getAttribute('tx')),
ty=float(node.getAttribute('ty')),
tz=float(node.getAttribute('tz'))
)
)
elif node.nodeName == 'Annotations':
for childnode in node.childNodes:
if childnode.nodeName == 'Annotation' and childnode.hasAttribute('designname'):
@@ -483,23 +531,73 @@ class Primitive:
elif node.nodeName == 'Collision':
for childnode in node.childNodes:
if childnode.nodeName == 'Box':
self.CollisionBoxes.append(CollisionBox(sX=float(childnode.getAttribute('sX')), sY=float(childnode.getAttribute('sY')), sZ=float(childnode.getAttribute('sZ')), angle=float(childnode.getAttribute('angle')), ax=float(childnode.getAttribute('ax')), ay=float(childnode.getAttribute('ay')), az=float(childnode.getAttribute('az')), tx=float(childnode.getAttribute('tx')), ty=float(childnode.getAttribute('ty')), tz=float(childnode.getAttribute('tz'))))
self.CollisionBoxes.append(
CollisionBox(
sX=float(childnode.getAttribute('sX')),
sY=float(childnode.getAttribute('sY')),
sZ=float(childnode.getAttribute('sZ')),
angle=float(childnode.getAttribute('angle')),
ax=float(childnode.getAttribute('ax')),
ay=float(childnode.getAttribute('ay')),
az=float(childnode.getAttribute('az')),
tx=float(childnode.getAttribute('tx')),
ty=float(childnode.getAttribute('ty')),
tz=float(childnode.getAttribute('tz'))
)
)
elif node.nodeName == 'PhysicsAttributes':
self.PhysicsAttributes = {"inertiaTensor": node.getAttribute('inertiaTensor'),"centerOfMass": node.getAttribute('centerOfMass'),"mass": node.getAttribute('mass'),"frictionType": node.getAttribute('frictionType')}
self.PhysicsAttributes = {
"inertiaTensor": node.getAttribute('inertiaTensor'),
"centerOfMass": node.getAttribute('centerOfMass'),
"mass": node.getAttribute('mass'),
"frictionType": node.getAttribute('frictionType')
}
elif node.nodeName == 'Bounding':
for childnode in node.childNodes:
if childnode.nodeName == 'AABB':
self.Bounding = {"minX": childnode.getAttribute('minX'), "minY": childnode.getAttribute('minY'), "minZ": childnode.getAttribute('minZ'), "maxX": childnode.getAttribute('maxX'), "maxY": childnode.getAttribute('maxY'), "maxZ": childnode.getAttribute('maxZ')}
self.Bounding = {
"minX": childnode.getAttribute('minX'),
"minY": childnode.getAttribute('minY'),
"minZ": childnode.getAttribute('minZ'),
"maxX": childnode.getAttribute('maxX'),
"maxY": childnode.getAttribute('maxY'),
"maxZ": childnode.getAttribute('maxZ')
}
elif node.nodeName == 'GeometryBounding':
for childnode in node.childNodes:
if childnode.nodeName == 'AABB':
self.GeometryBounding = {"minX": childnode.getAttribute('minX'), "minY": childnode.getAttribute('minY'), "minZ": childnode.getAttribute('minZ'), "maxX": childnode.getAttribute('maxX'), "maxY": childnode.getAttribute('maxY'), "maxZ": childnode.getAttribute('maxZ')}
self.GeometryBounding = {
"minX": childnode.getAttribute('minX'),
"minY": childnode.getAttribute('minY'),
"minZ": childnode.getAttribute('minZ'),
"maxX": childnode.getAttribute('maxX'),
"maxY": childnode.getAttribute('maxY'),
"maxZ": childnode.getAttribute('maxZ')
}
elif node.nodeName == 'Connectivity':
for childnode in node.childNodes:
if childnode.nodeName == 'Custom2DField':
self.Fields2D.append(Field2D(type=int(childnode.getAttribute('type')), width=int(childnode.getAttribute('width')), height=int(childnode.getAttribute('height')), angle=float(childnode.getAttribute('angle')), ax=float(childnode.getAttribute('ax')), ay=float(childnode.getAttribute('ay')), az=float(childnode.getAttribute('az')), tx=float(childnode.getAttribute('tx')), ty=float(childnode.getAttribute('ty')), tz=float(childnode.getAttribute('tz')), field2DRawData=str(childnode.firstChild.data)))
self.Fields2D.append(
Field2D(
type=int(childnode.getAttribute('type')),
width=int(childnode.getAttribute('width')),
height=int(childnode.getAttribute('height')),
angle=float(childnode.getAttribute('angle')),
ax=float(childnode.getAttribute('ax')),
ay=float(childnode.getAttribute('ay')),
az=float(childnode.getAttribute('az')),
tx=float(childnode.getAttribute('tx')),
ty=float(childnode.getAttribute('ty')),
tz=float(childnode.getAttribute('tz')),
field2DRawData=str(childnode.firstChild.data)
)
)
elif node.nodeName == 'Decoration':
self.Decoration = {"faces": node.getAttribute('faces'), "subMaterialRedirectLookupTable": node.getAttribute('subMaterialRedirectLookupTable')}
self.Decoration = {
"faces": node.getAttribute('faces'),
"subMaterialRedirectLookupTable": node.getAttribute('subMaterialRedirectLookupTable')
}
class Materials:
def __init__(self, data):
@@ -514,13 +612,14 @@ class Materials:
b=int(node.getAttribute('Blue')),
a=int(node.getAttribute('Alpha')),
mtype=str(node.getAttribute('MaterialType'))
)
)
def getMaterialbyId(self, mid):
return self.Materials[mid]
class Material:
def __init__(self,id, r, g, b, a, mtype):
def __init__(self, id, r, g, b, a, mtype):
self.id = id
self.name = id
self.mattype = mtype
@@ -528,18 +627,25 @@ class Material:
self.g = float(g)
self.b = float(b)
self.a = float(a)
def string(self):
out = 'Kd {0} {1} {2}\nKa 1.600000 1.600000 1.600000\nKs 0.400000 0.400000 0.400000\nNs 3.482202\nTf 1 1 1\n'.format( self.r / 255, self.g / 255,self.b / 255)
out = 'Kd {0} {1} {2}\nKa 1.600000 1.600000 1.600000\nKs 0.400000 0.400000 0.400000\nNs 3.482202\nTf 1 1 1\n'.format(
self.r / 255,
self.g / 255,
self.b / 255
)
if self.a < 255:
out += 'Ni 1.575\n' + 'd {0}'.format(0.05) + '\n' + 'Tr {0}\n'.format(0.05)
return out
class DBinfo:
def __init__(self, data):
xml = minidom.parseString(data)
self.Version = xml.getElementsByTagName('Bricks')[0].attributes['version'].value
# print('DB Version: ' + str(self.Version))
class DBFolderFile:
def __init__(self, name, handle):
self.handle = handle
@@ -554,6 +660,7 @@ class DBFolderFile:
finally:
reader.close()
class LIFFile:
def __init__(self, name, offset, size, handle):
self.handle = handle
@@ -565,6 +672,7 @@ class LIFFile:
self.handle.seek(self.offset, 0)
return self.handle.read(self.size)
class DBFolderReader:
def __init__(self, folder):
self.filelist = {}
@@ -574,14 +682,14 @@ class DBFolderReader:
try:
os.path.isdir(self.location)
except Exception as e:
except Exception:
self.initok = False
# print("db folder read FAIL")
return
else:
self.parse()
if self.fileexist(os.path.join(self.location,'Materials.xml')) and self.fileexist(os.path.join(self.location, 'info.xml')):
self.dbinfo = DBinfo(data=self.filelist[os.path.join(self.location,'info.xml')].read())
if self.fileexist(os.path.join(self.location, 'Materials.xml')) and self.fileexist(os.path.join(self.location, 'info.xml')):
self.dbinfo = DBinfo(data=self.filelist[os.path.join(self.location, 'info.xml')].read())
# print("DB folder OK.")
self.initok = True
else:
@@ -593,7 +701,6 @@ class DBFolderReader:
# print(MATERIALNAMESPATH)
pass
def fileexist(self, filename):
return filename in self.filelist
@@ -603,6 +710,7 @@ class DBFolderReader:
entryName = os.path.join(path, name)
self.filelist[entryName] = DBFolderFile(name=entryName, handle=entryName)
class LIFReader:
def __init__(self, file):
self.packedFilesOffset = 84
@@ -614,7 +722,7 @@ class LIFReader:
try:
self.filehandle = open(self.location, "rb")
self.filehandle.seek(0, 0)
except Exception as e:
except Exception:
self.initok = False
# print("Database FAIL")
return
@@ -632,7 +740,7 @@ class LIFReader:
# print("Database FAIL")
self.initok = False
def fileexist(self,filename):
def fileexist(self, filename):
return filename in self.filelist
def parse(self, prefix='', offset=0):
@@ -648,7 +756,7 @@ class LIFReader:
entryType = self.readShort(offset=offset)
offset += 6
entryName = '{0}{1}'.format(prefix,'/');
entryName = '{0}{1}'.format(prefix, '/')
self.filehandle.seek(offset + 1, 0)
if sys.version_info < (3, 0):
t = ord(self.filehandle.read(1))
@@ -656,7 +764,7 @@ class LIFReader:
t = int.from_bytes(self.filehandle.read(1), byteorder='big')
while not t == 0:
entryName ='{0}{1}'.format(entryName,chr(t))
entryName = '{0}{1}'.format(entryName, chr(t))
self.filehandle.seek(1, 1)
if sys.version_info < (3, 0):
t = ord(self.filehandle.read(1))
@@ -692,33 +800,32 @@ class LIFReader:
else:
return int.from_bytes(self.filehandle.read(2), byteorder='big')
class Converter:
def LoadDBFolder(self, dbfolderlocation):
self.database = DBFolderReader(folder=dbfolderlocation)
if self.database.initok and self.database.fileexist(os.path.join(dbfolderlocation,'Materials.xml')):
self.allMaterials = Materials(data=self.database.filelist[os.path.join(dbfolderlocation,'Materials.xml')].read());
if self.database.initok and self.database.fileexist(os.path.join(dbfolderlocation, 'Materials.xml')):
self.allMaterials = Materials(data=self.database.filelist[os.path.join(dbfolderlocation, 'Materials.xml')].read())
def LoadDatabase(self,databaselocation):
def LoadDatabase(self, databaselocation):
self.database = LIFReader(file=databaselocation)
if self.database.initok and self.database.fileexist('/Materials.xml'):
self.allMaterials = Materials(data=self.database.filelist['/Materials.xml'].read());
self.allMaterials = Materials(data=self.database.filelist['/Materials.xml'].read())
def LoadScene(self,filename):
def LoadScene(self, filename):
if self.database.initok:
self.scene = Scene(file=filename)
def Export(self,filename):
def Export(self, filename):
invert = Matrix3D()
#invert.n33 = -1 #uncomment to invert the Z-Axis
# invert.n33 = -1 #uncomment to invert the Z-Axis
indexOffset = 1
textOffset = 1
usedmaterials = []
geometriecache = {}
start_time = time.time()
out = open(filename + ".obj.tmp", "w+")
out.truncate(0)
out.write("mtllib " + filename + ".mtl" + '\n\n')
@@ -735,12 +842,12 @@ class Converter:
if pa.designID not in geometriecache:
geo = Geometry(designID=pa.designID, database=self.database)
progress(current ,total , "(" + geo.designID + ") " + geo.Partname, ' ')
progress(current, total, "(" + geo.designID + ") " + geo.Partname, ' ')
geometriecache[pa.designID] = geo
else:
geo = geometriecache[pa.designID]
progress(current ,total , "(" + geo.designID + ") " + geo.Partname ,'-')
progress(current, total, "(" + geo.designID + ") " + geo.Partname, '-')
out.write("o\n")
@@ -752,11 +859,11 @@ class Converter:
# positions
for j, p in enumerate(geo.Parts[part].outpositions):
if (geo.Parts[part].bonemap[j] == i):
p.transform( invert * b.matrix)
p.transform(invert * b.matrix)
# normals
for k, n in enumerate(geo.Parts[part].outnormals):
if (geo.Parts[part].bonemap[k] == i):
n.transformW( invert * b.matrix)
n.transformW(invert * b.matrix)
for point in geo.Parts[part].outpositions:
out.write(point.string("v"))
@@ -772,13 +879,12 @@ class Converter:
last_color = 0
for part in geo.Parts:
#try catch here for possible problems in materials assignment of various g, g1, g2, .. files in lxf file
# try catch here for possible problems in materials assignment of various g, g1, g2, .. files in lxf file
try:
materialCurrentPart = pa.materials[part]
last_color = pa.materials[part]
except IndexError:
# print('WARNING: {0}.g{1} has NO material assignment in lxf. Replaced with color {2}. Fix {0}.xml faces values.'.format(pa.designID, part, last_color))
materialCurrentPart = last_color
lddmat = self.allMaterials.getMaterialbyId(materialCurrentPart)
@@ -786,7 +892,7 @@ class Converter:
deco = '0'
if hasattr(pa, 'decoration') and len(geo.Parts[part].textures) > 0:
#if decoCount <= len(pa.decoration):
# if decoCount <= len(pa.decoration):
if decoCount < len(pa.decoration):
deco = pa.decoration[decoCount]
decoCount += 1
@@ -801,7 +907,7 @@ class Converter:
f.write(self.database.filelist[decofilename].read())
f.close()
if not matname in usedmaterials:
if matname not in usedmaterials:
usedmaterials.append(matname)
outtext.write("newmtl " + matname + '\n')
outtext.write(lddmat.string())
@@ -811,9 +917,9 @@ class Converter:
out.write("usemtl " + matname + '\n')
for face in geo.Parts[part].faces:
if len(geo.Parts[part].textures) > 0:
out.write(face.string("f",indexOffset,textOffset))
out.write(face.string("f", indexOffset, textOffset))
else:
out.write(face.string("f",indexOffset))
out.write(face.string("f", indexOffset))
indexOffset += len(geo.Parts[part].outpositions)
textOffset += len(geo.Parts[part].textures)
@@ -825,6 +931,7 @@ class Converter:
sys.stdout.write('%s\r' % (' '))
# print("--- %s seconds ---" % (time.time() - start_time))
def setDBFolderVars(dbfolderlocation, lod):
global PRIMITIVEPATH
global GEOMETRIEPATH
@@ -836,44 +943,8 @@ def setDBFolderVars(dbfolderlocation, lod):
MATERIALNAMESPATH = os.path.join(dbfolderlocation, 'MaterialNames', '')
# print(MATERIALNAMESPATH)
def FindDatabase():
lddliftree = os.getenv('LDDLIFTREE')
if lddliftree is not None:
if os.path.isdir(str(lddliftree)): #LDDLIFTREE points to folder
return str(lddliftree)
elif os.path.isfile(str(lddliftree)): #LDDLIFTREE points to file (should be db.lif)
return str(lddliftree)
else: #Env variable LDDLIFTREE not set. Check for default locations per different platform.
if platform.system() == 'Darwin':
if os.path.isdir(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'Library','Application Support','LEGO Company','LEGO Digital Designer','db'))):
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'Library','Application Support','LEGO Company','LEGO Digital Designer','db'))
elif os.path.isfile(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'Library','Application Support','LEGO Company','LEGO Digital Designer','db.lif'))):
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'Library','Application Support','LEGO Company','LEGO Digital Designer','db.lif'))
else:
# print("no LDD database found please install LEGO-Digital-Designer")
os._exit()
elif platform.system() == 'Windows':
if os.path.isdir(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'AppData','Roaming','LEGO Company','LEGO Digital Designer','db'))):
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'AppData','Roaming','LEGO Company','LEGO Digital Designer','db'))
elif os.path.isfile(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'AppData','Roaming','LEGO Company','LEGO Digital Designer','db.lif'))):
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'AppData','Roaming','LEGO Company','LEGO Digital Designer','db.lif'))
else:
# print("no LDD database found please install LEGO-Digital-Designer")
os._exit()
elif platform.system() == 'Linux':
if os.path.isdir(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'.wine','drive_c','users',os.getenv('USER'),'Application Data','LEGO Company','LEGO Digital Designer','db'))):
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'.wine','drive_c','users',os.getenv('USER'),'Application Data','LEGO Company','LEGO Digital Designer','db'))
elif os.path.isfile(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'.wine','drive_c','users',os.getenv('USER'),'Application Data','LEGO Company','LEGO Digital Designer','db.lif'))):
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'.wine','drive_c','users',os.getenv('USER'),'Application Data','LEGO Company','LEGO Digital Designer','db.lif'))
else:
# print("no LDD database found please install LEGO-Digital-Designer")
os._exit()
else:
# print('Your OS {0} is not supported yet.'.format(platform.system()))
os._exit()
def progress(count, total, status='', suffix = ''):
def progress(count, total, status='', suffix=''):
bar_len = 40
filled_len = int(round(bar_len * count / float(total)))
percents = round(100.0 * count / float(total), 1)
@@ -882,6 +953,7 @@ def progress(count, total, status='', suffix = ''):
sys.stdout.write('Progress: [%s] %s%s %s %s\r' % (bar, percents, '%', suffix, status))
sys.stdout.flush()
def main(lxf_filename, obj_filename, lod="2"):
# print("- - - pylddlib - - -")
# print(" _ ")
@@ -896,10 +968,11 @@ def main(lxf_filename, obj_filename, lod="2"):
GEOMETRIEPATH = GEOMETRIEPATH + f"LOD{lod}/"
converter = Converter()
# print("Found DB folder. Will use this instead of db.lif!")
setDBFolderVars(dbfolderlocation = "app/luclient/res/", lod=lod)
converter.LoadDBFolder(dbfolderlocation = "app/luclient/res/")
setDBFolderVars(dbfolderlocation="app/luclient/res/", lod=lod)
converter.LoadDBFolder(dbfolderlocation="app/luclient/res/")
converter.LoadScene(filename=lxf_filename)
converter.Export(filename=obj_filename)
if __name__ == "__main__":
main()