added webpack dev server, slightly improved ui and render preview

This commit is contained in:
2022-06-13 01:15:17 +02:00
parent 7ea3b514ce
commit 6f6c84dc46
19 changed files with 722 additions and 869 deletions

5
.gitignore vendored
View File

@@ -264,4 +264,7 @@ out/
output/
blender-win/
blender-linux/
blender-linux/
blender/
index.build.js

View File

@@ -1,16 +1,15 @@
from ast import Str
import csv
from importlib.resources import path
import logging
import math
import sys
import time
import bpy
import xml.etree.ElementTree as ET
argv = sys.argv
argv = argv[argv.index("--") + 1:]
settings = ET.parse(argv[0])
logger = logging.getLogger('simple_example')
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(message)s')
@@ -19,20 +18,16 @@ 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
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
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
@@ -41,128 +36,175 @@ ryMax = -0.436
ryMin = 0.436
rxMax = 0.436
rxMin = -0.436
logs = settingsRoot[4].text[1:][:-1].split("\"\"")
logCount = len(logs)
logNumber = 1
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
for log in logs:
logger.info("Lognr:" + ((str)(logNumber)) + ":")
logger.info("Blender started successfully!")
while True:
command = input("Waiting for command: ")
logTime = []
rud = []
ele = []
thr = []
ail = []
time.sleep(0.5)
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']))
settingsRoot = ET.parse(argv[0]+"/settings.xml").getroot()
StickMode = settingsRoot[3].text
if(StickMode == "true"):
StickMode = 2
else:
StickMode = 1
fps = int(settingsRoot[0].text)
width = int(settingsRoot[1].text)
StickDistance = _map(int(settingsRoot[2].text), 0, 100, 5, 105)
logs = settingsRoot[4].text[1:][:-1].split("\"\"")
output = settingsRoot[5].text
if(command == "startRendering"):
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
logCount = len(logs)
logNumber = 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
for log in logs:
logger.info("Lognr:" + ((str)(logNumber)) + ":")
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
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:
logger.error("Can't read Log: " + (Str)(e))
bpy.context.scene.render.image_settings.file_format = 'FFMPEG'
bpy.context.scene.render.ffmpeg.format = "QUICKTIME"
bpy.context.scene.render.ffmpeg.codec = "QTRLE"
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 = output + "\\" + 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
logger.info("Frames:" + str(frameCount) + ":")
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
bpy.ops.render.render(animation=True)
if(logCount <= logNumber):
logger.info("Finished")
logNumber+=1
elif(command == "getRender"):
bpy.context.scene.render.image_settings.file_format = 'PNG'
bpy.context.scene.frame_set(0)
bpy.context.scene.render.filepath = argv[0] + "\\render.png"
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))
StickL.rotation_euler=[0,0,0]
StickL.rotation_euler.rotate_axis("Y", 0.436)
StickL.keyframe_insert(data_path="rotation_euler", index=-1)
GimbalL.rotation_euler=[0,0,0]
GimbalL.rotation_euler.rotate_axis("X", 0.236)
GimbalL.keyframe_insert(data_path="rotation_euler", index=-1)
StickR.rotation_euler=[0,0,0]
StickR.rotation_euler.rotate_axis("Y", -0.056)
StickR.keyframe_insert(data_path="rotation_euler", index=-1)
GimbalR.rotation_euler=[0,0,0]
GimbalR.rotation_euler.rotate_axis("X", -0.436)
GimbalR.keyframe_insert(data_path="rotation_euler", index=-1)
bpy.ops.render.render(write_still=True)

View File

