Merge pull request #12 from DarkflameUniverse/property-viewer-v2

Property viewer v2
This commit is contained in:
Aaron Kimbrell 2022-02-11 17:51:40 -06:00 committed by GitHub
commit 87384c1b98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 146 additions and 59 deletions

View File

@ -12,7 +12,7 @@ from flask_wtf.csrf import CSRFProtect
from flask_apscheduler import APScheduler from flask_apscheduler import APScheduler
from app.luclient import query_cdclient, register_luclient_jinja_helpers from app.luclient import query_cdclient, register_luclient_jinja_helpers
from app.commands import init_db, init_accounts, load_property from app.commands import init_db, init_accounts, load_property, gen_image_cache, gen_model_cache
from app.models import Account, AccountInvitation from app.models import Account, AccountInvitation
# Instantiate Flask extensions # Instantiate Flask extensions
@ -65,6 +65,8 @@ def create_app():
app.cli.add_command(init_db) app.cli.add_command(init_db)
app.cli.add_command(init_accounts) app.cli.add_command(init_accounts)
app.cli.add_command(load_property) app.cli.add_command(load_property)
app.cli.add_command(gen_image_cache)
app.cli.add_command(gen_model_cache)
register_settings(app) register_settings(app)
register_extensions(app) register_extensions(app)

View File

@ -7,6 +7,12 @@ from app import db
from app.models import Account, PlayKey, CharacterInfo, Property, PropertyContent, UGC from app.models import Account, PlayKey, CharacterInfo, Property, PropertyContent, UGC
import pathlib import pathlib
import zlib import zlib
import os
from wand import image
from wand.exceptions import BlobError as BE
import app.pylddlib as ldd
from multiprocessing import Pool
from functools import partial
@click.command("init_db") @click.command("init_db")
@click.argument('drop_tables', nargs=1) @click.argument('drop_tables', nargs=1)
@ -89,6 +95,54 @@ def load_property(zone, player):
) )
new_prop_content.save() new_prop_content.save()
@click.command("gen_image_cache")
def gen_image_cache():
luclient = pathlib.Path('app/luclient/res')
files = [path for path in luclient.rglob("*.dds") if path.is_file()]
for file in files:
cache = get_cache_file(file).with_suffix(".png")
if not cache.exists():
try:
print(f"Convert {file.as_posix()} to {cache}")
cache.parent.mkdir(parents=True, exist_ok=True)
with image.Image(filename=str(file.as_posix())) as img:
img.compression = "no"
img.save(filename=str(cache.as_posix()))
except BE:
return print(f"Error on {file}")
@click.command("gen_model_cache")
def gen_model_cache():
luclient = pathlib.Path('app/luclient/res')
files = [path for path in luclient.rglob("*.lxfml") if path.is_file()]
pool = Pool(processes=4)
pool.map(partial(convert_lxfml_to_obj, lod=0), files)
pool.map(partial(convert_lxfml_to_obj, lod=1), files)
pool.map(partial(convert_lxfml_to_obj, lod=2), files)
def convert_lxfml_to_obj(file, lod):
mtl = get_cache_file(file).with_suffix(f".lod{lod}.mtl")
if not mtl.exists():
mtl.parent.mkdir(parents=True, exist_ok=True)
print(f"Convert LXFML {file.as_posix()} to obj and mtl @ {mtl}")
try:
ldd.main(str(file.as_posix()), str(mtl.with_suffix("").as_posix()), lod) # convert to OBJ
except Exception as e:
print(f"ERROR on {file}:\n {e}")
else:
# print(f"Already Exists: {file} with LOD {lod}")
return
def get_cache_file(path):
# convert to list so that we can change elements
parts = list(path.parts)
# replace part that matches src with dst
parts[parts.index("luclient")] = "cache"
del parts[parts.index("res")]
return pathlib.Path(*parts)
def find_or_create_account(name, email, password, gm_level=9): def find_or_create_account(name, email, password, gm_level=9):
""" Find existing account or create new account """ """ Find existing account or create new account """
@ -119,5 +173,3 @@ def find_or_create_account(name, email, password, gm_level=9):
db.session.add(play_key) db.session.add(play_key)
db.session.commit() db.session.commit()
return # account return # account

