Added 3D Renderer, First UI and First Settings

This commit is contained in:
2022-05-06 23:02:57 +02:00
parent acbbbc8d05
commit cf00d2b619
11 changed files with 13659 additions and 17 deletions

95
.gitignore vendored
View File

@@ -171,3 +171,98 @@ cython_debug/
# Built Visual Studio Code Extensions # Built Visual Studio Code Extensions
*.vsix *.vsix
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
.DS_Store
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Webpack
.webpack/
# Electron-Forge
out/
blender/
logs/
src/settings.xml

13
2D.py
View File

@@ -1,18 +1,11 @@
from ast import While
import csv import csv
from math import fabs
from msilib import make_id
from posixpath import split
from tkinter.filedialog import askopenfilename from tkinter.filedialog import askopenfilename
from turtle import width
import pygame import pygame
import time import time
Pixelsize = 10 Pixelsize = 10
Size = 500 Size = 500
Height = Size/2 Height = Size/2
Width = Size Width = Size
HeightMiddle = Height/2 HeightMiddle = Height/2
@@ -34,6 +27,7 @@ ele = []
thr = [] thr = []
ail = [] ail = []
try:
with open(log, newline='') as csvfile: with open(log, newline='') as csvfile:
reader = csv.DictReader(csvfile) reader = csv.DictReader(csvfile)
for row in reader: for row in reader:
@@ -42,7 +36,9 @@ with open(log, newline='') as csvfile:
ele.append(row['Ele']) ele.append(row['Ele'])
thr.append(row['Thr']) thr.append(row['Thr'])
ail.append(row['Ail']) ail.append(row['Ail'])
except:
print("Can't read Log File!")
exit()
i = 0 i = 0
while i != len(rud): while i != len(rud):
@@ -73,7 +69,6 @@ while i != len(rud):
i+=1 i+=1
splitTime = [] splitTime = []
for e in ltime: for e in ltime:
splitTime.append(e.split(":").pop(2).replace(".", "")) splitTime.append(e.split(":").pop(2).replace(".", ""))

12291
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

61
package.json Normal file
View File

@@ -0,0 +1,61 @@
{
"name": "opentx-stickexporter3d",
"productName": "opentx-stickexporter3d",
"version": "1.0.0",
"description": "My Electron application description",
"main": "src/index.js",
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"lint": "echo \"No linting configured\""
},
"keywords": [],
"author": {
"name": "Lino Schmidt",
"email": "linoschmidt@lino3d.de"
},
"license": "MIT",
"config": {
"forge": {
"packagerConfig": {},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"name": "opentx_stickexporter3d"
}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
]
},
{
"name": "@electron-forge/maker-deb",
"config": {}
},
{
"name": "@electron-forge/maker-rpm",
"config": {}
}
]
}
},
"dependencies": {
"@electron/remote": "^2.0.8",
"electron-squirrel-startup": "^1.0.0",
"log4js": "^6.4.6",
"xml-formatter": "^2.6.1"
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.63",
"@electron-forge/maker-deb": "^6.0.0-beta.63",
"@electron-forge/maker-rpm": "^6.0.0-beta.63",
"@electron-forge/maker-squirrel": "^6.0.0-beta.63",
"@electron-forge/maker-zip": "^6.0.0-beta.63",
"electron": "18.1.0"
}
}

164
src/assets/blenderScript.py Normal file
View File

