59 Commits

Author SHA1 Message Date
Lino Schmidt
a6c4ab37f7 Merge pull request #19 from LinoSchmidt/dependabot/npm_and_yarn/postcss-8.4.35
Bump postcss from 8.4.14 to 8.4.35
2024-02-14 23:09:56 +01:00
dependabot[bot]
c095f57f98 Bump postcss from 8.4.14 to 8.4.35
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.14 to 8.4.35.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.14...8.4.35)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-14 22:08:20 +00:00
Lino Schmidt
a28b678719 Merge pull request #18 from LinoSchmidt/dependabot/npm_and_yarn/follow-redirects-1.15.5
Bump follow-redirects from 1.15.1 to 1.15.5
2024-02-14 23:06:42 +01:00
dependabot[bot]
5b1445d507 Bump follow-redirects from 1.15.1 to 1.15.5
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.1 to 1.15.5.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.1...v1.15.5)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-14 22:05:42 +00:00
Lino Schmidt
476359e4ce Merge pull request #17 from LinoSchmidt/dependabot/npm_and_yarn/babel/traverse-7.23.9
Bump @babel/traverse from 7.18.2 to 7.23.9
2024-02-14 23:04:31 +01:00
dependabot[bot]
7fefef0ea9 Bump @babel/traverse from 7.18.2 to 7.23.9
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.18.2 to 7.23.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.9/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-14 22:02:25 +00:00
Lino Schmidt
7b7560743f Merge pull request #16 from LinoSchmidt/dependabot/npm_and_yarn/electron-22.3.25
Bump electron from 22.3.24 to 22.3.25
2024-02-14 23:00:20 +01:00
dependabot[bot]
b2a44cec24 Bump electron from 22.3.24 to 22.3.25
Bumps [electron](https://github.com/electron/electron) from 22.3.24 to 22.3.25.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v22.3.24...v22.3.25)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-06 22:00:19 +00:00
Lino Schmidt
f9072092b5 Merge pull request #15 from LinoSchmidt/dependabot/npm_and_yarn/electron-22.3.24
Bump electron from 22.0.2 to 22.3.24
2023-09-28 08:20:21 +02:00
dependabot[bot]
0e526df376 Bump electron from 22.0.2 to 22.3.24
Bumps [electron](https://github.com/electron/electron) from 22.0.2 to 22.3.24.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v22.0.2...v22.3.24)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-28 06:18:17 +00:00
Lino Schmidt
8b1d5ae04f Merge pull request #14 from LinoSchmidt/dependabot/npm_and_yarn/semver-5.7.2
Bump semver from 5.7.1 to 5.7.2
2023-09-03 16:50:57 +02:00
dependabot[bot]
b081149759 Bump semver from 5.7.1 to 5.7.2
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-03 14:49:45 +00:00
ac8368adf4 Fixed progress hop 2023-04-13 12:03:57 +02:00
d90c811e83 Added extra file for progress 2023-04-13 11:51:26 +02:00
05d195f9d1 Fixed terminal width 2023-04-13 10:58:17 +02:00
d2d3ed696b Added more instructions 2023-04-12 20:56:37 +02:00
59e5c4e150 Fixed dependencies 2023-04-11 21:16:02 +02:00
244d4c94b4 Fixed setting warning 2023-04-11 21:13:31 +02:00
2e9731e69a Saving terminal state 2023-04-11 21:02:16 +02:00
248f676185 Added render Terminal 2023-04-11 20:40:10 +02:00
1cf9ca8541 Added build instructions 2023-04-11 12:11:10 +02:00
b72a44fd3b Fixed Profile not found error 2023-04-05 13:04:25 +02:00
fd1d8e2b51 Removed ability to delete last Profile 2023-04-05 12:50:18 +02:00
Lino Schmidt
f24ea21d8c Merge pull request #13 from LinoSchmidt/dependabot/npm_and_yarn/webpack-5.76.0
Bump webpack from 5.72.1 to 5.76.0
2023-03-22 19:49:34 +01:00
dependabot[bot]
365f7d8b34 Bump webpack from 5.72.1 to 5.76.0
Bumps [webpack](https://github.com/webpack/webpack) from 5.72.1 to 5.76.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.72.1...v5.76.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-22 18:49:15 +00:00
Lino Schmidt
8dff49751b Merge pull request #12 from LinoSchmidt/dependabot/npm_and_yarn/minimatch-and-electron-builder-3.1.2
Bump minimatch and electron-builder
2023-03-05 14:05:17 +01:00
dependabot[bot]
2e55a6dc52 Bump minimatch and electron-builder
Bumps [minimatch](https://github.com/isaacs/minimatch) to 3.1.2 and updates ancestor dependency [electron-builder](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-builder). These dependencies need to be updated together.


Updates `minimatch` from 3.0.4 to 3.1.2
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

Updates `electron-builder` from 23.6.0 to 24.0.0
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-builder/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/v24.0.0/packages/electron-builder)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
- dependency-name: electron-builder
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-05 13:01:27 +00:00
Lino Schmidt
fb9dcabec2 Merge pull request #11 from LinoSchmidt/dependabot/npm_and_yarn/dns-packet-5.4.0
Bump dns-packet from 5.3.1 to 5.4.0
2023-03-04 17:23:55 +01:00
dependabot[bot]
7ac92ffef6 Bump dns-packet from 5.3.1 to 5.4.0
Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/mafintosh/dns-packet/releases)
- [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mafintosh/dns-packet/compare/v5.3.1...5.4.0)

---
updated-dependencies:
- dependency-name: dns-packet
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-04 15:44:51 +00:00
Lino Schmidt
db48a8b04d Merge pull request #10 from LinoSchmidt/dependabot/npm_and_yarn/got-and-electron-builder-11.8.6
Bump got and electron-builder
2023-02-13 11:06:37 +01:00
dependabot[bot]
6223624ff9 Bump got and electron-builder
Bumps [got](https://github.com/sindresorhus/got) to 11.8.6 and updates ancestor dependency [electron-builder](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-builder). These dependencies need to be updated together.


Updates `got` from 9.6.0 to 11.8.6
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v9.6.0...v11.8.6)

Updates `electron-builder` from 22.10.3 to 23.6.0
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-builder/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/v23.6.0/packages/electron-builder)

---
updated-dependencies:
- dependency-name: got
  dependency-type: indirect
- dependency-name: electron-builder
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-13 10:06:10 +00:00
Lino Schmidt
119066d649 Merge pull request #9 from LinoSchmidt/dependabot/npm_and_yarn/http-cache-semantics-4.1.1
Bump http-cache-semantics from 4.1.0 to 4.1.1
2023-02-13 11:01:46 +01:00
dependabot[bot]
88d56c0197 Bump http-cache-semantics from 4.1.0 to 4.1.1
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-13 10:00:42 +00:00
f496cad846 Patched minimatch vulnerability 2023-01-16 09:40:52 +01:00
Lino Schmidt
4dc3bac6bf Merge pull request #8 from LinoSchmidt/dependabot/npm_and_yarn/got-and-electron-11.8.6
Bump got and electron
2023-01-14 17:56:40 +01:00
dependabot[bot]
5aa61e4290 Bump got and electron
Bumps [got](https://github.com/sindresorhus/got) to 11.8.6 and updates ancestor dependency [electron](https://github.com/electron/electron). These dependencies need to be updated together.


Updates `got` from 9.6.0 to 11.8.6
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v9.6.0...v11.8.6)

Updates `electron` from 18.3.7 to 22.0.2
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v18.3.7...v22.0.2)

---
updated-dependencies:
- dependency-name: got
  dependency-type: indirect