View File

@ -23,6 +23,7 @@ import zlib
import xmltodict import xmltodict
import os import os
import app.pylddlib as ldd import app.pylddlib as ldd
import pathlib
property_blueprint = Blueprint('properties', __name__) property_blueprint = Blueprint('properties', __name__)
@ -206,16 +207,16 @@ def get(status="all"):
return data return data
@property_blueprint.route('/view_model/<id>', methods=['GET']) @property_blueprint.route('/view_model/<id>/<lod>', methods=['GET'])
@login_required @login_required
def view_model(id): def view_model(id, lod):
property_content_data = PropertyContent.query.filter(PropertyContent.id==id).all() property_content_data = PropertyContent.query.filter(PropertyContent.id==id).all()
# TODO: Restrict somehow # TODO: Restrict somehow
formatted_data = [ formatted_data = [
{ {
"obj": url_for('properties.get_model', id=property_content_data[0].id, file_format='obj'), "obj": url_for('properties.get_model', id=property_content_data[0].id, file_format='obj', lod=lod),
"mtl": url_for('properties.get_model', id=property_content_data[0].id, file_format='mtl'), "mtl": url_for('properties.get_model', id=property_content_data[0].id, file_format='mtl', lod=lod),
"lot": property_content_data[0].lot, "lot": property_content_data[0].lot,
"id": property_content_data[0].id, "id": property_content_data[0].id,
"pos": [{ "pos": [{
@ -232,7 +233,8 @@ def view_model(id):
return render_template( return render_template(
'ldd/ldd.html.j2', 'ldd/ldd.html.j2',
content=formatted_data content=formatted_data,
lod=lod
) )
property_center = { property_center = {
@ -245,9 +247,9 @@ property_center = {
} }
@property_blueprint.route('/view_models/<id>', methods=['GET']) @property_blueprint.route('/view_models/<id>/<lod>', methods=['GET'])
@login_required @login_required
def view_models(id): def view_models(id, lod):
property_content_data = PropertyContent.query.filter( property_content_data = PropertyContent.query.filter(
PropertyContent.property_id==id PropertyContent.property_id==id
).order_by(PropertyContent.lot).all() ).order_by(PropertyContent.lot).all()
@ -273,8 +275,8 @@ def view_models(id):
# add new lot # add new lot
consolidated_list.append( consolidated_list.append(
{ {
"obj": url_for('properties.get_model', id=property_content_data[item].id, file_format='obj'), "obj": url_for('properties.get_model', id=property_content_data[item].id, file_format='obj', lod=lod),
"mtl": url_for('properties.get_model', id=property_content_data[item].id, file_format='mtl'), "mtl": url_for('properties.get_model', id=property_content_data[item].id, file_format='mtl', lod=lod),
"lot": property_content_data[item].lot, "lot": property_content_data[item].lot,
"id": property_content_data[item].id, "id": property_content_data[item].id,
"pos": [{ "pos": [{
@ -293,18 +295,20 @@ def view_models(id):
'ldd/ldd.html.j2', 'ldd/ldd.html.j2',
property_data=property_data, property_data=property_data,
content=consolidated_list, content=consolidated_list,
center=property_center[property_data.zone_id] center=property_center[property_data.zone_id],
lod=lod
) )
@property_blueprint.route('/get_model/<id>/<file_format>', methods=['GET']) @property_blueprint.route('/get_model/<id>/<file_format>/<lod>', methods=['GET'])
@login_required @login_required
def get_model(id, file_format): def get_model(id, file_format, lod):
content = PropertyContent.query.filter(PropertyContent.id==id).first() content = PropertyContent.query.filter(PropertyContent.id==id).first()
if not(0 <= int(lod) <= 2):
abort(404)
if content.lot == 14: # ugc model if content.lot == 14: # ugc model
response = ugc(content)[0] response = ugc(content)[0]
else: # prebuild model else: # prebuild model
response = prebuilt(content, file_format)[0] response = prebuilt(content, file_format, lod)[0]
response.headers.set('Content-Type', 'text/xml') response.headers.set('Content-Type', 'text/xml')
return response return response
@ -337,7 +341,7 @@ def ugc(content):
return response, ugc_data.filename return response, ugc_data.filename
def prebuilt(content, file_format): def prebuilt(content, file_format, lod):
# translate LOT to component id # translate LOT to component id
# we need to get a type of 2 because reasons # we need to get a type of 2 because reasons
render_component_id = query_cdclient( render_component_id = query_cdclient(
@ -357,7 +361,7 @@ def prebuilt(content, file_format):
return f"No filename for LOT {content.lot}" return f"No filename for LOT {content.lot}"
if file_format == "lxfml": if file_format == "lxfml":
lxfml = f'app/luclient/res/BrickModels/{filename.split(".")[0]}.lxfml' lxfml = pathilob.Path(f'app/luclient/res/BrickModels/{filename.split(".")[0]}.lxfml')
with open(lxfml, 'r') as file: with open(lxfml, 'r') as file:
lxfml_data = file.read() lxfml_data = file.read()
# print(lxfml_data) # print(lxfml_data)
@ -365,21 +369,15 @@ def prebuilt(content, file_format):
elif file_format in ["obj", "mtl"]: elif file_format in ["obj", "mtl"]:
cache = f"app/cache/{filename}.{file_format}" cache = f"app/cache/BrickModels/{filename}.lod{lod}.{file_format}"
if os.path.exists(cache): if not os.path.exists(cache):
with open(cache, 'r') as file:
cache_data = file.read()
response = make_response(cache_data)
else:
lxfml = f'app/luclient/res/BrickModels/{filename.split(".")[0]}.lxfml' lxfml = f'app/luclient/res/BrickModels/{filename.split(".")[0]}.lxfml'
ldd.main(lxfml, cache.split('.')[0]) # convert to OBJ ldd.main(lxfml, cache.split('.')[0]) # convert to OBJ
if os.path.exists(cache): with open(cache, 'r') as file:
with open(cache, 'r') as file: cache_data = file.read()
cache_data = file.read() response = make_response(cache_data)
response = make_response(cache_data)
else: else:
raise(Exception("INVALID FILE FORMAT")) raise(Exception("INVALID FILE FORMAT"))

View File

@ -30,7 +30,7 @@ if sys.version_info < (3, 0):
sys.setdefaultencoding('utf-8') sys.setdefaultencoding('utf-8')
PRIMITIVEPATH = '/Primitives/' PRIMITIVEPATH = '/Primitives/'
GEOMETRIEPATH = PRIMITIVEPATH + 'LOD0/' GEOMETRIEPATH = PRIMITIVEPATH
DECORATIONPATH = '/Decorations/' DECORATIONPATH = '/Decorations/'
MATERIALNAMESPATH = '/MaterialNames/' MATERIALNAMESPATH = '/MaterialNames/'
@ -238,10 +238,10 @@ class Scene:
for childnode in node.childNodes: for childnode in node.childNodes:
if childnode.nodeName == 'BrickSet': if childnode.nodeName == 'BrickSet':
self.Version = str(childnode.getAttribute('version')) self.Version = str(childnode.getAttribute('version'))
elif node.nodeName == 'Cameras': # elif node.nodeName == 'Cameras':
for childnode in node.childNodes: # for childnode in node.childNodes:
if childnode.nodeName == 'Camera': # if childnode.nodeName == 'Camera':
self.Scenecamera.append(SceneCamera(node=childnode)) # self.Scenecamera.append(SceneCamera(node=childnode))
elif node.nodeName == 'Bricks': elif node.nodeName == 'Bricks':
for childnode in node.childNodes: for childnode in node.childNodes:
if childnode.nodeName == 'Brick': if childnode.nodeName == 'Brick':
@ -720,8 +720,10 @@ class Converter:
start_time = time.time() start_time = time.time()
out = open(filename + ".obj.tmp", "w+") out = open(filename + ".obj.tmp", "w+")
out.truncate(0)
out.write("mtllib " + filename + ".mtl" + '\n\n') out.write("mtllib " + filename + ".mtl" + '\n\n')
outtext = open(filename + ".mtl.tmp", "w+") outtext = open(filename + ".mtl.tmp", "w+")
outtext.truncate(0)
total = len(self.scene.Bricks) total = len(self.scene.Bricks)
current = 0 current = 0
@ -823,13 +825,13 @@ class Converter:
sys.stdout.write('%s\r' % (' ')) sys.stdout.write('%s\r' % (' '))
# print("--- %s seconds ---" % (time.time() - start_time)) # print("--- %s seconds ---" % (time.time() - start_time))
def setDBFolderVars(dbfolderlocation): def setDBFolderVars(dbfolderlocation, lod):
global PRIMITIVEPATH global PRIMITIVEPATH
global GEOMETRIEPATH global GEOMETRIEPATH
global DECORATIONPATH global DECORATIONPATH
global MATERIALNAMESPATH global MATERIALNAMESPATH
PRIMITIVEPATH = os.path.join(dbfolderlocation, 'Primitives', '') PRIMITIVEPATH = os.path.join(dbfolderlocation, 'Primitives', '')
GEOMETRIEPATH = os.path.join(dbfolderlocation, 'brickprimitives', 'lod0', '') GEOMETRIEPATH = os.path.join(dbfolderlocation, 'brickprimitives', f'lod{lod}', '')
DECORATIONPATH = os.path.join(dbfolderlocation, 'Decorations', '') DECORATIONPATH = os.path.join(dbfolderlocation, 'Decorations', '')
MATERIALNAMESPATH = os.path.join(dbfolderlocation, 'MaterialNames', '') MATERIALNAMESPATH = os.path.join(dbfolderlocation, 'MaterialNames', '')
# print(MATERIALNAMESPATH) # print(MATERIALNAMESPATH)
@ -880,7 +882,7 @@ def progress(count, total, status='', suffix = ''):
sys.stdout.write('Progress: [%s] %s%s %s %s\r' % (bar, percents, '%', suffix, status)) sys.stdout.write('Progress: [%s] %s%s %s %s\r' % (bar, percents, '%', suffix, status))
sys.stdout.flush() sys.stdout.flush()
def main(lxf_filename, obj_filename): def main(lxf_filename, obj_filename, lod="2"):
# print("- - - pylddlib - - -") # print("- - - pylddlib - - -")
# print(" _ ") # print(" _ ")
# print(" [_]") # print(" [_]")
@ -890,10 +892,11 @@ def main(lxf_filename, obj_filename):
# print(" [=|=]") # print(" [=|=]")
# print("") # print("")
# print("- - - - - - - - - - - -") # print("- - - - - - - - - - - -")
global GEOMETRIEPATH
GEOMETRIEPATH = GEOMETRIEPATH + f"LOD{lod}/"
converter = Converter() converter = Converter()
# print("Found DB folder. Will use this instead of db.lif!") # print("Found DB folder. Will use this instead of db.lif!")
setDBFolderVars(dbfolderlocation = "app/luclient/res/") setDBFolderVars(dbfolderlocation = "app/luclient/res/", lod=lod)
converter.LoadDBFolder(dbfolderlocation = "app/luclient/res/") converter.LoadDBFolder(dbfolderlocation = "app/luclient/res/")
converter.LoadScene(filename=lxf_filename) converter.LoadScene(filename=lxf_filename)
converter.Export(filename=obj_filename) converter.Export(filename=obj_filename)

View File

@ -467,16 +467,16 @@
constructor(designID, database){ constructor(designID, database){
this.designID = designID this.designID = designID
this.Parts = [] this.Parts = []
this.studsFields2D = [] this.studsFields2D = []
let GeometryLocation = `${designID}.g` let lod = {{ lod }}
let GeometryLocation = `lod${lod}/${designID}.g`
let PrimitiveLocation = `${designID}.xml` let PrimitiveLocation = `${designID}.xml`
let GeometryCount = 0 let GeometryCount = 0
while (GeometryLocation in database.filelist) { while (GeometryLocation in database.filelist) {
this.Parts[GeometryCount] = new GeometryReader(database.filelist[GeometryLocation].read()) this.Parts[GeometryCount] = new GeometryReader(database.filelist[GeometryLocation].read())
GeometryCount = GeometryCount + 1 GeometryCount = GeometryCount + 1
GeometryLocation = `${designID}.g${GeometryCount}` GeometryLocation = `lod${lod}/${designID}.g${GeometryCount}`
} }
let primitive = new Primitive(database.filelist[PrimitiveLocation].read()) let primitive = new Primitive(database.filelist[PrimitiveLocation].read())
this.Partname = primitive.Designname this.Partname = primitive.Designname
@ -1019,7 +1019,7 @@
return self.filelist[filename]; return self.filelist[filename];
} }
parse(dburl) { parse(dburl, folder="") {
let self = this; let self = this;
let xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.open('GET', dburl, false); xhr.open('GET', dburl, false);
@ -1038,10 +1038,16 @@
let obj = data[i]; let obj = data[i];
if (obj.type == 'directory'){ if (obj.type == 'directory'){
// parse subdirs // parse subdirs
self.parse(dburl + obj.name + '/') self.parse(dburl + obj.name + '/', obj.name)
} }
else if (obj.type == 'file'){ else if (obj.type == 'file'){
self.filelist[obj.name] = new DBURLFile(dburl + obj.name, obj.name)
if (folder.includes("lod")){
self.filelist[`${folder}/${obj.name}`] = new DBURLFile(dburl + obj.name, obj.name)
} else {
self.filelist[obj.name] = new DBURLFile(dburl + obj.name, obj.name)
}
} }
else { else {
console.log('Strange object parsed: ' + obj.type) console.log('Strange object parsed: ' + obj.type)
@ -1063,7 +1069,7 @@
let lxfml_file_list = [ let lxfml_file_list = [
{% for model in content %} {% for model in content %}
{% if model.lot == 14 %} {% if model.lot == 14 %}
"{{url_for('properties.get_model', id=model.id, file_format='lxfml')}}"{{ ", " if not loop.last else "" }} "{{url_for('properties.get_model', id=model.id, file_format='lxfml', lod=lod)}}"{{ ", " if not loop.last else "" }}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
] ]

View File

@ -81,11 +81,24 @@
</div> </div>
{% else %} {% else %}
<br/> <br/>
<h5 class="text-center">Render Quality</h5>
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<a role="button" class="btn btn-primary btn-block" <a role="button" class="btn btn-primary btn-block"
href='{{ url_for('properties.view_models', id=property.id) }}'> href='{{ url_for('properties.view_models', id=property.id, lod=0) }}'>
Render Property High (0)
</a>
</div>
<div class="col text-center">
<a role="button" class="btn btn-primary btn-block"
href='{{ url_for('properties.view_models', id=property.id, lod=1) }}'>
Med (1)
</a>
</div>
<div class="col text-center">
<a role="button" class="btn btn-primary btn-block"
href='{{ url_for('properties.view_models', id=property.id, lod=2) }}'>
Low (2)
</a> </a>
</div> </div>
</div> </div>

View File

@ -44,21 +44,12 @@
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<a role="button" class="btn btn-primary btn-block" <a role="button" class="btn btn-primary btn-block"
href='{{ url_for('properties.get_model', id=item.id, file_format="lxfml") }}'> href='{{ url_for('properties.get_model', id=item.id, file_format="lxfml", lod=0) }}'>
View Model XML View Model XML
</a> </a>
</div> </div>
</div> </div>
<br/> <br/>
<div class="row">
<div class="col text-center">
<a role="button" class="btn btn-primary btn-block"
href='{{ url_for('properties.view_model', id=item.id) }}'>
Render Model
</a>
</div>
</div>
<br/>
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<a role="button" class="btn btn-primary btn-block" <a role="button" class="btn btn-primary btn-block"
@ -67,5 +58,27 @@
</a> </a>
</div> </div>
</div> </div>
<br/>
<h5 class="text-center">Render Quality</h5>
<div class="row">
<div class="col text-center">
<a role="button" class="btn btn-primary btn-block"
href='{{ url_for('properties.view_model', id=item.id, lod=0) }}'>
High (0)
</a>
</div>
<div class="col text-center">
<a role="button" class="btn btn-primary btn-block"
href='{{ url_for('properties.view_model', id=item.id, lod=1) }}'>
Med (1)
</a>
</div>
<div class="col text-center">
<a role="button" class="btn btn-primary btn-block"
href='{{ url_for('properties.view_model', id=item.id, lod=2) }}'>
Low (2)
</a>
</div>
</div>
</div> </div>
</div> </div>