@@ -0,0 +1,164 @@
import csv
import logging
import math
import sys
import bpy
import xml.etree.ElementTree as ET
settings = ET.parse("./src/settings.xml")
logger = logging.getLogger('simple_example')
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(message)s')
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(formatter)
console_handler.setLevel(logging.INFO)
logger.addHandler(console_handler)
def _map(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
settingsRoot = settings.getroot()
FPS = int(settingsRoot[0].text)
Width = int(settingsRoot[1].text)
StickDistance = _map(int(settingsRoot[2].text), 0, 100, 5, 105)
StickMode = settingsRoot[3].text
if(StickMode == "true"):
StickMode = 2
else:
StickMode = 1
lyMax = 0.436
lyMin = -0.436
lxMax = -0.436
lxMin = 0.436
ryMax = -0.436
ryMin = 0.436
rxMax = 0.436
rxMin = -0.436
logs = settingsRoot[4].text[1:][:-1].split("\"\"")
logCount = len(logs)
logNumber = 1
for log in logs:
logger.info("Lognr:" + ((str)(logNumber)) + ":")
logTime = []
rud = []
ele = []
thr = []
ail = []
try:
with open(log, newline='') as csvFile:
reader = csv.DictReader(csvFile)
for row in reader:
logTime.append(row['Time'].split(":").pop(2).replace(".", ""))
rud.append(int(row['Rud']))
ele.append(int(row['Ele']))
thr.append(int(row['Thr']))
ail.append(int(row['Ail']))
meanTime = []
i = 0
while i < len(logTime)-1:
if int(logTime[i]) > int(logTime[i+1]):
meanTime.append(60000 - int(logTime[i]) + int(logTime[i+1]))
else:
meanTime.append(int(logTime[i+1]) - int(logTime[i]))
i+=1
totalTime = 0
for e in meanTime:
totalTime+=e
frameCount = math.floor(totalTime/1000*FPS-1)
FPSxxx = 1000/FPS
except Exception as e:
print("Can't read Log!")
exit()
GimbalL = bpy.data.objects["GimbalL"]
StickL = bpy.data.objects["StickL"]
GimbalR = bpy.data.objects["GimbalR"]
StickR = bpy.data.objects["StickR"]
GimbalCoverR = bpy.data.objects["GimbalCoverR"]
TrailR = bpy.data.objects["TrailR"]
Camera = bpy.data.objects["Camera"]
Plane = bpy.data.objects["Plane.001"]
scn = bpy.context.scene
scn.render.resolution_x = Width
GimbalCoverR.location[0] = StickDistance
GimbalR.location[0] = StickDistance
TrailR.location[0] = StickDistance
Plane.location[0] = StickDistance
Camera.location[0] = StickDistance/2
Camera.data.ortho_scale = StickDistance+5
scn.render.resolution_y = int(Width/_map(StickDistance, 5, 105, 2, 21.6))
bpy.context.scene.render.filepath = settingsRoot[5].text + "\\" + log.split("/")[-1].split("\\")[-1].replace(".csv", ".mov")
scn.render.fps = 1000
scn.render.fps_base = FPSxxx
scn.frame_start = 0
scn.frame_end = frameCount+1
logger.info("Frames:" + str(frameCount+1) + ":")
frame = 0
log = 0
pastTime = 0
while frame <= frameCount:
currentTime = math.floor(FPSxxx*frame)
while currentTime >= pastTime+meanTime[log]:
pastTime+=meanTime[log]
log+=1
multiplier = (currentTime-pastTime)/meanTime[log]
ailP = _map(ail[log]+(ail[log+1]-ail[log])*multiplier, -1024, 1024, rxMin, rxMax)
eleP = _map(ele[log]+(ele[log+1]-ele[log])*multiplier, -1024, 1024, ryMin, ryMax)
rudP = _map(rud[log]+(rud[log+1]-rud[log])*multiplier, -1024, 1024, lyMin, lyMax)
thrP = _map(thr[log]+(thr[log+1]-thr[log])*multiplier, -1024, 1024, lxMin, lxMax)
bpy.context.scene.frame_set(frame)
if StickMode == "1":
StickL.rotation_euler=[0,0,0]
StickL.rotation_euler.rotate_axis("Y", ailP)
StickL.keyframe_insert(data_path="rotation_euler", index=-1)
GimbalL.rotation_euler=[0,0,0]
GimbalL.rotation_euler.rotate_axis("X", eleP)
GimbalL.keyframe_insert(data_path="rotation_euler", index=-1)
StickR.rotation_euler=[0,0,0]
StickR.rotation_euler.rotate_axis("Y", rudP)
StickR.keyframe_insert(data_path="rotation_euler", index=-1)
GimbalR.rotation_euler=[0,0,0]
GimbalR.rotation_euler.rotate_axis("X", thrP)
GimbalR.keyframe_insert(data_path="rotation_euler", index=-1)
else:
StickL.rotation_euler=[0,0,0]
StickL.rotation_euler.rotate_axis("Y", rudP)
StickL.keyframe_insert(data_path="rotation_euler", index=-1)
GimbalL.rotation_euler=[0,0,0]
GimbalL.rotation_euler.rotate_axis("X", thrP)
GimbalL.keyframe_insert(data_path="rotation_euler", index=-1)
StickR.rotation_euler=[0,0,0]
StickR.rotation_euler.rotate_axis("Y", ailP)
StickR.keyframe_insert(data_path="rotation_euler", index=-1)
GimbalR.rotation_euler=[0,0,0]
GimbalR.rotation_euler.rotate_axis("X", eleP)
GimbalR.keyframe_insert(data_path="rotation_euler", index=-1)
logger.info("Init:" + ((str)(frame)) + ":")
frame+=1
bpy.ops.render.render(animation=True)
if(logCount <= logNumber):
logger.info("Finished")
logNumber+=1

BIN
src/assets/template.blend Normal file

Binary file not shown.

29
src/index.html Normal file
View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>OpenTX StickExporter3D</title>
<link rel="stylesheet" href="./style.css"/>
</head>
<body>
<button onclick="startRender()">Start Render</button>
<p id="logNumber">Log 0/0</p>
<p id="status">Idle</p>
<hr>
<p id="fps">FPS: </p>
<p id="width">Width: </p>
<p id="stickDistance">Stick Distance: </p>
<p id="stickMode">Stick Mode: </p>
<div class="dataDiv">
<p id="log">Logs:<br/></p>
<button onclick="openLog();">Open Log</button>
</div>
<div class="dataDiv">
<p id="output">Output Folder: </p>
<button onclick="openVid();">Open Video</button>
</div>
<script src="./js/render.js"></script>
</body>
</html>

55
src/index.js Normal file
View File

@@ -0,0 +1,55 @@
const { app, BrowserWindow } = require('electron');
const path = require('path');
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
// eslint-disable-line global-require
app.quit();
}
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true
}
});
require('@electron/remote/main').initialize();
require("@electron/remote/main").enable(mainWindow.webContents);
// and load the index.html of the app.
mainWindow.loadFile(path.join(__dirname, 'index.html'));
// Open the DevTools.
// mainWindow.webContents.openDevTools();
};
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.

