commit 4c8bf1460f24846d3f0d3dac2c371803160854ef Author: indigo Date: Sun Sep 24 15:04:35 2023 +0800 Init Repo diff --git a/LDD.py b/LDD.py new file mode 100644 index 0000000..48d228e --- /dev/null +++ b/LDD.py @@ -0,0 +1,143 @@ +import maya.OpenMaya as OpenMaya +import maya.OpenMayaMPx as OpenMayaMPx +import sys +import os +import logging +import subprocess as sp +import reader.lxf_loader as lxf_loader +import reader.ldd_command as ldd_command + +reload(lxf_loader) +reload(ldd_command) + +logger = logging.getLogger('LDD') + +kFileVersion = '' +kExtension = '.lxf' +kPluginName = 'Lego Digital Designer Reader for Maya ' +kFilterName = '*.lxf' +kTranslatorName = 'LXF File' + + +class LXFReader(OpenMayaMPx.MPxFileTranslator): + def __init__(self): + OpenMayaMPx.MPxFileTranslator.__init__(self) + + def haveReadMethod(self): + return True + + def haveWriteMethod(self): + return False + + def canBeOpened(self): + return True + + def translatorName(self): + return kTranslatorName + + def defaultExtension(self): + return kExtension + + def filter(self): + return kFilterName + + def identifyFile(self, fileName, buf, size): + if 'lxf' in fileName.name().lower(): + logger.info('File is LDD lxf %s' % fileName.name()) + return OpenMayaMPx.MPxFileTranslator.kIsMyFileType + + return OpenMayaMPx.MPxFileTranslator.kNotMyFileType + + def reader(self, fileName, options, mode): + logger.info('reader') + try: + loader = lxf_loader.LXFLoader() + loader.read(fileName.rawFullName()) + except: + sys.stderr.write("Failed reader : %s" % kPluginName) + (typ, value, traceback) = sys.exc_info() + sys.excepthook(typ, value, traceback) + raise + + +def lxf_reader_creator(): + return OpenMayaMPx.asMPxPtr(LXFReader()) + + +def check_ldd_install(): + program_dir = os.getenv('ProgramFiles(x86)') + ldd_install_dir = os.path.join(program_dir, 'LEGO Company', 'LEGO Digital Designer') + exec_file = os.path.normpath(os.path.join(ldd_install_dir, 'LDD.exe')) + + if not os.path.exists(exec_file): + return False + + cmd = r'powershell.exe [System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"%s\").FileVersion' % exec_file + ldd_version = '' + p = sp.Popen(cmd, shell=True, stdout=sp.PIPE) + + for li in p.stdout: + ldd_version = li.strip() + + asset_file = os.path.join(ldd_install_dir, 'Assets.lif') + + if not os.path.exists(asset_file): + logger.warning('LEGO Digital Designer Assets.lif not found.') + return False + + logger.debug('LEGO Digital Designer %s installed.' % ldd_version) + return True + + +def init_ldd_asset_map(): + """ Pre generate asset.lif data index + for reading geometry data + """ + + +def initializePlugin(mobject): + if os.getenv('OS') not in ['Windows_NT']: + sys.stderr.write('LDD.py only support window platform.') + return + + if not check_ldd_install(): + sys.stderr.write('LEGO Digital Designer not install.') + return + + mplugin = OpenMayaMPx.MFnPlugin(mobject, "CGCG", "1.0", "Any") + try: + mplugin.registerFileTranslator(kTranslatorName, + None, + lxf_reader_creator, + None, + None, + False) + logger.debug('Register LDD File Translater :: %s' % kTranslatorName) + except: + sys.stderr.write("Failed to register translator: %s\n" % kPluginName) + raise + + try: + mplugin.registerCommand(ldd_command.kPluginCmdName, + ldd_command.ldd_command_creater, + ldd_command.ldd_command_syntaxCreator) + logger.debug('Register LDD Command :: %s' % ldd_command.kPluginCmdName) + except: + sys.stderr.write('Failed to register command: ' + ldd_command.kPluginCmdName) + raise + + +def uninitializePlugin(mobject): + mplugin = OpenMayaMPx.MFnPlugin(mobject) + try: + mplugin.deregisterFileTranslator(kTranslatorName) + logger.debug('Deregister LDD File Translator :: %s' % kTranslatorName) + except: + sys.stderr.write("Failed to deregister translator: %s\n" % kPluginName) + raise + + try: + mplugin.deregisterCommand(ldd_command.kPluginCmdName) + except: + sys.stderr.write('Failed to deregister command: ' + ldd_command.kPluginCmdName) + raise diff --git a/README.md b/README.md new file mode 100644 index 0000000..df70344 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Lego Digital Designer Reader for Maya +This plugin is for tranlate geometry from Lego Digital Designer, extract grometry from lego part number and material color. The base idea is from git project ***[LIF Extractor](https://github.com/JrMasterModelBuilder/LIF-Extractor)*** + + +### Requirement +* Lego Digital Designer 4.3+ +* Maya 2018 + + +### Limitation +Current some geometry with hole will translate incorrect result and uvs. \ No newline at end of file diff --git a/reader/__init__.py b/reader/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reader/__init__.pyc b/reader/__init__.pyc new file mode 100644 index 0000000..e650d6c Binary files /dev/null and b/reader/__init__.pyc differ diff --git a/reader/asset_reader.py b/reader/asset_reader.py new file mode 100644 index 0000000..504c2bb --- /dev/null +++ b/reader/asset_reader.py @@ -0,0 +1,169 @@ +""" + LIF Extractor - LEGO Digital Designer LIF extractor. + + Copyright (C) 2012 JrMasterModelBuilder + + You accept full responsibility for how you use this program. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +""" + +import os +import logging + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +LDD_INSTALL_PATH = r'C:\Program Files (x86)\LEGO Company\LEGO Digital Designer' +LDD_ASSET_PATH = os.path.join(LDD_INSTALL_PATH, 'Assets.lif') + +fileData = '' + + +def asset_extract(design_ids=None, material_only=False, debug=True): + global fileData + + def uint32(offset): + if bytesAre == "str": + return (ord(fileData[offset]) * 16777216) + (ord(fileData[offset + 1]) * 65536) + ( + ord(fileData[offset + 2]) * 256) + ord(fileData[offset + 3]) + else: + return (fileData[offset] * 16777216) + (fileData[offset + 1] * 65536) + (fileData[offset + 2] * 256) + \ + fileData[offset + 3] + + def uint16(offset): + if bytesAre == "str": + return (ord(fileData[offset]) * 256) + ord(fileData[offset + 1]) + else: + return (fileData[offset] * 256) + fileData[offset + 1] + + def recurse(prefix, offset): + if prefix == "": + offset += 36 + else: + folderList.extend([prefix]) + offset += 4 + for i in range(uint32(offset)): + offset += 4 + entryType = uint16(offset) # 1 = directory, 2 = file + print 'xxx', entryType, offset + offset += 6 + # Build the name. + entryName = os.sep + # Check both integer and byte for different versions of Python. + while fileData[offset + 1] != 0 and fileData[offset + 1] != b"\x00": + print '---', offset, fileData[offset + 1] + if bytesAre == "str": + entryName += fileData[offset + 1] + else: + entryName += chr(fileData[offset + 1]) + offset += 2 + offset += 6 + + if entryType == 1: + # Recurse through the director, and update the offset. + packedFilesOffset[0] += 20 + offset = recurse(prefix + entryName, offset) + elif entryType == 2: + # File offset. + packedFilesOffset[0] += 20 + fileOffset = packedFilesOffset[0] + # File size. + fileSize = uint32(offset) - 20 + offset += 24 + packedFilesOffset[0] += fileSize + fileList.extend([[prefix + entryName, fileOffset, fileSize]]) + # Return the offset at the end of this run to update parent runnings. + return offset + if debug: + logger.debug('PROCESSING: %s' % LDD_ASSET_PATH) + # Open the file if valid. + try: + with open(LDD_ASSET_PATH, "rb") as f: + fileData = f.read() + + except IOError as e: + logger.error('ERROR: Failed to read file.') + return + + if len(fileData) < 4 or fileData[0:4] != b"LIFF": + logger.error('ERROR: Not a LIF file.') + return + + if debug: + logger.debug('EXTRACTING: Asset.lif: Please wait.') + + # Recurse through, creating a list of all the files. + packedFilesOffset = [84] # Array for non-global function-modifiable variable. + folderList = [] + fileList = [] + + # Check if this version of Python treats bytes as int or str + bytesAre = type(b'a'[0]).__name__ + + # Recuse through the archive. + recurse("", (uint32(72) + 64)) + + for i in fileList: + if 'db.lif' in i[0]: + fileData = fileData[i[1]:i[1] + i[2]] + break + if debug: + logger.debug('PROCESSING: db.lif') + if len(fileData) < 4 or fileData[0:4] != b"LIFF": + logger.error("\tERROR: Not a LIF file.") + return + if debug: + logger.debug('EXTRACTING db.lif: Please wait.') + # Recurse through, creating a list of all the files. + packedFilesOffset = [84] # Array for non-global function-modifiable variable. + folderList = [] + fileList = [] + + # Check if this version of Python treats bytes as int or str + bytesAre = type(b'a'[0]).__name__ + + # Recuse through the archive. + recurse("", (uint32(72) + 64)) + + if material_only: + for i in fileList: + material_file = r'\Materials.xml' + if i[0] in [material_file]: + m_data = fileData[i[1]:i[1] + i[2]] + return m_data + return None + + if design_ids: + g_data_map = {} + g_file_list = [r'\Primitives\LOD0\%s.g' % d for d in design_ids] + for i in fileList: + if i[0] not in g_file_list: + continue + g_data = fileData[i[1]:i[1] + i[2]] + design_id = design_ids[g_file_list.index(i[0])] + g_data_map[design_id] = g_data + + return g_data_map + else: + design_id_list = [] + for i in fileList: + if r'\Primitives\LOD0' not in i[0]: + continue + design_id = os.path.splitext(os.path.basename(i[0]))[0] + design_id_list.append(design_id) + + return design_id_list +if __name__ == '__main__': + asset_extract('99818', material_only=True) diff --git a/reader/ldd_command.py b/reader/ldd_command.py new file mode 100644 index 0000000..6ae8d69 --- /dev/null +++ b/reader/ldd_command.py @@ -0,0 +1,106 @@ +import asset_reader +from lxf_loader import LXFLoader +import maya.api.OpenMaya as OpenMaya +import maya.OpenMaya as om +import maya.OpenMayaMPx as OpenMayaMPx +import logging +import maya.cmds as mc + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +def load_part(design_ids, debug=False): + # design_ids = ['99251'] + part_data_map = asset_reader.asset_extract(design_ids=design_ids, debug=debug) + + mesh_created = [] + if not part_data_map: + return mesh_created + + for design_id in design_ids: + if debug: + logger.debug('Load LDD part %s' % design_id) + status, v_list, n_list, f_ids = LXFLoader.get_geometry(design_id, part_data_map, debug=debug) + vertices = [OpenMaya.MPoint(v_list[x], v_list[x + 1], v_list[x + 2]) for x in + range(0, len(v_list), 3)] + + polygon_counts = [3 for i in range(0, len(f_ids), 3)] + fn_mesh = OpenMaya.MFnMesh() + fn_mesh.create(vertices, polygon_counts, f_ids) + mesh_parent = fn_mesh.parent(0) + mesh_transform = OpenMaya.MFnTransform(mesh_parent) + mesh_transform.setName('lego_part_%s_mesh' % design_id) + force_element = 'initialShadingGroup' + mc.sets(mesh_transform.fullPathName(), e=True, forceElement=force_element) + if debug: + logger.debug('Load LDD part %s finished.' % design_id) + mesh_created.append(mesh_transform.fullPathName()) + + return mesh_created + + +def part_exists(design_id, debug=False): + design_id_list = asset_reader.asset_extract(debug=debug) + if design_id in design_id_list: + return True + return False + + +def get_part_list(debug=False): + return asset_reader.asset_extract(debug=debug) + + +kPluginCmdName = 'ldd' +kImportFlag = '-ip' +kImportFlagLong = '-importPart' +kExistsFlag = '-ex' +kExistsFlagLong = '-exists' +kListFlag = '-lp' +kListFlagLong = '-listParts' +kDebugFlag = '-d' +kDebugFlagLong = '-debug' + + +class LddCommand(OpenMayaMPx.MPxCommand): + def __init__(self): + """Constructor. """ + OpenMayaMPx.MPxCommand.__init__(self) + + def doIt(self, args): + return self.parseArguments(args) + + def parseArguments(self, args): + arg_data = om.MArgParser(self.syntax(), args) + + debug = False + if arg_data.isFlagSet(kDebugFlagLong): + debug = True + + if arg_data.isFlagSet(kImportFlagLong): + design_id_str = arg_data.flagArgumentString(kImportFlagLong, 0) + design_ids = [design_id_str] + if ',' in design_id_str: + design_ids = design_id_str.split(',') + + self.setResult(load_part(design_ids, debug=debug)) + + if arg_data.isFlagSet(kExistsFlagLong): + design_id = arg_data.flagArgumentString(kExistsFlagLong, 0) + self.setResult(part_exists(design_id, debug=debug)) + + if arg_data.isFlagSet(kListFlagLong): + self.setResult(sorted(get_part_list(debug=debug))) + + +def ldd_command_syntaxCreator(): + syntax = om.MSyntax() + syntax.addFlag(kImportFlag, kImportFlagLong, om.MSyntax.kString) + syntax.addFlag(kExistsFlag, kExistsFlagLong, om.MSyntax.kString) + syntax.addFlag(kListFlag, kListFlagLong, om.MSyntax.kNoArg) + syntax.addFlag(kDebugFlag, kDebugFlagLong, om.MSyntax.kNoArg) + return syntax + + +def ldd_command_creater(): + return OpenMayaMPx.asMPxPtr(LddCommand()) diff --git a/reader/lif_reader.py b/reader/lif_reader.py new file mode 100644 index 0000000..c11ee58 --- /dev/null +++ b/reader/lif_reader.py @@ -0,0 +1,94 @@ +import os +import struct + +LDD_INSTALL_PATH = r'C:\Program Files (x86)\LEGO Company\LEGO Digital Designer' +LDD_ASSET_PATH = os.path.join(LDD_INSTALL_PATH, 'Assets.lif') + + +def read(): + def uint32(fd, offset): + fd.seek(offset) + buff = fd.read(4) + if bytesAre == 'str': + return (ord(buff[0]) * 16777216) + \ + (ord(buff[1]) * 65536) + \ + (ord(buff[2]) * 256) + \ + ord(buff[3]) + else: + return (buff[0] * 16777216) + \ + (buff[1] * 65536) + \ + (buff[2] * 256) + \ + buff[3] + + def uint16(fd, offset): + fd.seek(offset) + buff = fd.read(2) + if bytesAre == "str": + return (ord(buff[0]) * 256) + \ + ord(buff[1]) + else: + return (fd.read(1) * 256) + \ + fd.read(1) + + def recurse(fd, prefix, offset): + if prefix == "": + offset += 36 + else: + folder_list.extend([prefix]) + offset += 4 + for i in range(uint32(fd, offset)): + offset += 4 + entryType = uint16(fd, offset) # 1 = directory, 2 = file + print 'xxx', entryType, offset + offset += 6 + + # Build the name.offset + entryName = os.sep + + while True: + fd.seek(offset) + read_chr = ''.join(struct.unpack('