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

@ -10,7 +10,7 @@ from app.forms import CustomUserManager
from flask_user import user_registered, current_user, user_logged_in
from flask_wtf.csrf import CSRFProtect
from flask_apscheduler import APScheduler
from app.luclient import query_cdclient, register_luclient_jinja_helpers
from app.luclient import register_luclient_jinja_helpers
from app.commands import init_db, init_accounts, load_property, gen_image_cache, gen_model_cache
from app.models import Account, AccountInvitation, AuditLog
@ -40,13 +40,12 @@ def create_app():
app.logger.info(
f"USERS::REGISTRATION User with ID {user.id} and name {user.username} Registered \
using Play Key ID {play_key_used.id} : {play_key_used.key_string}"
)
)
db.session.add(play_key_used)
db.session.commit()
else:
app.logger.info(f"USERS::REGISTRATION User with ID {user.id} and name {user.username} Registered")
@user_logged_in.connect_via(app)
def _after_login_hook(sender, user, **extra):
app.logger.info(f"{user.username} Logged in")
@ -55,7 +54,7 @@ def create_app():
@app.template_filter('ctime')
def timectime(s):
if s:
return time.ctime(s) # or datetime.datetime.fromtimestamp(s)
return time.ctime(s) # or datetime.datetime.fromtimestamp(s)
else:
return "Never"
@ -246,6 +245,7 @@ def gm_level(gm_level):
return wrapper
return decorator
def log_audit(message):
AuditLog(
account_id=current_user.id,

View File

@ -1,10 +1,8 @@
from flask import render_template, Blueprint, redirect, url_for, request, abort, current_app, flash, current_app
from flask import render_template, Blueprint, redirect, url_for, request, current_app, flash
from flask_user import login_required, current_user
import json
from datatables import ColumnDT, DataTables
import datetime
import time
from app.models import Account, AccountInvitation, db
from app.models import Account, db
from app.schemas import AccountSchema
from app import gm_level, log_audit
from app.forms import EditGMLevelForm
@ -13,6 +11,7 @@ accounts_blueprint = Blueprint('accounts', __name__)
account_schema = AccountSchema()
@accounts_blueprint.route('/', methods=['GET'])
@login_required
@gm_level(3)
@ -38,7 +37,7 @@ def edit_gm_level(id):
if current_user.id == int(id):
flash("You cannot your own GM Level", "danger")
return redirect(request.referrer if request.referrer else url_for("main.index"))
account_data = Account.query.filter(Account.id==id).first()
account_data = Account.query.filter(Account.id == id).first()
if account_data.gm_level >= 8 and current_user.gm_level == 8:
flash("You cannot edit this user's GM Level", "warning")
return redirect(request.referrer if request.referrer else url_for("main.index"))
@ -138,10 +137,10 @@ def get():
View
</a>
"""
# <a role="button" class="btn btn-danger btn btn-block"
# href='{url_for('acounts.delete', id=account["0"])}'>
# Delete
# </a>
# <a role="button" class="btn btn-danger btn btn-block"
# href='{url_for('acounts.delete', id=account["0"])}'>
# Delete
# </a>
if account["4"]:
account["4"] = '''<h2 class="far fa-times-circle text-danger"></h2>'''
@ -154,13 +153,13 @@ def get():
account["5"] = '''<h2 class="far fa-check-square text-success"></h2>'''
if account["6"]:
account["6"] = f'''<h2 class="far fa-times-circle text-danger"></h2>'''
account["6"] = '''<h2 class="far fa-times-circle text-danger"></h2>'''
else:
account["6"] = '''<h2 class="far fa-check-square text-success"></h2>'''
if current_app.config["USER_ENABLE_EMAIL"]:
if account["8"]:
account["8"] = f'''<h2 class="far fa-check-square text-success"></h2>'''
account["8"] = '''<h2 class="far fa-check-square text-success"></h2>'''
else:
account["8"] = '''<h2 class="far fa-times-circle text-danger"></h2>'''
else:
@ -175,4 +174,3 @@ def get():
del account["8"]
return data

View File

@ -1,4 +1,4 @@
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash
from flask import render_template, Blueprint, redirect, url_for, request, flash
from flask_user import login_required, current_user
from app.models import db, BugReport, CharacterInfo
from datatables import ColumnDT, DataTables
@ -8,6 +8,7 @@ from app.luclient import translate_from_locale
bug_report_blueprint = Blueprint('bug_reports', __name__)
@bug_report_blueprint.route('/<status>', methods=['GET'])
@login_required
@gm_level(3)
@ -23,7 +24,7 @@ def view(id):
if report.resoleved_by:
rb = report.resoleved_by.username
else:
rb=""
rb = ""
return render_template('bug_reports/view.html.j2', report=report, resolved_by=rb)
@ -62,12 +63,12 @@ def get(status):
]
query = None
if status=="all":
if status == "all":
query = db.session.query().select_from(BugReport)
elif status=="resolved":
query = db.session.query().select_from(BugReport).filter(BugReport.resolved_time != None)
elif status=="unresolved":
query = db.session.query().select_from(BugReport).filter(BugReport.resolved_time == None)
elif status == "resolved":
query = db.session.query().select_from(BugReport).filter(BugReport.resolved_time not None)
elif status == "unresolved":
query = db.session.query().select_from(BugReport).filter(BugReport.resolved_time is None)
else:
raise Exception("Not a valid filter")

View File

@ -1,8 +1,7 @@
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash, make_response
from flask_user import login_required, current_user
import json
from datatables import ColumnDT, DataTables
import datetime, time
import time
from app.models import CharacterInfo, CharacterXML, Account, db
from app.schemas import CharacterInfoSchema
from app.forms import RescueForm
@ -16,6 +15,7 @@ character_blueprint = Blueprint('characters', __name__)
character_schema = CharacterInfoSchema()
@character_blueprint.route('/', methods=['GET'])
@login_required
@gm_level(3)
@ -27,7 +27,7 @@ def index():
@login_required
@gm_level(3)
def approve_name(id, action):
character = CharacterInfo.query.filter(CharacterInfo.id == id).first()
character = CharacterInfo.query.filter(CharacterInfo.id == id).first()
if action == "approve":
log_audit(f"Approved ({character.id}){character.pending_name} from {character.name}")
@ -42,9 +42,12 @@ def approve_name(id, action):
elif action == "rename":
character.needs_rename = True
log_audit(f"Marked character ({character.id}){character.name} (Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename")
log_audit(
f"Marked character ({character.id}){character.name} \
(Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename")
flash(
f"Marked character {character.name} (Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename",
f"Marked character {character.name} \
(Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename",
"danger"
)
@ -67,11 +70,11 @@ def view(id):
abort(403)
return
character_json = xmltodict.parse(
CharacterXML.query.filter(
CharacterXML.id==id
).first().xml_data,
attr_prefix="attr_"
)
CharacterXML.query.filter(
CharacterXML.id == id
).first().xml_data,
attr_prefix="attr_"
)
# print json for reference
# with open("errorchar.json", "a") as file:
@ -84,8 +87,7 @@ def view(id):
# sort by items slot index
for inv in character_json["obj"]["inv"]["holdings"]["in"]:
if "i" in inv.keys() and type(inv["i"]) == list:
inv["i"] = sorted(inv["i"], key = lambda i: int(i['attr_s']))
inv["i"] = sorted(inv["i"], key=lambda i: int(i['attr_s']))
return render_template(
'character/view.html.j2',
@ -110,13 +112,14 @@ def view_xml(id):
return
character_xml = CharacterXML.query.filter(
CharacterXML.id==id
).first().xml_data
CharacterXML.id == id
).first().xml_data
response = make_response(character_xml)
response.headers.set('Content-Type', 'text/xml')
return response
@character_blueprint.route('/get_xml/<id>', methods=['GET'])
@login_required
def get_xml(id):
@ -132,8 +135,8 @@ def get_xml(id):
return
character_xml = CharacterXML.query.filter(
CharacterXML.id==id
).first().xml_data
CharacterXML.id == id
).first().xml_data
response = make_response(character_xml)
response.headers.set('Content-Type', 'attachment/xml')
@ -144,6 +147,7 @@ def get_xml(id):
)
return response
@character_blueprint.route('/restrict/<bit>/<id>', methods=['GET'])
@login_required
@gm_level(3)
@ -177,8 +181,8 @@ def rescue(id):
form = RescueForm()
character_data = CharacterXML.query.filter(
CharacterXML.id==id
).first()
CharacterXML.id == id
).first()
character_xml = ET.XML(character_data.xml_data)
for zone in character_xml.findall('.//r'):
@ -205,6 +209,7 @@ def rescue(id):
return render_template("character/rescue.html.j2", form=form)
@character_blueprint.route('/get/<status>', methods=['GET'])
@login_required
@gm_level(3)
@ -220,12 +225,12 @@ def get(status):
]
query = None
if status=="all":
if status == "all":
query = db.session.query().select_from(CharacterInfo).join(Account)
elif status=="approved":
query = db.session.query().select_from(CharacterInfo).join(Account).filter((CharacterInfo.pending_name == "") & (CharacterInfo.needs_rename == False))
elif status=="unapproved":
query = db.session.query().select_from(CharacterInfo).join(Account).filter((CharacterInfo.pending_name != "") | (CharacterInfo.needs_rename == True))
elif status == "approved":
query = db.session.query().select_from(CharacterInfo).join(Account).filter((CharacterInfo.pending_name == "") & (CharacterInfo.needs_rename is False))
elif status == "unapproved":
query = db.session.query().select_from(CharacterInfo).join(Account).filter((CharacterInfo.pending_name != "") | (CharacterInfo.needs_rename is True))
else:
raise Exception("Not a valid filter")
@ -286,6 +291,4 @@ def get(status):
if perm_map & (1 << 6):
character["6"] += "Restricted Chat</br>"
return data

View File

@ -1,19 +1,20 @@
import click
import json
from flask.cli import with_appcontext
import random, string, datetime
import random
import string
import datetime
from flask_user import current_app
from app import db
from app.models import Account, PlayKey, CharacterInfo, Property, PropertyContent, UGC
import pathlib
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.argument('drop_tables', nargs=1)
@with_appcontext
@ -45,6 +46,7 @@ def init_accounts():
return
@click.command("load_property")
@click.argument('zone')
@click.argument('player')
@ -56,7 +58,7 @@ def load_property(zone, player):
print("Character not Found")
return 404
prop = Property.query.filter(Property.owner_id==char.id).filter(Property.zone_id==zone).first()
prop = Property.query.filter(Property.owner_id == char.id).filter(Property.zone_id == zone).first()
if not prop:
print(f"Property {zone} not claimed by Character: {char.name}")
@ -95,6 +97,7 @@ def load_property(zone, player):
)
new_prop_content.save()
@click.command("gen_image_cache")
def gen_image_cache():
"""generates image cache"""
@ -113,6 +116,7 @@ def gen_image_cache():
except BE:
return print(f"Error on {file}")
@click.command("gen_model_cache")
def gen_model_cache():
"""generate model obj cache"""
@ -123,19 +127,21 @@ def gen_model_cache():
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
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):
"""helper"""
# convert to list so that we can change elements
@ -147,6 +153,7 @@ def get_cache_file(path):
return pathlib.Path(*parts)
def find_or_create_account(name, email, password, gm_level=9):
""" Find existing account or create new account """
account = Account.query.filter(Account.email == email).first()
@ -164,15 +171,16 @@ def find_or_create_account(name, email, password, gm_level=9):
db.session.commit()
play_key = PlayKey.query.filter(PlayKey.key_string == key).first()
account = Account(email=email,
username=name,
password=current_app.user_manager.password_manager.hash_password(password),
play_key_id=play_key.id,
email_confirmed_at=datetime.datetime.utcnow(),
gm_level=gm_level
)
account = Account(
email=email,
username=name,
password=current_app.user_manager.password_manager.hash_password(password),
play_key_id=play_key.id,
email_confirmed_at=datetime.datetime.utcnow(),
gm_level=gm_level
)
play_key.key_uses = 0
db.session.add(account)
db.session.add(play_key)
db.session.commit()
return # account
return # account

View File

@ -16,13 +16,13 @@ from wtforms import (
SubmitField,
validators,
IntegerField,
StringField,
SelectField
)
from wtforms.validators import DataRequired, Optional
from app.models import PlayKey
def validate_play_key(form, field):
"""Validates a field for a valid phone number
Args:
@ -85,6 +85,7 @@ class CustomRegisterForm(FlaskForm):
submit = SubmitField('Register')
class CreatePlayKeyForm(FlaskForm):
count = IntegerField(
@ -97,6 +98,7 @@ class CreatePlayKeyForm(FlaskForm):
)
submit = SubmitField('Create!')
class EditPlayKeyForm(FlaskForm):
active = BooleanField(
@ -119,7 +121,7 @@ class EditGMLevelForm(FlaskForm):
gm_level = IntegerField(
'GM Level',
widget=NumberInput(min = 0, max = 9)
widget=NumberInput(min=0, max=9)
)
submit = SubmitField('Submit')
@ -142,8 +144,8 @@ class SendMailForm(FlaskForm):
'Recipient: ',
coerce=str,
choices=[
("",""),
("0","All Characters"),
("", ""),
("0", "All Characters"),
],
validators=[validators.DataRequired()]
)
@ -162,7 +164,7 @@ class SendMailForm(FlaskForm):
attachment = SelectField(
"Attachment",
coerce=str,
choices=[(0,"No Attachment")]
choices=[(0, "No Attachment")]
)
attachment_count = IntegerField(
@ -179,7 +181,7 @@ class RescueForm(FlaskForm):
'Move to:',
coerce=str,
choices=[
("",""),
("", ""),
],
validators=[validators.DataRequired()]
)

View File

@ -1,5 +1,5 @@
from flask import render_template, Blueprint, request, url_for
from flask_user import login_required, current_user
from flask_user import login_required
from app.models import CommandLog, ActivityLog, db, Account, CharacterInfo, AuditLog
from datatables import ColumnDT, DataTables
import time
@ -7,6 +7,7 @@ from app import gm_level
log_blueprint = Blueprint('log', __name__)
@log_blueprint.route('/activities', methods=['GET'])
@login_required
@gm_level(8)
@ -40,11 +41,11 @@ def audit():
@gm_level(8)
def get_activities():
columns = [
ColumnDT(ActivityLog.id), # 0
ColumnDT(ActivityLog.character_id), # 1
ColumnDT(ActivityLog.activity), # 2
ColumnDT(ActivityLog.time), # 3
ColumnDT(ActivityLog.map_id), # 4
ColumnDT(ActivityLog.id), # 0
ColumnDT(ActivityLog.character_id), # 1
ColumnDT(ActivityLog.activity), # 2
ColumnDT(ActivityLog.time), # 3
ColumnDT(ActivityLog.map_id), # 4
]
query = db.session.query().select_from(ActivityLog)
@ -82,9 +83,9 @@ def get_activities():
@gm_level(8)
def get_commands():
columns = [
ColumnDT(CommandLog.id), # 0
ColumnDT(CommandLog.character_id), # 1
ColumnDT(CommandLog.command), # 2
ColumnDT(CommandLog.id), # 0
ColumnDT(CommandLog.character_id), # 1
ColumnDT(CommandLog.command), # 2
]
query = db.session.query().select_from(CommandLog)

View File

@ -19,6 +19,7 @@ import xml.etree.ElementTree as ET
luclient_blueprint = Blueprint('luclient', __name__)
locale = {}
@luclient_blueprint.route('/get_dds_as_png/<filename>')
@login_required
def get_dds_as_png(filename):
@ -37,7 +38,7 @@ def get_dds_as_png(filename):
with image.Image(filename=path) as img:
img.compression = "no"
img.save(filename='app/cache/'+filename.split('.')[0] + '.png')
img.save(filename='app/cache/' + filename.split('.')[0] + '.png')
return send_file(cache)
@ -69,7 +70,8 @@ def get_icon_lot(id):
)[0]
# find the asset from rendercomponent given the component id
filename = query_cdclient('select icon_asset from RenderComponent where id = ?',
filename = query_cdclient(
'select icon_asset from RenderComponent where id = ?',
[render_component_id],
one=True
)[0]
@ -117,6 +119,7 @@ def get_icon_iconid(id):
return send_file(pathlib.Path(cache).resolve())
@luclient_blueprint.route('/unknown')
@login_required
def unknown():
@ -195,6 +198,7 @@ def translate_from_locale(trans_string):
else:
return trans_string
def register_luclient_jinja_helpers(app):
@app.template_filter('get_zone_name')
@ -203,15 +207,17 @@ def register_luclient_jinja_helpers(app):
@app.template_filter('get_skill_desc')
def get_skill_desc(skill_id):
return translate_from_locale(f'SkillBehavior_{skill_id}_descriptionUI').replace(
"%(DamageCombo)", "Damage Combo: "
).replace(
"%(AltCombo)", "<br/>Skeleton Combo: "
).replace(
"%(Description)", "<br/>"
).replace(
"%(ChargeUp)", "<br/>Charge-up: "
)
return translate_from_locale(
f'SkillBehavior_{skill_id}_descriptionUI'
).replace(
"%(DamageCombo)", "Damage Combo: "
).replace(
"%(AltCombo)", "<br/>Skeleton Combo: "
).replace(
"%(Description)", "<br/>"
).replace(
"%(ChargeUp)", "<br/>Charge-up: "
)
@app.template_filter('parse_lzid')
def parse_lzid(lzid):
@ -240,7 +246,7 @@ def register_luclient_jinja_helpers(app):
one=True
)
if intermed:
name = intermed[7] if (intermed[7] != "None" and intermed[7] !="" and intermed[7] != None) else intermed[1]
name = intermed[7] if (intermed[7] != "None" and intermed[7] != "" and intermed[7] is None) else intermed[1]
return name
@app.template_filter('get_lot_rarity')
@ -252,7 +258,8 @@ def register_luclient_jinja_helpers(app):
one=True
)[0]
rarity = query_cdclient('select rarity from ItemComponent where id = ?',
rarity = query_cdclient(
'select rarity from ItemComponent where id = ?',
[render_component_id],
one=True
)
@ -302,7 +309,6 @@ def register_luclient_jinja_helpers(app):
return consolidate_stats(stats)
@app.template_filter('get_set_stats')
def get_set_stats(lot_id):
stats = query_cdclient(
@ -331,7 +337,7 @@ def register_luclient_jinja_helpers(app):
def consolidate_stats(stats):
if len(stats) > 1:
consolidated_stats = {"im": 0,"life": 0,"armor": 0, "skill": []}
consolidated_stats = {"im": 0, "life": 0, "armor": 0, "skill": []}
for stat in stats:
if stat[0]:
consolidated_stats["im"] += stat[0]
@ -340,8 +346,7 @@ def consolidate_stats(stats):
if stat[2]:
consolidated_stats["armor"] += stat[2]
if stat[3]:
consolidated_stats["skill"].append([stat[3],stat[4]])
consolidated_stats["skill"].append([stat[3], stat[4]])
stats = consolidated_stats
elif len(stats) == 1:

View File

@ -1,7 +1,6 @@
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash, request
from flask import render_template, Blueprint, redirect, url_for, flash, request
from flask_user import login_required, current_user
from app.models import db, Mail, CharacterInfo
from datatables import ColumnDT, DataTables
from app.models import Mail, CharacterInfo
from app.forms import SendMailForm
from app import gm_level, log_audit
from app.luclient import translate_from_locale, query_cdclient
@ -31,37 +30,40 @@ def send():
log_audit(f"Sending {form.subject.data}: {form.body.data} to All Characters with {form.attachment_count.data} of item {form.attachment.data}")
for character in CharacterInfo.query.all():
Mail(
sender_id = 0,
sender_name = f"[GM] {current_user.username}",
receiver_id = character.id,
receiver_name = character.name,
time_sent = time.time(),
subject = form.subject.data,
body = form.body.data,
attachment_id = 0,
attachment_lot = form.attachment.data,
attachment_count = form.attachment_count.data
sender_id=0,
sender_name=f"[GM] {current_user.username}",
receiver_id=character.id,
receiver_name=character.name,
time_sent=time.time(),
subject=form.subject.data,
body=form.body.data,
attachment_id=0,
attachment_lot=form.attachment.data,
attachment_count=form.attachment_count.data
).save()
log_audit(f"Sent {form.subject.data}: {form.body.data} to ({character.id}){character.name} with {form.attachment_count.data} of item {form.attachment.data}")
log_audit(f"Sent {form.subject.data}: \
{form.body.data} to ({character.id}){character.name} \
with {form.attachment_count.data} of item {form.attachment.data}")
else:
Mail(
sender_id = 0,
sender_name = f"[GM] {current_user.username}",
receiver_id = form.recipient.data,
receiver_name = CharacterInfo.query.filter(CharacterInfo.id == form.recipient.data).first().name,
time_sent = time.time(),
subject = form.subject.data,
body = form.body.data,
attachment_id = 0,
attachment_lot = form.attachment.data,
attachment_count = form.attachment_count.data
sender_id=0,
sender_name=f"[GM] {current_user.username}",
receiver_id=form.recipient.data,
receiver_name=CharacterInfo.query.filter(CharacterInfo.id == form.recipient.data).first().name,
time_sent=time.time(),
subject=form.subject.data,
body=form.body.data,
attachment_id=0,
attachment_lot=form.attachment.data,
attachment_count=form.attachment_count.data
).save()
log_audit(f"Sent {form.subject.data}: {form.body.data} to ({form.recipient.data}){CharacterInfo.query.filter(CharacterInfo.id == form.recipient.data).first().name} with {form.attachment_count.data} of item {form.attachment.data}")
log_audit(f"Sent {form.subject.data}: \
{form.body.data} to ({form.recipient.data}){CharacterInfo.query.filter(CharacterInfo.id == form.recipient.data).first().name} \
with {form.attachment_count.data} of item {form.attachment.data}")
flash("Sent Mail", "success")
return redirect(url_for('mail.send'))
recipients = CharacterInfo.query.all()
for character in recipients:
form.recipient.choices.append((character.id, character.name))
@ -74,7 +76,7 @@ def send():
for item in items:
name = translate_from_locale(f'Objects_{item[0]}_name')
if name == f'Objects_{item[0]}_name':
name = (item[2] if (item[2] != "None" and item[2] !="" and item[2] != None) else item[1])
name = (item[2] if (item[2] != "None" and item[2] != "" and item[2] not None) else item[1])
form.attachment.choices.append(
(
item[0],

View File

@ -1,18 +1,15 @@
from flask import render_template, Blueprint, redirect, request, send_from_directory, make_response, send_file, current_app
from flask_user import login_required, current_user
import json, glob, os
from wand import image
from flask import render_template, Blueprint, send_from_directory
from flask_user import current_user
from app.models import Account, AccountInvitation, CharacterInfo
from app.models import Account
from app.schemas import AccountSchema, CharacterInfoSchema
from app.luclient import query_cdclient
from app import gm_level
main_blueprint = Blueprint('main', __name__)
account_schema = AccountSchema()
char_info_schema = CharacterInfoSchema()
@main_blueprint.route('/', methods=['GET'])
def index():
"""Home/Index Page"""

View File

@ -1,6 +1,6 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_user import UserMixin, current_user
from flask_user import UserMixin
from wtforms import ValidationError
import logging
@ -12,6 +12,7 @@ from time import sleep
import random
import string
# retrying query to work around python trash collector
# killing connections of other gunicorn workers
class RetryingQuery(BaseQuery):
@ -46,9 +47,11 @@ class RetryingQuery(BaseQuery):
raise
self.session.rollback()
db = SQLAlchemy(query_class=RetryingQuery)
migrate = Migrate()
class PlayKey(db.Model):
__tablename__ = 'play_keys'
id = db.Column(db.Integer, primary_key=True)
@ -121,6 +124,7 @@ class PlayKey(db.Model):
db.session.commit()
db.session.refresh(self)
class Account(db.Model, UserMixin):
__tablename__ = 'accounts'
id = db.Column(
@ -200,7 +204,7 @@ class Account(db.Model, UserMixin):
@staticmethod
def get_user_by_id(*, user_id=None):
return User.query.filter(user_id == User.id).first()
return Account.query.filter(user_id == Account.id).first()
def save(self):
db.session.add(self)
@ -211,6 +215,7 @@ class Account(db.Model, UserMixin):
db.session.delete(self)
db.session.commit()
class AccountInvitation(db.Model):
__tablename__ = 'account_invites'
id = db.Column(db.Integer, primary_key=True)
@ -245,14 +250,10 @@ class AccountInvitation(db.Model):
db.session.delete(self)
db.session.commit()
@staticmethod
def get_user_by_id(*, user_id=None):
return User.query.filter(user_id == User.id).first()
return Account.query.filter(user_id == Account.id).first()
def delete(self):
db.session.delete(self)
db.session.commit()
# This table is cursed, see prop_clone_id
class CharacterInfo(db.Model):
@ -327,6 +328,7 @@ class CharacterInfo(db.Model):
db.session.delete(self)
db.session.commit()
class CharacterXML(db.Model):
__tablename__ = 'charxml'
id = db.Column(
@ -348,6 +350,7 @@ class CharacterXML(db.Model):
db.session.delete(self)
db.session.commit()
class CommandLog(db.Model):
__tablename__ = 'command_log'
id = db.Column(db.Integer, primary_key=True)
@ -364,7 +367,7 @@ class CommandLog(db.Model):
passive_deletes=True
)
command = db.Column(
command = db.Column(
mysql.VARCHAR(256),
nullable=False
)
@ -378,6 +381,7 @@ class CommandLog(db.Model):
db.session.delete(self)
db.session.commit()
class Friends(db.Model):
__tablename__ = 'friends'
player_id = db.Column(
@ -423,6 +427,7 @@ class Friends(db.Model):
db.session.delete(self)
db.session.commit()
class Leaderboard(db.Model):
__tablename__ = 'leaderboard'
id = db.Column(db.Integer, primary_key=True)
@ -472,6 +477,7 @@ class Leaderboard(db.Model):
db.session.delete(self)
db.session.commit()
class Mail(db.Model):
__tablename__ = 'mail'
id = db.Column(
@ -560,6 +566,7 @@ class Mail(db.Model):
db.session.delete(self)
db.session.commit()
class ObjectIDTracker(db.Model):
__tablename__ = 'object_id_tracker'
last_object_id = db.Column(
@ -569,6 +576,7 @@ class ObjectIDTracker(db.Model):
server_default='0'
)
class PetNames(db.Model):
__tablename__ = 'pet_names'
id = db.Column(mysql.BIGINT, primary_key=True)
@ -582,7 +590,7 @@ class PetNames(db.Model):
server_default='0'
)
owner_id = db.Column(
owner_id = db.Column(
mysql.BIGINT,
nullable=True
)
@ -605,7 +613,7 @@ class Property(db.Model):
autoincrement=False
)
owner_id = db.Column(
owner_id = db.Column(
mysql.BIGINT,
db.ForeignKey(CharacterInfo.id, ondelete='CASCADE'),
nullable=False
@ -623,7 +631,7 @@ class Property(db.Model):
nullable=False,
)
clone_id = db.Column(
clone_id = db.Column(
mysql.BIGINT(unsigned=True),
db.ForeignKey(CharacterInfo.prop_clone_id, ondelete='CASCADE'),
)
@ -763,6 +771,7 @@ class UGC(db.Model):
db.session.delete(self)
db.session.commit()
class PropertyContent(db.Model):
__tablename__ = 'properties_contents'
id = db.Column(
@ -843,6 +852,7 @@ class PropertyContent(db.Model):
db.session.delete(self)
db.session.commit()
class ActivityLog(db.Model):
__tablename__ = 'activity_log'
id = db.Column(mysql.INTEGER, primary_key=True)
@ -883,6 +893,7 @@ class ActivityLog(db.Model):
db.session.delete(self)
db.session.commit()
class BugReport(db.Model):
__tablename__ = 'bug_reports'
id = db.Column(mysql.INTEGER, primary_key=True)
@ -944,6 +955,7 @@ class BugReport(db.Model):
db.session.delete(self)
db.session.commit()
class Server(db.Model):
__tablename__ = 'servers'
id = db.Column(
@ -1016,6 +1028,7 @@ class Reports(db.Model):
db.session.delete(self)
db.session.commit()
class AuditLog(db.Model):
__tablename__ = 'audit_logs'
id = db.Column(

View File

@ -2,7 +2,6 @@ from flask import render_template, Blueprint, redirect, url_for, request, flash,
from flask_user import login_required
from app.models import PetNames, db, CharacterXML, CharacterInfo
from datatables import ColumnDT, DataTables
from app.forms import CreatePlayKeyForm, EditPlayKeyForm
from app import gm_level, log_audit, scheduler
moderation_blueprint = Blueprint('moderation', __name__)
@ -20,7 +19,7 @@ def index(status):
@gm_level(3)
def approve_pet(id):
pet_data = PetNames.query.filter(PetNames.id == id).first()
pet_data = PetNames.query.filter(PetNames.id == id).first()
pet_data.approved = 2
log_audit(f"Approved pet name {pet_data.pet_name} from {pet_data.owner_id}")
@ -34,7 +33,7 @@ def approve_pet(id):
@gm_level(3)
def reject_pet(id):
pet_data = PetNames.query.filter(PetNames.id == id).first()
pet_data = PetNames.query.filter(PetNames.id == id).first()
pet_data.approved = 0
log_audit(f"Rejected pet name {pet_data.pet_name} from {pet_data.owner_id}")
@ -55,16 +54,15 @@ def get_pets(status="all"):
]
query = None
if status=="all":
if status == "all":
query = db.session.query().select_from(PetNames)
elif status=="approved":
query = db.session.query().select_from(PetNames).filter(PetNames.approved==2)
elif status=="unapproved":
query = db.session.query().select_from(PetNames).filter(PetNames.approved==1)
elif status == "approved":
query = db.session.query().select_from(PetNames).filter(PetNames.approved == 2)
elif status == "unapproved":
query = db.session.query().select_from(PetNames).filter(PetNames.approved == 1)
else:
raise Exception("Not a valid filter")
params = request.args.to_dict()
rowTable = DataTables(params, query, columns)
@ -116,8 +114,8 @@ def get_pets(status="all"):
{CharacterInfo.query.filter(CharacterInfo.id==pet_data['3']).first().name}
</a>
"""
except Exception as e:
PetNames.query.filter(PetNames.id==id).first().delete()
except Exception:
PetNames.query.filter(PetNames.id == id).first().delete()
pet_data["0"] = "<span class='text-danger'>Deleted Refresh to make go away</span>"
pet_data["3"] = "<span class='text-danger'>Character Deleted</span>"
else:
@ -131,7 +129,7 @@ def pet_name_maintenance():
with scheduler.app.app_context():
# associate pet names to characters
current_app.logger.info("Started Pet Name Maintenance")
unassociated_pets = PetNames.query.filter(PetNames.owner_id == None).all()
unassociated_pets = PetNames.query.filter(PetNames.owner_id is None).all()
if unassociated_pets:
current_app.logger.info("Found un-associated pets")
for pet in unassociated_pets:
@ -143,11 +141,11 @@ def pet_name_maintenance():
pet.delete()
# auto-moderate based on already moderated names
unmoderated_pets = PetNames.query.filter(PetNames.approved==1).all()
unmoderated_pets = PetNames.query.filter(PetNames.approved == 1).all()
if unmoderated_pets:
current_app.logger.info("Found un-moderated Pets")
for pet in unmoderated_pets:
existing_pet = PetNames.query.filter(PetNames.approved.in_([0,2])).filter(PetNames.pet_name == pet.pet_name).first()
existing_pet = PetNames.query.filter(PetNames.approved.in_([0, 2])).filter(PetNames.pet_name == pet.pet_name).first()
if existing_pet:
pet.approved = existing_pet.approved
pet.save()

View File

@ -1,14 +1,14 @@
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash
from flask_user import login_required, current_user
from app.models import Account, AccountInvitation, PlayKey, db
from flask import render_template, Blueprint, redirect, url_for, request, flash
from flask_user import login_required
from app.models import Account, PlayKey, db
from datatables import ColumnDT, DataTables
from app.forms import CreatePlayKeyForm, EditPlayKeyForm
from app import gm_level, log_audit
play_keys_blueprint = Blueprint('play_keys', __name__)
# Key creation page
# Key creation page
@play_keys_blueprint.route('/', methods=['GET'])
@login_required
@gm_level(9)
@ -45,7 +45,7 @@ def bulk_create():
@gm_level(9)
def delete(id):
key = PlayKey.query.filter(PlayKey.id == id).first()
associated_accounts = Account.query.filter(Account.play_key_id==id).all()
# associated_accounts = Account.query.filter(Account.play_key_id==id).all()
log_audit(f"Deleted Play Key {key.key_string}")
flash(f"Deleted Play Key {key.key_string}", "danger")
key.delete()
@ -56,7 +56,7 @@ def delete(id):
@login_required
@gm_level(9)
def edit(id):
key = PlayKey.query.filter(PlayKey.id==id).first()
key = PlayKey.query.filter(PlayKey.id == id).first()
form = EditPlayKeyForm()
if form.validate_on_submit():
@ -84,7 +84,7 @@ def edit(id):
@gm_level(9)
def view(id):
key = PlayKey.query.filter(PlayKey.id == id).first()
accounts = Account.query.filter(Account.play_key_id==id).all()
accounts = Account.query.filter(Account.play_key_id == id).all()
return render_template('play_keys/view.html.j2', key=key, accounts=accounts)
@ -125,7 +125,12 @@ def get():
Delete
</a>
<div class="modal fade bd-example-modal-lg" id="delete-{play_key["1"]}-modal" tabindex="-1" role="dialog" aria-labelledby="delete-{play_key["1"]}-modalLabel" aria-hidden="true">
<div class="modal
fade bd-example-modal-lg"
id="delete-{play_key["1"]}-modal"
tabindex="-1" role="dialog"
aria-labelledby="delete-{play_key["1"]}-modalLabel"
aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content bg-dark border-primary">
<div class="modal-header">

View File

@ -5,14 +5,11 @@ from flask import (
url_for,
request,
abort,
jsonify,
send_from_directory,
make_response,
flash,
current_app
)
from flask_user import login_required, current_user
import json
from datatables import ColumnDT, DataTables
import time
from app.models import Property, db, UGC, CharacterInfo, PropertyContent, Account
@ -21,8 +18,6 @@ from app import gm_level, log_audit
from app.luclient import query_cdclient
import zlib
import xmltodict
import os
import app.pylddlib as ldd
import pathlib
@ -30,6 +25,7 @@ property_blueprint = Blueprint('properties', __name__)
property_schema = PropertySchema()
@property_blueprint.route('/', methods=['GET'])
@login_required
@gm_level(3)
@ -42,7 +38,7 @@ def index():
@gm_level(3)
def approve(id):
property_data = Property.query.filter(Property.id == id).first()
property_data = Property.query.filter(Property.id == id).first()
property_data.mod_approved = not property_data.mod_approved
@ -89,8 +85,6 @@ def approve(id):
else:
go_to = url_for('main.index')
return redirect(go_to)
@ -134,16 +128,19 @@ def get(status="all"):
]
query = None
if status=="all":
query = db.session.query().select_from(Property).join(CharacterInfo, CharacterInfo.id==Property.owner_id).join(Account)
elif status=="approved":
query = db.session.query().select_from(Property).join(CharacterInfo, CharacterInfo.id==Property.owner_id).join(Account).filter(Property.mod_approved==True).filter(Property.privacy_option==2)
elif status=="unapproved":
query = db.session.query().select_from(Property).join(CharacterInfo, CharacterInfo.id==Property.owner_id).join(Account).filter(Property.mod_approved==False).filter(Property.privacy_option==2)
if status == "all":
query = db.session.query().select_from(Property).join(CharacterInfo, CharacterInfo.id == Property.owner_id).join(Account)
elif status == "approved":
query = db.session.query().select_from(Property).join(
CharacterInfo, CharacterInfo.id == Property.owner_id
).join(Account).filter(Property.mod_approved is True).filter(Property.privacy_option == 2)
elif status == "unapproved":
query = db.session.query().select_from(Property).join(
CharacterInfo, CharacterInfo.id == Property.owner_id
).join(Account).filter(Property.mod_approved is False).filter(Property.privacy_option == 2)
else:
raise Exception("Not a valid filter")
params = request.args.to_dict()
rowTable = DataTables(params, query, columns)
@ -183,10 +180,10 @@ def get(status="all"):
if property_data["4"] == "":
property_data["4"] = query_cdclient(
'select DisplayDescription from ZoneTable where zoneID = ?',
[property_data["12"]],
one=True
)
'select DisplayDescription from ZoneTable where zoneID = ?',
[property_data["12"]],
one=True
)
if property_data["6"] == 0:
property_data["6"] = "Private"
@ -215,7 +212,7 @@ def get(status="all"):
@property_blueprint.route('/view_model/<id>/<lod>', methods=['GET'])
@login_required
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
formatted_data = [
@ -242,6 +239,7 @@ def view_model(id, lod):
lod=lod
)
property_center = {
1150: "(-17, 432, -60)",
1151: "(0, 455, -110)",
@ -256,7 +254,7 @@ property_center = {
@login_required
def view_models(id, lod):
property_content_data = PropertyContent.query.filter(
PropertyContent.property_id==id
PropertyContent.property_id == id
).order_by(PropertyContent.lot).all()
consolidated_list = []
@ -295,7 +293,7 @@ def view_models(id, lod):
}]
}
)
property_data = Property.query.filter(Property.id==id).first()
property_data = Property.query.filter(Property.id == id).first()
return render_template(
'ldd/ldd.html.j2',
property_data=property_data,
@ -304,30 +302,30 @@ def view_models(id, lod):
lod=lod
)
@property_blueprint.route('/get_model/<id>/<file_format>/<lod>', methods=['GET'])
@login_required
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]
else: # prebuild model
else: # prebuilt model
response = prebuilt(content, file_format, lod)[0]
response.headers.set('Content-Type', 'text/xml')
return response
@property_blueprint.route('/download_model/<id>', methods=['GET'])
@login_required
def download_model(id):
content = PropertyContent.query.filter(PropertyContent.id==id).first()
content = PropertyContent.query.filter(PropertyContent.id == id).first()
if content.lot == 14: # ugc model
if content.lot == 14: # ugc model
response, filename = ugc(content)
else: # prebuild model
else: # prebuilt model
response, filename = prebuilt(content, "lxfml")
response.headers.set('Content-Type', 'attachment/xml')
@ -340,7 +338,7 @@ def download_model(id):
def ugc(content):
ugc_data = UGC.query.filter(UGC.id==content.ugc_id).first()
ugc_data = UGC.query.filter(UGC.id == content.ugc_id).first()
uncompressed_lxfml = zlib.decompress(ugc_data.lxfml)
response = make_response(uncompressed_lxfml)
return response, ugc_data.filename
@ -355,7 +353,8 @@ def prebuilt(content, file_format, lod):
one=True
)[0]
# find the asset from rendercomponent given the component id
filename = query_cdclient('select render_asset from RenderComponent where id = ?',
filename = query_cdclient(
'select render_asset from RenderComponent where id = ?',
[render_component_id],
one=True
)
@ -375,7 +374,7 @@ def prebuilt(content, file_format, lod):
cache = pathlib.Path(f'app/cache/BrickModels/{filename}.lod{lod}.{file_format}')
cache.parent.mkdir(parents=True, exist_ok=True)
try:
ldd.main(str(lxfml.as_posix()), str(cache.with_suffix("").as_posix()), lod) # convert to OBJ
ldd.main(str(lxfml.as_posix()), str(cache.with_suffix("").as_posix()), lod) # convert to OBJ
except Exception as e:
current_app.logger.error(f"ERROR on {cache}:\n {e}")

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()

View File

@ -1,11 +1,13 @@
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash, request, current_app
from flask_user import login_required, current_user
from app.models import db, CharacterInfo, Account, CharacterXML, Reports
from flask import render_template, Blueprint, current_app
from flask_user import login_required
from app.models import CharacterInfo, Account, CharacterXML, Reports
from app import gm_level, scheduler
import datetime, xmltodict, json
import datetime
import xmltodict
reports_blueprint = Blueprint('reports', __name__)
@reports_blueprint.route('/', methods=['GET', 'POST'])
@login_required
@gm_level(3)
@ -13,25 +15,28 @@ def index():
reports = Reports.query.distinct(Reports.date).group_by(Reports.date).all()
return render_template('reports/index.html.j2', reports=reports)
@reports_blueprint.route('/items/by_date/<date>', methods=['GET', 'POST'])
@login_required
@gm_level(3)
def items_by_date(date):
data = Reports.query.filter(Reports.date==date).filter(Reports.report_type=="items").first().data
data = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "items").first().data
return render_template('reports/items/by_date.html.j2', data=data, date=date)
@reports_blueprint.route('/currency/by_date/<date>', methods=['GET', 'POST'])
@login_required
@gm_level(3)
def currency_by_date(date):
data = Reports.query.filter(Reports.date==date).filter(Reports.report_type=="currency").first().data
data = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "currency").first().data
return render_template('reports/currency/by_date.html.j2', data=data, date=date)
@reports_blueprint.route('/uscore/by_date/<date>', methods=['GET', 'POST'])
@login_required
@gm_level(3)
def uscore_by_date(date):
data = Reports.query.filter(Reports.date==date).filter(Reports.report_type=="uscore").first().data
data = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "uscore").first().data
return render_template('reports/uscore/by_date.html.j2', data=data, date=date)
@ -42,22 +47,22 @@ def gen_item_report():
current_app.logger.info("Start Item Report Generation")
date = datetime.date.today().strftime('%Y-%m-%d')
report = Reports.query.filter(Reports.date==date).filter(Reports.report_type=="items").first()
report = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "items").first()
# Only one report per day
if report != None:
if report not None:
current_app.logger.info(f"Item Report Already Generated for {date}")
return
char_xmls = CharacterXML.query.join(
CharacterInfo,
CharacterInfo.id==CharacterXML.id
).join(
Account,
CharacterInfo.account_id==Account.id
).filter(Account.gm_level < 3).all()
CharacterInfo,
CharacterInfo.id == CharacterXML.id
).join(
Account,
CharacterInfo.account_id == Account.id
).filter(Account.gm_level < 3).all()
report_data={}
report_data = {}
for char_xml in char_xmls:
try:
@ -66,7 +71,7 @@ def gen_item_report():
attr_prefix="attr_"
)
for inv in character_json["obj"]["inv"]["items"]["in"]:
if "i" in inv.keys() and type(inv["i"]) == list and (int(inv["attr_t"])==0 or int(inv["attr_t"])==1):
if "i" in inv.keys() and type(inv["i"]) == list and (int(inv["attr_t"]) == 0 or int(inv["attr_t"]) == 1):
for item in inv["i"]:
if item["attr_l"] in report_data:
report_data[item["attr_l"]] = report_data[item["attr_l"]] + int(item["attr_c"])
@ -96,22 +101,22 @@ def gen_currency_report():
current_app.logger.info("Start Currency Report Generation")
date = datetime.date.today().strftime('%Y-%m-%d')
report = Reports.query.filter(Reports.date==date).filter(Reports.report_type=="currency").first()
report = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "currency").first()
# Only one report per day
if report != None:
if report not None:
current_app.logger.info(f"Currency Report Already Generated for {date}")
return
characters = CharacterXML.query.join(
CharacterInfo,
CharacterInfo.id==CharacterXML.id
).join(
Account,
CharacterInfo.account_id==Account.id
).filter(Account.gm_level < 3).all()
CharacterInfo,
CharacterInfo.id == CharacterXML.id
).join(
Account,
CharacterInfo.account_id == Account.id
).filter(Account.gm_level < 3).all()
report_data={}
report_data = {}
for character in characters:
try:
@ -119,7 +124,7 @@ def gen_currency_report():
character.xml_data,
attr_prefix="attr_"
)
report_data[CharacterInfo.query.filter(CharacterInfo.id==character.id).first().name] = int(character_json["obj"]["char"]["attr_cc"])
report_data[CharacterInfo.query.filter(CharacterInfo.id == character.id).first().name] = int(character_json["obj"]["char"]["attr_cc"])
except Exception as e:
current_app.logger.error(f"REPORT::CURRENCY - ERROR PARSING CHARACTER {char_xml.id}")
current_app.logger.error(f"REPORT::CURRENCY - {e}")
@ -144,22 +149,22 @@ def gen_uscore_report():
current_app.logger.info("Start U-Score Report Generation")
date = datetime.date.today().strftime('%Y-%m-%d')
report = Reports.query.filter(Reports.date==date).filter(Reports.report_type=="uscore").first()
report = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "uscore").first()
# Only one report per day
if report != None:
if report not None:
current_app.logger.info(f"U-Score Report Already Generated for {date}")
return
characters = CharacterXML.query.join(
CharacterInfo,
CharacterInfo.id==CharacterXML.id
).join(
Account,
CharacterInfo.account_id==Account.id
).filter(Account.gm_level < 3).all()
CharacterInfo,
CharacterInfo.id == CharacterXML.id
).join(
Account,
CharacterInfo.account_id == Account.id
).filter(Account.gm_level < 3).all()
report_data={}
report_data = {}
for character in characters:
try:
@ -167,7 +172,7 @@ def gen_uscore_report():
character.xml_data,
attr_prefix="attr_"
)
report_data[CharacterInfo.query.filter(CharacterInfo.id==character.id).first().name] = int(character_json["obj"]["char"]["attr_ls"])
report_data[CharacterInfo.query.filter(CharacterInfo.id == character.id).first().name] = int(character_json["obj"]["char"]["attr_ls"])
except Exception as e:
current_app.logger.error(f"REPORT::U-SCORE - ERROR PARSING CHARACTER {char_xml.id}")
current_app.logger.error(f"REPORT::U-SCORE - {e}")

View File

@ -1,7 +1,21 @@
from flask_marshmallow import Marshmallow
from app.models import *
from app.models import (
PlayKey
PetNames
Mail
UGC
PropertyContent
Property
CharacterXML
CharacterInfo
Account
AccountInvitation
ActivityLog
CommandLog
)
ma = Marshmallow()
class PlayKeySchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = PlayKey
@ -44,7 +58,6 @@ class PropertyContentSchema(ma.SQLAlchemyAutoSchema):
ugc = ma.Nested(UGCSchema)
class PropertySchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Property

6
pylama.ini Normal file
View File

@ -0,0 +1,6 @@
[pylama]
ignore = D203, D212, D213, D406, D407, D408, D409, D100, D104, D401, F722
max_line_length = 160
[pylama:mccabe]
max-complexity = 35