@@ -3,7 +3,6 @@ import ESLintPlugin from "eslint-webpack-plugin";
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
export default {
mode: 'production',
module: {
rules: [
{

View File

@@ -0,0 +1,11 @@
import baseConfig from "./webpack.base.config.js";
import {merge} from 'webpack-merge';
export default merge(baseConfig, {
mode: 'development',
target: 'electron-main',
entry: './src/index.ts',
output: {
filename: '../index.build.js'
},
});

View File

@@ -2,9 +2,10 @@ import baseConfig from "./webpack.base.config.js";
import {merge} from 'webpack-merge';
export default merge(baseConfig, {
mode: 'production',
target: 'electron-main',
entry: './src/index.ts',
output: {
filename: 'index.js'
filename: '../index.build.js'
},
});

View File

@@ -0,0 +1,31 @@
import baseConfig from "./webpack.base.config.js";
import {merge} from 'webpack-merge';
import { spawn } from 'child_process';
const host = process.env.HOST || 'localhost';
const port = process.env.PORT || 3000;
export default merge(baseConfig, {
mode: 'development',
target: 'electron-renderer',
entry: './src/renderer.tsx',
output: {
filename: 'renderer.js',
},
devServer: {
compress: true,
hot: true,
host,
port,
onBeforeSetupMiddleware() {
console.log('Starting Main Process...');
spawn('npm', ['run', 'start:main'], {
shell: true,
env: process.env,
stdio: 'inherit',
})
.on('close', (code) => process.exit(code))
.on('error', (spawnError) => console.error(spawnError));
},
},
});

View File

@@ -2,6 +2,7 @@ import baseConfig from "./webpack.base.config.js";
import {merge} from 'webpack-merge';
export default merge(baseConfig, {
mode: 'production',
target: 'electron-renderer',
entry: './src/renderer.tsx',
output: {

706
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,13 +3,15 @@
"name": "stickexportertx",
"productName": "StickExporterTX",
"description": "3D stick exporter for EdgeTX/OpenTX logs",
"main": "src/.webpack/index.js",
"main": "src/index.build.js",
"scripts": {
"build:main": "webpack --config configs/webpack.main.config.babel.js",
"build:renderer": "webpack --config configs/webpack.renderer.config.babel.js",
"build": "npm run build:main && npm run build:renderer",
"start": "npm run build && electron .",
"build:app": "npm run build && electron-builder build"
"build:main": "cross-env NODE_ENV=production webpack --config configs/webpack.main.prod.config.babel.js",
"build:renderer": "cross-env NODE_ENV=production webpack --config configs/webpack.renderer.prod.config.babel.js",
"build": "cross-env npm run build:main && cross-env npm run build:renderer",
"bundle": "cross-env npm run build && electron-builder build",
"start:renderer": "cross-env NODE_ENV=development webpack serve --config configs/webpack.renderer.dev.config.babel.js",
"start:main": "cross-env NODE_ENV=development webpack --config configs/webpack.main.dev.config.babel.js && cross-env NODE_ENV=development electron .",
"start": "cross-env npm run start:renderer"
},
"keywords": [],
"author": {
@@ -18,33 +20,6 @@
"url": "https://link.lino3d.de"
},
"license": "MIT",
"config": {
"forge": {
"packagerConfig": {},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"name": "stickexportertx"
}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
]
},
{
"name": "@electron-forge/maker-deb",
"config": {}
},
{
"name": "@electron-forge/maker-rpm",
"config": {}
}
]
}
},
"build": {
"appId": "de.lino3d.stickexportertx",
"productName": "StickExporterTX",
@@ -54,7 +29,8 @@
"package.json"
],
"extraFiles": [
"assets/template.blend"
"assets/template.blend",
"assets/blenderScript.py"
],
"win": {
"icon": "icon.png",
@@ -63,7 +39,7 @@
],
"extraFiles": [
{
"from": "assets/blender-win/",
"from": "assets/blender/",
"to": "assets/blender/",
"filter": [
"**/*"
@@ -86,7 +62,7 @@
],
"extraFiles": [
{
"from": "assets/blender-linux/",
"from": "assets/blender/",
"to": "assets/blender/",
"filter": [
"**/*"
@@ -102,7 +78,7 @@
],
"extraFiles": [
{
"from": "assets/blender-mac/",
"from": "assets/blender/",
"to": "assets/blender/",
"filter": [
"**/*"
@@ -124,6 +100,7 @@
"@electron/remote": "^2.0.8",
"electron-log": "^4.4.7",
"electron-squirrel-startup": "^1.0.0",
"electron-updater": "^5.0.1",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"xml-formatter": "^2.6.1"
@@ -144,6 +121,7 @@
"@typescript-eslint/parser": "^5.27.0",
"@vercel/webpack-asset-relocator-loader": "^1.7.2",
"babel-loader": "^8.2.5",
"cross-env": "^7.0.3",
"css-loader": "^6.7.1",
"electron": "18.1.0",
"electron-builder": "^23.0.3",
@@ -153,7 +131,6 @@
"eslint-plugin-react-hooks": "^4.5.0",
"eslint-webpack-plugin": "^3.1.1",
"fork-ts-checker-webpack-plugin": "^7.2.11",
"html-webpack-plugin": "^5.5.0",
"node-loader": "^2.0.0",
"sass": "^1.52.1",
"sass-loader": "^13.0.0",

View File

@@ -0,0 +1,145 @@
import { blenderPath, blenderScriptPath, dataPath, templatePath } from "./paths";
import {spawn} from "child_process";
import logger from "./logger";
import { setBlenderLoading, setBlenderStatus } from "./ui/menu";
import { setLogNumber, setStatus } from "./ui/mainSide";
import {imageLoading, imageLoaded} from "./ui/settingsSide";
const blenderStartString = [
templatePath,
"--background",
"--python",
blenderScriptPath,
"--",
dataPath.replaceAll("\\", "/")
]
let blenderConsole = spawn(blenderPath, blenderStartString);
let readyToAcceptCommand = false;
let renderingPicture = false;
let renderingVideo = false;
let waitingForRender = false;
function startBlender() {
let frames = "0";
let lastFrame = "0";
blenderConsole.stdout.on('data', function(data) {
const dataStr = data.toString();
logger.info("Blender: " + dataStr);
if (dataStr.includes("Blender started successfully")) {
renderingPicture = false;
renderingVideo = false;
setBlenderStatus("Started");
}
if (dataStr.includes("Blender quit")) {
if(renderingPicture) {
logger.errorMSG("Rendering preview Failed!");
} else if(renderingVideo) {
logger.errorMSG("Rendering video Failed!");
}
readyToAcceptCommand = false;
renderingPicture = false;
renderingVideo = false;
setBlenderStatus("Restarting");
setBlenderLoading(true);
restartBlender();
}
if(dataStr.includes("Frames:")) {
frames = dataStr.split(":")[1];
renderingVideo = true;
readyToAcceptCommand = false;
setBlenderStatus("Rendering");
setBlenderLoading(true);
}
if(dataStr.includes("Fra:") && renderingVideo) {
lastFrame = dataStr.split(":")[1].split(" ")[0];
setStatus("Rendering Frame " + lastFrame + "/" + frames);
}
if(dataStr.includes("Finished") && renderingVideo) {
if(lastFrame == frames) {
setStatus("Finished Render Successfully!");
} else {
logger.errorMSG("Render Failed!");
}
}
if(dataStr.includes("Init:") && renderingVideo) {
setStatus("Initialize Frame " + dataStr.split(":")[1] + "/" + frames);
}
if(dataStr.includes("Lognr:") && renderingVideo) {
setLogNumber(dataStr.split(":")[1]);
}
if(dataStr.includes("Waiting for command")) {
if(renderingPicture) {
imageLoaded();
}
if(!waitingForRender) {
readyToAcceptCommand = true;
renderingPicture = false;
renderingVideo = false;
setBlenderStatus("Ready");
setBlenderLoading(false);
} else {
waitingForRender = false;
renderingPicture = true;
blenderConsole.stdin.write("getRender\n");
setBlenderStatus("Rendering");
setBlenderLoading(true);
imageLoading();
}
}
});
blenderConsole.stderr.on('data', function(data:string) {
logger.errorMSG("Blender: " + data);
});
}
function restartBlender() {
blenderConsole.kill();
blenderConsole = spawn(blenderPath, blenderStartString);
startBlender();
}
enum blenderCmd {
getRender,
startRendering,
stopRendering,
}
function blender(command:blenderCmd) {
if(command === blenderCmd.getRender) {
if(readyToAcceptCommand) {
readyToAcceptCommand = false;
renderingPicture = true;
imageLoading();
blenderConsole.stdin.write("getRender\n");
} else {
waitingForRender = true;
}
} else if(command === blenderCmd.startRendering) {
if(readyToAcceptCommand) {
readyToAcceptCommand = false;
renderingVideo = true;
blenderConsole.stdin.write("startRendering\n");
}
} else if(command === blenderCmd.stopRendering) {
restartBlender();
readyToAcceptCommand = false;
renderingPicture = false;
renderingVideo = false;
}
}
export {
blender,
blenderCmd,
startBlender,
renderingPicture
}

View File

@@ -4,6 +4,6 @@ import {app} from '@electron/remote';
export const dataPath = app.getPath('userData');
export const SettingPath = path.join(dataPath, "settings.xml");
export const blenderPath = path.join("assets", "blender-win", "blender");
export const blenderPath = path.join("assets", "blender", "blender");
export const templatePath = path.join("assets", "template.blend");
export const blenderScriptPath = path.join("assets", "blenderScript.py");

View File

@@ -1,43 +0,0 @@
import {blenderPath, blenderScriptPath, SettingPath, templatePath} from "./paths";
import {exec} from "child_process";
import logger from "./logger";
import React from "react";
function Render(setStatusDisplay:React.Dispatch<React.SetStateAction<string>>, setLogNumber:React.Dispatch<React.SetStateAction<string>>) {
const blenderCons = exec('"' + blenderPath + '" "' + templatePath + '" --background --python "' + blenderScriptPath + '" -- "' + SettingPath.replaceAll('\\', '/') + '"', {maxBuffer: Infinity});
let frames = "0";
let lastFrame = "0";
let lastData = "Initializing...";
setStatusDisplay(lastData);
blenderCons.stdout?.on("data", (data:string) => {
logger.info(data);
if(data.startsWith("Frames:")) {
frames = data.split(":")[1];
} else if(data.startsWith("Fra:")) {
lastFrame = data.split(":")[1].split(" ")[0]
lastData = "Render Frame " + lastFrame + "/" + frames;
} else if(data.startsWith("Finished")) {
if(lastFrame == frames) {
lastData = "Finished Render Successfully!"
} else {
logger.errorMSG("Render Failed!");
}
} else if(data.includes("Blender quit")) {
if(lastData != "Finished Render Successfully!") {
logger.errorMSG("Render Failed!");
}
} else if(data.startsWith("Init:")) {
lastData = "Initialize Frame " + data.split(":")[1] + "/" + frames;
} else if(data.startsWith("Lognr:")) {
setLogNumber(data.split(":")[1]);
}
setStatusDisplay(lastData);
});
}
export default Render;

View File

@@ -3,11 +3,16 @@ import { dialog } from "@electron/remote";
import { settingList, updateSettings } from "../settings";
import logger from "../logger";
import {exec} from "child_process";
import Render from "../render";
import {blender, blenderCmd} from "../blender-controller";
let setStatus:React.Dispatch<React.SetStateAction<string>>;
let setLogNumber:React.Dispatch<React.SetStateAction<string>>;
function MainSide() {
const [status, setStatus] = useState("Idle");
const [logNumber, setLogNumber] = useState("0");
const [status, setStatusInner] = useState("Idle");
setStatus = setStatusInner;
const [logNumber, setLogNumberInner] = useState("0");
setLogNumber = setLogNumberInner;
const [logs, setLogs] = useState(settingList.log);
const [output, setOutput] = useState(settingList.output);
const [logTable, setLogTable] = useState(logs.substring(1).slice(0, -1).split('""').map((log, index) => {
@@ -25,7 +30,12 @@ function MainSide() {
return (
<div id="content">
<button onClick={() => Render(setStatus, setLogNumber)}>Start Render</button>
<button id="start-render" onClick={() => blender(blenderCmd.startRendering)}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
{/* <!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z"/>
</svg>
</button>
<p>{"Log " + logNumber + "/" + String(settingList.log.split("\"\"").length)}</p>
<div className="dataDiv">
<p>{status}</p>
@@ -95,4 +105,8 @@ function openOutputFolder() {
}
}
export default MainSide;
export default MainSide;
export {
setStatus,
setLogNumber
}

View File

@@ -1,4 +1,4 @@
import React from "react";
import React, {useState} from "react";
import { openSide, Side } from "../../renderer";
const UpdateButton = () => (
@@ -25,12 +25,46 @@ const OtherSideButtons = () => (
<button id="settings-back" onClick={() => openSide(Side.Main)}>Back</button>
)
const Menu = ({updateAvailable, side}:{updateAvailable:boolean, side:Side}) => (
<header>
<h1 id="main-headline">{(side == Side.Main)? "StickExporterTX" : "Settings"}</h1>
{updateAvailable? <UpdateButton/> : null}
{(side == Side.Main)? <MainSideButtons/> : <OtherSideButtons/>}
</header>
const BlenderLoadingSVG = () => (
<svg id="blender-loading-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
{/* Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M304 48C304 74.51 282.5 96 256 96C229.5 96 208 74.51 208 48C208 21.49 229.5 0 256 0C282.5 0 304 21.49 304 48zM304 464C304 490.5 282.5 512 256 512C229.5 512 208 490.5 208 464C208 437.5 229.5 416 256 416C282.5 416 304 437.5 304 464zM0 256C0 229.5 21.49 208 48 208C74.51 208 96 229.5 96 256C96 282.5 74.51 304 48 304C21.49 304 0 282.5 0 256zM512 256C512 282.5 490.5 304 464 304C437.5 304 416 282.5 416 256C416 229.5 437.5 208 464 208C490.5 208 512 229.5 512 256zM74.98 437C56.23 418.3 56.23 387.9 74.98 369.1C93.73 350.4 124.1 350.4 142.9 369.1C161.6 387.9 161.6 418.3 142.9 437C124.1 455.8 93.73 455.8 74.98 437V437zM142.9 142.9C124.1 161.6 93.73 161.6 74.98 142.9C56.24 124.1 56.24 93.73 74.98 74.98C93.73 56.23 124.1 56.23 142.9 74.98C161.6 93.73 161.6 124.1 142.9 142.9zM369.1 369.1C387.9 350.4 418.3 350.4 437 369.1C455.8 387.9 455.8 418.3 437 437C418.3 455.8 387.9 455.8 369.1 437C350.4 418.3 350.4 387.9 369.1 369.1V369.1z"/>
</svg>
)
const BlenderReadySVG = () => (
<svg id="blender-ready-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
{/* Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M438.6 105.4C451.1 117.9 451.1 138.1 438.6 150.6L182.6 406.6C170.1 419.1 149.9 419.1 137.4 406.6L9.372 278.6C-3.124 266.1-3.124 245.9 9.372 233.4C21.87 220.9 42.13 220.9 54.63 233.4L159.1 338.7L393.4 105.4C405.9 92.88 426.1 92.88 438.6 105.4H438.6z"/>
</svg>
)
export default Menu;
let setBlenderLoading:React.Dispatch<React.SetStateAction<boolean>>;
let setBlenderStatus:React.Dispatch<React.SetStateAction<string>>;
function Menu({updateAvailable, side}:{updateAvailable:boolean, side:Side}) {
const [blenderLoading, setBlenderLoadingInner] = useState(true);
setBlenderLoading = setBlenderLoadingInner;
const [blenderStatus, setBlenderStatusInner] = useState("Starting");
setBlenderStatus = setBlenderStatusInner;
return (
<header>
<h1 id="main-headline">{(side == Side.Main)? "StickExporterTX" : "Settings"}</h1>
<div id="blender-info">
<div id="blender-icon">
{blenderLoading? <BlenderLoadingSVG/> : <BlenderReadySVG/>}
</div>
<p>{blenderStatus}</p>
</div>
{updateAvailable? <UpdateButton/> : null}
{(side == Side.Main)? <MainSideButtons/> : <OtherSideButtons/>}
</header>
)
}
export default Menu;
export {
setBlenderLoading,
setBlenderStatus
}

View File

@@ -1,5 +1,25 @@
import React, {useState} from "react";
import React, {useState, useEffect} from "react";
import { settingList, updateSettings, settingListLoadDefault } from "../settings";
import {blender, blenderCmd, renderingPicture} from "../blender-controller";
import {dataPath} from "../paths";
import path from "path";
let setRenderImg:React.Dispatch<React.SetStateAction<string>>;
let setRenderLoading:React.Dispatch<React.SetStateAction<boolean>>;
let sideLoaded = false;
function picturePath() {
return path.join(dataPath, "render.png?t="+Date.now());
}
const RenderLoadingSpinner = () => (
<div id="renderLoadingDiv">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
{/* Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M304 48C304 74.51 282.5 96 256 96C229.5 96 208 74.51 208 48C208 21.49 229.5 0 256 0C282.5 0 304 21.49 304 48zM304 464C304 490.5 282.5 512 256 512C229.5 512 208 490.5 208 464C208 437.5 229.5 416 256 416C282.5 416 304 437.5 304 464zM0 256C0 229.5 21.49 208 48 208C74.51 208 96 229.5 96 256C96 282.5 74.51 304 48 304C21.49 304 0 282.5 0 256zM512 256C512 282.5 490.5 304 464 304C437.5 304 416 282.5 416 256C416 229.5 437.5 208 464 208C490.5 208 512 229.5 512 256zM74.98 437C56.23 418.3 56.23 387.9 74.98 369.1C93.73 350.4 124.1 350.4 142.9 369.1C161.6 387.9 161.6 418.3 142.9 437C124.1 455.8 93.73 455.8 74.98 437V437zM142.9 142.9C124.1 161.6 93.73 161.6 74.98 142.9C56.24 124.1 56.24 93.73 74.98 74.98C93.73 56.23 124.1 56.23 142.9 74.98C161.6 93.73 161.6 124.1 142.9 142.9zM369.1 369.1C387.9 350.4 418.3 350.4 437 369.1C455.8 387.9 455.8 418.3 437 437C418.3 455.8 387.9 455.8 369.1 437C350.4 418.3 350.4 387.9 369.1 369.1V369.1z"/>
</svg>
</div>
);
function SettingsSide() {
@@ -7,36 +27,47 @@ function SettingsSide() {
const [width, setWidth] = useState(settingList.width);
const [stickDistance, setStickDistance] = useState(settingList.stickDistance);
const [stickMode2, setStickMode2] = useState(settingList.stickMode2);
const [renderImg, setRenderImgInner] = useState(picturePath());
setRenderImg = setRenderImgInner;
const [renderLoading, setRenderLoadingInner] = useState(renderingPicture);
setRenderLoading = setRenderLoadingInner;
useEffect(() => {
const timer = setTimeout(() => {
updateSettings({fps, width, stickDistance, stickMode2});
blender(blenderCmd.getRender);
}, 500);
return () => clearTimeout(timer);
}, [fps, width, stickDistance, stickMode2]);
sideLoaded = true;
return (
<div id="content">
<div className="dataDiv">
<p>FPS: </p>
<input id="fpsInput" type="number" value={fps.toString()} min="1" step="1" onChange={e => {
updateSettings({fps:parseInt(e.target.value)});
setFps(settingList.fps);
<input id="fpsInput" type="number" defaultValue={fps.toString()} min="1" step="1" onChange={e => {
if(e.target.value.trim().length !== 0) setFps(parseInt(e.target.value));
}}/>
</div>
<div className="dataDiv">
<p>Width: </p>
<input id="widthInput" type="number" value={width.toString()} min="1" step="1" onChange={e => {
updateSettings({width:parseInt(e.target.value)});
setWidth(settingList.width);
<input id="widthInput" type="number" defaultValue={width.toString()} min="1" step="1" onChange={e => {
if(e.target.value.trim().length !== 0) setWidth(parseInt(e.target.value));
}}/>
</div>
<div className="dataDiv">
<p>Stick Distance: </p>
<input id="stickDistanceInput" type="number" value={stickDistance.toString()} min="0" step="1" onChange={e => {
updateSettings({stickDistance:parseInt(e.target.value)});
setStickDistance(settingList.stickDistance);
<input id="stickDistanceInput" type="number" defaultValue={stickDistance.toString()} min="0" step="1" onChange={e => {
if(e.target.value.trim().length !== 0) setStickDistance(parseInt(e.target.value));
}}/>
</div>
<div className="dataDiv">
<p>Stick Mode:</p>
<label htmlFor="stickMode" className="toggle-switchy" data-style="rounded" data-text="12">
<input checked={stickMode2} type="checkbox" id="stickMode" onChange={e => {
updateSettings({stickMode2:e.target.checked});
setStickMode2(settingList.stickMode2);
<input defaultChecked={stickMode2} type="checkbox" id="stickMode" onChange={e => {
setStickMode2(e.target.checked);
}}/>
<span className="toggle">
<span className="switch"></span>
@@ -51,8 +82,26 @@ function SettingsSide() {
setStickDistance(settingList.stickDistance);
setStickMode2(settingList.stickMode2);
}}>Reset Settings</button>
<div id="renderImgDiv">
<img id="render-ex" src={renderImg}></img>
{renderLoading? <RenderLoadingSpinner/> : null}
</div>
</div>
)
}
export default SettingsSide;
function imageLoading() {
if(sideLoaded) setRenderLoading(true);
}
function imageLoaded() {
if(sideLoaded) {
setRenderImg(picturePath());
setRenderLoading(false);
}
}
export default SettingsSide;
export {
imageLoading,
imageLoaded
};

View File

@@ -36,6 +36,8 @@ header {
}
header h1 {
margin-right: auto;
display: flex;
width: auto;
}
#settings-back {
@@ -90,7 +92,6 @@ header h1 {
border: none;
outline: none;
margin-right: 15px;
display: none;
}
#update-available svg {
width: 35px;
@@ -101,4 +102,91 @@ header h1 {
}
#update-available:hover {
transform: scale(1.05);
}
#blender-info {
margin-right: auto;
}
#blender-info p {
margin-top: 5px;
margin-bottom: 0;
}
#blender-icon {
width: 25px;
height: 25px;
align-items: center;
margin-left: auto;
margin-right: auto;
}
#blender-icon svg {
width: 25px;
height: 25px;
fill: white;
}
#blender-loading-icon {
animation: rotate-icon 1.2s linear infinite;
}
#blender-loading-icon path {
fill: #2196F3;
}
#blender-ready-icon path {
fill: #00c24a;
}
#renderLoadingDiv {
position: absolute;
background-color: rgba(0,0,0,0.9);
width: 100%;
height: 100%;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px;
}
#renderLoadingDiv svg {
height: 50%;
fill: #2196F3;
animation: rotate-icon 1.2s linear infinite;
}
@keyframes rotate-icon {
to {
transform: rotate(1turn);
}
}
#start-render {
cursor: pointer;
background-color: #00c24a;
border-radius: 50%;
width: 100px;
height: 100px;
align-items: center;
border: none;
outline: none;
margin-left: auto;
margin-right: auto;
}
#start-render svg {
width: 50px;
height: 50px;
fill: white;
margin-left: 5px;
margin-top: 5px;
}
#start-render:hover {
transform: scale(1.05);
}
#render-ex {
width: 100%;
height: auto;
display: block;
border-radius: 10px;
}
#renderImgDiv {
position: relative;
margin-top: 15px;
width: 100%;
}

View File

@@ -7,6 +7,13 @@
<body style="background-color: #172336;margin:0;padding:0;color:white;font-family:sans-serif;">
<div id="root"></div>
<script src="./.webpack/renderer.js"></script>
<script>
if(process.env.NODE_ENV === 'development') {
const port = process.env.PORT || 3000;
document.write('<script src="http://localhost:'+port+'/renderer.js"><\/script>');
} else {
document.write('<script src="./.webpack/renderer.js"><\/script>');
}
</script>
</body>
</html>

View File

@@ -1,5 +1,6 @@
import {app, BrowserWindow} from 'electron';
import {initialize as remoteInitialize, enable as remoteEnable} from '@electron/remote/main';
import path from 'path';
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
@@ -14,20 +15,21 @@ const createWindow = () => {
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
contextIsolation: false,
}
});
if(app.isPackaged) mainWindow.setMenu(null);
// remove the menu bar when in production.
if(process.env.NODE_ENV === 'production') mainWindow.setMenu(null);
remoteInitialize();
remoteEnable(mainWindow.webContents);
// and load the index.html of the app.
mainWindow.loadFile("src/index.html");
// load the index.html of the app.
mainWindow.loadFile(path.join(__dirname, 'index.html'));
// Open the DevTools.
if(!app.isPackaged) mainWindow.webContents.openDevTools();
// Open the DevTools when in development mode.
if(process.env.NODE_ENV === 'development') mainWindow.webContents.openDevTools();
};
// This method will be called when Electron has finished

View File

@@ -1,30 +1,32 @@
import React from "react";
import ReactDOM from "react-dom";
import ReactDOM from "react-dom/client";
import Menu from "./components/ui/menu";
import MainSide from "./components/ui/mainSide";
import SettingsSite from "./components/ui/settingsSide";
import "./index.css";
import "./toggle-switchy.css";
import { startBlender } from "./components/blender-controller";
enum Side {
Main,
Settings
}
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
function openSide(side:Side) {
ReactDOM.render(
root.render(
<React.StrictMode>
<Menu updateAvailable={true} side={side}/>
<Menu updateAvailable={false} side={side}/>
{(side == Side.Main)? <MainSide/> : <SettingsSite/>}
</React.StrictMode>,
document.getElementById('root'));
</React.StrictMode>
);
}
openSide(Side.Main);
startBlender();
export {
openSide,
Side,
Side
}