- dependency-name: electron
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-14 16:33:16 +00:00
Lino Schmidt
bb5ca04fd2 Merge pull request #7 from LinoSchmidt/dependabot/npm_and_yarn/json5-1.0.2
Bump json5 from 1.0.1 to 1.0.2
2023-01-14 17:30:33 +01:00
dependabot[bot]
3a78bfdee3 Bump json5 from 1.0.1 to 1.0.2
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-14 16:28:49 +00:00
477fccdb2e Made escape key functional 2022-12-15 13:24:03 +01:00
fac8118b28 Fixed button spacing 2022-12-11 17:58:37 +01:00
a4d504962d Added import/export and profil optiones 2022-12-11 17:35:09 +01:00
c9c3316437 New log saving system 2022-12-02 21:46:26 +01:00
f2318cccf0 Centerd warning 2022-11-28 09:15:14 +01:00
efeea62def Fixed select flaw 2022-11-26 23:03:48 +01:00
367f91fcb4 Added reset button to each setting 2022-11-26 21:46:04 +01:00
c8397308a3 Added style to selection 2022-11-24 09:04:06 +01:00
ce0f03cff7 New videoplayer selection 2022-11-24 08:45:10 +01:00
13706757ee Better error message 2022-11-24 08:11:37 +01:00
377e26c124 Updated preview design 2022-11-23 20:00:59 +01:00
57a6d7479b settings get to blender per console 2022-11-23 19:47:28 +01:00
c768cf225f New setting-save method 2022-11-23 15:04:29 +01:00
Lino Schmidt
f585c81d38 Merge pull request #6 from LinoSchmidt/dependabot/npm_and_yarn/xmldom/xmldom-0.7.9
Bump @xmldom/xmldom from 0.7.5 to 0.7.9
2022-11-16 12:27:10 +01:00
dependabot[bot]
b6b94e67b8 Bump @xmldom/xmldom from 0.7.5 to 0.7.9
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.7.5 to 0.7.9.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.7.5...0.7.9)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-16 11:26:43 +00:00
Lino Schmidt
2938b98ab8 Merge pull request #5 from LinoSchmidt/dependabot/npm_and_yarn/electron-18.3.7
Bump electron from 18.1.0 to 18.3.7
2022-11-16 12:19:00 +01:00
dependabot[bot]
3a83fb77ca Bump electron from 18.1.0 to 18.3.7
Bumps [electron](https://github.com/electron/electron) from 18.1.0 to 18.3.7.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v18.1.0...v18.3.7)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-16 09:12:54 +00:00
Lino Schmidt
8f32025238 Merge pull request #4 from LinoSchmidt/dependabot/npm_and_yarn/loader-utils-2.0.4
Bump loader-utils from 2.0.2 to 2.0.4
2022-11-16 10:07:15 +01:00
dependabot[bot]
2541d5ff9a Bump loader-utils from 2.0.2 to 2.0.4
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.2 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.2...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-16 09:05:25 +00:00
Lino Schmidt
f5d45c3b9a Merge pull request #3 from TrellixVulnTeam/main
CVE-2007-4559 Patch
2022-11-15 09:52:08 +01:00
TrellixVulnTeam
a013bced01 Adding tarfile member sanitization to extractall() 2022-11-15 01:38:20 +00:00
17 changed files with 4036 additions and 1663 deletions

View File

@@ -2,7 +2,7 @@
StickExporterTX is a 3D Stick Exporter for EdgeTX/OpenTX logs. StickExporterTX is a 3D Stick Exporter for EdgeTX/OpenTX logs.
## Quick Start Guide ## Controller Setup Guide
To log your sticks on each model, go to `settings -> global functions` in your RC controller. To log your sticks on each model, go to `settings -> global functions` in your RC controller.
@@ -18,6 +18,24 @@ If you only want to set up logging for one model, go to `model -> special functi
![special-functions](readme/pictures/special-functions.bmp) ![special-functions](readme/pictures/special-functions.bmp)
## How to use
### Importing logs
1. Connect your RC controller to your computer via USB.
2. Select `USB Storage (SD)` on the controller.
3. Open the StickExporterTX app and click on `Add Log(s)`.
4. Select the log files you want to import. They should be located in the `LOGS` folder on your controller.
## How to build (for developers)
The following software is required to build the project:
- [Node.js](https://nodejs.org/)
- [Python 3](https://www.python.org/)
To build the project, run the following commands in the project directory:
```bash
npm install
npm run bundle
```
If the commands were successful, a new folder called `dist` should have been created in the project directory containing the compiled files.
## Licence: ## Licence:
This project is released under the MIT license, for more information, check the [LICENSE](LICENSE) file. This project is released under the MIT license, for more information, check the [LICENSE](LICENSE) file.

View File

@@ -5,10 +5,7 @@ import math
import sys import sys
import time import time
import bpy import bpy
import xml.etree.ElementTree as ET import json
argv = sys.argv
argv = argv[argv.index("--") + 1:]
logger = logging.getLogger('simple_example') logger = logging.getLogger('simple_example')
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
@@ -43,28 +40,22 @@ def _map(x, in_min, in_max, out_min, out_max):
logger.info("Blender started successfully!") logger.info("Blender started successfully!")
while True: while True:
command = input("Waiting for command: ") command = input("Waiting for command: ").split(" -- ")
time.sleep(0.5) time.sleep(0.5)
settingsRoot = ET.parse(argv[0]+"/settings.xml").getroot() settings = json.loads(command[1])
StickMode = settingsRoot[3].text stickMode2 = settings["stickMode2"]
if(StickMode == "true"): width = settings["width"]
StickMode = 2 stickDistance = _map(settings["stickDistance"], 0, 100, 5, 105)
else: fps = settings["fps"]
StickMode = 1 videoFormat = settings["videoFormat"]
logs = settings["logs"]
output = settings["output"]
dataPath = settings["dataPath"]
width = int(settingsRoot[1].text) if(command[0] == "startRendering"):
StickDistance = _map(int(settingsRoot[2].text), 0, 100, 5, 105)
if(command == "startRendering"):
fps = int(settingsRoot[0].text)
videoFormat = settingsRoot[4].text
logs = settingsRoot[5].text[1:][:-1].split("\"\"")
output = settingsRoot[6].text
logCount = len(logs) logCount = len(logs)
logNumber = 1 logNumber = 1
@@ -129,14 +120,14 @@ while True:
bpy.context.scene.render.image_settings.color_mode = 'RGBA' bpy.context.scene.render.image_settings.color_mode = 'RGBA'
scn.render.resolution_x = width scn.render.resolution_x = width
GimbalCoverR.location[0] = StickDistance GimbalCoverR.location[0] = stickDistance
GimbalR.location[0] = StickDistance GimbalR.location[0] = stickDistance
TrailR.location[0] = StickDistance TrailR.location[0] = stickDistance
Plane.location[0] = StickDistance Plane.location[0] = stickDistance
Camera.location[0] = StickDistance/2 Camera.location[0] = stickDistance/2
Camera.data.ortho_scale = StickDistance+5 Camera.data.ortho_scale = stickDistance+5
scn.render.resolution_y = int(width/_map(StickDistance, 5, 105, 2, 21.6)) 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", "."+videoFormat) bpy.context.scene.render.filepath = output[logNumber-1]
scn.render.fps = 1000 scn.render.fps = 1000
scn.render.fps_base = FPSxxx scn.render.fps_base = FPSxxx
@@ -168,7 +159,7 @@ while True:
StickR.rotation_euler=[0,0,0] StickR.rotation_euler=[0,0,0]
GimbalR.rotation_euler=[0,0,0] GimbalR.rotation_euler=[0,0,0]
if StickMode == "1": if stickMode2 == False:
StickL.rotation_euler.rotate_axis("Y", ailP) StickL.rotation_euler.rotate_axis("Y", ailP)
GimbalL.rotation_euler.rotate_axis("X", eleP) GimbalL.rotation_euler.rotate_axis("X", eleP)
StickR.rotation_euler.rotate_axis("Y", rudP) StickR.rotation_euler.rotate_axis("Y", rudP)
@@ -194,19 +185,19 @@ while True:
logNumber+=1 logNumber+=1
elif(command == "getRender"): elif(command[0] == "getRender"):
bpy.context.scene.render.image_settings.file_format = 'PNG' bpy.context.scene.render.image_settings.file_format = 'PNG'
bpy.context.scene.render.filepath = argv[0] + "\\render.png" bpy.context.scene.render.filepath = dataPath + "\\render.png"
scn.render.resolution_x = width scn.render.resolution_x = width
GimbalCoverR.location[0] = StickDistance GimbalCoverR.location[0] = stickDistance
GimbalR.location[0] = StickDistance GimbalR.location[0] = stickDistance
TrailR.location[0] = StickDistance TrailR.location[0] = stickDistance
Plane.location[0] = StickDistance Plane.location[0] = stickDistance
Camera.location[0] = StickDistance/2 Camera.location[0] = stickDistance/2
Camera.data.ortho_scale = StickDistance+5 Camera.data.ortho_scale = stickDistance+5
scn.render.resolution_y = int(width/_map(StickDistance, 5, 105, 2, 21.6)) scn.render.resolution_y = int(width/_map(stickDistance, 5, 105, 2, 21.6))
bpy.context.scene.frame_set(0) bpy.context.scene.frame_set(0)
@@ -215,7 +206,7 @@ while True:
StickR.rotation_euler=[0,0,0] StickR.rotation_euler=[0,0,0]
GimbalR.rotation_euler=[0,0,0] GimbalR.rotation_euler=[0,0,0]
if(StickMode == 2): if(stickMode2 == True):
StickL.rotation_euler.rotate_axis("Y", 0) StickL.rotation_euler.rotate_axis("Y", 0)
GimbalL.rotation_euler.rotate_axis("X", 0.436) GimbalL.rotation_euler.rotate_axis("X", 0.436)
StickR.rotation_euler.rotate_axis("Y", 0) StickR.rotation_euler.rotate_axis("Y", 0)
@@ -238,7 +229,7 @@ while True:
StickR.rotation_euler=[0,0,0] StickR.rotation_euler=[0,0,0]
GimbalR.rotation_euler=[0,0,0] GimbalR.rotation_euler=[0,0,0]
if(StickMode == 2): if(stickMode2 == True):
StickL.rotation_euler.rotate_axis("Y", 0) StickL.rotation_euler.rotate_axis("Y", 0)
GimbalL.rotation_euler.rotate_axis("X", 0.436) GimbalL.rotation_euler.rotate_axis("X", 0.436)
StickR.rotation_euler.rotate_axis("Y", 0) StickR.rotation_euler.rotate_axis("Y", 0)

4123
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -134,8 +134,8 @@
"babel-loader": "^8.2.5", "babel-loader": "^8.2.5",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"electron": "18.1.0", "electron": "22.3.25",
"electron-builder": "^23.0.3", "electron-builder": "^24.0.0",
"eslint": "^8.16.0", "eslint": "^8.16.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-react": "^7.30.0", "eslint-plugin-react": "^7.30.0",
@@ -150,7 +150,7 @@
"ts-loader": "^9.3.0", "ts-loader": "^9.3.0",
"ts-node": "^10.8.0", "ts-node": "^10.8.0",
"typescript": "^4.7.2", "typescript": "^4.7.2",
"webpack": "^5.72.1", "webpack": "^5.76.0",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.1", "webpack-dev-server": "^4.9.1",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"

View File

@@ -34,7 +34,26 @@ if(platform.system() == 'Linux'):
urllib.request.urlretrieve(linuxURL, './dependencies/linux/blender.tar.xz') urllib.request.urlretrieve(linuxURL, './dependencies/linux/blender.tar.xz')
print("Extracting linux version") print("Extracting linux version")
with tarfile.open('./dependencies/linux/blender.tar.xz') as tfile: with tarfile.open('./dependencies/linux/blender.tar.xz') as tfile:
tfile.extractall('./dependencies/linux') def is_within_directory(directory, target):
abs_directory = os.path.abspath(directory)
abs_target = os.path.abspath(target)
prefix = os.path.commonprefix([abs_directory, abs_target])
return prefix == abs_directory
def safe_extract(tar, path=".", members=None, *, numeric_owner=False):
for member in tar.getmembers():
member_path = os.path.join(path, member.name)
if not is_within_directory(path, member_path):
raise Exception("Attempted Path Traversal in Tar File")
tar.extractall(path, members, numeric_owner=numeric_owner)
safe_extract(tfile, "./dependencies/linux")
print("Adjust linux version") print("Adjust linux version")
oldLinuxName = linuxURL.split('/')[-1].replace('.tar.xz', '') oldLinuxName = linuxURL.split('/')[-1].replace('.tar.xz', '')

View File

@@ -1,28 +1,22 @@
import { blenderPath, blenderScriptPath, dataPath, templatePath, finsishedIconPath } from "./paths"; import { blenderPath, blenderScriptPath, templatePath, finsishedIconPath, dataPath } from "./paths";
import {spawn} from "child_process"; import {spawn} from "child_process";
import logger from "./logger"; import logger from "./logger";
import { setBlenderLoading, setBlenderStatus } from "./ui/menu"; import { setBlenderLoading, setBlenderStatus } from "./ui/menu";
import { setLogNumber, setPastTime, setRemainingTime, setRenderDisplayProgress, setStatus, setPastTimeNow, setRemainingTimeNow } from "./ui/renderingPage"; import { setLogNumber, setStatus, addTerminalLine } from "./ui/renderingPage";
import {imageLoading, imageLoaded} from "./ui/settingsPage"; import {imageLoading, imageLoaded} from "./ui/settingsPage";
import { getLogList, getLogSize, settingList } from "./settings"; import { getInOutSettings, getActiveProfile } from "./settings";
import isValid from "is-valid-path";
import { pageSetRendering, setProgress, openPage, Page } from "../renderer"; import { pageSetRendering, setProgress, openPage, Page } from "../renderer";
import { setLog, setRenderProgress, startProgress, stopProgress } from "./progressController";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import path from 'path';
import fs from "fs";
// import { getDoNotDisturb } from "electron-notification-state"; // import { getDoNotDisturb } from "electron-notification-state";
export const renderInfo = {
time: "0min 0sec",
startTime: 0,
endTime: 0
}
const blenderStartString = [ const blenderStartString = [
templatePath, templatePath,
"--background", "--background",
"--python", "--python",
blenderScriptPath, blenderScriptPath
"--",
dataPath.replaceAll("\\", "/")
] ]
let blenderConsole = spawn(blenderPath, blenderStartString).on('error', function(err) { let blenderConsole = spawn(blenderPath, blenderStartString).on('error', function(err) {
@@ -33,41 +27,40 @@ let renderingPicture = false;
let renderingVideo = false; let renderingVideo = false;
let waitingForRender = false; let waitingForRender = false;
let logPortionList:number[] = []; function getOutPath(log:string) {
let currentLogPortion = 0; let fullOutPath = path.join(getInOutSettings().output, log.substring(log.lastIndexOf("\\")).replace(".csv", "."+getActiveProfile().videoFormat));
const estimatedRenderPortion = 0.97;
function setRenderProgress(log:number, init:boolean, frameCount:number, frame:number) {
let progress = 0;
if(init) {
progress = logPortionList[log-1] * (frame / frameCount * (1 - estimatedRenderPortion)) + currentLogPortion;
} else {
progress = logPortionList[log-1] * (frame / frameCount * estimatedRenderPortion + (1 - estimatedRenderPortion)) + currentLogPortion;
}
setProgress(progress);
setRenderDisplayProgress(parseFloat((progress*100).toFixed(2)));
const timeNow = new Date().getTime(); if(fs.existsSync(fullOutPath)) {
const timeDiff = timeNow - renderInfo.startTime; let i = 1;
let timeDiffSeconds = timeDiff / 1000; while(fs.existsSync(fullOutPath.replace("."+getActiveProfile().videoFormat, " ("+i+")."+getActiveProfile().videoFormat))) {
let timeDiffMinutes = 0; i++;
while(timeDiffSeconds > 60) {
timeDiffMinutes++;
timeDiffSeconds -= 60;
}
renderInfo.time = timeDiffMinutes + "m " + timeDiffSeconds.toFixed(0) + "s";
setPastTimeNow(renderInfo.time);
if(progress > 0) {
const timeRemaining = (timeDiff / progress) * (1 - progress);
timeDiffSeconds = timeRemaining / 1000;
timeDiffMinutes = 0;
while(timeDiffSeconds > 60) {
timeDiffMinutes++;
timeDiffSeconds -= 60;
} }
setRemainingTimeNow(timeDiffMinutes + "m " + timeDiffSeconds.toFixed(0) + "s"); fullOutPath = fullOutPath.replace("."+getActiveProfile().videoFormat, " ("+i+")."+getActiveProfile().videoFormat);
} }
return fullOutPath;
}
let outputArgs:string[] = [];
function blenderArgs() {
const outputList:string[] = [];
getInOutSettings().logs.forEach(log => {
outputList.push(getOutPath(log));
});
outputArgs = outputList;
return JSON.stringify({
stickMode2:getActiveProfile().stickMode2,
width:getActiveProfile().width,
stickDistance:getActiveProfile().stickDistance,
fps:getActiveProfile().fps,
videoFormat:getActiveProfile().videoFormat,
logs:getInOutSettings().logs,
output:outputList,
dataPath:dataPath
});
} }
function startBlender() { function startBlender() {
@@ -80,6 +73,10 @@ function startBlender() {
logger.info("Blender: " + dataStr); logger.info("Blender: " + dataStr);
if(renderingVideo) {
addTerminalLine(dataStr);
}
if (dataStr.includes("Blender started successfully")) { if (dataStr.includes("Blender started successfully")) {
renderingPicture = false; renderingPicture = false;
renderingVideo = false; renderingVideo = false;
@@ -108,13 +105,13 @@ function startBlender() {
setRenderProgress(parseInt(log), true, 0, 0); setRenderProgress(parseInt(log), true, 0, 0);
} }
if(dataStr.includes("Fra:") && renderingVideo) { if(dataStr.includes("Fra:") && renderingVideo) {
lastFrame = dataStr.split(":")[1].split(" ")[0]; lastFrame = dataStr.split("Fra:")[1].split(" ")[0];
setStatus("Rendering Frame " + lastFrame + "/" + frames); setStatus("Rendering Frame " + lastFrame + "/" + frames);
setRenderProgress(parseInt(log), false, parseInt(frames), parseInt(lastFrame)); setRenderProgress(parseInt(log), false, parseInt(frames), parseInt(lastFrame));
} }
if(dataStr.includes("Finished") && renderingVideo) { if(dataStr.includes("Finished") && renderingVideo) {
pageSetRendering(false); pageSetRendering(false);
renderInfo.endTime = new Date().getTime(); stopProgress();
if(lastFrame == frames) { if(lastFrame == frames) {
openPage(Page.RenderFinish); openPage(Page.RenderFinish);
ipcRenderer.send("renderFinished"); ipcRenderer.send("renderFinished");
@@ -136,7 +133,7 @@ function startBlender() {
if(dataStr.includes("Lognr:") && renderingVideo) { if(dataStr.includes("Lognr:") && renderingVideo) {
log = dataStr.split(":")[1]; log = dataStr.split(":")[1];
if(log !== "1") { if(log !== "1") {
currentLogPortion += logPortionList[parseInt(log)-2]; setLog(parseInt(log));
} }
setLogNumber(log); setLogNumber(log);
} }
@@ -158,7 +155,7 @@ function startBlender() {
} else { } else {
waitingForRender = false; waitingForRender = false;
renderingPicture = true; renderingPicture = true;
blenderConsole.stdin.write("getRender\n"); blenderConsole.stdin.write("getRender -- "+blenderArgs()+"\n");
setBlenderStatus("Rendering"); setBlenderStatus("Rendering");
setBlenderLoading(true); setBlenderLoading(true);
imageLoading(); imageLoading();
@@ -192,44 +189,23 @@ function blender(command:blenderCmd) {
imageLoading(); imageLoading();
setBlenderStatus("Rendering"); setBlenderStatus("Rendering");
setBlenderLoading(true); setBlenderLoading(true);
blenderConsole.stdin.write("getRender\n"); blenderConsole.stdin.write("getRender -- "+blenderArgs()+"\n");
} else { } else {
waitingForRender = true; waitingForRender = true;
} }
} else if(command === blenderCmd.startRendering) { } else if(command === blenderCmd.startRendering) {
if(readyToAcceptCommand) { if(readyToAcceptCommand) {
if(settingList.log == "") { if(getInOutSettings().logs.length === 0) {
logger.warningMSG("No log selected!"); logger.warningMSG("No log selected!");
} else if(!isValid(settingList.log)) {
logger.warningMSG("Output path is invalid!");
} else { } else {
currentLogPortion = 0;
const logSizeList:number[] = [];
getLogList().forEach(function (value, index) {
logSizeList.push(getLogSize(index));
});
let fullLogSize = 0;
for(let i = 0; i < logSizeList.length; i++) {
fullLogSize += logSizeList[i];
}
logPortionList = [];
logSizeList.forEach(function (value) {
logPortionList.push(value / fullLogSize);
});
readyToAcceptCommand = false; readyToAcceptCommand = false;
renderingVideo = true; renderingVideo = true;
pageSetRendering(true); pageSetRendering(true);
setBlenderStatus("Rendering"); setBlenderStatus("Rendering");
setBlenderLoading(true); setBlenderLoading(true);
blenderConsole.stdin.write("startRendering\n"); blenderConsole.stdin.write("startRendering -- "+blenderArgs()+"\n");
renderInfo.startTime = new Date().getTime(); startProgress();
setPastTime("0min 0sec");
setRemainingTime("calculating...");
} }
} }
} else if(command === blenderCmd.stopRendering) { } else if(command === blenderCmd.stopRendering) {
@@ -252,5 +228,6 @@ export {
blender, blender,
blenderCmd, blenderCmd,
startBlender, startBlender,
renderingPicture renderingPicture,
outputArgs
} }

View File

@@ -1,8 +1,9 @@
import logger from "./logger"; import logger from "./logger";
import {parse as csvParse} from "csv-parse"; import {parse as csvParse} from "csv-parse";
import {settingList} from "./settings"; import {getInOutSettings} from "./settings";
import {platformCharacter} from "./paths"; import {platformCharacter} from "./paths";
import {formatDate} from "./dateFormat"; import {formatDate} from "./dateFormat";
import fs from "fs";
async function openLogFile(filePath:string, rawData:boolean) { async function openLogFile(filePath:string, rawData:boolean) {
const data = await fetch(filePath).then(function(response) { const data = await fetch(filePath).then(function(response) {
@@ -20,7 +21,7 @@ async function openLogFile(filePath:string, rawData:boolean) {
let logData:string[]|undefined = undefined; let logData:string[]|undefined = undefined;
csvParse(data, {}, (err, output:string[]) => { csvParse(data, {}, (err, output:string[]) => {
if(err) { if(err) {
logger.errorMSG(`Error parsing csv file: ${err}`); logger.errorMSG(`Error parsing file "${filePath}": ${err}`);
logData = []; logData = [];
} else { } else {
logData = output; logData = output;
@@ -162,10 +163,8 @@ async function getLogTime(filePath:string) {
async function getAllLogs() { async function getAllLogs() {
const loadList = []; const loadList = [];
if(settingList.log.length > 0) { if(getInOutSettings().logs.length > 0) {
const logs = settingList.log.substring(1).slice(0, -1).split('""'); for(const log of getInOutSettings().logs) {
for(const log of logs) {
loadList.push({ loadList.push({
name: log.split(platformCharacter())[log.split(platformCharacter()).length - 1].replace(".csv", ""), name: log.split(platformCharacter())[log.split(platformCharacter()).length - 1].replace(".csv", ""),
path: log, path: log,
@@ -184,9 +183,8 @@ async function reloadAllLogs() {
} }
async function updateLogs() { async function updateLogs() {
if(settingList.log.length > 0) { if(getInOutSettings().logs.length > 0) {
const logs = settingList.log.substring(1).slice(0, -1).split('""'); for(const log of getInOutSettings().logs) {
for(const log of logs) {
if(!logList.some(x => x.path === log)) { if(!logList.some(x => x.path === log)) {
logList.push({ logList.push({
name: log.split(platformCharacter())[log.split(platformCharacter()).length - 1].replace(".csv", ""), name: log.split(platformCharacter())[log.split(platformCharacter()).length - 1].replace(".csv", ""),
@@ -197,7 +195,7 @@ async function updateLogs() {
} }
for(const log of logList) { for(const log of logList) {
if(!logs.some(x => x === log.path)) { if(!getInOutSettings().logs.some(x => x === log.path)) {
logList.splice(logList.indexOf(log), 1); logList.splice(logList.indexOf(log), 1);
} }
} }
@@ -206,8 +204,13 @@ async function updateLogs() {
} }
} }
function getLogSize(index:number) {
return fs.statSync(getInOutSettings().logs[index]).size;
}
export { export {
reloadAllLogs, reloadAllLogs,
logList, logList,
updateLogs updateLogs,
getLogSize
}; };

View File

@@ -4,7 +4,8 @@ import { platformFolder, platform, Platform } from './platform';
export const dataPath = app.getPath('userData'); export const dataPath = app.getPath('userData');
export const appPath = app.getAppPath().replace("app.asar", ""); export const appPath = app.getAppPath().replace("app.asar", "");
export const SettingPath = path.join(dataPath, "settings.xml"); export const SettingPath = path.join(dataPath, "settings.json");
export const OLDSettingPath = path.join(dataPath, "settings.xml");
export const defaultOutputPath = path.join(app.getPath('videos'), "StickExporterTX"); export const defaultOutputPath = path.join(app.getPath('videos'), "StickExporterTX");

View File

@@ -0,0 +1,92 @@
import {setProgress} from "../renderer";
import {getLogSize} from "./logReader";
import {getInOutSettings} from "./settings";
import { setRenderDisplayProgress, setPastTimeNow, setRemainingTimeNow } from "./ui/renderingPage";
const estimatedRenderPortion = 0.97;
const renderInfo = {
time: "0min 0sec",
startTime: 0,
endTime: 0
}
let logPortionList:number[] = [];
let currentLogPortion = 0;
function setRenderProgress(log:number, init:boolean, frameCount:number, frame:number) {
let progress = 0;
if(init) {
progress = logPortionList[log-1] * (frame / frameCount * (1 - estimatedRenderPortion)) + currentLogPortion;
} else {
progress = logPortionList[log-1] * (frame / frameCount * estimatedRenderPortion + (1 - estimatedRenderPortion)) + currentLogPortion;
}
setProgress(progress);
setRenderDisplayProgress(parseFloat((progress*100).toFixed(2)));
const timeNow = new Date().getTime();
const timeDiff = timeNow - renderInfo.startTime;
let timeDiffSeconds = timeDiff / 1000;
let timeDiffMinutes = 0;
while(timeDiffSeconds > 60) {
timeDiffMinutes++;
timeDiffSeconds -= 60;
}
renderInfo.time = timeDiffMinutes + "m " + timeDiffSeconds.toFixed(0) + "s";
setPastTimeNow(renderInfo.time);
if(progress > 0) {
const timeRemaining = (timeDiff / progress) * (1 - progress);
timeDiffSeconds = timeRemaining / 1000;
timeDiffMinutes = 0;
while(timeDiffSeconds > 60) {
timeDiffMinutes++;
timeDiffSeconds -= 60;
}
setRemainingTimeNow(timeDiffMinutes + "m " + timeDiffSeconds.toFixed(0) + "s");
}
}
function resetRenderProgress() {
logPortionList = [];
currentLogPortion = 0;
const logSizeList:number[] = [];
getInOutSettings().logs.forEach(function (value, index) {
logSizeList.push(getLogSize(index));
});
let fullLogSize = 0;
for(let i = 0; i < logSizeList.length; i++) {
fullLogSize += logSizeList[i];
}
logSizeList.forEach(function (value) {
logPortionList.push(value / fullLogSize);
});
setPastTimeNow("0min 0sec");
setRemainingTimeNow("calculating...");
}
function setLog(log:number) {
currentLogPortion += logPortionList[log-2];
}
function stopProgress() {
renderInfo.endTime = new Date().getTime();
}
function startProgress() {
renderInfo.startTime = new Date().getTime();
resetRenderProgress();
}
export {
setRenderProgress,
resetRenderProgress,
setLog,
stopProgress,
startProgress,
renderInfo
}

View File

@@ -1,11 +1,7 @@
import formatXML from "xml-formatter"; import {SettingPath, defaultOutputPath, OLDSettingPath} from './paths';
import {SettingPath, defaultOutputPath} from './paths'; import {dialog} from '@electron/remote';
import fs from "fs";
import logger from "./logger"; import logger from "./logger";
import fs from "fs";
function getXMLChild(doc:Document, child:string) {
return String(doc.getElementsByTagName(child)[0].childNodes[0].nodeValue);
}
enum VideoFormat { enum VideoFormat {
mp4="mp4", mp4="mp4",
@@ -15,138 +11,470 @@ enum VideoFormat {
mkv="mkv", mkv="mkv",
} }
const defaultSettings = { type JSONProfile = {
fps: 30, profileName: string,
width: 540, fps: number,
stickDistance: 5, width: number,
stickMode2: true, stickDistance: number,
videoFormat: VideoFormat.webm, stickMode2: boolean,
log: '', videoFormat: VideoFormat
output: defaultOutputPath };
type JSONSettings = {
activeProfile: string,
profiles: JSONProfile[],
logs: string[],
output: string,
showRenderTerminal: boolean
} }
let allSettingsFound = true; const defaultSettings:JSONSettings = {
activeProfile: "Default",
profiles: [
{
profileName: "Default",
fps: 30,
width: 540,
stickDistance: 5,
stickMode2: true,
videoFormat: VideoFormat.webm
}
],
logs: [],
output: defaultOutputPath,
showRenderTerminal: false
};
function catchSetting(tryFunc:()=>string, catchFunc:()=>string) { function catchSetting(tryFunc:()=>string, catchFunc:()=>string) {
let val; let val;
try { try {
val = tryFunc(); val = tryFunc();
} catch(err) { } catch(err) {
logger.info("Failed to get setting value. Using default value:" + String(err)); logger.info("Failed to get setting value. Using default value:" + String(err));
allSettingsFound = false;
val = catchFunc(); val = catchFunc();
} }
return val; return val;
} }
function getXMLChild(doc:Document, child:string) {
return String(doc.getElementsByTagName(child)[0].childNodes[0].nodeValue);
}
let fetchFailed = "";
const settingList = await fetch(SettingPath).then(function(response) { const settingList = await fetch(SettingPath).then(function(response) {
return response.text(); return response.text();
}).catch(function(err) { }).catch(function(err) {
logger.info(err); logger.info(err);
return "fileLoadFailed"; return "fileLoadFailed";
}).then(function(data) { }).then(async function(data) {
if(data === "fileLoadFailed") { if(data === "fileLoadFailed") {
allSettingsFound = false; return await fetch(OLDSettingPath).then(function(response) {
return defaultSettings; return response.text();
}).catch(function(err) {
logger.info(err);
return "fileLoadFailed";
}).then(function(data) {
if(data === "fileLoadFailed") {
fetchFailed = "fileLoadFailed";
return defaultSettings;
}
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(data, 'text/xml');
const allLogs = catchSetting(function() {return (getXMLChild(xmlDoc, "log") === "None")? "":getXMLChild(xmlDoc, "log");},function() {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
return ""
});
const newLogs = defaultSettings.logs;
if(allLogs !== "") {
const allLogsList = allLogs.split("\"\"");
allLogsList.forEach(log => {
if(log !== "") {
newLogs.push(log.replace('"', ''));
}
});
}
return {
activeProfile: defaultSettings.activeProfile,
profiles: [
{
profileName: defaultSettings.profiles[0].profileName,
fps: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "fps");},function() {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
return defaultSettings.profiles[0].fps.toString();
})),
width: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "width");},function() {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
return defaultSettings.profiles[0].width.toString();
})),
stickDistance: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "stickDistance");},function() {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
return defaultSettings.profiles[0].stickDistance.toString();
})),
stickMode2: catchSetting(function() {return getXMLChild(xmlDoc, "stickMode2");},function() {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
return defaultSettings.profiles[0].stickMode2.toString();
}) === "true",
videoFormat: catchSetting(function() {return getXMLChild(xmlDoc, "videoFormat");},function() {
return defaultSettings.profiles[0].videoFormat.toString();
}) as VideoFormat as VideoFormat
}
],
logs: newLogs,
output: catchSetting(function() {return getXMLChild(xmlDoc, "output");},function() {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
return defaultSettings.output;
}),
showRenderTerminal: defaultSettings.showRenderTerminal
} as JSONSettings;
});
}
const parsedData = JSON.parse(data);
let profiles:JSONProfile[] = [];
if(parsedData.profiles !== undefined) {
parsedData.profiles.forEach((profile:JSONProfile) => {
if(typeof profile.profileName !== "string") {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
profile.profileName = defaultSettings.profiles[0].profileName;
}
if(typeof profile.fps !== "number") {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
profile.fps = defaultSettings.profiles[0].fps;
}
if(typeof profile.width !== "number") {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
profile.width = defaultSettings.profiles[0].width;
}
if(typeof profile.stickDistance !== "number") {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
profile.stickDistance = defaultSettings.profiles[0].stickDistance;
}
if(typeof profile.stickMode2 !== "boolean") {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
profile.stickMode2 = defaultSettings.profiles[0].stickMode2;
}
if(typeof profile.videoFormat !== "string") {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
profile.videoFormat = defaultSettings.profiles[0].videoFormat;
}
profiles.push({
profileName: profile.profileName,
fps: profile.fps,
width: profile.width,
stickDistance: profile.stickDistance,
stickMode2: profile.stickMode2,
videoFormat: profile.videoFormat
});
});
} else {
fetchFailed = "multiSetting";
profiles = defaultSettings.profiles;
}
const logs:string[] = [];
if(parsedData.logs !== undefined) {
parsedData.logs.forEach((log:string) => {
if(typeof log === "string") {
logs.push(log);
}
});
} else {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
} }
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(data, 'text/xml');
return { return {
fps: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "fps");},function() {return defaultSettings.fps.toString();})), activeProfile: catchSetting(function() {return parsedData.activeProfile;},function() {
width: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "width");},function() {return defaultSettings.width.toString();})), fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
stickDistance: parseInt(catchSetting(function() {return getXMLChild(xmlDoc, "stickDistance");},function() {return defaultSettings.stickDistance.toString();})), return defaultSettings.activeProfile;
stickMode2: catchSetting(function() {return getXMLChild(xmlDoc, "stickMode2");},function() {return defaultSettings.stickMode2.toString();}) === "true", }),
videoFormat: catchSetting(function() {return getXMLChild(xmlDoc, "videoFormat");},function() {return defaultSettings.videoFormat.toString();}) as VideoFormat as VideoFormat, profiles,
log: catchSetting(function() {return (getXMLChild(xmlDoc, "log") === "None")? "":getXMLChild(xmlDoc, "log");},function() {return defaultSettings.log;}), logs,
output: catchSetting(function() {return getXMLChild(xmlDoc, "output");},function() {return defaultSettings.output;}) output: catchSetting(function() {return parsedData.output;},function() {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
return defaultSettings.output;
}),
showRenderTerminal: function() {
if(typeof parsedData.showRenderTerminal === "boolean") {
return parsedData.showRenderTerminal;
} else {
fetchFailed === "singleSetting"? fetchFailed = "multiSetting":fetchFailed = "singleSetting";
return defaultSettings.showRenderTerminal;
}
}()
} }
}); });
if(!allSettingsFound) { if(fetchFailed !== "") {
updateSettings({}); writeSettings();
} }
function updateSettings(optiones:{fps?:number, width?:number, stickDistance?:number, stickMode2?:boolean, videoFormat?:VideoFormat, log?:string, output?:string}) { function getProfiles() {
if(optiones.fps === undefined) { return settingList.profiles.map((profile) => {
optiones.fps = settingList.fps; return profile.profileName;
} else { });
settingList.fps = optiones.fps; }
}
if(optiones.width === undefined) { function createProfile(profileName:string, clone:boolean) {
optiones.width = settingList.width; settingList.profiles.push({
} else { profileName: profileName,
settingList.width = optiones.width; fps: clone? getActiveProfile().fps:defaultSettings.profiles[0].fps,
} width: clone? getActiveProfile().width:defaultSettings.profiles[0].width,
if(optiones.stickDistance === undefined) { stickDistance: clone? getActiveProfile().stickDistance:defaultSettings.profiles[0].stickDistance,
optiones.stickDistance = settingList.stickDistance; stickMode2: clone? getActiveProfile().stickMode2:defaultSettings.profiles[0].stickMode2,
} else { videoFormat: clone? getActiveProfile().videoFormat:defaultSettings.profiles[0].videoFormat
settingList.stickDistance = optiones.stickDistance; });
}
if(optiones.stickMode2 === undefined) { writeSettings();
optiones.stickMode2 = settingList.stickMode2; }
} else {
settingList.stickMode2 = optiones.stickMode2; function ProfileLoadDefault(reset:{fps?:boolean, width?:boolean, stickDistance?:boolean, stickMode2?:boolean, videoFormat?:boolean, all?:boolean}, profileName?:string) {
} if(profileName === undefined) {
if(optiones.videoFormat === undefined) { profileName = getActiveProfile().profileName;
optiones.videoFormat = settingList.videoFormat;
} else {
settingList.videoFormat = optiones.videoFormat;
}
if(optiones.log === undefined) {
optiones.log = settingList.log;
} else {
settingList.log = optiones.log;
}
if(optiones.output === undefined) {
optiones.output = settingList.output;
} else {
settingList.output = optiones.output;
} }
const xmlStr = ` settingList.profiles.forEach(profile => {
<?xml version="1.0" encoding="UTF-8"?> if(profile.profileName === profileName) {
<settings> if(reset.all || reset.fps) {
<fps>${optiones.fps}</fps> profile.fps = defaultSettings.profiles[0].fps;
<width>${optiones.width}</width> }
<stickDistance>${optiones.stickDistance}</stickDistance> if(reset.all || reset.width) {
<stickMode2>${optiones.stickMode2}</stickMode2> profile.width = defaultSettings.profiles[0].width;
<videoFormat>${optiones.videoFormat}</videoFormat> }
<log>${(optiones.log === "")?"None":optiones.log}</log> if(reset.all || reset.stickDistance) {
<output>${optiones.output}</output> profile.stickDistance = defaultSettings.profiles[0].stickDistance;
</settings> }
`; if(reset.all || reset.stickMode2) {
profile.stickMode2 = defaultSettings.profiles[0].stickMode2;
}
if(reset.all || reset.videoFormat) {
profile.videoFormat = defaultSettings.profiles[0].videoFormat;
}
}
});
fs.writeFile(SettingPath, formatXML(xmlStr, {collapseContent: true}), function(err) { writeSettings();
}
function editProfile(optiones:{profileName?:string, fps?:number, width?:number, stickDistance?:number, stickMode2?:boolean, videoFormat?:VideoFormat}, profileName?:string) {
if(profileName === undefined) {
profileName = getActiveProfile().profileName;
}
settingList.profiles.forEach(profile => {
if(profile.profileName === profileName) {
if(optiones.profileName !== undefined) {
profile.profileName = optiones.profileName;
}
if(optiones.fps !== undefined) {
profile.fps = optiones.fps;
}
if(optiones.width !== undefined) {
profile.width = optiones.width;
}
if(optiones.stickDistance !== undefined) {
profile.stickDistance = optiones.stickDistance;
}
if(optiones.stickMode2 !== undefined) {
profile.stickMode2 = optiones.stickMode2;
}
if(optiones.videoFormat !== undefined) {
profile.videoFormat = optiones.videoFormat;
}
}
});
writeSettings();
}
function setInOutSettings(args:{logs?:string[], output?:string}) {
if(args.logs !== undefined) {
settingList.logs = args.logs;
}
if(args.output !== undefined) {
settingList.output = args.output;
}
writeSettings();
}
function getInOutSettings() {
return {logs: settingList.logs, output: settingList.output};
}
function setShowRenderTerminal(show:boolean) {
settingList.showRenderTerminal = show;
writeSettings();
}
function getShowRenderTerminal() {
return settingList.showRenderTerminal;
}
function removeProfile(profileName?:string) {
if(profileName === undefined) {
profileName = getActiveProfile().profileName;
}
settingList.profiles.forEach(profile => {
if(profile.profileName === profileName) {
settingList.profiles.splice(settingList.profiles.indexOf(profile), 1);
}
});
writeSettings();
}
function setActiveProfile(profileName:string) {
settingList.profiles.forEach(profile => {
if(profile.profileName === profileName) {
settingList.activeProfile = profile.profileName;
}
});
writeSettings();
}
function writeSettings(settings?:JSONSettings) {
if(settings === undefined) {
settings = settingList;
}
fs.writeFile(SettingPath, JSON.stringify(settings), function(err) {
if(err) { if(err) {
logger.errorMSG(String(err)); logger.errorMSG(String(err));
} }
}); });
} }
function settingListLoadDefault() { function getActiveProfile() {
updateSettings({ let activeProfile;
fps:defaultSettings.fps, settingList.profiles.forEach(profile => {
width:defaultSettings.width, if(profile.profileName === settingList.activeProfile) {
stickDistance:defaultSettings.stickDistance, activeProfile = profile;
stickMode2:defaultSettings.stickMode2, }
videoFormat:defaultSettings.videoFormat, });
if(activeProfile === undefined) {
activeProfile = defaultSettings.profiles[0];
logger.error("Active profile not found, using default profile");
}
return activeProfile;
}
function importProfile(importSucces:CallableFunction) {
dialog.showOpenDialog({
properties: [
"openFile"
],
filters: [
{
name: "StickExporterTX-Profile",
extensions: [
"setp"
]
}
]
}).then(async result => {
if(result.filePaths.length === 1) {
const rawData = await fs.promises.readFile(result.filePaths[0], "utf8");
const jsonData = JSON.parse(rawData) as JSONProfile[];
const importProfiles:JSONProfile[] = [];
jsonData.forEach(profile => {
importProfiles.push({
profileName: profile.profileName,
fps: profile.fps,
width: profile.width,
stickDistance: profile.stickDistance,
stickMode2: profile.stickMode2,
videoFormat: profile.videoFormat
});
});
importProfiles.forEach(importProfile => {
while(settingList.profiles.find(profile => profile.profileName === importProfile.profileName) !== undefined) {
importProfile.profileName = importProfile.profileName + " (imported)";
}
settingList.profiles.push(importProfile);
});
writeSettings();
importSucces();
}
}).catch(err => {
logger.errorMSG("Import faulty: "+err);
}); });
} }
function getLogList() { function exportProfile() {
return settingList.log.split("\"\""); dialog.showMessageBox({
} type: "question",
noLink: true,
function getLogSize(index:number) { buttons: ["This", "All", "Cancel"],
const logList = settingList.log.substring(1).slice(0, -1).split('""'); title: "Export profile",
message: "Do you want to export all profiles or just the active one?"
return fs.statSync(logList[index]).size; }).then(result => {
if(result.response === 0) {
dialog.showSaveDialog({
filters: [
{
name: "StickExporterTX-Profile",
extensions: [
"setp"
]
}
]
}).then(result => {
if(result.filePath !== undefined) {
fs.writeFile(result.filePath, JSON.stringify([getActiveProfile()]), function(err) {
if(err) {
logger.errorMSG(String(err));
}
});
}
}).catch(err => {
logger.errorMSG("Export faulty: "+err);
});
}
else if(result.response === 1) {
dialog.showSaveDialog({
filters: [
{
name: "StickExporterTX-Profile",
extensions: [
"setp"
]
}
]
}).then(result => {
if(result.filePath !== undefined) {
fs.writeFile(result.filePath, JSON.stringify(settingList.profiles), function(err) {
if(err) {
logger.errorMSG(String(err));
}
});
}
}).catch(err => {
logger.errorMSG("Export faulty: "+err);
});
}
}).catch(err => {
logger.errorMSG("Export faulty: "+err);
});
} }
export { export {
updateSettings, getProfiles,
settingListLoadDefault, createProfile,
settingList, ProfileLoadDefault,
getLogList, editProfile,
getLogSize, removeProfile,
setActiveProfile,
getActiveProfile,
setInOutSettings,
getInOutSettings,
setShowRenderTerminal,
getShowRenderTerminal,
importProfile,
exportProfile,
VideoFormat VideoFormat
} }

View File

@@ -1,6 +1,6 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import { dialog } from "@electron/remote"; import { dialog } from "@electron/remote";
import { settingList, updateSettings } from "../settings"; import { setInOutSettings, getInOutSettings } from "../settings";
import logger from "../logger"; import logger from "../logger";
import {blender, blenderCmd} from "../blenderController"; import {blender, blenderCmd} from "../blenderController";
import openFolder from "../openFolder"; import openFolder from "../openFolder";
@@ -8,7 +8,7 @@ import {platformCharacter} from "../paths";
import {logList, reloadAllLogs, updateLogs} from "../logReader"; import {logList, reloadAllLogs, updateLogs} from "../logReader";
function MainPage() { function MainPage() {
const [output, setOutput] = useState(settingList.output); const [output, setOutput] = useState(getInOutSettings().output);
const [logTable, setLogTable] = useState([<tr key={0}></tr>]); const [logTable, setLogTable] = useState([<tr key={0}></tr>]);
useEffect(() => { useEffect(() => {
@@ -32,14 +32,14 @@ function MainPage() {
<div className="dataDiv"> <div className="dataDiv">
<button id="openLogButton" onClick={() => addLog(setLogTable)}>Add Log(s)</button> <button id="openLogButton" onClick={() => addLog(setLogTable)}>Add Log(s)</button>
<button id="deleteLogsButton" onClick={async () => { <button id="deleteLogsButton" onClick={async () => {
updateSettings({log:""}); setInOutSettings({logs:[]});
await reloadAllLogs(); await reloadAllLogs();
updateLogTable(setLogTable); updateLogTable(setLogTable);
}}>Delete All</button> }}>Delete All</button>
</div> </div>
<div className="dataDiv" id="outputDiv"> <div className="dataDiv" id="outputDiv">
<h4>Output Folder:</h4> <h4>Output Folder:</h4>
<p id="output" onClick={() => openFolder(settingList.output)}>{output}</p> <p id="output" onClick={() => openFolder(getInOutSettings().output)}>{output}</p>
<button onClick={() => openVid(setOutput)}>Select Folder</button> <button onClick={() => openVid(setOutput)}>Select Folder</button>
</div> </div>
</div> </div>
@@ -60,8 +60,8 @@ function updateLogTable(setLogTable:React.Dispatch<React.SetStateAction<JSX.Elem
fontWeight: "lighter" fontWeight: "lighter"
}}>({log.time.length.formatted})</td> }}>({log.time.length.formatted})</td>
<td><button className="listButton" onClick={async () => { <td><button className="listButton" onClick={async () => {
const newLogs = settingList.log.replace('"'+log.path+'"', ""); const newLogs = getInOutSettings().logs.filter(value => value !== log.path);
updateSettings({log:newLogs}); setInOutSettings({logs:newLogs});
await updateLogs(); await updateLogs();
updateLogTable(setLogTable); updateLogTable(setLogTable);
}}>Delete</button></td> }}>Delete</button></td>
@@ -69,7 +69,7 @@ function updateLogTable(setLogTable:React.Dispatch<React.SetStateAction<JSX.Elem
})); }));
} }
if(settingList.log == "") { if(getInOutSettings().logs.length === 0) {
setLogTable([]); setLogTable([]);
} else { } else {
getData(); getData();
@@ -90,17 +90,15 @@ function addLog(setLogTable:React.Dispatch<React.SetStateAction<JSX.Element[]>>)
} }
] ]
}).then(async result => { }).then(async result => {
let logStr = ""; const newLogs = getInOutSettings().logs;
result.filePaths.forEach(value => { result.filePaths.forEach(value => {
const logToAdd = "\"" + value + "\""; if(getInOutSettings().logs.includes(value)) {
if(settingList.log.includes(logToAdd)) { logger.warningMSG("Log \"" + value + "\" already added.");
logger.warningMSG("Log " + logToAdd + " already added.");
} else { } else {
logStr += "\"" + String(value) + "\""; newLogs.push(value);
} }
}); });
const newLogs = settingList.log + logStr; setInOutSettings({logs:newLogs});
updateSettings({log:newLogs});
await updateLogs(); await updateLogs();
updateLogTable(setLogTable); updateLogTable(setLogTable);
}).catch(err => { }).catch(err => {
@@ -115,7 +113,7 @@ function openVid(updateHook:React.Dispatch<React.SetStateAction<string>>) {
] ]
}).then(result => { }).then(result => {
if(result.filePaths.length > 0) { if(result.filePaths.length > 0) {
updateSettings({output:String(result.filePaths)}); setInOutSettings({output:String(result.filePaths)});
updateHook(String(result.filePaths)); updateHook(String(result.filePaths));
} }
}).catch(err => { }).catch(err => {

View File

@@ -1,13 +1,13 @@
import React, { CSSProperties } from "react"; import React, { CSSProperties } from "react";
import {openPage, Page} from "../../renderer"; import {openPage, Page} from "../../renderer";
import {renderInfo} from "../blenderController"; import {outputArgs} from "../blenderController";
import openFolder from "../openFolder"; import openFolder from "../openFolder";
import {settingList} from "../settings"; import {getInOutSettings, getActiveProfile} from "../settings";
import VideoPlayer from "./videoPlayer"; import VideoPlayer from "./videoPlayer";
import path from 'path'; import path from 'path';
import {platformCharacter} from "../paths";
import {VideoJsPlayerOptions} from "video.js"; import {VideoJsPlayerOptions} from "video.js";
import {logList} from "../logReader"; import {logList} from "../logReader";
import {renderInfo} from "../progressController";
const detailsStyle:CSSProperties = { const detailsStyle:CSSProperties = {
display: "flex", display: "flex",
@@ -18,10 +18,21 @@ const detailsInnerStyle:CSSProperties = {
marginRight: "5px", marginRight: "5px",
} }
const VideoSpanStyle:CSSProperties = {
display: "flex",
alignItems: "center"
}
const videoSelectStyle:CSSProperties = {
padding: "4px 0",
border: "0",
borderRadius: "14px",
cursor: "pointer",
textAlign: "center",
fontSize: "large"
}
function RenderFinishPage() { function RenderFinishPage() {
const [logPlaying, setLogPlaying] = React.useState(path.join(settingList.output, settingList.log.substring(1).slice(0, -1).split('""')[0].split(platformCharacter())[settingList.log.substring(1).slice(0, -1).split('""')[0].split(platformCharacter()).length - 1].replace(".csv", "."+settingList.videoFormat))); const [logPlaying, setLogPlaying] = React.useState(logList[0].name);
const [outputList, setOutputList] = React.useState([<li key={0}></li>]);
const videoPlayerOptions:VideoJsPlayerOptions = { const videoPlayerOptions:VideoJsPlayerOptions = {
controls: true, controls: true,
@@ -32,25 +43,17 @@ function RenderFinishPage() {
} }
}; };
const [videoSource, setVideoSource] = React.useState({src: logPlaying, type: 'video/'+settingList.videoFormat.toUpperCase()}); const [videoSource, setVideoSource] = React.useState({src: path.join(getInOutSettings().output, logPlaying+"."+getActiveProfile().videoFormat), type: 'video/'+getActiveProfile().videoFormat.toUpperCase()});
const OutputList = outputArgs.map((output, index) => {
const outputName = output.substring(output.lastIndexOf("\\")+1);
return <option key={index} value={outputName} title={output}>{outputName}</option>
});
React.useEffect(() => { React.useEffect(() => {
setOutputList(logList.map((inputLog, index) => {
const outputLogPath = path.join(settingList.output, inputLog.name+"."+settingList.videoFormat);
return <li key={index}>
<p style={{
textDecoration: logPlaying === outputLogPath ? "underline" : "none",
cursor: logPlaying === outputLogPath ? "default" : "pointer",
}} onClick={() => {
setLogPlaying(outputLogPath);
}} title={outputLogPath}>{inputLog.name}</p>
</li>
}));
setVideoSource({ setVideoSource({
src: logPlaying, src: path.join(getInOutSettings().output, logPlaying.replace(".csv", "."+getActiveProfile().videoFormat)),
type: 'video/'+settingList.videoFormat.toUpperCase() type: 'video/'+getActiveProfile().videoFormat.toUpperCase()
}); });
}, [logPlaying]); }, [logPlaying]);
@@ -78,14 +81,20 @@ function RenderFinishPage() {
}} onClick={() => { }} onClick={() => {
openPage(Page.Main); openPage(Page.Main);
}}>Finish</button> }}>Finish</button>
<button onClick={() => openFolder(settingList.output)}>Open Output Folder</button> <button onClick={() => openFolder(getInOutSettings().output)}>Open Output Folder</button>
<div style={{ <div style={{
marginTop: "10px" marginTop: "10px"
}}> }}>
<span style={VideoSpanStyle}>
<label htmlFor="vslct">
<select style={videoSelectStyle} id="vslct" required={true} value={logPlaying} onChange={e => {
setLogPlaying(e.target.value);
}}>
{OutputList}
</select>
</label>
</span>
<VideoPlayer options={videoPlayerOptions} src={videoSource} /> <VideoPlayer options={videoPlayerOptions} src={videoSource} />
<ol>
{outputList}
</ol>
</div> </div>
</div> </div>
); );

View File

@@ -1,5 +1,5 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useRef, useState} from "react";
import { settingList, getLogList } from "../settings"; import {getInOutSettings, getShowRenderTerminal, setShowRenderTerminal} from "../settings";
import openFolder from "../openFolder"; import openFolder from "../openFolder";
import { blenderCmd, blender } from "../blenderController"; import { blenderCmd, blender } from "../blenderController";
@@ -8,11 +8,15 @@ let setStatus:React.Dispatch<React.SetStateAction<string>>;
let setRenderDisplayProgress:React.Dispatch<React.SetStateAction<number>>; let setRenderDisplayProgress:React.Dispatch<React.SetStateAction<number>>;
let setPastTime:React.Dispatch<React.SetStateAction<string>>; let setPastTime:React.Dispatch<React.SetStateAction<string>>;
let setRemainingTime:React.Dispatch<React.SetStateAction<string>>; let setRemainingTime:React.Dispatch<React.SetStateAction<string>>;
let setTerminalLines:React.Dispatch<React.SetStateAction<JSX.Element[]>>;
let pastTimeNow = "0m 0s"; let pastTimeNow = "0m 0s";
let remainingTimeNow = "calculating..."; let remainingTimeNow = "calculating...";
function RenderingPage() { function RenderingPage() {
const [terminalHidden, setTerminalHidden] = useState(getShowRenderTerminal() ? "block" : "none");
const [terminalScroll, setTerminalScroll] = useState(true);
const [scrollButtonText, setScrollButtonText] = useState("pause scroll");
const [logNumber, setLogNumberInner] = useState("0"); const [logNumber, setLogNumberInner] = useState("0");
setLogNumber = setLogNumberInner; setLogNumber = setLogNumberInner;
const [status, setStatusInner] = useState("Idle"); const [status, setStatusInner] = useState("Idle");
@@ -23,6 +27,24 @@ function RenderingPage() {
setPastTime = setPastTimeInner; setPastTime = setPastTimeInner;
const [remainingTime, setRemainingTimeInner] = useState("calculating..."); const [remainingTime, setRemainingTimeInner] = useState("calculating...");
setRemainingTime = setRemainingTimeInner; setRemainingTime = setRemainingTimeInner;
const [terminalLines, setTerminalLinesInner] = useState([
<tr key={0}>
<td className="terminalTableNumber">0:</td>
<td>==== Start ====</td>
</tr>
]);
setTerminalLines = setTerminalLinesInner;
const messagesEndRef = useRef() as React.MutableRefObject<HTMLDivElement>;
useEffect(() => {
if(terminalScroll) {
messagesEndRef.current?.scrollIntoView();
setScrollButtonText("pause scroll");
} else {
setScrollButtonText("continue scroll");
}
}, [terminalLines, messagesEndRef, terminalScroll]);
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
@@ -34,7 +56,7 @@ function RenderingPage() {
return ( return (
<div id="content"> <div id="content">
<p>{"Log " + logNumber + "/" + getLogList().length}</p> <p>{"Log " + logNumber + "/" + getInOutSettings().logs.length}</p>
<p>{status}</p> <p>{status}</p>
<div className="progress"> <div className="progress">
<div className="progress-done" style={{ <div className="progress-done" style={{
@@ -69,7 +91,27 @@ function RenderingPage() {
}>{remainingTime}</p> }>{remainingTime}</p>
</div> </div>
<button id="stopRenderButton" onClick={() => blender(blenderCmd.stopRendering)}>Stop</button> <button id="stopRenderButton" onClick={() => blender(blenderCmd.stopRendering)}>Stop</button>
<button onClick={() => openFolder(settingList.output)}>Open Output Folder</button> <button onClick={() => openFolder(getInOutSettings().output)}>Open Output Folder</button>
<button onClick={() => {
if (getShowRenderTerminal()) {
setTerminalHidden("none");
setShowRenderTerminal(false);
} else {
setTerminalHidden("block");
setShowRenderTerminal(true);
}
}} style={{marginLeft:"10px"}}>Details</button>
<div style={{display: terminalHidden}}>
<div id="outerTerminal">
<div id="innerTerminal">
<table id="terminalTable">
{terminalLines}
</table>
<div ref={messagesEndRef}/>
</div>
</div>
<button style={{padding:"4px"}} onClick={() => setTerminalScroll(!terminalScroll)}>{scrollButtonText}</button>
</div>
</div> </div>
) )
} }
@@ -82,6 +124,17 @@ function setRemainingTimeNow(time:string) {
remainingTimeNow = time; remainingTimeNow = time;
} }
function addTerminalLine(line:string) {
setTerminalLines((prev:JSX.Element[]) => {
return [...prev,
<tr key={prev.length}>
<td className="terminalTableNumber">{prev.length}:</td>
<td>{line}</td>
</tr>
];
});
}
export default RenderingPage; export default RenderingPage;
export { export {
setLogNumber, setLogNumber,
@@ -90,5 +143,6 @@ export {
setPastTime, setPastTime,
setRemainingTime, setRemainingTime,
setPastTimeNow, setPastTimeNow,
setRemainingTimeNow setRemainingTimeNow,
addTerminalLine
}; };

View File

@@ -1,17 +1,162 @@
import React, {useState, useEffect, CSSProperties} from "react"; import React, {useState, useEffect, CSSProperties} from "react";
import { settingList, updateSettings, settingListLoadDefault, VideoFormat } from "../settings"; import { VideoFormat, editProfile, getActiveProfile, ProfileLoadDefault, getProfiles, setActiveProfile, createProfile, removeProfile, exportProfile, importProfile } from "../settings";
import {blender, blenderCmd, renderingPicture} from "../blenderController"; import {blender, blenderCmd, renderingPicture} from "../blenderController";
import {dataPath} from "../paths"; import {dataPath} from "../paths";
import path from "path"; import path from "path";
import logger from "../logger";
let setRenderImg:React.Dispatch<React.SetStateAction<string>>; let setRenderImg:React.Dispatch<React.SetStateAction<string>>;
let setRenderLoading:React.Dispatch<React.SetStateAction<boolean>>; let setRenderLoading:React.Dispatch<React.SetStateAction<boolean>>;
let pageLoaded = false; let pageLoaded = false;
const VideoFormatOptions = Object.keys(VideoFormat).filter((el) => { return isNaN(Number(el)) }).map(key => {
return <option key={key} value={key}>{key}</option>;
});
function picturePath() { function picturePath() {
return path.join(dataPath, "render.png?t="+Date.now()); return path.join(dataPath, "render.png?t="+Date.now());
} }
const overlayArrowStyle:CSSProperties = {
width: "50%",
height: "50%",
fill: "white"
}
const settingLabelStyle:CSSProperties = {
position: "relative",
paddingLeft: "10px",
paddingRight: "10px",
height: "100%",
background: "#00c24a",
borderTopLeftRadius: "19px",
borderBottomLeftRadius: "19px",
fontWeight: "bolder",
overflow: "hidden",
display: "flex",
alignItems: "center"
}
function InputSpan({name, value, min, step, onChange, onReset}:{name:string, value:number, min:number, step:number, onChange:React.ChangeEventHandler<HTMLInputElement>, onReset:CallableFunction}) {
const [dispayOverlay, setDisplayOverlay] = useState("none");
return (
<span className="inputSelectSpan">
<div style={settingLabelStyle} onMouseEnter={() => {setDisplayOverlay("flex");}} onMouseLeave={() => {setDisplayOverlay("none");}}>
{name}
<div className="overlay" style={{display: dispayOverlay}} onClick={() => {onReset()}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={overlayArrowStyle}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M125.7 160H176c17.7 0 32 14.3 32 32s-14.3 32-32 32H48c-17.7 0-32-14.3-32-32V64c0-17.7 14.3-32 32-32s32 14.3 32 32v51.2L97.6 97.6c87.5-87.5 229.3-87.5 316.8 0s87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3s-163.8-62.5-226.3 0L125.7 160z"/>
</svg>
</div>
</div>
<input id={name+" Input"} type="number" value={value.toString()} min={min.toString()} step={step.toString()} onChange={onChange}/>
</span>
)
}
function TextSpan({name, value, placeholder, onChange, onReset}:{name:string, value:string, placeholder:string, onChange:React.ChangeEventHandler<HTMLInputElement>, onReset?:CallableFunction}) {
const [dispayOverlay, setDisplayOverlay] = useState("none");
const Overlay = (
<div className="overlay" style={{display: dispayOverlay}} onClick={() => {
if(onReset !== undefined) {
onReset()
}
}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={overlayArrowStyle}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M125.7 160H176c17.7 0 32 14.3 32 32s-14.3 32-32 32H48c-17.7 0-32-14.3-32-32V64c0-17.7 14.3-32 32-32s32 14.3 32 32v51.2L97.6 97.6c87.5-87.5 229.3-87.5 316.8 0s87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3s-163.8-62.5-226.3 0L125.7 160z"/>
</svg>
</div>
)
return (
<span className="inputSelectSpan">
<div style={settingLabelStyle} onMouseEnter={() => {setDisplayOverlay("flex");}} onMouseLeave={() => {setDisplayOverlay("none");}}>
{name}
{onReset !== undefined ? Overlay : null}
</div>
<input id={name+" Input"} type="text" value={value.toString()} placeholder={placeholder} onChange={onChange}/>
</span>
)
}
function SelectSpan({name, value, optiones, onChange, onReset}:{name:string, value:string, optiones:JSX.Element[], onChange:React.ChangeEventHandler<HTMLSelectElement>, onReset?:CallableFunction}) {
const [dispayOverlay, setDisplayOverlay] = useState("none");
const Overlay = (
<div className="overlay" style={{display: dispayOverlay}} onClick={() => {
if(onReset !== undefined) {
onReset()
}
}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={overlayArrowStyle}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M125.7 160H176c17.7 0 32 14.3 32 32s-14.3 32-32 32H48c-17.7 0-32-14.3-32-32V64c0-17.7 14.3-32 32-32s32 14.3 32 32v51.2L97.6 97.6c87.5-87.5 229.3-87.5 316.8 0s87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3s-163.8-62.5-226.3 0L125.7 160z"/>
</svg>
</div>
)
return (
<span className="inputSelectSpan">
<div style={settingLabelStyle} onMouseEnter={() => {setDisplayOverlay("flex");}} onMouseLeave={() => {setDisplayOverlay("none");}}>
{name}
{onReset !== undefined ? Overlay : null}
</div>
<select id={name+" slct"} required={true} value={value} onChange={onChange}>
{optiones}
</select>
</span>
)
}
function ToggleSpan({name, state, checkedValue, uncheckedValue, onChange, onReset}:{name:string, state:boolean, checkedValue:string, uncheckedValue:string, onChange(checked:boolean):void, onReset:CallableFunction}) {
const [dispayOverlay, setDisplayOverlay] = useState("none");
const [checked, setChecked] = useState(state);
useEffect(() => {
setChecked(state);
}, [state]);
const toggleStyle:CSSProperties = {
height: "100%",
border: "0",
borderTopRightRadius: "18px",
borderBottomRightRadius: "18px",
textAlign: "center",
fontSize: "large",
paddingLeft: "15px",
paddingRight: "15px",
display: "flex",
alignItems: "center",
backgroundColor: checked ? "#2196F3" : "white",
color: checked ? "white" : "black",
cursor: "pointer",
userSelect: "none"
}
return (
<span className="inputSelectSpan">
<div style={settingLabelStyle} onMouseEnter={() => {setDisplayOverlay("flex");}} onMouseLeave={() => {setDisplayOverlay("none");}}>
{name}
<div className="overlay" style={{display: dispayOverlay}} onClick={() => {onReset()}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={overlayArrowStyle}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M125.7 160H176c17.7 0 32 14.3 32 32s-14.3 32-32 32H48c-17.7 0-32-14.3-32-32V64c0-17.7 14.3-32 32-32s32 14.3 32 32v51.2L97.6 97.6c87.5-87.5 229.3-87.5 316.8 0s87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3s-163.8-62.5-226.3 0L125.7 160z"/>
</svg>
</div>
</div>
<div style={toggleStyle} onClick={() => {
onChange(!checked);
setChecked(!checked);
}}>
{checked ? checkedValue : uncheckedValue}
</div>
</span>
)
}
const RenderLoadingSpinner = () => ( const RenderLoadingSpinner = () => (
<div id="renderLoadingDiv"> <div id="renderLoadingDiv">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
@@ -40,9 +185,9 @@ function VideoFormatWarning({videoFormat}:{videoFormat:VideoFormat}) {
} }
const style:CSSProperties = { const style:CSSProperties = {
height: "30px", height: "35px",
width: "30px", width: "35px",
fill: "orange", fill: "yellow",
paddingLeft: "5px", paddingLeft: "5px",
}; };
@@ -57,21 +202,91 @@ function VideoFormatWarning({videoFormat}:{videoFormat:VideoFormat}) {
); );
} }
function ProfileSettings({setNameProfile, setProfileName, setNewProfileName, profileName, setNewProfileType, setProfileOptions}:{setNameProfile:CallableFunction, setProfileName:CallableFunction, setNewProfileName:CallableFunction, profileName:string, setNewProfileType:CallableFunction, setProfileOptions:CallableFunction}) {
return (
<>
<button title="Rename" style={{width:"35px", height:"35px", marginLeft:"5px"}} onClick={() => {
setNewProfileName(profileName);
setNameProfile(true);
setNewProfileType("rename");
}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={{fill:"white"}}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M471.6 21.7c-21.9-21.9-57.3-21.9-79.2 0L362.3 51.7l97.9 97.9 30.1-30.1c21.9-21.9 21.9-57.3 0-79.2L471.6 21.7zm-299.2 220c-6.1 6.1-10.8 13.6-13.5 21.9l-29.6 88.8c-2.9 8.6-.6 18.1 5.8 24.6s15.9 8.7 24.6 5.8l88.8-29.6c8.2-2.8 15.7-7.4 21.9-13.5L437.7 172.3 339.7 74.3 172.4 241.7zM96 64C43 64 0 107 0 160V416c0 53 43 96 96 96H352c53 0 96-43 96-96V320c0-17.7-14.3-32-32-32s-32 14.3-32 32v96c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V160c0-17.7 14.3-32 32-32h96c17.7 0 32-14.3 32-32s-14.3-32-32-32H96z"/>
</svg>
</button>
<button title="Create" style={{width:"35px", height:"35px", marginLeft:"5px"}} onClick={() => {
setNewProfileName("");
setNameProfile(true);
setNewProfileType("create");
}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style={{fill:"white"}}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32V224H48c-17.7 0-32 14.3-32 32s14.3 32 32 32H192V432c0 17.7 14.3 32 32 32s32-14.3 32-32V288H400c17.7 0 32-14.3 32-32s-14.3-32-32-32H256V80z"/>
</svg>
</button>
<button title="Duplicate" style={{width:"35px", height:"35px", marginLeft:"5px"}} onClick={() => {
setNewProfileName("");
setNameProfile(true);
setNewProfileType("duplicate");
}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={{fill:"white"}}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M502.6 70.63l-61.25-61.25C435.4 3.371 427.2 0 418.7 0H255.1c-35.35 0-64 28.66-64 64l.0195 256C192 355.4 220.7 384 256 384h192c35.2 0 64-28.8 64-64V93.25C512 84.77 508.6 76.63 502.6 70.63zM464 320c0 8.836-7.164 16-16 16H255.1c-8.838 0-16-7.164-16-16L239.1 64.13c0-8.836 7.164-16 16-16h128L384 96c0 17.67 14.33 32 32 32h47.1V320zM272 448c0 8.836-7.164 16-16 16H63.1c-8.838 0-16-7.164-16-16L47.98 192.1c0-8.836 7.164-16 16-16H160V128H63.99c-35.35 0-64 28.65-64 64l.0098 256C.002 483.3 28.66 512 64 512h192c35.2 0 64-28.8 64-64v-32h-47.1L272 448z"/>
</svg>
</button>
<button title="Delete" style={{width:"35px", height:"35px", backgroundColor:"#e1334e", marginLeft:"5px"}} onClick={() => {
if (getProfiles().length === 1) {
logger.warningMSG("You can't delete the last profile!");
return;
}
removeProfile(profileName);
const newActiveProfile = getProfiles()[getProfiles().length - 1];
setActiveProfile(newActiveProfile);
setProfileName(newActiveProfile);
setProfileOptions(getProfiles().map(p => {
return <option key={p} value={p}>{p}</option>;
}));
}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style={{fill:"white"}}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/>
</svg>
</button>
</>
)
}
function SettingsPage() { function SettingsPage() {
const [fps, setFps] = useState(settingList.fps); const [fps, setFps] = useState(getActiveProfile().fps);
const [width, setWidth] = useState(settingList.width); const [width, setWidth] = useState(getActiveProfile().width);
const [stickDistance, setStickDistance] = useState(settingList.stickDistance); const [stickDistance, setStickDistance] = useState(getActiveProfile().stickDistance);
const [stickMode2, setStickMode2] = useState(settingList.stickMode2); const [stickMode2, setStickMode2] = useState(getActiveProfile().stickMode2);
const [videoFormat, setVideoFormat] = useState(settingList.videoFormat); const [videoFormat, setVideoFormat] = useState(getActiveProfile().videoFormat);
const [renderImg, setRenderImgInner] = useState(picturePath()); const [renderImg, setRenderImgInner] = useState(picturePath());
setRenderImg = setRenderImgInner; setRenderImg = setRenderImgInner;
const [renderLoading, setRenderLoadingInner] = useState(renderingPicture); const [renderLoading, setRenderLoadingInner] = useState(renderingPicture);
setRenderLoading = setRenderLoadingInner; setRenderLoading = setRenderLoadingInner;
const [profileOptions, setProfileOptions] = useState(getProfiles().map(p => {
return <option key={p} value={p}>{p}</option>;
}));
const [profileName, setProfileName] = useState(getActiveProfile().profileName);
const [newProfileName, setNewProfileName] = useState("");
const [nameProfile, setNameProfile] = useState(false);
const [newProfileType, setNewProfileType] = useState("");
useEffect(() => {
setFps(getActiveProfile().fps);
setWidth(getActiveProfile().width);
setStickDistance(getActiveProfile().stickDistance);
setStickMode2(getActiveProfile().stickMode2);
setVideoFormat(getActiveProfile().videoFormat);
}, [profileName]);
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
updateSettings({width, stickDistance, stickMode2}); editProfile({width, stickDistance, stickMode2});
blender(blenderCmd.getRender); blender(blenderCmd.getRender);
}, 500); }, 500);
@@ -80,7 +295,7 @@ function SettingsPage() {
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
updateSettings({fps, videoFormat}); editProfile({fps, videoFormat});
}, 500); }, 500);
return () => clearTimeout(timer); return () => clearTimeout(timer);
@@ -88,66 +303,133 @@ function SettingsPage() {
pageLoaded = true; pageLoaded = true;
const VideoFormatOptions = Object.keys(VideoFormat).filter((el) => { return isNaN(Number(el)) }).map(key => {
return <option key={key} value={key}>{key}</option>;
});
return ( return (
<div id="content"> <div id="content">
<div id="settingRow"> <div id="settingRow">
<span className="inputSpan"> <div style={{
<label>FPS</label> display: "flex",
<input id="fpsInput" type="number" value={fps.toString()} min="1" step="1" onChange={e => { }}>
if(e.target.value.trim().length !== 0) setFps(parseInt(e.target.value)); {nameProfile? <TextSpan name="Set Profile Name" value={newProfileName} placeholder="Enter Profile Name Here" onChange={e => {
}}/> setNewProfileName(e.target.value);
</span> }}/> : <SelectSpan name="Select Profile" value={profileName} optiones={profileOptions} onChange={ e => {
<span className="inputSpan"> setActiveProfile(e.target.value);
<label>Width</label> setProfileName(e.target.value);
<input id="widthInput" type="number" value={width.toString()} min="1" step="1" onChange={e => { }}/>
if(e.target.value.trim().length !== 0) setWidth(parseInt(e.target.value)); }
}}/> {nameProfile? <button title="Save" style={{width:"35px", height:"35px", marginLeft:"5px"}} onClick={() => {
</span> let profileExists = false;
<span className="inputSpan"> getProfiles().forEach(profile => {
<label>Stick Distance</label> if (profile === newProfileName) {
<input id="stickDistanceInput" type="number" value={stickDistance.toString()} min="0" step="1" onChange={e => { profileExists = true;
if(e.target.value.trim().length !== 0) setStickDistance(parseInt(e.target.value)); }
}}/> });
</span>
if (profileExists) {
logger.warningMSG("Profile with the name \""+newProfileName+"\" already exists");
} else {
setProfileName(newProfileName);
if(newProfileType === "rename") {
editProfile({profileName: newProfileName});
} else if (newProfileType === "create") {
createProfile(newProfileName, false);
} else if (newProfileType === "duplicate") {
createProfile(newProfileName, true);
}
setActiveProfile(newProfileName);
}
setProfileOptions(getProfiles().map(p => {
return <option key={p} value={p}>{p}</option>;
}));
setNameProfile(false);
}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style={{fill:"white"}}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M224 256c-35.2 0-64 28.8-64 64c0 35.2 28.8 64 64 64c35.2 0 64-28.8 64-64C288 284.8 259.2 256 224 256zM433.1 129.1l-83.9-83.9C341.1 37.06 328.8 32 316.1 32H64C28.65 32 0 60.65 0 96v320c0 35.35 28.65 64 64 64h320c35.35 0 64-28.65 64-64V163.9C448 151.2 442.9 138.9 433.1 129.1zM128 80h144V160H128V80zM400 416c0 8.836-7.164 16-16 16H64c-8.836 0-16-7.164-16-16V96c0-8.838 7.164-16 16-16h16v104c0 13.25 10.75 24 24 24h192C309.3 208 320 197.3 320 184V83.88l78.25 78.25C399.4 163.2 400 164.8 400 166.3V416z"/>
</svg>
</button> : <ProfileSettings setNameProfile={setNameProfile} setProfileName={setProfileName} setNewProfileName={setNewProfileName} profileName={profileName} setNewProfileType={setNewProfileType} setProfileOptions={setProfileOptions}/>
}
</div>
<div style={{
display: "flex",
}}>
<button title="Import" style={{width:"35px", height:"35px"}} onClick={() => {
importProfile(() => {
setProfileOptions(getProfiles().map(p => {
return <option key={p} value={p}>{p}</option>;
}));
});
}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={{fill:"white"}}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M128 64c0-35.3 28.7-64 64-64H352V128c0 17.7 14.3 32 32 32H512V448c0 35.3-28.7 64-64 64H192c-35.3 0-64-28.7-64-64V336H302.1l-39 39c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l80-80c9.4-9.4 9.4-24.6 0-33.9l-80-80c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l39 39H128V64zm0 224v48H24c-13.3 0-24-10.7-24-24s10.7-24 24-24H128zM512 128H384V0L512 128z"/>
</svg>
</button>
<button title="Export" style={{width:"35px", height:"35px", marginLeft:"5px"}} onClick={() => {
exportProfile();
}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" style={{fill:"white"}}>
{/* <!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> */}
<path d="M32 64C32 28.7 60.7 0 96 0H256V128c0 17.7 14.3 32 32 32H416V288H248c-13.3 0-24 10.7-24 24s10.7 24 24 24H416V448c0 35.3-28.7 64-64 64H96c-35.3 0-64-28.7-64-64V64zM416 336V288H526.1l-39-39c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l80 80c9.4 9.4 9.4 24.6 0 33.9l-80 80c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l39-39H416zm0-208H288V0L416 128z"/>
</svg>
</button>
</div>
</div> </div>
<div id="settingRow"> <div id="settingRow">
<div className="dataDiv"> {<InputSpan name={"FPS"} value={fps} min={1} step={1} onChange={
<p>Stick Mode</p> e => {
<label htmlFor="stickMode" className="toggle-switchy" data-style="rounded" data-text="12"> if(e.target.value.trim().length !== 0) setFps(parseInt(e.target.value));
<input checked={stickMode2} type="checkbox" id="stickMode" onChange={e => { }
setStickMode2(e.target.checked); } onReset={() => {
}}/> ProfileLoadDefault({fps: true});
<span className="toggle"> setFps(getActiveProfile().fps);
<span className="switch"></span> }}/>}
</span> {<InputSpan name="Width" value={width} min={1} step={1} onChange={
</label> e => {
</div> if(e.target.value.trim().length !== 0) setWidth(parseInt(e.target.value));
<span className="selectSpan"> }
<label className="selectSpanLabel">Format</label> } onReset={() => {
<label className="selectSpanSelect" htmlFor="slct"> ProfileLoadDefault({width: true});
<select id="slct" required={true} value={videoFormat} onChange={e => { setWidth(getActiveProfile().width);
setVideoFormat(e.target.value as unknown as VideoFormat); }}/>}
}}> {<InputSpan name="Stick Distance" value={stickDistance} min={0} step={1} onChange={
{VideoFormatOptions} e => {
</select> if(e.target.value.trim().length !== 0) setStickDistance(parseInt(e.target.value));
</label> }
} onReset={() => {
ProfileLoadDefault({stickDistance: true});
setStickDistance(getActiveProfile().stickDistance);
}}/>}
</div>
<div id="settingRow">
{<ToggleSpan name="Stick Mode" state={stickMode2} checkedValue={"2"} uncheckedValue={"1"} onChange={checked => {
setStickMode2(checked);
}} onReset={() => {
ProfileLoadDefault({stickMode2: true});
setStickMode2(getActiveProfile().stickMode2);
}}/>}
<div style={{
display: "flex",
}}>
{<SelectSpan name="Format" value={videoFormat} optiones={VideoFormatOptions} onChange={ e => {
setVideoFormat(e.target.value as unknown as VideoFormat);
}} onReset={() => {
ProfileLoadDefault({videoFormat: true});
setVideoFormat(getActiveProfile().videoFormat);
}}/>}
{videoFormat === VideoFormat.mov? <VideoFormatWarning videoFormat={videoFormat}/> : null} {videoFormat === VideoFormat.mov? <VideoFormatWarning videoFormat={videoFormat}/> : null}
{videoFormat === VideoFormat.mp4? <VideoFormatWarning videoFormat={videoFormat}/> : null} {videoFormat === VideoFormat.mp4? <VideoFormatWarning videoFormat={videoFormat}/> : null}
{videoFormat === VideoFormat.avi? <VideoFormatWarning videoFormat={videoFormat}/> : null} {videoFormat === VideoFormat.avi? <VideoFormatWarning videoFormat={videoFormat}/> : null}
</span> {videoFormat === VideoFormat.mkv? <VideoFormatWarning videoFormat={videoFormat}/> : null}
</div>
<button id="resetSettingsButton" onClick={() => { <button id="resetSettingsButton" onClick={() => {
settingListLoadDefault(); ProfileLoadDefault({all: true});
setFps(settingList.fps); setFps(getActiveProfile().fps);
setWidth(settingList.width); setWidth(getActiveProfile().width);
setStickDistance(settingList.stickDistance); setStickDistance(getActiveProfile().stickDistance);
setStickMode2(settingList.stickMode2); setStickMode2(getActiveProfile().stickMode2);
setVideoFormat(settingList.videoFormat); setVideoFormat(getActiveProfile().videoFormat);
}}>Reset Settings</button> }}>Reset Profile</button>
</div> </div>
<div id="renderImgDiv"> <div id="renderImgDiv">
<img id="render-ex" src={renderImg}></img> <img id="render-ex" src={renderImg}></img>

View File

@@ -1,3 +1,12 @@
body {
background-color: #172336;
margin: 0;
padding: 0;
color: white;
font-family: sans-serif;
overflow: hidden;
}
.dataDiv { .dataDiv {
display: flex; display: flex;
} }
@@ -249,16 +258,18 @@ header h1 {
} }
#render-ex { #render-ex {
width: 100%; width: auto;
height: auto; height: auto;
max-width: 100%;
display: block; display: block;
border-radius: 10px; border-radius: 10px;
} }
#renderImgDiv { #renderImgDiv {
position: relative; position: relative;
margin-top: 15px; margin-top: 15px;
width: 100%;
image-rendering: pixelated; image-rendering: pixelated;
width: fit-content;
height: fit-content;
} }
.noMarginBottom { .noMarginBottom {
@@ -310,38 +321,64 @@ button:hover {
transform: scale(1.05); transform: scale(1.05);
} }
.inputSpan { .inputSelectSpan {
position: relative; position: relative;
display: inline-block; display: flex;
align-items: center; align-items: center;
padding-bottom: 15px; height: 35px;
margin-bottom: 15px;
} }
.inputSpan input { .inputSelectSpan input {
width: 75px; width: 75px;
padding: 8px 0; height: 100%;
border: 0; border: 0;
border-top-right-radius: 18px; border-top-right-radius: 18px;
border-bottom-right-radius: 18px; border-bottom-right-radius: 18px;
text-align: center; text-align: center;
font-size: large; font-size: large;
line-height: 0px; padding: 0;
} }
.inputSpan label { .inputSelectSpan label {
padding: 10px 10px; padding-left: 10px;
padding-right: 10px;
height: 100%;
background: #00c24a; background: #00c24a;
border-top-left-radius: 18px; border-top-left-radius: 18px;
border-bottom-left-radius: 18px; border-bottom-left-radius: 18px;
font-weight: bolder; font-weight: bolder;
overflow: hidden;
}
.inputSelectSpan select {
height: 100%;
border: 0;
border-top-right-radius: 18px;
border-bottom-right-radius: 18px;
cursor: pointer;
text-align: center;
font-size: large;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.8);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
} }
#settingRow { #settingRow {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
align-items: center;
} }
#resetSettingsButton { #resetSettingsButton {
height: 35px; height: 35px;
background-color: #e1334e;
} }
#logList-Name:hover { #logList-Name:hover {
@@ -410,22 +447,39 @@ button:hover {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.selectSpanSelect select {
padding: 8px 0;
border: 0;
border-top-right-radius: 18px;
border-bottom-right-radius: 18px;
cursor: pointer;
text-align: center;
font-size: large;
}
.selectSpanLabel {
padding: 10px 10px;
background: #00c24a;
border-top-left-radius: 18px;
border-bottom-left-radius: 18px;
font-weight: bolder;
}
#videoFormatWarning:hover { #videoFormatWarning:hover {
transform: scale(1.05); transform: scale(1.05);
}
#outerTerminal {
margin-top: 10px;
margin-bottom: 5px;
width: 100%;
height: 300px;
background-color: #0d131e;
color: white;
font-family: monospace;
font-size: 12px;
border-radius: 10px;
padding: 10px;
box-sizing: border-box;
}
#outerTerminal p {
margin: 2px;
}
#innerTerminal {
width: 100%;
height: 100%;
overflow: auto;
}
#terminalTable tr {
display: flex;
}
.terminalTableNumber {
color: #9DA8B9;
background-color: #172336;
margin-right: 5px;
} }

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>StickExporterTX</title> <title>StickExporterTX</title>
</head> </head>
<body style="background-color: #172336;margin:0;padding:0;color:white;font-family:sans-serif;"> <body>
<div id="root"></div> <div id="root"></div>
<script> <script>

View File

@@ -7,7 +7,7 @@ import RenderingPage from "./components/ui/renderingPage";
import RenderFinishPage from "./components/ui/renderFinishPage"; import RenderFinishPage from "./components/ui/renderFinishPage";
import "./index.css"; import "./index.css";
import "./toggle-switchy.css"; import "./toggle-switchy.css";
import { startBlender } from "./components/blenderController"; import { blender, blenderCmd, startBlender } from "./components/blenderController";
import {ipcRenderer} from "electron"; import {ipcRenderer} from "electron";
enum Page { enum Page {
@@ -40,6 +40,18 @@ function openPage(page:Page) {
openPage(currentPage); openPage(currentPage);
window.addEventListener("keydown", (e:KeyboardEvent) => {
if(e.key === "Escape") {
if(currentPage === Page.Main) {
ipcRenderer.send("closeApp");
} else if(currentPage === Page.Rendering) {
blender(blenderCmd.stopRendering);
} else {
openPage(Page.Main);
}
}
});
startBlender(); startBlender();
function pageSetRendering(value:boolean) { function pageSetRendering(value:boolean) {