197
src/js/render.js Normal file
View File

@@ -0,0 +1,197 @@
var lastData = "Initialise..."
var frames = "0";
var lastFrame = "0";
var logCount = 0;
var fps = 25
var width = 540
var stickDistance = 5
var stickMode2 = true
var log = " "
var output = " "
const statusDisplay = document.getElementById("status");
const fpsDisplay = document.getElementById("fps");
const widthDistplay = document.getElementById("width");
const stickDistanceDisplay = document.getElementById("stickDistance");
const stickModeDisplay = document.getElementById("stickMode");
const logDisplay = document.getElementById("log");
const outputDisplay = document.getElementById("output");
const logNumberDisplay = document.getElementById("logNumber");
const log4js = require("log4js");
const fs = require("fs");
const formatXml = require("xml-formatter");
const {dialog} = require("@electron/remote");
log4js.configure({
appenders: {
render: {
type: "dateFile",
filename: "./logs/render",
layout: {
type: "pattern",
pattern: "%d-[%p]: %m"
},
flags: "w",
pattern: "yyyy-MM-dd.log",
alwaysIncludePattern: true,
numToKeep: 10
}
},
categories: {
default: {
appenders: ["render"],
level: "info"
}
}
});
const logger = log4js.getLogger("render");
function startRender() {
const {exec} = require("child_process");
var blenderCons = exec("\"blender/blender\" src\\assets\\template.blend --background --python src\\assets\\blenderScript.py", {maxBuffer: Infinity});
frames = "0";
lastFrame = "0";
statusDisplay.innerHTML = lastData = "Initialise...";
statusDisplay.style.color = "white";
logNumberDisplay.innerHTML = "Log 0/" + String(logCount);
blenderCons.stdout.on("data", (data) => {
var dataStr = String(data);
logger.info(dataStr);
if(dataStr.startsWith("Frames:")) {
frames = dataStr.split(":")[1];
} else if(dataStr.startsWith("Fra:")) {
lastFrame = dataStr.split(":")[1].split(" ")[0]
lastData = "Render Frame " + lastFrame + "/" + frames;
} else if(dataStr.startsWith("Finished")) {
if(lastFrame == frames) {
lastData = "Finished Render Successfully!"
statusDisplay.style.color = "white";
} else {
lastData = "Something went wrong! Check Logs."
statusDisplay.style.color = "red";
}
} else if(dataStr.includes("Blender quit")) {
if(lastData != "Finished Render Successfully!") {
lastData = "Something went wrong! Check Logs.";
statusDisplay.style.color = "red";
}
} else if(dataStr.startsWith("Init:")) {
lastData = "Initialize Frame " + dataStr.split(":")[1] + "/" + frames;
} else if(dataStr.startsWith("Lognr:")) {
logNumberDisplay.innerHTML = "Log " + dataStr.split(":")[1] + "/" + String(logCount);
}
if(statusDisplay.innerHTML != lastData) {
statusDisplay.innerHTML = lastData;
}
});
}
function getXMLChild(doc, child) {
return String(doc.getElementsByTagName(child)[0].childNodes[0].nodeValue);
}
function updateSettingDisplay() {
fpsDisplay.innerHTML = "FPS: " + String(fps);
widthDistplay.innerHTML = "Width: " + String(width);
stickDistanceDisplay.innerHTML = "Stick Distance: " + String(stickDistance);
if(stickMode2 == true) {
stickModeDisplay.innerHTML = "Stick Mode: 2";
} else {
stickModeDisplay.innerHTML = "Stick Mode: 1";
}
logDisplay.innerHTML = "Logs:<br/>" + log.substring(1).slice(0, -1).replaceAll("\"\"", "<br/>");
outputDisplay.innerHTML = "Output Folder: " + output;
logCount = log.split("\"\"").length;
logNumberDisplay.innerHTML = "Log 0/" + String(logCount);
}
function updateSettings() {
var xmlStr = '<?xml version="1.0"?><settings><fps>' + String(fps) +
'</fps><width>' + String(width) +
'</width><stickDistance>' + String(stickDistance) +
'</stickDistance><stickMode2>' + ((stickMode2)?"true":"false") +
'</stickMode2><log>' + log +
'</log><output>' + output +
'</output></settings>';
fs.writeFile("src/settings.xml", formatXml(xmlStr, {collapseContent: true}), function(err) {
if(err) {
statusDisplay.innerHTML = "Couldn't write Log! Check Logs.";
statusDisplay.style.color = "red";
logger.error(error);
}
});
}
fetch("settings.xml").then(function(response){
return response.text();
}).then(function(data){
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(data, 'text/xml');
fps = parseInt(getXMLChild(xmlDoc, "fps"));
width = parseInt(getXMLChild(xmlDoc, "width"));
stickDistance = parseInt(getXMLChild(xmlDoc, "stickDistance"));
if(getXMLChild(xmlDoc, "stickMode2") == "false") {
stickMode2 = false;
} else {
stickMode2 = true;
}
log = getXMLChild(xmlDoc, "log");
output = getXMLChild(xmlDoc, "output");
updateSettingDisplay();
}).catch(function(error) {
logger.error(error);
updateSettingDisplay();
updateSettings();
});
function openLog() {
dialog.showOpenDialog({
properties: [
"multiSelections"
],
filters: [
{
name: "TX-Logs",
extensions: [
"csv"
]
}
]
}).then(result => {
logStr = "";
result.filePaths.forEach(value => {
logStr += "\"" + String(value) + "\"";
});
log = logStr;
updateSettingDisplay();
updateSettings();
}).catch(err => {
statusDisplay.innerHTML = "Something went wrong! Check Logs.";
statusDisplay.style.color = "red";
logger.error(err);
});
}
function openVid() {
dialog.showOpenDialog({
properties: [
"openDirectory"
]
}).then(result => {
output = String(result.filePaths);
updateSettingDisplay();
updateSettings();
}).catch(err => {
statusDisplay.innerHTML = "Something went wrong! Check Logs.";
statusDisplay.style.color = "red";
logger.error(err);
});
}

11
src/style.css Normal file
View File

@@ -0,0 +1,11 @@
body {
margin: 0;
overflow: hidden;
background-color: #172336;
color: white;
font-family: Arial, Helvetica, sans-serif;
}
.dataDiv {
display: flex;
}

744
src/template.gltf Normal file

File diff suppressed because one or more lines are too long