mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-02-25 00:54:44 +08:00
Compare commits
8 Commits
0e2ecdf93e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a68b2863c | ||
|
|
9c0e172630 | ||
|
|
fb7fc9fc66 | ||
|
|
5ef585eb34 | ||
|
|
db53d20762 | ||
|
|
d1cd8d4b3e | ||
|
|
c4911fe583 | ||
|
|
6ba2705386 |
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
.vscode/extensions.json
vendored
Normal file → Executable file
0
.vscode/extensions.json
vendored
Normal file → Executable file
27
.vscode/launch.json
vendored
27
.vscode/launch.json
vendored
@@ -5,31 +5,12 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch index.html",
|
||||
"name": "Launch Blueprintue",
|
||||
"type": "firefox",
|
||||
"request": "launch",
|
||||
"reAttach": true,
|
||||
"file": "${workspaceFolder}/index.html"
|
||||
"url": "http://127.0.0.1:8080/debug.html",
|
||||
"tmpDir": "~/.tmp/"
|
||||
},
|
||||
{
|
||||
"name": "Launch localhost",
|
||||
"type": "firefox",
|
||||
"request": "launch",
|
||||
"reAttach": true,
|
||||
"url": "http://localhost/index.html",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Attach",
|
||||
"type": "firefox",
|
||||
"request": "attach"
|
||||
},
|
||||
{
|
||||
"name": "Launch WebExtension",
|
||||
"type": "firefox",
|
||||
"request": "launch",
|
||||
"reAttach": true,
|
||||
"addonPath": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
0
.vscode/settings.json
vendored
Normal file → Executable file
0
.vscode/settings.json
vendored
Normal file → Executable file
20
CONTRIBUTING.md
Normal file → Executable file
20
CONTRIBUTING.md
Normal file → Executable file
@@ -47,20 +47,28 @@ And refresh the HTML page possibly holding `Shift`.
|
||||
There are a few concepts that must be assimilated in order to understand the design. Those concepts are in general each mapped into a subfolder in `js/`.
|
||||
|
||||
### Entity
|
||||
An Entity is just a data holder object that does not really do anything by itself, it has a purely information storage related purpose. The top class at the hierarchy of entities is `IEntity`. It is responsible for the initialization of the entity in its constructor according to the information contained in the object provided as an argument or from the attributes static field. This ended up being a somewhat wacky runtime type system. Each subclass can specify its attributes in the form of a static member variable of type vanilla object where each entry is a subclass of IEntity. The entities moreover are responsible for their serialization and deserialization: each class has a `serialize()` method and a `grammar` attribute to parse entities.
|
||||
An **Entity** is a simple object that only stores data, it doesn’t perform any actions. The base class for all entities is `IEntity`. Its main job is to initialize an entity using data from an input object or a predefined attributes field. This setup has resulted in a somewhat unconventional runtime type system. Each subclass of `IEntity` defines its `attributes` as a object static field, where each entry represents another `IEntity` subclass (class object, not instance). Additionally, entities handle their own serialization and deserialization using the `serialize()` method and a `grammar` attribute, which parses entity data from the blueprint code.
|
||||
|
||||
### Element
|
||||
Each element is just a custom HTML element type and its tag name is defined in the class file. The top level of the hierarchy is `IElement` and it inherits from `LitElement`. This class can be thought as an association between an entity and a template (and those are the arguments of the constructor). The top class `IElement` does propagate the lifecycle provided by `LitElement` to the template so that a template can hook into it.
|
||||
An **Element** is a custom HTML element, with its tag name defined in the class file. At the top of the hierarchy is `IElement`, which extends `LitElement`. You can think of `IElement` as a bridge between an *Entity* and a *Template*, these are passed as arguments to its constructor. Moreover, `IElement` ensures that the lifecycle events provided by `LitElement` are passed down to the template, allowing the template to hook into them.
|
||||
|
||||
### Template
|
||||
When looking at the Lit documentation, it might be noticed that usually HTML templates are returned as part of the `render()` method of an Element. The problem with such approach is that it makes it hard to have very different templates and UI behavior for the same element in a natural way (by means of inheritance because a custom element cannot be mapped to multiple classes). Take for example a `<ueb-pin>` in a graph node, it may or may not have any input and if it has one, the input might be a checkbox, a vector or something completely different like a texture. For this reason the responsibility to render the HTML content is moved from the Element to the Template and inheritance is replaced with composition so that two same elements can have different templates.
|
||||
Templates do have access to the same lifecycle as elements have, this is implemented in the IElement class that calls, for each method in the lifecycle, the relative method in the template. Moreover the templates hierarchy can also introduce new behaviors that can be replaced by subclasses, one example of such is IInputPinTemplate.
|
||||
In Lit, HTML templates are typically returned from an element’s `render()` method. However, this approach makes it difficult to apply different templates and UI behaviors to the same element, since a custom element can only be associated with one class.
|
||||
For example, consider a `<ueb-pin>` element inside a graph node. It may or may not have an input, and if it does, the input type could be text, a checkbox, a vector. To handle this flexibility, rendering is moved from the *Element* to the *Template*. Instead of using inheritance (which is limiting), we use composition, allowing the same element to have different templates.
|
||||
|
||||
Templates have access to the same lifecycle methods as elements. This is achieved in `IElement`, which forwards lifecycle calls to the corresponding methods in the template. The template hierarchy can introduce new behaviors that subclasses can override. For example, `IInputPinTemplate` defines behaviors that can be customized by its subclasses.
|
||||
|
||||
### Input
|
||||
Classes used to map input events (generated from a mouse or a keyboard for example) to operations on the graph. They do model advanced user interaction (like mouse drag) that are originated by input JavaScript events. Simpler events (like click or focus), are implemented in the lit templates directly.
|
||||
These classes handle input events (such as mouse and keyboard interactions) and map them to operations on the graph. They support complex interactions, like dragging with the mouse, which originate from JavaScript input events. In contrast, simpler events such as click or focus are handled directly within Lit templates.
|
||||
|
||||
### Selection
|
||||
It contains just a few classes related exclusively to the operation of selecting nodes. It is an (arguably useless) attempt to optimize the selection in case of graphs with a very large numbers of nodes (it is not really usefull because in the case of many many nodes, the bootleneck becomes the DOM rendering, not deciding in JavaScript which nodes are selected and which are not even though this happens every frame). Selection has two models: one very simple that checks every frame all the nodes in the graph to see whether or not they are selected by the selector, and the fast model that attemps to optimize the number of nodes that are looked up at, much more complicated and not super usefull as stated before.
|
||||
This module contains a few classes focused solely on selecting nodes. It was originally designed to optimize selection for graphs with a very large number of nodes, but in practice, this optimization is of limited use. The real performance bottleneck in such cases is DOM rendering, not the JavaScript logic that determines which nodes are selected.
|
||||
There are two selection models:
|
||||
1. Simple Model: every frame, it checks all nodes in the graph to determine whether they are selected.
|
||||
2. Fast Model: uses a structured index of nodes to limit lookups, reducing the need to scan the entire graph.
|
||||
|
||||
### Decoding
|
||||
This handles blueprint interpretation, defining node *Template* classes, colors, icons, and other attributes like the title of a node.
|
||||
|
||||
## Code Style
|
||||
|
||||
|
||||
11
README.md
Normal file → Executable file
11
README.md
Normal file → Executable file
@@ -1,6 +1,8 @@
|
||||
# UEBlueprint
|
||||
|
||||
A stand alone editor implementation of the UE's Blueprint visual language.
|
||||
A stand alone implementation of the UE's Blueprint visual language editor
|
||||
|
||||
https://github.com/barsdeveloper/ueblueprint ⭐
|
||||
|
||||
https://www.npmjs.com/package/ueblueprint
|
||||
|
||||
@@ -34,7 +36,7 @@ npx http-server
|
||||
|
||||
You can check `index.html` for a working example, the main steps are the following:
|
||||
1. Make the `dist` directory available in your website by copying it or installing through npm `npm i ueblueprint`.
|
||||
2. Include `dist/css/ueb-style.css` stylesheet in your page.
|
||||
2. Include `dist/css/ueb-style.min.css` stylesheet in your page.
|
||||
3. Define eventual CSS variables.
|
||||
```HTML
|
||||
<style>
|
||||
@@ -50,6 +52,11 @@ You can check `index.html` for a working example, the main steps are the followi
|
||||
</script>
|
||||
```
|
||||
5. Define your blueprint by writing the code inside a `template`, inside a `ueb-blueprint` element.
|
||||
Configuration:
|
||||
- Height: `<ueb-blueprint style="--ueb-height: 500px">`
|
||||
- Light mode: `<ueb-blueprint class="ueb-light-mode">`
|
||||
- Initial zoom: `<ueb-blueprint data-zoom="-4">`
|
||||
- Graph type: `<ueb-blueprint data-type="MATERIAL FUNCTION">`
|
||||
```HTML
|
||||
<ueb-blueprint>
|
||||
<template>
|
||||
|
||||
0
assets/fonts/roboto-bold.woff2
Normal file → Executable file
0
assets/fonts/roboto-bold.woff2
Normal file → Executable file
0
assets/fonts/roboto-condensed-bold.woff2
Normal file → Executable file
0
assets/fonts/roboto-condensed-bold.woff2
Normal file → Executable file
0
assets/fonts/roboto-light.woff2
Normal file → Executable file
0
assets/fonts/roboto-light.woff2
Normal file → Executable file
0
assets/fonts/roboto-regular.woff2
Normal file → Executable file
0
assets/fonts/roboto-regular.woff2
Normal file → Executable file
24
debug.html
Executable file
24
debug.html
Executable file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>UE Blueprint</title>
|
||||
<link rel="stylesheet" href="dist/css/ueb-style.css">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
--ueb-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="module">
|
||||
import { Blueprint } from "./dist/ueblueprint.js"
|
||||
</script>
|
||||
<ueb-blueprint></ueb-blueprint>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
144
dist/css/ueb-style.css
vendored
Normal file → Executable file
144
dist/css/ueb-style.css
vendored
Normal file → Executable file
@@ -11,6 +11,9 @@
|
||||
ueb-blueprint {
|
||||
--ueb-scale: 1;
|
||||
--ueb-grid-actual-size: var(--ueb-grid-size);
|
||||
--ueb-grid-line-color: #353535;
|
||||
--ueb-grid-set-line-color: #161616;
|
||||
--ueb-grid-axis-line-color: #000;
|
||||
display: block;
|
||||
position: relative;
|
||||
font-family: Roboto, Noto, Oxygen, Ubuntu, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
@@ -34,6 +37,15 @@ ueb-blueprint svg {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ueb-viewport-about {
|
||||
margin-top: -6px;
|
||||
padding: 0 8px;
|
||||
align-self: center;
|
||||
font-size: 20px;
|
||||
font-weight: bolder;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@keyframes ueb-zoom-animation {
|
||||
0% {
|
||||
color: #7f7f7f;
|
||||
@@ -43,7 +55,7 @@ ueb-blueprint svg {
|
||||
}
|
||||
}
|
||||
.ueb-zoom-changed .ueb-viewport-zoom {
|
||||
animation: 600ms ueb-zoom-animation;
|
||||
animation: 1500ms ueb-zoom-animation;
|
||||
}
|
||||
|
||||
.ueb-viewport-zoom {
|
||||
@@ -56,6 +68,18 @@ ueb-blueprint svg {
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.ueb-viewport-type {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 5px;
|
||||
font-size: 60px;
|
||||
font-weight: 900;
|
||||
color: rgba(128, 128, 128, 0.2509803922);
|
||||
font-stretch: condensed;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ueb-viewport-body {
|
||||
position: relative;
|
||||
height: var(--ueb-height, 30rem);
|
||||
@@ -90,7 +114,6 @@ ueb-blueprint[data-scrolling=false] .ueb-grid {
|
||||
|
||||
ueb-blueprint.ueb-zoom--4 {
|
||||
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 2);
|
||||
--ueb-node-radius: 0 !important;
|
||||
}
|
||||
|
||||
ueb-blueprint.ueb-zoom--6 {
|
||||
@@ -145,6 +168,50 @@ ueb-selector > * {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ueb-info-dialog {
|
||||
border: 6px solid #202020;
|
||||
padding: 5px 30px;
|
||||
background: #121212;
|
||||
color: #f0f0f0;
|
||||
box-shadow: 0 2px 10px 0 #000;
|
||||
}
|
||||
.ueb-info-dialog a:link,
|
||||
.ueb-info-dialog a:visited,
|
||||
.ueb-info-dialog a:hover,
|
||||
.ueb-info-dialog a:active {
|
||||
outline: none;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
.ueb-info-dialog::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.6274509804);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
/* Light mode */
|
||||
ueb-blueprint.ueb-light-mode {
|
||||
--ueb-grid-line-color: #ddd;
|
||||
--ueb-grid-set-line-color: #c0c0c0;
|
||||
--ueb-grid-axis-line-color: #a0a0a0;
|
||||
}
|
||||
|
||||
ueb-blueprint.ueb-light-mode .ueb-grid {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
ueb-blueprint.ueb-light-mode ueb-node.ueb-node-style-glass .ueb-node-wrapper {
|
||||
background-color: rgba(0, 0, 0, 0.5647058824);
|
||||
}
|
||||
|
||||
ueb-blueprint.ueb-light-mode ueb-link .ueb-link-path {
|
||||
filter: saturate(200%) brightness(0.6);
|
||||
}
|
||||
|
||||
ueb-blueprint.ueb-light-mode .ueb-viewport-header {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
color: #101010;
|
||||
}
|
||||
|
||||
ueb-node.ueb-node-style-minimal {
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -176,9 +243,7 @@ ueb-node.ueb-node-style-minimal[data-selected=true] .ueb-node-border {
|
||||
ueb-link {
|
||||
position: absolute;
|
||||
--ueb-link-color: rgb(var(--ueb-link-color-rgb));
|
||||
--ueb-from-input-coefficient: calc(2 * var(--ueb-from-input) - 1);
|
||||
/* when from-y > to-y */
|
||||
--ueb-y-reflected: clamp(0, var(--ueb-from-y) - var(--ueb-to-y) - 1, 1);
|
||||
display: block;
|
||||
margin-left: calc(var(--ueb-link-start) * -1px);
|
||||
min-width: calc(var(--ueb-link-min-width) * 1px);
|
||||
@@ -190,12 +255,11 @@ ueb-link {
|
||||
}
|
||||
|
||||
ueb-link > svg {
|
||||
--ueb-y-reflected-coefficient: calc(2 * var(--ueb-y-reflected) - 1);
|
||||
position: absolute;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 1px !important;
|
||||
transform: scaleY(calc(var(--ueb-y-reflected-coefficient) * var(--ueb-from-input-coefficient)));
|
||||
transform: scaleX(var(--ueb-link-scale-x)) scaleY(var(--ueb-link-scale-y));
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -222,10 +286,12 @@ ueb-link[data-dragging=true] .ueb-link-message {
|
||||
}
|
||||
|
||||
.ueb-link-message {
|
||||
--ueb-link-message-top: calc(50% * (var(--ueb-link-scale-y) + 1) + 22px);
|
||||
--ueb-link-message-left: calc(100% - var(--ueb-start-percentage) + 15px);
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% * (1 - var(--ueb-y-reflected)) + 22px);
|
||||
left: calc((1 - var(--ueb-from-input)) * 100% + (var(--ueb-from-input-coefficient)) * var(--ueb-start-percentage) + 15px);
|
||||
top: var(--ueb-link-message-top);
|
||||
left: var(--ueb-link-message-left);
|
||||
border: 1px solid #000;
|
||||
border-radius: 2px;
|
||||
background: linear-gradient(to bottom, #2a2a2a 0, #151515 50%, #2a2a2a 100%);
|
||||
@@ -234,6 +300,11 @@ ueb-link[data-dragging=true] .ueb-link-message {
|
||||
z-index: 1000000;
|
||||
}
|
||||
|
||||
ueb-link[data-from-input=true] .ueb-link-message {
|
||||
--ueb-link-message-top: calc(-50% * (var(--ueb-link-scale-y) - 1) + 22px);
|
||||
--ueb-link-message-left: calc(var(--ueb-start-percentage) + 15px);
|
||||
}
|
||||
|
||||
.ueb-link-message-icon {
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
@@ -267,10 +338,6 @@ ueb-node.ueb-node-style-comment {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ueb-zoom--2 ueb-node {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ueb-node-border {
|
||||
margin: -3px;
|
||||
padding: 3px;
|
||||
@@ -310,6 +377,10 @@ ueb-node[data-selected=true] > .ueb-node-border {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ueb-zoom--2 .ueb-node-wrapper {
|
||||
background: rgb(14, 16, 10);
|
||||
}
|
||||
|
||||
ueb-node.ueb-node-style-operation .ueb-node-wrapper {
|
||||
grid-template-rows: min-content auto auto min-content min-content;
|
||||
grid-template-columns: 50% 0% 1fr;
|
||||
@@ -335,7 +406,6 @@ ueb-blueprint[data-scrolling=false][data-selecting=false] .ueb-draggable {
|
||||
}
|
||||
|
||||
.ueb-zoom--2 .ueb-node-wrapper {
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
background: #101010;
|
||||
}
|
||||
@@ -372,10 +442,6 @@ ueb-node.ueb-node-style-event .ueb-node-top {
|
||||
background: linear-gradient(rgba(255, 255, 255, 0.1882352941) 1px, transparent 1px), linear-gradient(170deg, rgb(var(--ueb-node-color)) 0%, rgb(var(--ueb-node-color)) 50%, transparent 100%);
|
||||
}
|
||||
|
||||
.ueb-zoom--2 .ueb-node-top {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ueb-zoom--2 .ueb-node-style-default .ueb-node-top {
|
||||
background: rgb(var(--ueb-node-color));
|
||||
}
|
||||
@@ -524,6 +590,10 @@ ueb-node.ueb-node-style-glass .ueb-node-wrapper {
|
||||
background-repeat: repeat, no-repeat;
|
||||
}
|
||||
|
||||
.ueb-zoom--2 ueb-node.ueb-node-style-glass .ueb-node-wrapper {
|
||||
background: #101010;
|
||||
}
|
||||
|
||||
ueb-node.ueb-node-style-glass .ueb-node-name {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
@@ -723,6 +793,19 @@ ueb-blueprint[data-scrolling=false][data-selecting=false] .ueb-pin-wrapper:hover
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.ueb-pin-required-mark {
|
||||
width: 0;
|
||||
}
|
||||
.ueb-pin-required-mark::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 6px;
|
||||
height: 4px;
|
||||
background: var(--ueb-pin-color);
|
||||
margin-left: -13px;
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
.ueb-pin-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -811,6 +894,23 @@ ueb-node[data-type="/Script/BlueprintGraph.K2Node_VariableSet"] ueb-pin[data-dir
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ueb-pin-input[type=checkbox] {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 1px solid #353535;
|
||||
background: #0f0f0f;
|
||||
}
|
||||
.ueb-pin-input[type=checkbox]:checked::before {
|
||||
content: "";
|
||||
height: 0.7em;
|
||||
width: 0.8em;
|
||||
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
||||
background: #0070e0;
|
||||
}
|
||||
|
||||
ueb-pin[data-linked=true] .ueb-pin-input,
|
||||
ueb-pin[data-linked=true] .ueb-pin-input-wrapper {
|
||||
display: none;
|
||||
@@ -861,7 +961,7 @@ ueb-pin[data-type="/Script/CoreUObject.LinearColor"] .ueb-pin-input {
|
||||
max-height: 16em;
|
||||
/* 97% is to get an effective font size of 12.6px from --ueb-font-size which is 13 px by default */
|
||||
font-size: 97%;
|
||||
white-space: nowrap;
|
||||
white-space: pre;
|
||||
background: none;
|
||||
color: inherit;
|
||||
overflow: auto;
|
||||
@@ -1276,4 +1376,12 @@ ueb-ui-slider {
|
||||
color: #c0c0c0;
|
||||
}
|
||||
|
||||
/* To improve a bit performance */
|
||||
.ueb-zoom--2 * {
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: none !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=ueb-style.css.map */
|
||||
|
||||
2
dist/css/ueb-style.css.map
vendored
Normal file → Executable file
2
dist/css/ueb-style.css.map
vendored
Normal file → Executable file
File diff suppressed because one or more lines are too long
2
dist/css/ueb-style.min.css
vendored
Normal file → Executable file
2
dist/css/ueb-style.min.css
vendored
Normal file → Executable file
File diff suppressed because one or more lines are too long
2
dist/css/ueb-style.min.css.map
vendored
Normal file → Executable file
2
dist/css/ueb-style.min.css.map
vendored
Normal file → Executable file
File diff suppressed because one or more lines are too long
0
dist/font/roboto-bold.woff
vendored
Normal file → Executable file
0
dist/font/roboto-bold.woff
vendored
Normal file → Executable file
0
dist/font/roboto-bold.woff2
vendored
Normal file → Executable file
0
dist/font/roboto-bold.woff2
vendored
Normal file → Executable file
0
dist/font/roboto-condensed-bold.woff2
vendored
Normal file → Executable file
0
dist/font/roboto-condensed-bold.woff2
vendored
Normal file → Executable file
0
dist/font/roboto-condensed-regular.woff2
vendored
Normal file → Executable file
0
dist/font/roboto-condensed-regular.woff2
vendored
Normal file → Executable file
0
dist/font/roboto-light.woff
vendored
Normal file → Executable file
0
dist/font/roboto-light.woff
vendored
Normal file → Executable file
0
dist/font/roboto-light.woff2
vendored
Normal file → Executable file
0
dist/font/roboto-light.woff2
vendored
Normal file → Executable file
0
dist/font/roboto-regular.woff
vendored
Normal file → Executable file
0
dist/font/roboto-regular.woff
vendored
Normal file → Executable file
0
dist/font/roboto-regular.woff2
vendored
Normal file → Executable file
0
dist/font/roboto-regular.woff2
vendored
Normal file → Executable file
2468
dist/ueblueprint.js
vendored
2468
dist/ueblueprint.js
vendored
File diff suppressed because one or more lines are too long
12
dist/ueblueprint.min.js
vendored
Normal file → Executable file
12
dist/ueblueprint.min.js
vendored
Normal file → Executable file
File diff suppressed because one or more lines are too long
25
empty.html
25
empty.html
@@ -1,25 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>UE Blueprint</title>
|
||||
<link rel="stylesheet" href="dist/css/ueb-style.css">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
--ueb-height: 100vh;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="module">
|
||||
import { Blueprint } from "./dist/ueblueprint.js"
|
||||
</script>
|
||||
<ueb-blueprint></ueb-blueprint>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>UE Blueprint</title>
|
||||
<link rel="stylesheet" href="dist/css/ueb-style.css">
|
||||
<link rel="stylesheet" href="dist/css/ueb-style.min.css">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
@@ -5,12 +5,18 @@ import LinkElement from "./element/LinkElement.js"
|
||||
import NodeElement from "./element/NodeElement.js"
|
||||
import BlueprintEntity from "./entity/BlueprintEntity.js"
|
||||
import BooleanEntity from "./entity/BooleanEntity.js"
|
||||
import NiagaraClipboardContent from "./entity/objects/NiagaraClipboardContent.js"
|
||||
import BlueprintTemplate from "./template/BlueprintTemplate.js"
|
||||
|
||||
/** @extends {IElement<BlueprintEntity, BlueprintTemplate>} */
|
||||
export default class Blueprint extends IElement {
|
||||
|
||||
static properties = {
|
||||
blueprintType: {
|
||||
type: String,
|
||||
attribute: "data-type",
|
||||
reflect: true,
|
||||
},
|
||||
selecting: {
|
||||
type: Boolean,
|
||||
attribute: "data-selecting",
|
||||
@@ -84,10 +90,10 @@ export default class Blueprint extends IElement {
|
||||
nodesNames = new Map()
|
||||
/** @type {Coordinates} */
|
||||
mousePosition = [0, 0]
|
||||
waitingExpandUpdate = false
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.blueprintType = ""
|
||||
this.selecting = false
|
||||
this.scrolling = false
|
||||
this.focused = false
|
||||
@@ -297,29 +303,11 @@ export default class Blueprint extends IElement {
|
||||
return [x, y]
|
||||
}
|
||||
|
||||
getNodes(
|
||||
selected = false,
|
||||
[t, r, b, l] = [
|
||||
Number.MIN_SAFE_INTEGER,
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
Number.MIN_SAFE_INTEGER,
|
||||
]
|
||||
) {
|
||||
getNodes(selected = false) {
|
||||
let result = this.nodes
|
||||
if (selected) {
|
||||
result = result.filter(n => n.selected)
|
||||
}
|
||||
if (
|
||||
t > Number.MIN_SAFE_INTEGER
|
||||
|| r < Number.MAX_SAFE_INTEGER
|
||||
|| b < Number.MAX_SAFE_INTEGER
|
||||
|| l > Number.MIN_SAFE_INTEGER
|
||||
) {
|
||||
result = result.filter(n => {
|
||||
return n.topBoundary() >= t && n.rightBoundary() <= r && n.bottomBoundary() <= b && n.leftBoundary() >= l
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -354,25 +342,25 @@ export default class Blueprint extends IElement {
|
||||
getLinks(a = null, b = null) {
|
||||
if ((a == null) != (b == null)) {
|
||||
const pin = a ?? b
|
||||
return this.links.filter(link => link.source == pin || link.destination == pin)
|
||||
return this.links.filter(link => link.origin == pin || link.target == pin)
|
||||
}
|
||||
if (a != null && b != null) {
|
||||
return this.links.filter(link =>
|
||||
link.source == a && link.destination == b
|
||||
|| link.source == b && link.destination == a
|
||||
link.origin == a && link.target == b
|
||||
|| link.origin == b && link.target == a
|
||||
)
|
||||
}
|
||||
return this.links
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PinElement} sourcePin
|
||||
* @param {PinElement} destinationPin
|
||||
* @param {PinElement} originPin
|
||||
* @param {PinElement} targetPin
|
||||
*/
|
||||
getLink(sourcePin, destinationPin, strictDirection = false) {
|
||||
getLink(originPin, targetPin, strictDirection = false) {
|
||||
return this.links.find(link =>
|
||||
link.source == sourcePin && link.destination == destinationPin
|
||||
|| !strictDirection && link.source == destinationPin && link.destination == sourcePin
|
||||
link.origin == originPin && link.target == targetPin
|
||||
|| !strictDirection && link.origin == targetPin && link.target == originPin
|
||||
)
|
||||
}
|
||||
|
||||
@@ -384,23 +372,39 @@ export default class Blueprint extends IElement {
|
||||
this.getNodes().forEach(node => Blueprint.nodeSelectToggleFunction(node, false))
|
||||
}
|
||||
|
||||
getSerializedText() {
|
||||
const nodes = this.blueprint.getNodes(true).map(n => n.entity)
|
||||
let exports = false
|
||||
let result = nodes
|
||||
.filter(n => {
|
||||
exports ||= n.exported
|
||||
return !n.exported
|
||||
})
|
||||
.reduce((acc, cur) => acc + cur.serialize(), "")
|
||||
if (exports) {
|
||||
const object = new NiagaraClipboardContent(this.blueprint.entity, nodes)
|
||||
result = object.serialize() + result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** @param {...IElement} graphElements */
|
||||
addGraphElement(...graphElements) {
|
||||
/** @param {CustomEvent} event */
|
||||
const removeEventHandler = event => {
|
||||
const target = event.currentTarget
|
||||
target.removeEventListener(Configuration.removeEventName, removeEventHandler)
|
||||
const [graphElementsArray, entity] = target instanceof NodeElement
|
||||
const [container, entity] = target instanceof NodeElement
|
||||
? [this.nodes, target.entity]
|
||||
: target instanceof LinkElement
|
||||
? [this.links]
|
||||
: null
|
||||
// @ts-expect-error
|
||||
const index = graphElementsArray?.indexOf(target)
|
||||
const index = container?.indexOf(target)
|
||||
if (index >= 0) {
|
||||
const last = graphElementsArray.pop()
|
||||
if (index < graphElementsArray.length) {
|
||||
graphElementsArray[index] = last
|
||||
const last = container.pop()
|
||||
if (index < container.length) {
|
||||
container[index] = last
|
||||
}
|
||||
}
|
||||
if (entity) {
|
||||
@@ -410,14 +414,17 @@ export default class Blueprint extends IElement {
|
||||
for (const element of graphElements) {
|
||||
element.blueprint = this
|
||||
if (element instanceof NodeElement && !this.nodes.includes(element)) {
|
||||
const name = element.entity.getObjectName()
|
||||
this.entity.updateNameIndex(name)
|
||||
if (element.getType() == Configuration.paths.niagaraClipboardContent) {
|
||||
this.entity = this.entity.mergeWith(element.entity)
|
||||
const additionalSerialization = atob(element.entity.ExportedNodes.toString())
|
||||
this.template.getPasteInputObject().pasted(additionalSerialization)
|
||||
.forEach(node => node.entity._exported = true)
|
||||
const additionalSerialization = atob(element.entity.ExportedNodes?.toString() ?? "")
|
||||
if (additionalSerialization) {
|
||||
this.template.getPasteInputObject().pasted(additionalSerialization)
|
||||
.forEach(node => node.entity.exported = true)
|
||||
}
|
||||
continue
|
||||
}
|
||||
const name = element.entity.getObjectName()
|
||||
const homonym = this.entity.getHomonymObjectEntity(element.entity)
|
||||
if (homonym) {
|
||||
const newName = this.entity.takeFreeName(name)
|
||||
@@ -428,6 +435,9 @@ export default class Blueprint extends IElement {
|
||||
this.entity.addObjectEntity(element.entity)
|
||||
element.addEventListener(Configuration.removeEventName, removeEventHandler)
|
||||
this.template.nodesContainerElement?.appendChild(element)
|
||||
if (!this.blueprintType) {
|
||||
this.blueprintType = element.entity.getBlueprintType()
|
||||
}
|
||||
} else if (element instanceof LinkElement && !this.links.includes(element)) {
|
||||
this.links.push(element)
|
||||
element.addEventListener(Configuration.removeEventName, removeEventHandler)
|
||||
@@ -454,6 +464,9 @@ export default class Blueprint extends IElement {
|
||||
}
|
||||
element.remove()
|
||||
}
|
||||
if (this.nodes.length == 0) {
|
||||
this.blueprintType = ""
|
||||
}
|
||||
}
|
||||
|
||||
setFocused(value = true) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { css } from "lit"
|
||||
|
||||
export default class Configuration {
|
||||
static VERSION = "2.0.0"
|
||||
static nodeColors = {
|
||||
black: css`20, 20, 20`,
|
||||
blue: css`84, 122, 156`,
|
||||
@@ -23,7 +24,7 @@ export default class Configuration {
|
||||
static colorWindowName = "Color Picker"
|
||||
static defaultCommentHeight = 96
|
||||
static defaultCommentWidth = 400
|
||||
static distanceThreshold = 5 // px
|
||||
static distanceThreshold = 20 // px
|
||||
static dragEventName = "ueb-drag"
|
||||
static dragGeneralEventName = "ueb-drag-general"
|
||||
static edgeScrollThreshold = 50
|
||||
@@ -37,12 +38,9 @@ export default class Configuration {
|
||||
end: "blueprint-unfocus",
|
||||
}
|
||||
static fontSize = css`13px`
|
||||
static gridAxisLineColor = css`black`
|
||||
static gridExpandThreshold = 0.25 // remaining size factor threshold to cause an expansion event
|
||||
static gridLineColor = css`#353535`
|
||||
static gridLineWidth = 1 // px
|
||||
static gridSet = 8
|
||||
static gridSetLineColor = css`#161616`
|
||||
static gridShrinkThreshold = 4 // exceding size factor threshold to cause a shrink event
|
||||
static gridSize = 16 // px
|
||||
static hexColorRegex = /^\s*#(?<r>[0-9a-fA-F]{2})(?<g>[0-9a-fA-F]{2})(?<b>[0-9a-fA-F]{2})([0-9a-fA-F]{2})?|#(?<rs>[0-9a-fA-F])(?<gs>[0-9a-fA-F])(?<bs>[0-9a-fA-F])\s*$/
|
||||
@@ -79,9 +77,13 @@ export default class Configuration {
|
||||
* @param {Number} c1
|
||||
* @param {Number} c2
|
||||
*/
|
||||
static linkRightSVGPath = (start, c1, c2) => {
|
||||
let end = 100 - start
|
||||
return `M ${start} 0 C ${c1.toFixed(3)} 0, ${c2.toFixed(3)} 0, 50 50 S ${(end - c1 + start).toFixed(3)} 100, `
|
||||
static linkRightSVGPath = (start, c1, c2, arc = false) => {
|
||||
const end = 100 - start
|
||||
const mid = arc
|
||||
? 50 + (c2 - start)
|
||||
: 50
|
||||
const fin = arc ? end + c1 - start : end - c1 + start
|
||||
return `M ${start} 0 C ${c1.toFixed(2)} 0, ${c2.toFixed(2)} 0, ${mid.toFixed(2)} 50 S ${fin.toFixed(2)} 100, `
|
||||
+ `${end.toFixed(3)} 100`
|
||||
}
|
||||
static maxZoom = 7
|
||||
@@ -91,9 +93,9 @@ export default class Configuration {
|
||||
static mouseWheelZoomThreshold = 80
|
||||
static nodeDragEventName = "ueb-node-drag"
|
||||
static nodeDragGeneralEventName = "ueb-node-drag-general"
|
||||
static nodeTitle = (name, counter) => `${name}_${counter}`
|
||||
static nodeRadius = 8 // px
|
||||
static nodeReflowEventName = "ueb-node-reflow"
|
||||
static nodeTitle = (name, counter) => `${name}_${counter}`
|
||||
static nodeUpdateEventName = "ueb-node-update"
|
||||
static paths = {
|
||||
actorBoundEvent: "/Script/BlueprintGraph.K2Node_ActorBoundEvent",
|
||||
addDelegate: "/Script/BlueprintGraph.K2Node_AddDelegate",
|
||||
@@ -149,6 +151,7 @@ export default class Configuration {
|
||||
isValid: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:IsValid",
|
||||
kismetArrayLibrary: "/Script/Engine.KismetArrayLibrary",
|
||||
kismetMathLibrary: "/Script/Engine.KismetMathLibrary",
|
||||
kismetStringLibrary: "/Script/Engine.KismetStringLibrary",
|
||||
knot: "/Script/BlueprintGraph.K2Node_Knot",
|
||||
linearColor: "/Script/CoreUObject.LinearColor",
|
||||
literal: "/Script/BlueprintGraph.K2Node_Literal",
|
||||
@@ -178,12 +181,16 @@ export default class Configuration {
|
||||
multiGate: "/Script/BlueprintGraph.K2Node_MultiGate",
|
||||
niagaraBool: "/Script/Niagara.NiagaraBool",
|
||||
niagaraClipboardContent: "/Script/NiagaraEditor.NiagaraClipboardContent",
|
||||
niagaraDataInterfaceCollisionQuery: "/Script/Niagara.NiagaraDataInterfaceCollisionQuery",
|
||||
niagaraDataInterfaceCurlNoise: "/Script/Niagara.NiagaraDataInterfaceCurlNoise",
|
||||
niagaraDataInterfaceVolumeTexture: "/Script/Niagara.NiagaraDataInterfaceVolumeTexture",
|
||||
niagaraFloat: "/Script/Niagara.NiagaraFloat",
|
||||
niagaraMatrix: "/Script/Niagara.NiagaraMatrix",
|
||||
niagaraInt32: "/Script/Niagara.NiagaraInt32",
|
||||
niagaraNodeConvert: "/Script/NiagaraEditor.NiagaraNodeConvert",
|
||||
niagaraNodeFunctionCall: "/Script/NiagaraEditor.NiagaraNodeFunctionCall",
|
||||
niagaraNodeInput: "/Script/NiagaraEditor.NiagaraNodeInput",
|
||||
niagaraNodeOp: "/Script/NiagaraEditor.NiagaraNodeOp",
|
||||
niagaraNumeric: "/Script/Niagara.NiagaraNumeric",
|
||||
niagaraParameterMap: "/Script/Niagara.NiagaraParameterMap",
|
||||
niagaraPosition: "/Script/Niagara.NiagaraPosition",
|
||||
pawn: "/Script/Engine.Pawn",
|
||||
pcgEditorGraphNode: "/Script/PCGEditor.PCGEditorGraphNode",
|
||||
@@ -199,6 +206,8 @@ export default class Configuration {
|
||||
select: "/Script/BlueprintGraph.K2Node_Select",
|
||||
self: "/Script/BlueprintGraph.K2Node_Self",
|
||||
slateBlueprintLibrary: "/Script/UMG.SlateBlueprintLibrary",
|
||||
soundCueGraphNode: "/Script/AudioEditor.SoundCueGraphNode",
|
||||
soundNodeWavePlayer: "/Script/Engine.SoundNodeWavePlayer",
|
||||
spawnActorFromClass: "/Script/BlueprintGraph.K2Node_SpawnActorFromClass",
|
||||
switchEnum: "/Script/BlueprintGraph.K2Node_SwitchEnum",
|
||||
switchGameplayTag: "/Script/GameplayTagsEditor.GameplayTagsK2Node_SwitchGameplayTag",
|
||||
@@ -208,16 +217,19 @@ export default class Configuration {
|
||||
timeline: "/Script/BlueprintGraph.K2Node_Timeline",
|
||||
timeManagementBlueprintLibrary: "/Script/TimeManagement.TimeManagementBlueprintLibrary",
|
||||
transform: "/Script/CoreUObject.Transform",
|
||||
typedElementHandleLibrary: "/Script/TypedElementFramework.TypedElementHandleLibrary",
|
||||
userDefinedEnum: "/Script/Engine.UserDefinedEnum",
|
||||
variableGet: "/Script/BlueprintGraph.K2Node_VariableGet",
|
||||
variableSet: "/Script/BlueprintGraph.K2Node_VariableSet",
|
||||
vector: "/Script/CoreUObject.Vector",
|
||||
vector2D: "/Script/CoreUObject.Vector2D",
|
||||
vector2f: "/Script/CoreUObject.Vector2f",
|
||||
vector3f: "/Script/CoreUObject.Vector3f",
|
||||
vector4f: "/Script/CoreUObject.Vector4f",
|
||||
whileLoop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:WhileLoop",
|
||||
}
|
||||
static pinInputWrapWidth = 145 // px
|
||||
static pinUpdateEventName = "ueb-pin-update"
|
||||
static removeEventName = "ueb-element-delete"
|
||||
static scale = {
|
||||
[-12]: 0.133333,
|
||||
|
||||
7
js/SVGIcon.js
Normal file → Executable file
7
js/SVGIcon.js
Normal file → Executable file
@@ -388,6 +388,13 @@ export default class SVGIcon {
|
||||
</svg>
|
||||
`
|
||||
|
||||
static staticPin = html`
|
||||
<svg width="16" height="12" viewBox="1 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="ueb-pin-tofill" d="M1 7C1 4 3 1 7 1C10 1 14 3 17 6C18 7 18 7 17 8C14 11 10 13 7 13C3 13 1 10 1 7Z" fill="none" stroke="currentColor" stroke-width="2" />
|
||||
<path class="ueb-pin-tostroke" d="M 9 4 V 3.5 H 5 V 7 H 9 V 10.5 H 5 V 10" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
`
|
||||
|
||||
static switch = html`
|
||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="2" width="6" height="2" fill="white" />
|
||||
|
||||
0
js/Shortcuts.js
Normal file → Executable file
0
js/Shortcuts.js
Normal file → Executable file
@@ -80,7 +80,7 @@ export default class Utility {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Attribute} entity
|
||||
* @param {IEntity} entity
|
||||
* @param {String} key
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
@@ -147,29 +147,31 @@ export default class Utility {
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Array<T>} a
|
||||
* @param {Array<T>} b
|
||||
* @param {T[]} reference
|
||||
* @param {T[]} additional
|
||||
* @param {(v: T) => void} adding - Process added element
|
||||
* @param {(l: T, r: T) => Boolean} predicate
|
||||
* @returns {T[]}
|
||||
*/
|
||||
static mergeArrays(a = [], b = [], predicate = (l, r) => l == r) {
|
||||
static mergeArrays(reference = [], additional = [], predicate = (l, r) => l == r, adding = v => { }) {
|
||||
let result = []
|
||||
a = [...a]
|
||||
b = [...b]
|
||||
reference = [...reference]
|
||||
additional = [...additional]
|
||||
restart:
|
||||
while (true) {
|
||||
for (let j = 0; j < b.length; ++j) {
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
if (predicate(a[i], b[j])) {
|
||||
for (let j = 0; j < additional.length; ++j) {
|
||||
for (let i = 0; i < reference.length; ++i) {
|
||||
if (predicate(reference[i], additional[j])) {
|
||||
// Found an element in common in the two arrays
|
||||
result.push(
|
||||
// Take and append all the elements skipped from a
|
||||
...a.splice(0, i),
|
||||
...reference.splice(0, i),
|
||||
// Take and append all the elements skippend from b
|
||||
...b.splice(0, j),
|
||||
...additional.splice(0, j).map(v => (adding(v), v)),
|
||||
// Take and append the element in common
|
||||
...a.splice(0, 1)
|
||||
...reference.splice(0, 1)
|
||||
)
|
||||
b.shift() // Remove the same element from b
|
||||
additional.shift() // Remove the same element from b
|
||||
continue restart
|
||||
}
|
||||
}
|
||||
@@ -177,7 +179,13 @@ export default class Utility {
|
||||
break restart
|
||||
}
|
||||
// Append remaining the elements in the arrays and make it unique
|
||||
return [...(new Set(result.concat(...a, ...b)))]
|
||||
result.push(...reference)
|
||||
result.push(
|
||||
...additional
|
||||
.filter(vb => !result.some(vr => predicate(vr, vb)))
|
||||
.map((v, k) => (adding(v), v))
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
/** @param {String} value */
|
||||
|
||||
0
js/action/RemoveAllNodes.js
Normal file → Executable file
0
js/action/RemoveAllNodes.js
Normal file → Executable file
76
js/decoding/nodeColor.js
Normal file → Executable file
76
js/decoding/nodeColor.js
Normal file → Executable file
@@ -1,54 +1,61 @@
|
||||
import Configuration from "../Configuration.js"
|
||||
import LinearColorEntity from "../entity/LinearColorEntity.js"
|
||||
|
||||
const p = Configuration.paths
|
||||
|
||||
/** @param {ObjectEntity} entity */
|
||||
export default function nodeColor(entity) {
|
||||
switch (entity.getType()) {
|
||||
case Configuration.paths.materialExpressionConstant2Vector:
|
||||
case Configuration.paths.materialExpressionConstant3Vector:
|
||||
case Configuration.paths.materialExpressionConstant4Vector:
|
||||
case p.materialExpressionConstant2Vector:
|
||||
case p.materialExpressionConstant3Vector:
|
||||
case p.materialExpressionConstant4Vector:
|
||||
return Configuration.nodeColors.yellow
|
||||
case Configuration.paths.materialExpressionFunctionInput:
|
||||
case Configuration.paths.materialExpressionTextureCoordinate:
|
||||
case Configuration.paths.materialExpressionWorldPosition:
|
||||
case Configuration.paths.pcgEditorGraphNodeInput:
|
||||
case Configuration.paths.pcgEditorGraphNodeOutput:
|
||||
case p.materialExpressionFunctionInput:
|
||||
case p.materialExpressionTextureCoordinate:
|
||||
case p.materialExpressionWorldPosition:
|
||||
case p.pcgEditorGraphNodeInput:
|
||||
case p.pcgEditorGraphNodeOutput:
|
||||
return Configuration.nodeColors.red
|
||||
case Configuration.paths.makeStruct:
|
||||
case p.makeStruct:
|
||||
return Configuration.nodeColors.darkBlue
|
||||
case Configuration.paths.materialExpressionMaterialFunctionCall:
|
||||
case p.materialExpressionMaterialFunctionCall:
|
||||
return Configuration.nodeColors.blue
|
||||
case Configuration.paths.materialExpressionTextureSample:
|
||||
case p.materialExpressionTextureSample:
|
||||
return Configuration.nodeColors.darkTurquoise
|
||||
case p.niagaraNodeInput:
|
||||
switch (entity["Usage"]?.toString()) {
|
||||
case "Attribute": return Configuration.nodeColors.intenseGreen
|
||||
case "Parameter": return Configuration.nodeColors.red
|
||||
case "RapidIterationParameter": return Configuration.nodeColors.black
|
||||
case "SystemConstant": return Configuration.nodeColors.gray
|
||||
case "TranslatorConstant": return Configuration.nodeColors.gray
|
||||
default: return Configuration.nodeColors.red
|
||||
}
|
||||
}
|
||||
switch (entity.getClass()) {
|
||||
case Configuration.paths.callFunction:
|
||||
return entity.bIsPureFunc?.valueOf()
|
||||
? Configuration.nodeColors.green
|
||||
: Configuration.nodeColors.blue
|
||||
case Configuration.paths.niagaraNodeFunctionCall:
|
||||
case p.niagaraNodeFunctionCall:
|
||||
return Configuration.nodeColors.darkerBlue
|
||||
case Configuration.paths.dynamicCast:
|
||||
case p.dynamicCast:
|
||||
return Configuration.nodeColors.turquoise
|
||||
case Configuration.paths.inputDebugKey:
|
||||
case Configuration.paths.inputKey:
|
||||
case p.inputDebugKey:
|
||||
case p.inputKey:
|
||||
return Configuration.nodeColors.red
|
||||
case Configuration.paths.createDelegate:
|
||||
case Configuration.paths.enumLiteral:
|
||||
case Configuration.paths.makeArray:
|
||||
case Configuration.paths.makeMap:
|
||||
case Configuration.paths.materialGraphNode:
|
||||
case Configuration.paths.select:
|
||||
case p.createDelegate:
|
||||
case p.enumLiteral:
|
||||
case p.makeArray:
|
||||
case p.makeMap:
|
||||
case p.materialGraphNode:
|
||||
case p.select:
|
||||
return Configuration.nodeColors.green
|
||||
case Configuration.paths.executionSequence:
|
||||
case Configuration.paths.ifThenElse:
|
||||
case Configuration.paths.macro:
|
||||
case Configuration.paths.multiGate:
|
||||
case p.executionSequence:
|
||||
case p.ifThenElse:
|
||||
case p.macro:
|
||||
case p.multiGate:
|
||||
return Configuration.nodeColors.gray
|
||||
case Configuration.paths.functionEntry:
|
||||
case Configuration.paths.functionResult:
|
||||
case p.functionEntry:
|
||||
case p.functionResult:
|
||||
return Configuration.nodeColors.violet
|
||||
case Configuration.paths.timeline:
|
||||
case p.timeline:
|
||||
return Configuration.nodeColors.yellow
|
||||
}
|
||||
if (entity.switchTarget()) {
|
||||
@@ -73,8 +80,11 @@ export default function nodeColor(entity) {
|
||||
return Configuration.nodeColors.intenseGreen
|
||||
}
|
||||
}
|
||||
if (entity.bIsPureFunc?.valueOf()) {
|
||||
if (entity.bIsPureFunc?.valueOf() || entity.bDefaultsToPureFunc?.valueOf()) {
|
||||
return Configuration.nodeColors.green
|
||||
}
|
||||
if (entity["Input"]?.["Name"]) {
|
||||
return Configuration.nodeColors.gray
|
||||
}
|
||||
return Configuration.nodeColors.blue
|
||||
}
|
||||
|
||||
74
js/decoding/nodeIcon.js
Normal file → Executable file
74
js/decoding/nodeIcon.js
Normal file → Executable file
@@ -2,50 +2,52 @@ import Configuration from "../Configuration.js"
|
||||
import SVGIcon from "../SVGIcon.js"
|
||||
import nodeTitle from "./nodeTitle.js"
|
||||
|
||||
const p = Configuration.paths
|
||||
|
||||
/** @param {ObjectEntity} entity */
|
||||
export default function nodeIcon(entity) {
|
||||
if (entity.isMaterial() || entity.isPcg() || entity.isNiagara()) {
|
||||
if (entity.isMaterial() || entity.isPcg() || entity.isSoundCue() || entity.isNiagara()) {
|
||||
return null
|
||||
}
|
||||
switch (entity.getType()) {
|
||||
case Configuration.paths.addDelegate:
|
||||
case Configuration.paths.asyncAction:
|
||||
case Configuration.paths.callDelegate:
|
||||
case Configuration.paths.clearDelegate:
|
||||
case Configuration.paths.createDelegate:
|
||||
case Configuration.paths.functionEntry:
|
||||
case Configuration.paths.functionResult:
|
||||
case Configuration.paths.removeDelegate:
|
||||
case p.addDelegate:
|
||||
case p.asyncAction:
|
||||
case p.callDelegate:
|
||||
case p.clearDelegate:
|
||||
case p.createDelegate:
|
||||
case p.functionEntry:
|
||||
case p.functionResult:
|
||||
case p.removeDelegate:
|
||||
return SVGIcon.node
|
||||
case Configuration.paths.customEvent: return SVGIcon.event
|
||||
case Configuration.paths.doN: return SVGIcon.doN
|
||||
case Configuration.paths.doOnce: return SVGIcon.doOnce
|
||||
case Configuration.paths.dynamicCast: return SVGIcon.cast
|
||||
case Configuration.paths.enumLiteral: return SVGIcon.enum
|
||||
case Configuration.paths.event: return SVGIcon.event
|
||||
case Configuration.paths.executionSequence:
|
||||
case Configuration.paths.multiGate:
|
||||
case p.customEvent: return SVGIcon.event
|
||||
case p.doN: return SVGIcon.doN
|
||||
case p.doOnce: return SVGIcon.doOnce
|
||||
case p.dynamicCast: return SVGIcon.cast
|
||||
case p.enumLiteral: return SVGIcon.enum
|
||||
case p.event: return SVGIcon.event
|
||||
case p.executionSequence:
|
||||
case p.multiGate:
|
||||
return SVGIcon.sequence
|
||||
case Configuration.paths.flipflop:
|
||||
case p.flipflop:
|
||||
return SVGIcon.flipflop
|
||||
case Configuration.paths.forEachElementInEnum:
|
||||
case Configuration.paths.forLoop:
|
||||
case Configuration.paths.forLoopWithBreak:
|
||||
case Configuration.paths.whileLoop:
|
||||
case p.forEachElementInEnum:
|
||||
case p.forLoop:
|
||||
case p.forLoopWithBreak:
|
||||
case p.whileLoop:
|
||||
return SVGIcon.loop
|
||||
case Configuration.paths.forEachLoop:
|
||||
case Configuration.paths.forEachLoopWithBreak:
|
||||
case p.forEachLoop:
|
||||
case p.forEachLoopWithBreak:
|
||||
return SVGIcon.forEachLoop
|
||||
case Configuration.paths.ifThenElse: return SVGIcon.branchNode
|
||||
case Configuration.paths.isValid: return SVGIcon.questionMark
|
||||
case Configuration.paths.makeArray: return SVGIcon.makeArray
|
||||
case Configuration.paths.makeMap: return SVGIcon.makeMap
|
||||
case Configuration.paths.makeSet: return SVGIcon.makeSet
|
||||
case Configuration.paths.makeStruct: return SVGIcon.makeStruct
|
||||
case Configuration.paths.metasoundEditorGraphExternalNode: return SVGIcon.metasoundFunction
|
||||
case Configuration.paths.select: return SVGIcon.select
|
||||
case Configuration.paths.spawnActorFromClass: return SVGIcon.spawnActor
|
||||
case Configuration.paths.timeline: return SVGIcon.timer
|
||||
case p.ifThenElse: return SVGIcon.branchNode
|
||||
case p.isValid: return SVGIcon.questionMark
|
||||
case p.makeArray: return SVGIcon.makeArray
|
||||
case p.makeMap: return SVGIcon.makeMap
|
||||
case p.makeSet: return SVGIcon.makeSet
|
||||
case p.makeStruct: return SVGIcon.makeStruct
|
||||
case p.metasoundEditorGraphExternalNode: return SVGIcon.metasoundFunction
|
||||
case p.select: return SVGIcon.select
|
||||
case p.spawnActorFromClass: return SVGIcon.spawnActor
|
||||
case p.timeline: return SVGIcon.timer
|
||||
}
|
||||
if (entity.switchTarget()) {
|
||||
return SVGIcon.switch
|
||||
@@ -53,7 +55,7 @@ export default function nodeIcon(entity) {
|
||||
if (nodeTitle(entity).startsWith("Break")) {
|
||||
return SVGIcon.breakStruct
|
||||
}
|
||||
if (entity.getClass() === Configuration.paths.macro) {
|
||||
if (entity.getClass() === p.macro) {
|
||||
return SVGIcon.macro
|
||||
}
|
||||
const hidValue = entity.getHIDAttribute()?.toString()
|
||||
@@ -73,7 +75,7 @@ export default function nodeIcon(entity) {
|
||||
if (entity.getDelegatePin()) {
|
||||
return SVGIcon.event
|
||||
}
|
||||
if (entity.ObjectRef?.type === Configuration.paths.ambientSound) {
|
||||
if (entity.ObjectRef?.type === p.ambientSound) {
|
||||
return SVGIcon.sound
|
||||
}
|
||||
return SVGIcon.functionSymbol
|
||||
|
||||
11
js/decoding/nodeSubtitle.js
Normal file → Executable file
11
js/decoding/nodeSubtitle.js
Normal file → Executable file
@@ -2,20 +2,23 @@ import Configuration from "../Configuration.js"
|
||||
import Utility from "../Utility.js"
|
||||
import pinTitle from "./pinTitle.js"
|
||||
|
||||
const p = Configuration.paths
|
||||
|
||||
/**
|
||||
* @param {ObjectEntity} entity
|
||||
* @returns {String?}
|
||||
*/
|
||||
export default function nodeSubtitle(entity) {
|
||||
switch (entity.getType()) {
|
||||
case Configuration.paths.addDelegate:
|
||||
case Configuration.paths.clearDelegate:
|
||||
case Configuration.paths.removeDelegate:
|
||||
case p.addDelegate:
|
||||
case p.clearDelegate:
|
||||
case p.callDelegate:
|
||||
case p.removeDelegate:
|
||||
return null
|
||||
}
|
||||
const targetPin = entity
|
||||
.getPinEntities()
|
||||
.find(pin => pin.PinName?.toString() === "self" && pinTitle(pin) === "Target")
|
||||
.find(pin => !pin.isHidden() && pin.PinName?.toString() === "self" && pinTitle(pin) === "Target")
|
||||
if (targetPin) {
|
||||
const target = entity.FunctionReference?.MemberParent?.getName()
|
||||
?? targetPin.PinType?.PinSubCategoryObject?.getName()
|
||||
|
||||
116
js/decoding/nodeTemplate.js
Normal file → Executable file
116
js/decoding/nodeTemplate.js
Normal file → Executable file
@@ -9,22 +9,89 @@ import VariableAccessNodeTemplate from "../template/node/VariableAccessNodeTempl
|
||||
import VariableConversionNodeTemplate from "../template/node/VariableConversionNodeTemplate.js"
|
||||
import VariableOperationNodeTemplate from "../template/node/VariableOperationNodeTemplate.js"
|
||||
|
||||
const niagaraOperationNodes = [
|
||||
"Boolean::LogicEq",
|
||||
"Boolean::LogicNEq",
|
||||
"Integer::EnumNEq",
|
||||
"Integer::EnumEq",
|
||||
...[
|
||||
"Abs",
|
||||
"Add",
|
||||
"ArcCosine(Degrees)",
|
||||
"ArcCosine(Radians)",
|
||||
"ArcSine(Degrees)",
|
||||
"ArcSine(Radians)",
|
||||
"ArcTangent(Degrees)",
|
||||
"ArcTangent(Radians)",
|
||||
"Ceil",
|
||||
"CmpEQ",
|
||||
"CmpGE",
|
||||
"CmpGT",
|
||||
"CmpLE",
|
||||
"CmpLT",
|
||||
"CmpNEQ",
|
||||
"Cosine(Degrees)",
|
||||
"Cosine(Radians)",
|
||||
"DegreesToRadians",
|
||||
"Div",
|
||||
"Dot",
|
||||
"Exp",
|
||||
"Exp2",
|
||||
"Floor",
|
||||
"FMod",
|
||||
"Frac",
|
||||
"Length",
|
||||
"Lerp",
|
||||
"Log",
|
||||
"Log2",
|
||||
"Madd",
|
||||
"Max",
|
||||
"Min",
|
||||
"Mul",
|
||||
"Negate",
|
||||
"Normalize",
|
||||
"OneMinus",
|
||||
"PI",
|
||||
"RadiansToDegrees",
|
||||
"Rcp",
|
||||
"RcpFast",
|
||||
"Round",
|
||||
"RSqrt",
|
||||
"Sign",
|
||||
"Sine(Degrees)",
|
||||
"Sine(Radians)",
|
||||
"Sqrt",
|
||||
"Step",
|
||||
"Subtract",
|
||||
"Tangent(Degrees)",
|
||||
"Tangent(Radians)",
|
||||
"Trunc",
|
||||
"TWO_PI",
|
||||
].map(v => "Numeric::" + v),
|
||||
"Vector3::Cross",
|
||||
]
|
||||
|
||||
const p = Configuration.paths
|
||||
|
||||
/**
|
||||
* @param {ObjectEntity} nodeEntity
|
||||
* @return {new () => NodeTemplate}
|
||||
*/
|
||||
export default function nodeTemplateClass(nodeEntity) {
|
||||
const className = nodeEntity.getClass()
|
||||
if (
|
||||
nodeEntity.getClass() === Configuration.paths.callFunction
|
||||
|| nodeEntity.getClass() === Configuration.paths.commutativeAssociativeBinaryOperator
|
||||
|| nodeEntity.getClass() === Configuration.paths.callArrayFunction
|
||||
className === p.callFunction
|
||||
|| className === p.commutativeAssociativeBinaryOperator
|
||||
|| className === p.callArrayFunction
|
||||
) {
|
||||
const memberParent = nodeEntity.FunctionReference?.MemberParent?.path ?? ""
|
||||
const memberName = nodeEntity.FunctionReference?.MemberName?.toString()
|
||||
if (
|
||||
memberName && (
|
||||
memberParent === Configuration.paths.kismetMathLibrary
|
||||
|| memberParent === Configuration.paths.kismetArrayLibrary
|
||||
memberParent === p.kismetArrayLibrary
|
||||
|| memberParent === p.kismetMathLibrary
|
||||
|| memberParent === p.kismetStringLibrary
|
||||
|| memberParent === p.typedElementHandleLibrary
|
||||
)) {
|
||||
if (memberName.startsWith("Conv_")) {
|
||||
return VariableConversionNodeTemplate
|
||||
@@ -51,6 +118,7 @@ export default function nodeTemplateClass(nodeEntity) {
|
||||
case "BMin":
|
||||
case "CrossProduct2D":
|
||||
case "DotProduct2D":
|
||||
case "Equal":
|
||||
case "Exp":
|
||||
case "FMax":
|
||||
case "FMin":
|
||||
@@ -77,45 +145,37 @@ export default function nodeTemplateClass(nodeEntity) {
|
||||
return VariableOperationNodeTemplate
|
||||
}
|
||||
}
|
||||
if (memberParent === Configuration.paths.blueprintSetLibrary) {
|
||||
if (memberParent === p.blueprintSetLibrary) {
|
||||
return VariableOperationNodeTemplate
|
||||
}
|
||||
if (memberParent === Configuration.paths.blueprintMapLibrary) {
|
||||
if (memberParent === p.blueprintMapLibrary) {
|
||||
return VariableOperationNodeTemplate
|
||||
}
|
||||
}
|
||||
switch (nodeEntity.getClass()) {
|
||||
case Configuration.paths.comment:
|
||||
case Configuration.paths.materialGraphNodeComment:
|
||||
switch (className) {
|
||||
case p.comment:
|
||||
case p.materialGraphNodeComment:
|
||||
return CommentNodeTemplate
|
||||
case Configuration.paths.createDelegate:
|
||||
case p.createDelegate:
|
||||
return NodeTemplate
|
||||
case Configuration.paths.metasoundEditorGraphExternalNode:
|
||||
case p.metasoundEditorGraphExternalNode:
|
||||
if (nodeEntity["ClassName"]?.["Name"] == "Add") {
|
||||
return MetasoundOperationTemplate
|
||||
}
|
||||
return MetasoundNodeTemplate
|
||||
case Configuration.paths.niagaraNodeOp:
|
||||
if (
|
||||
[
|
||||
"Boolean::LogicEq",
|
||||
"Boolean::LogicNEq",
|
||||
"Numeric::Abs",
|
||||
"Numeric::Add",
|
||||
"Numeric::Mul",
|
||||
].includes(nodeEntity.OpName?.toString())
|
||||
) {
|
||||
case p.niagaraNodeOp:
|
||||
if (niagaraOperationNodes.includes(nodeEntity.OpName?.toString())) {
|
||||
return VariableOperationNodeTemplate
|
||||
}
|
||||
break
|
||||
case Configuration.paths.promotableOperator:
|
||||
case p.promotableOperator:
|
||||
return VariableOperationNodeTemplate
|
||||
case Configuration.paths.knot:
|
||||
case p.knot:
|
||||
return KnotNodeTemplate
|
||||
case Configuration.paths.literal:
|
||||
case Configuration.paths.self:
|
||||
case Configuration.paths.variableGet:
|
||||
case Configuration.paths.variableSet:
|
||||
case p.literal:
|
||||
case p.self:
|
||||
case p.variableGet:
|
||||
case p.variableSet:
|
||||
return VariableAccessNodeTemplate
|
||||
}
|
||||
if (nodeEntity.isEvent()) {
|
||||
|
||||
248
js/decoding/nodeTitle.js
Normal file → Executable file
248
js/decoding/nodeTitle.js
Normal file → Executable file
@@ -34,6 +34,68 @@ const keyNameValue = {
|
||||
"Subtract": "Num -",
|
||||
"Tilde": "`",
|
||||
}
|
||||
const niagaraNodeNames = {
|
||||
"Boolean::LogicAnd": "Logic AND",
|
||||
"Boolean::LogicEq": "==",
|
||||
"Boolean::LogicNEq": "!=",
|
||||
"Boolean::LogicNot": "Logic NOT",
|
||||
"Boolean::LogicOr": "Logic OR",
|
||||
"Integer::BitAnd": "Bitwise AND",
|
||||
"Integer::BitLShift": "Bitwise Left Shift",
|
||||
"Integer::BitNot": "Bitwise NOT",
|
||||
"Integer::BitOr": "Bitwise OR",
|
||||
"Integer::BitRShift": "Bitwise Right Shift",
|
||||
"Integer::BitXOr": "Bitwise XOR",
|
||||
"Integer::EnumEq": "==",
|
||||
"Integer::EnumNEq": "!=",
|
||||
"Matrix::MatrixMultiply": "Multiply (Matrix * Matrix)",
|
||||
"Matrix::MatrixVectorMultiply": "Multiply (Matrix * Vector4)",
|
||||
// Numeric::
|
||||
...Object.fromEntries(Object.entries({
|
||||
"Add": "+",
|
||||
"ArcCosine": "ArcCosine",
|
||||
"ArcCosine(Degrees)": "ArcCos(D)",
|
||||
"ArcCosine(Radians)": "ArcCos(R)",
|
||||
"ArcSine": "ArcSine",
|
||||
"ArcSine(Degrees)": "ArcSin(D)",
|
||||
"ArcSine(Radians)": "ArcSin(R)",
|
||||
"ArcTangent(Degrees)": "ArcTan(D)",
|
||||
"ArcTangent(Radians)": "ArcTan(R)",
|
||||
"CmpEQ": "==",
|
||||
"CmpGE": ">=",
|
||||
"CmpGT": ">",
|
||||
"CmpLE": "<=",
|
||||
"CmpLT": "<",
|
||||
"CmpNEQ": "!=",
|
||||
"Cosine(Degrees)": "Cos(D)",
|
||||
"Cosine(Radians)": "Cos(R)",
|
||||
"DegreesToRadians": "DegToRad",
|
||||
"DistancePos": "Distance",
|
||||
"Div": String.fromCharCode(0x00f7),
|
||||
"FMod": "%",
|
||||
"FModFast": "Modulo Fast",
|
||||
"Length": "Len",
|
||||
"Madd": `(A${String.fromCharCode(0x2a2f)}B)+C`,
|
||||
"Mul": String.fromCharCode(0x2a2f),
|
||||
"Negate": "-A",
|
||||
"OneMinus": "1-A",
|
||||
"PI": String.fromCharCode(0x03C0),
|
||||
"RadiansToDegrees": "RadToDeg",
|
||||
"Rand Float": "Random Float",
|
||||
"Rand Integer": "Random Integer",
|
||||
"Rand": "Random",
|
||||
"Rcp": "Reciprocal",
|
||||
"RSqrt": "Rcp Sqrt",
|
||||
"Sine(Degrees)": "Sin(D)",
|
||||
"Sine(Radians)": "Sin(R)",
|
||||
"Subtract": "-",
|
||||
"Tangent(Degrees)": "Tan(D)",
|
||||
"Tangent(Radians)": "Tan(R)",
|
||||
"TWO_PI": `2 ${String.fromCharCode(0x03C0)}`,
|
||||
}).map(([k, v]) => ["Numeric::" + k, v])),
|
||||
}
|
||||
const p = Configuration.paths
|
||||
const format = Utility.formatStringName
|
||||
|
||||
/** @param {String} value */
|
||||
function numberFromText(value = "") {
|
||||
@@ -78,58 +140,58 @@ function keyName(value) {
|
||||
export default function nodeTitle(entity) {
|
||||
let value
|
||||
switch (entity.getType()) {
|
||||
case Configuration.paths.addDelegate:
|
||||
case p.addDelegate:
|
||||
value ??= "Bind Event to "
|
||||
case Configuration.paths.clearDelegate:
|
||||
case p.clearDelegate:
|
||||
value ??= "Unbind all Events from "
|
||||
case Configuration.paths.removeDelegate:
|
||||
case p.removeDelegate:
|
||||
value ??= "Unbind Event from "
|
||||
return value + Utility.formatStringName(
|
||||
return value + format(
|
||||
entity.DelegateReference?.MemberName?.toString().replace(/Delegate$/, "") ?? "None"
|
||||
)
|
||||
case Configuration.paths.asyncAction:
|
||||
case p.asyncAction:
|
||||
if (entity.ProxyFactoryFunctionName) {
|
||||
return Utility.formatStringName(entity.ProxyFactoryFunctionName?.toString())
|
||||
return format(entity.ProxyFactoryFunctionName?.toString())
|
||||
}
|
||||
case Configuration.paths.actorBoundEvent:
|
||||
case Configuration.paths.componentBoundEvent:
|
||||
return `${Utility.formatStringName(entity.DelegatePropertyName?.toString())} (${entity.ComponentPropertyName?.toString() ?? "Unknown"})`
|
||||
case Configuration.paths.callDelegate:
|
||||
case p.actorBoundEvent:
|
||||
case p.componentBoundEvent:
|
||||
return `${format(entity.DelegatePropertyName?.toString())} (${entity.ComponentPropertyName?.toString() ?? "Unknown"})`
|
||||
case p.callDelegate:
|
||||
return `Call ${entity.DelegateReference?.MemberName?.toString() ?? "None"}`
|
||||
case Configuration.paths.createDelegate:
|
||||
case p.createDelegate:
|
||||
return "Create Event"
|
||||
case Configuration.paths.customEvent:
|
||||
case p.customEvent:
|
||||
if (entity.CustomFunctionName) {
|
||||
return entity.CustomFunctionName?.toString()
|
||||
}
|
||||
case Configuration.paths.dynamicCast:
|
||||
case p.dynamicCast:
|
||||
if (!entity.TargetType) {
|
||||
return "Bad cast node" // Target type not found
|
||||
}
|
||||
return `Cast To ${entity.TargetType?.getName()}`
|
||||
case Configuration.paths.enumLiteral:
|
||||
case p.enumLiteral:
|
||||
return `Literal enum ${entity.Enum?.getName()}`
|
||||
case Configuration.paths.event:
|
||||
case p.event:
|
||||
return `Event ${(entity.EventReference?.MemberName?.toString() ?? "").replace(/^Receive/, "")}`
|
||||
case Configuration.paths.executionSequence:
|
||||
case p.executionSequence:
|
||||
return "Sequence"
|
||||
case Configuration.paths.forEachElementInEnum:
|
||||
case p.forEachElementInEnum:
|
||||
return `For Each ${entity.Enum?.getName()}`
|
||||
case Configuration.paths.forEachLoopWithBreak:
|
||||
case p.forEachLoopWithBreak:
|
||||
return "For Each Loop with Break"
|
||||
case Configuration.paths.functionEntry:
|
||||
case p.functionEntry:
|
||||
return entity.FunctionReference?.MemberName?.toString() === "UserConstructionScript"
|
||||
? "Construction Script"
|
||||
: entity.FunctionReference?.MemberName?.toString()
|
||||
case Configuration.paths.functionResult:
|
||||
case p.functionResult:
|
||||
return "Return Node"
|
||||
case Configuration.paths.ifThenElse:
|
||||
case p.ifThenElse:
|
||||
return "Branch"
|
||||
case Configuration.paths.makeStruct:
|
||||
case p.makeStruct:
|
||||
if (entity.StructType) {
|
||||
return `Make ${entity.StructType.getName()}`
|
||||
}
|
||||
case Configuration.paths.materialExpressionComponentMask: {
|
||||
case p.materialExpressionComponentMask: {
|
||||
const materialObject = entity.getMaterialSubobject()
|
||||
if (materialObject) {
|
||||
return `Mask ( ${Configuration.rgba
|
||||
@@ -138,15 +200,15 @@ export default function nodeTitle(entity) {
|
||||
.join("")})`
|
||||
}
|
||||
}
|
||||
case Configuration.paths.materialExpressionConstant:
|
||||
case p.materialExpressionConstant:
|
||||
value ??= [entity.getCustomproperties().find(pinEntity => pinEntity.PinName.toString() == "Value")?.DefaultValue]
|
||||
case Configuration.paths.materialExpressionConstant2Vector:
|
||||
case p.materialExpressionConstant2Vector:
|
||||
value ??= [
|
||||
entity.getCustomproperties().find(pinEntity => pinEntity.PinName?.toString() == "X")?.DefaultValue,
|
||||
entity.getCustomproperties().find(pinEntity => pinEntity.PinName?.toString() == "Y")?.DefaultValue,
|
||||
]
|
||||
case Configuration.paths.materialExpressionConstant3Vector:
|
||||
case Configuration.paths.materialExpressionConstant4Vector:
|
||||
case p.materialExpressionConstant3Vector:
|
||||
case p.materialExpressionConstant4Vector:
|
||||
if (!value) {
|
||||
const vector = entity.getCustomproperties()
|
||||
.find(pinEntity => pinEntity.PinName?.toString() == "Constant")
|
||||
@@ -160,32 +222,32 @@ export default function nodeTitle(entity) {
|
||||
}
|
||||
value = undefined
|
||||
break
|
||||
case Configuration.paths.materialExpressionFunctionInput: {
|
||||
case p.materialExpressionFunctionInput: {
|
||||
const materialObject = entity.getMaterialSubobject()
|
||||
const inputName = materialObject?.InputName ?? "In"
|
||||
const inputType = materialObject?.InputType?.value.match(/^.+?_(\w+)$/)?.[1] ?? "Vector3"
|
||||
return `Input ${inputName} (${inputType})`
|
||||
}
|
||||
case Configuration.paths.materialExpressionLogarithm:
|
||||
case p.materialExpressionLogarithm:
|
||||
return "Ln"
|
||||
case Configuration.paths.materialExpressionLogarithm10:
|
||||
case p.materialExpressionLogarithm10:
|
||||
return "Log10"
|
||||
case Configuration.paths.materialExpressionLogarithm2:
|
||||
case p.materialExpressionLogarithm2:
|
||||
return "Log2"
|
||||
case Configuration.paths.materialExpressionMaterialFunctionCall:
|
||||
case p.materialExpressionMaterialFunctionCall:
|
||||
const materialFunction = entity.getMaterialSubobject()?.MaterialFunction
|
||||
if (materialFunction) {
|
||||
return materialFunction.getName()
|
||||
}
|
||||
break
|
||||
case Configuration.paths.materialExpressionSquareRoot:
|
||||
case p.materialExpressionSquareRoot:
|
||||
return "Sqrt"
|
||||
case Configuration.paths.materialExpressionSubtract:
|
||||
case p.materialExpressionSubtract:
|
||||
const materialObject = entity.getMaterialSubobject()
|
||||
if (materialObject) {
|
||||
return `Subtract(${materialObject.ConstA ?? "1"},${materialObject.ConstB ?? "1"})`
|
||||
}
|
||||
case Configuration.paths.metasoundEditorGraphExternalNode: {
|
||||
case p.metasoundEditorGraphExternalNode: {
|
||||
const name = entity["ClassName"]?.["Name"]
|
||||
if (name) {
|
||||
switch (name) {
|
||||
@@ -194,11 +256,26 @@ export default function nodeTitle(entity) {
|
||||
}
|
||||
}
|
||||
}
|
||||
case Configuration.paths.pcgEditorGraphNodeInput:
|
||||
case p.niagaraNodeConvert:
|
||||
/** @type {String} */
|
||||
const targetType = (entity["AutowireMakeType"]?.["ClassStructOrEnum"] ?? "")
|
||||
.toString()
|
||||
.match(/(?:Niagara)?(\w+)['"]*$/)
|
||||
?.[1]
|
||||
?? ""
|
||||
return `Make ${targetType}`
|
||||
case p.pcgEditorGraphNodeInput:
|
||||
return "Input"
|
||||
case Configuration.paths.pcgEditorGraphNodeOutput:
|
||||
case p.pcgEditorGraphNodeOutput:
|
||||
return "Output"
|
||||
case Configuration.paths.spawnActorFromClass:
|
||||
case p.soundNodeWavePlayer:
|
||||
return `Wave Player : ${entity.getSounCueSubobject()
|
||||
?.SoundWaveAssetPtr
|
||||
?.type
|
||||
.match(/([^.]+)$/)
|
||||
?.[0]
|
||||
?? "NONE"}`
|
||||
case p.spawnActorFromClass:
|
||||
let className = entity.getCustomproperties()
|
||||
.find(pinEntity => pinEntity.PinName.toString() == "ReturnValue")
|
||||
?.PinType
|
||||
@@ -207,20 +284,21 @@ export default function nodeTitle(entity) {
|
||||
if (className === "Actor") {
|
||||
className = null
|
||||
}
|
||||
return `SpawnActor ${Utility.formatStringName(className ?? "NONE")}`
|
||||
case Configuration.paths.switchEnum:
|
||||
return `SpawnActor ${format(className ?? "NONE")}`
|
||||
case p.switchEnum:
|
||||
return `Switch on ${entity.Enum?.getName() ?? "Enum"}`
|
||||
case Configuration.paths.switchInteger:
|
||||
case p.switchInteger:
|
||||
return `Switch on Int`
|
||||
case Configuration.paths.variableGet:
|
||||
case p.variableGet:
|
||||
return ""
|
||||
case Configuration.paths.variableSet:
|
||||
case p.variableSet:
|
||||
return "SET"
|
||||
}
|
||||
const className = entity.getClass()
|
||||
let switchTarget = entity.switchTarget()
|
||||
if (switchTarget) {
|
||||
if (switchTarget[0] !== "E") {
|
||||
switchTarget = Utility.formatStringName(switchTarget)
|
||||
switchTarget = format(switchTarget)
|
||||
}
|
||||
return `Switch on ${switchTarget}`
|
||||
}
|
||||
@@ -230,19 +308,20 @@ export default function nodeTitle(entity) {
|
||||
const keyNameSymbol = entity.getHIDAttribute()
|
||||
if (keyNameSymbol) {
|
||||
const name = keyNameSymbol.toString()
|
||||
let title = keyName(name) ?? Utility.formatStringName(name)
|
||||
if (entity.getClass() === Configuration.paths.inputDebugKey) {
|
||||
let title = keyName(name) ?? format(name)
|
||||
if (className === p.inputDebugKey) {
|
||||
title = "Debug Key " + title
|
||||
} else if (entity.getClass() === Configuration.paths.getInputAxisKeyValue) {
|
||||
} else if (className === p.getInputAxisKeyValue) {
|
||||
title = "Get " + title
|
||||
}
|
||||
return title
|
||||
}
|
||||
if (entity.getClass() === Configuration.paths.macro) {
|
||||
return Utility.formatStringName(entity.MacroGraphReference?.getMacroName())
|
||||
if (className === p.macro) {
|
||||
return format(entity.MacroGraphReference?.getMacroName())
|
||||
}
|
||||
if (entity.isMaterial() && entity.getMaterialSubobject()) {
|
||||
let result = nodeTitle(entity.getMaterialSubobject())
|
||||
const materialSubobject = entity.getMaterialSubobject()
|
||||
if (materialSubobject) {
|
||||
let result = nodeTitle(materialSubobject)
|
||||
result = result.match(/Material Expression (.+)/)?.[1] ?? result
|
||||
return result
|
||||
}
|
||||
@@ -251,25 +330,29 @@ export default function nodeTitle(entity) {
|
||||
let result = pcgSubobject.NodeTitle ? pcgSubobject.NodeTitle.toString() : nodeTitle(pcgSubobject)
|
||||
return result
|
||||
}
|
||||
const soundCueSubobject = entity.getSounCueSubobject()
|
||||
if (soundCueSubobject) {
|
||||
return Utility.formatStringName(soundCueSubobject.getObjectName(true).replace(/^SoundNode/, ""))
|
||||
}
|
||||
const subgraphObject = entity.getSubgraphObject()
|
||||
if (subgraphObject) {
|
||||
return subgraphObject.Graph.getName()
|
||||
}
|
||||
const settingsObject = entity.getSettingsObject()
|
||||
if (settingsObject) {
|
||||
if (settingsObject.ExportPath.type === Configuration.paths.pcgHiGenGridSizeSettings) {
|
||||
if (settingsObject.ExportPath?.valueOf()?.type === p.pcgHiGenGridSizeSettings) {
|
||||
return `Grid Size: ${(
|
||||
settingsObject.HiGenGridSize?.toString().match(/\d+/)?.[0]?.concat("00")
|
||||
?? settingsObject.HiGenGridSize?.toString().match(/^\w+$/)?.[0]
|
||||
) ?? "256"}`
|
||||
}
|
||||
if (settingsObject.BlueprintElementInstance) {
|
||||
return Utility.formatStringName(settingsObject.BlueprintElementType.getName())
|
||||
return format(settingsObject.BlueprintElementType.getName())
|
||||
}
|
||||
if (settingsObject.Operation) {
|
||||
const match = settingsObject.Name?.toString().match(/PCGMetadata(\w+)Settings_\d+/)
|
||||
if (match) {
|
||||
return Utility.formatStringName(match[1] + ": " + settingsObject.Operation)
|
||||
return format(match[1] + ": " + settingsObject.Operation)
|
||||
}
|
||||
}
|
||||
const settingsSubgraphObject = settingsObject.getSubgraphObject()
|
||||
@@ -284,7 +367,7 @@ export default function nodeTitle(entity) {
|
||||
case "AddKey":
|
||||
let result = memberParent.match(sequencerScriptingNameRegex)
|
||||
if (result) {
|
||||
return `Add Key (${Utility.formatStringName(result[1])})`
|
||||
return `Add Key (${format(result[1])})`
|
||||
}
|
||||
case "Concat_StrStr":
|
||||
return "Append"
|
||||
@@ -295,14 +378,16 @@ export default function nodeTitle(entity) {
|
||||
+ (memberNameTraceLineMatch[1] === "Multi" ? " Multi " : " ")
|
||||
+ (memberNameTraceLineMatch[2] === ""
|
||||
? "By Channel"
|
||||
: Utility.formatStringName(memberNameTraceLineMatch[2])
|
||||
: format(memberNameTraceLineMatch[2])
|
||||
)
|
||||
}
|
||||
switch (memberParent) {
|
||||
case Configuration.paths.blueprintGameplayTagLibrary:
|
||||
case Configuration.paths.kismetMathLibrary:
|
||||
case Configuration.paths.slateBlueprintLibrary:
|
||||
case Configuration.paths.timeManagementBlueprintLibrary:
|
||||
case p.blueprintGameplayTagLibrary:
|
||||
case p.kismetMathLibrary:
|
||||
case p.kismetStringLibrary:
|
||||
case p.slateBlueprintLibrary:
|
||||
case p.timeManagementBlueprintLibrary:
|
||||
case p.typedElementHandleLibrary:
|
||||
const leadingLetter = memberName.match(/[BF]([A-Z]\w+)/)
|
||||
if (leadingLetter) {
|
||||
// Some functions start with B or F (Like FCeil, FMax, BMin)
|
||||
@@ -313,6 +398,7 @@ export default function nodeTitle(entity) {
|
||||
case "BooleanAND": return "AND"
|
||||
case "BooleanNAND": return "NAND"
|
||||
case "BooleanOR": return "OR"
|
||||
case "Equal": return "=="
|
||||
case "Exp": return "e"
|
||||
case "LineTraceSingle": return "Line Trace By Channel"
|
||||
case "Max": return "MAX"
|
||||
@@ -386,23 +472,23 @@ export default function nodeTitle(entity) {
|
||||
return "^"
|
||||
}
|
||||
break
|
||||
case Configuration.paths.blueprintSetLibrary:
|
||||
case p.blueprintSetLibrary:
|
||||
{
|
||||
const setOperationMatch = memberName.match(/Set_(\w+)/)
|
||||
if (setOperationMatch) {
|
||||
return Utility.formatStringName(setOperationMatch[1]).toUpperCase()
|
||||
return format(setOperationMatch[1]).toUpperCase()
|
||||
}
|
||||
}
|
||||
break
|
||||
case Configuration.paths.blueprintMapLibrary:
|
||||
case p.blueprintMapLibrary:
|
||||
{
|
||||
const setOperationMatch = memberName.match(/Map_(\w+)/)
|
||||
if (setOperationMatch) {
|
||||
return Utility.formatStringName(setOperationMatch[1]).toUpperCase()
|
||||
return format(setOperationMatch[1]).toUpperCase()
|
||||
}
|
||||
}
|
||||
break
|
||||
case Configuration.paths.kismetArrayLibrary:
|
||||
case p.kismetArrayLibrary:
|
||||
{
|
||||
const arrayOperationMath = memberName.match(/Array_(\w+)/)
|
||||
if (arrayOperationMath) {
|
||||
@@ -411,29 +497,27 @@ export default function nodeTitle(entity) {
|
||||
}
|
||||
break
|
||||
}
|
||||
return Utility.formatStringName(memberName)
|
||||
return format(memberName)
|
||||
}
|
||||
if (entity.OpName) {
|
||||
switch (entity.OpName.toString()) {
|
||||
case "Boolean::LogicAnd": return "Logic AND"
|
||||
case "Boolean::LogicEq": return "=="
|
||||
case "Boolean::LogicNEq": return "!="
|
||||
case "Boolean::LogicNot": return "Logic NOT"
|
||||
case "Boolean::LogicOr": return "Logic OR"
|
||||
case "Matrix::MatrixMultiply": return "Multiply (Matrix * Matrix)"
|
||||
case "Matrix::MatrixVectorMultiply": return "Multiply (Matrix * Vector4)"
|
||||
case "Numeric::Abs": return "Abs"
|
||||
case "Numeric::Add": return "+"
|
||||
case "Numeric::DistancePos": return "Distance"
|
||||
case "Numeric::Mul": return String.fromCharCode(0x2a2f)
|
||||
}
|
||||
return Utility.formatStringName(entity.OpName.toString()).replaceAll("::", " ")
|
||||
return niagaraNodeNames[entity.OpName.toString()]
|
||||
?? format(entity.OpName.toString().replaceAll(/(?:^\w+(?<!^Matrix))?::/g, " "))
|
||||
}
|
||||
if (entity.FunctionDisplayName) {
|
||||
return Utility.formatStringName(entity.FunctionDisplayName.toString())
|
||||
return format(entity.FunctionDisplayName.toString())
|
||||
}
|
||||
if (entity.ObjectRef) {
|
||||
return entity.ObjectRef.getName()
|
||||
}
|
||||
return Utility.formatStringName(entity.getNameAndCounter()[0])
|
||||
let prefix
|
||||
if (
|
||||
className.startsWith(prefix = "/Script/NiagaraEditor.NiagaraNodeParameter")
|
||||
|| className.startsWith(prefix = "/Script/NiagaraEditor.NiagaraNode")
|
||||
) {
|
||||
return entity["Input"]?.["Name"]?.toString() ?? format(className.substring(prefix.length))
|
||||
}
|
||||
if (entity.ParameterName) {
|
||||
return entity.ParameterName.toString()
|
||||
}
|
||||
return format(entity.getNameAndCounter()[0])
|
||||
}
|
||||
|
||||
19
js/decoding/nodeVariadic.js
Normal file → Executable file
19
js/decoding/nodeVariadic.js
Normal file → Executable file
@@ -8,6 +8,7 @@ import StringEntity from "../entity/StringEntity.js"
|
||||
/** @param {PinEntity} pinEntity */
|
||||
const indexFromUpperCaseLetterName = pinEntity =>
|
||||
pinEntity.PinName?.toString().match(/^\s*([A-Z])\s*$/)?.[1]?.charCodeAt(0) - "A".charCodeAt(0)
|
||||
const p = Configuration.paths
|
||||
|
||||
/** @param {ObjectEntity} entity */
|
||||
export default function nodeVariadic(entity) {
|
||||
@@ -21,8 +22,8 @@ export default function nodeVariadic(entity) {
|
||||
let prefix
|
||||
let name
|
||||
switch (type) {
|
||||
case Configuration.paths.commutativeAssociativeBinaryOperator:
|
||||
case Configuration.paths.promotableOperator:
|
||||
case p.commutativeAssociativeBinaryOperator:
|
||||
case p.promotableOperator:
|
||||
name = entity.FunctionReference?.MemberName?.toString()
|
||||
switch (name) {
|
||||
default:
|
||||
@@ -60,9 +61,9 @@ export default function nodeVariadic(entity) {
|
||||
break
|
||||
}
|
||||
break
|
||||
case Configuration.paths.executionSequence:
|
||||
case p.executionSequence:
|
||||
prefix ??= "Then"
|
||||
case Configuration.paths.multiGate:
|
||||
case p.multiGate:
|
||||
prefix ??= "Out"
|
||||
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isOutput())
|
||||
pinIndexFromEntity ??= pinEntity => Number(
|
||||
@@ -71,7 +72,7 @@ export default function nodeVariadic(entity) {
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) =>
|
||||
`${prefix} ${index >= 0 ? index : min > 0 ? `${prefix} 0` : max + 1}`
|
||||
break
|
||||
// case Configuration.paths.niagaraNodeOp:
|
||||
// case p.niagaraNodeOp:
|
||||
// pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isInput())
|
||||
// pinIndexFromEntity ??= indexFromUpperCaseLetterName
|
||||
// pinNameFromIndex ??= (index, min = -1, max = -1, newPin) => {
|
||||
@@ -81,12 +82,12 @@ export default function nodeVariadic(entity) {
|
||||
// return result
|
||||
// }
|
||||
// break
|
||||
case Configuration.paths.switchInteger:
|
||||
case p.switchInteger:
|
||||
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isOutput())
|
||||
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName?.toString().match(/^\s*(\d+)\s*$/)?.[1])
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) => (index < 0 ? max + 1 : index).toString()
|
||||
break
|
||||
case Configuration.paths.switchGameplayTag:
|
||||
case p.switchGameplayTag:
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) => {
|
||||
const result = `Case_${index >= 0 ? index : min > 0 ? "0" : max + 1}`
|
||||
entity.PinNames ??= new ArrayEntity()
|
||||
@@ -95,8 +96,8 @@ export default function nodeVariadic(entity) {
|
||||
entity.PinTags.valueOf()[entity.PinTags.length] = null
|
||||
return result
|
||||
}
|
||||
case Configuration.paths.switchName:
|
||||
case Configuration.paths.switchString:
|
||||
case p.switchName:
|
||||
case p.switchString:
|
||||
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isOutput())
|
||||
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.toString().match(/^\s*Case[_\s]+(\d+)\s*$/i)?.[1])
|
||||
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) => {
|
||||
|
||||
49
js/decoding/pinColor.js
Normal file → Executable file
49
js/decoding/pinColor.js
Normal file → Executable file
@@ -1,25 +1,14 @@
|
||||
import { css } from "lit"
|
||||
import Configuration from "../Configuration.js"
|
||||
|
||||
const p = Configuration.paths
|
||||
const colors = {
|
||||
[Configuration.paths.niagaraBool]: css`146, 0, 0`,
|
||||
[Configuration.paths.niagaraDataInterfaceVolumeTexture]: css`0, 168, 242`,
|
||||
[Configuration.paths.niagaraFloat]: css`160, 250, 68`,
|
||||
[Configuration.paths.niagaraMatrix]: css`0, 88, 200`,
|
||||
[Configuration.paths.niagaraNumeric]: css`0, 88, 200`,
|
||||
[Configuration.paths.niagaraPosition]: css`251, 146, 251`,
|
||||
[Configuration.paths.quat4f]: css`0, 88, 200`,
|
||||
[Configuration.paths.rotator]: css`157, 177, 251`,
|
||||
[Configuration.paths.transform]: css`227, 103, 0`,
|
||||
[Configuration.paths.vector]: css`251, 198, 34`,
|
||||
[Configuration.paths.vector3f]: css`250, 200, 36`,
|
||||
[Configuration.paths.vector4f]: css`0, 88, 200`,
|
||||
"Any": css`132, 132, 132`,
|
||||
"Any[]": css`132, 132, 132`,
|
||||
"audio": css`252, 148, 252`,
|
||||
"blue": css`0, 0, 255`,
|
||||
"bool": css`146, 0, 0`,
|
||||
"byte": css`0, 109, 99`,
|
||||
"byte": css`0, 110, 100`,
|
||||
"class": css`88, 0, 186`,
|
||||
"default": css`255, 255, 255`,
|
||||
"delegate": css`255, 56, 56`,
|
||||
@@ -27,16 +16,16 @@ const colors = {
|
||||
"exec": css`240, 240, 240`,
|
||||
"float": css`160, 252, 70`,
|
||||
"green": css`0, 255, 0`,
|
||||
"int": css`31, 224, 172`,
|
||||
"int": css`30, 224, 172`,
|
||||
"int32": css`30, 224, 172`,
|
||||
"int64": css`169, 223, 172`,
|
||||
"int64": css`170, 224, 172`,
|
||||
"interface": css`238, 252, 168`,
|
||||
"name": css`201, 128, 251`,
|
||||
"name": css`200, 128, 252`,
|
||||
"object": css`0, 168, 242`,
|
||||
"Param": css`255, 166, 39`,
|
||||
"Param[]": css`255, 166, 39`,
|
||||
"Point": css`63, 137, 255`,
|
||||
"Point[]": css`63, 137, 255`,
|
||||
"Param": css`255, 166, 40`,
|
||||
"Param[]": css`255, 166, 40`,
|
||||
"Point": css`64, 138, 255`,
|
||||
"Point[]": css`64, 137, 255`,
|
||||
"real": css`54, 208, 0`,
|
||||
"red": css`255, 0, 0`,
|
||||
"string": css`251, 0, 208`,
|
||||
@@ -48,6 +37,21 @@ const colors = {
|
||||
"Volume": css`230, 69, 188`,
|
||||
"Volume[]": css`230, 69, 188`,
|
||||
"wildcard": css`128, 120, 120`,
|
||||
[p.linearColor]: css`0, 88, 200`,
|
||||
[p.niagaraBool]: css`146, 0, 0`,
|
||||
[p.niagaraDataInterfaceCollisionQuery]: css`0, 168, 242`,
|
||||
[p.niagaraDataInterfaceCurlNoise]: css`0, 168, 242`,
|
||||
[p.niagaraDataInterfaceVolumeTexture]: css`0, 168, 242`,
|
||||
[p.niagaraFloat]: css`160, 250, 68`,
|
||||
[p.niagaraInt32]: css`30, 224, 172`,
|
||||
[p.niagaraPosition]: css`251, 146, 251`,
|
||||
[p.quat4f]: css`0, 88, 200`,
|
||||
[p.rotator]: css`157, 177, 251`,
|
||||
[p.transform]: css`227, 103, 0`,
|
||||
[p.vector]: css`251, 198, 34`,
|
||||
[p.vector2f]: css`0, 88, 200`,
|
||||
[p.vector3f]: css`250, 200, 36`,
|
||||
[p.vector4f]: css`0, 88, 200`,
|
||||
}
|
||||
|
||||
const pinColorMaterial = css`120, 120, 120`
|
||||
@@ -62,7 +66,8 @@ export default function pinColor(entity) {
|
||||
} else if (entity.PinType.PinCategory?.toString() === "optional") {
|
||||
return pinColorMaterial
|
||||
}
|
||||
return colors[entity.getType()]
|
||||
const type = entity.getType()
|
||||
return colors[type]
|
||||
?? colors[entity.PinType.PinCategory?.toString().toLowerCase()]
|
||||
?? colors["default"]
|
||||
?? (type.startsWith("/Script/Niagara.") ? colors["struct"] : colors["default"])
|
||||
}
|
||||
|
||||
30
js/decoding/pinTemplate.js
Normal file → Executable file
30
js/decoding/pinTemplate.js
Normal file → Executable file
@@ -7,6 +7,7 @@ import IntPinTemplate from "../template/pin/IntPinTemplate.js"
|
||||
import LinearColorPinTemplate from "../template/pin/LinearColorPinTemplate.js"
|
||||
import NamePinTemplate from "../template/pin/NamePinTemplate.js"
|
||||
import PinTemplate from "../template/pin/PinTemplate.js"
|
||||
import ReadonlyNamePinTemplate from "../template/pin/ReadonlyInputPinTemplate.js"
|
||||
import RealPinTemplate from "../template/pin/RealPinTemplate.js"
|
||||
import ReferencePinTemplate from "../template/pin/ReferencePinTemplate.js"
|
||||
import RotatorPinTemplate from "../template/pin/RotatorPinTemplate.js"
|
||||
@@ -14,11 +15,14 @@ import StringPinTemplate from "../template/pin/StringPinTemplate.js"
|
||||
import Vector2DPinTemplate from "../template/pin/Vector2DPinTemplate.js"
|
||||
import Vector4DPinTemplate from "../template/pin/Vector4DPinTemplate.js"
|
||||
import VectorPinTemplate from "../template/pin/VectorPinTemplate.js"
|
||||
import pinTitle from "./pinTitle.js"
|
||||
|
||||
const p = Configuration.paths
|
||||
const inputPinTemplates = {
|
||||
"bool": BoolPinTemplate,
|
||||
"byte": IntPinTemplate,
|
||||
"enum": EnumPinTemplate,
|
||||
"float": RealPinTemplate,
|
||||
"int": IntPinTemplate,
|
||||
"int64": Int64PinTemplate,
|
||||
"MUTABLE_REFERENCE": ReferencePinTemplate,
|
||||
@@ -26,14 +30,17 @@ const inputPinTemplates = {
|
||||
"real": RealPinTemplate,
|
||||
"rg": Vector2DPinTemplate,
|
||||
"string": StringPinTemplate,
|
||||
[Configuration.paths.linearColor]: LinearColorPinTemplate,
|
||||
[Configuration.paths.niagaraBool]: BoolPinTemplate,
|
||||
[Configuration.paths.niagaraPosition]: VectorPinTemplate,
|
||||
[Configuration.paths.rotator]: RotatorPinTemplate,
|
||||
[Configuration.paths.vector]: VectorPinTemplate,
|
||||
[Configuration.paths.vector2D]: Vector2DPinTemplate,
|
||||
[Configuration.paths.vector3f]: VectorPinTemplate,
|
||||
[Configuration.paths.vector4f]: Vector4DPinTemplate,
|
||||
[p.linearColor]: LinearColorPinTemplate,
|
||||
[p.niagaraBool]: BoolPinTemplate,
|
||||
[p.niagaraFloat]: RealPinTemplate,
|
||||
[p.niagaraInt32]: IntPinTemplate,
|
||||
[p.niagaraPosition]: VectorPinTemplate,
|
||||
[p.rotator]: RotatorPinTemplate,
|
||||
[p.vector]: VectorPinTemplate,
|
||||
[p.vector2D]: Vector2DPinTemplate,
|
||||
[p.vector2f]: Vector2DPinTemplate,
|
||||
[p.vector3f]: VectorPinTemplate,
|
||||
[p.vector4f]: Vector4DPinTemplate,
|
||||
}
|
||||
|
||||
/** @param {PinEntity<IEntity>} entity */
|
||||
@@ -44,9 +51,12 @@ export default function pinTemplate(entity) {
|
||||
if (entity.PinType.bIsReference?.valueOf() && !entity.PinType.bIsConst?.valueOf()) {
|
||||
return inputPinTemplates["MUTABLE_REFERENCE"]
|
||||
}
|
||||
const type = entity.getType()
|
||||
if (type === "exec") {
|
||||
if (entity.isExecution()) {
|
||||
return ExecPinTemplate
|
||||
}
|
||||
if (entity.PinName?.toString() === "self" && pinTitle(entity) === "Target") {
|
||||
return ReadonlyNamePinTemplate
|
||||
}
|
||||
const type = entity.getType()
|
||||
return (entity.isInput() ? inputPinTemplates[type] : PinTemplate) ?? PinTemplate
|
||||
}
|
||||
|
||||
1
js/decoding/pinTitle.js
Normal file → Executable file
1
js/decoding/pinTitle.js
Normal file → Executable file
@@ -11,5 +11,6 @@ export default function pinTitle(entity) {
|
||||
return match[1] // In case they match, then keep the case of the PinToolTip
|
||||
}
|
||||
}
|
||||
result = result.replace(/^Module\./, "")
|
||||
return result
|
||||
}
|
||||
|
||||
0
js/element/ColorHandlerElement.js
Normal file → Executable file
0
js/element/ColorHandlerElement.js
Normal file → Executable file
0
js/element/ColorSliderElement.js
Normal file → Executable file
0
js/element/ColorSliderElement.js
Normal file → Executable file
0
js/element/DropdownElement.js
Normal file → Executable file
0
js/element/DropdownElement.js
Normal file → Executable file
0
js/element/ElementFactory.js
Normal file → Executable file
0
js/element/ElementFactory.js
Normal file → Executable file
0
js/element/IDraggableControlElement.js
Normal file → Executable file
0
js/element/IDraggableControlElement.js
Normal file → Executable file
0
js/element/IDraggableElement.js
Normal file → Executable file
0
js/element/IDraggableElement.js
Normal file → Executable file
0
js/element/IElement.js
Normal file → Executable file
0
js/element/IElement.js
Normal file → Executable file
36
js/element/IFromToPositionedElement.js
Normal file → Executable file
36
js/element/IFromToPositionedElement.js
Normal file → Executable file
@@ -9,19 +9,19 @@ export default class IFromToPositionedElement extends IElement {
|
||||
|
||||
static properties = {
|
||||
...super.properties,
|
||||
fromX: {
|
||||
originX: {
|
||||
type: Number,
|
||||
attribute: false,
|
||||
},
|
||||
fromY: {
|
||||
originY: {
|
||||
type: Number,
|
||||
attribute: false,
|
||||
},
|
||||
toX: {
|
||||
targetX: {
|
||||
type: Number,
|
||||
attribute: false,
|
||||
},
|
||||
toY: {
|
||||
targetY: {
|
||||
type: Number,
|
||||
attribute: false,
|
||||
},
|
||||
@@ -29,35 +29,35 @@ export default class IFromToPositionedElement extends IElement {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.fromX = 0
|
||||
this.fromY = 0
|
||||
this.toX = 0
|
||||
this.toY = 0
|
||||
this.originX = 0
|
||||
this.originY = 0
|
||||
this.targetX = 0
|
||||
this.targetY = 0
|
||||
}
|
||||
|
||||
/** @param {Coordinates} param0 */
|
||||
setBothLocations([x, y]) {
|
||||
this.fromX = x
|
||||
this.fromY = y
|
||||
this.toX = x
|
||||
this.toY = y
|
||||
this.originX = x
|
||||
this.originY = y
|
||||
this.targetX = x
|
||||
this.targetY = y
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} x
|
||||
* @param {Number} y
|
||||
*/
|
||||
addSourceLocation(x, y) {
|
||||
this.fromX += x
|
||||
this.fromY += y
|
||||
addOriginLocation(x, y) {
|
||||
this.originX += x
|
||||
this.originY += y
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} x
|
||||
* @param {Number} y
|
||||
*/
|
||||
addDestinationLocation(x, y) {
|
||||
this.toX += x
|
||||
this.toY += y
|
||||
addTargetLocation(x, y) {
|
||||
this.targetX += x
|
||||
this.targetY += y
|
||||
}
|
||||
}
|
||||
|
||||
0
js/element/ISelectableDraggableElement.js
Normal file → Executable file
0
js/element/ISelectableDraggableElement.js
Normal file → Executable file
0
js/element/InputElement.js
Normal file → Executable file
0
js/element/InputElement.js
Normal file → Executable file
249
js/element/LinkElement.js
Normal file → Executable file
249
js/element/LinkElement.js
Normal file → Executable file
@@ -5,6 +5,7 @@ import Utility from "../Utility.js"
|
||||
import BooleanEntity from "../entity/BooleanEntity.js"
|
||||
import LinkTemplate from "../template/LinkTemplate.js"
|
||||
import IFromToPositionedElement from "./IFromToPositionedElement.js"
|
||||
import LinearColorEntity from "../entity/LinearColorEntity.js"
|
||||
|
||||
/** @extends {IFromToPositionedElement<Object, LinkTemplate>} */
|
||||
export default class LinkElement extends IFromToPositionedElement {
|
||||
@@ -17,9 +18,34 @@ export default class LinkElement extends IFromToPositionedElement {
|
||||
converter: BooleanEntity.booleanConverter,
|
||||
reflect: true,
|
||||
},
|
||||
originNode: {
|
||||
type: String,
|
||||
attribute: "data-origin-node",
|
||||
reflect: true,
|
||||
},
|
||||
originPin: {
|
||||
type: String,
|
||||
attribute: "data-origin-pin",
|
||||
reflect: true,
|
||||
},
|
||||
targetNode: {
|
||||
type: String,
|
||||
attribute: "data-target-node",
|
||||
reflect: true,
|
||||
},
|
||||
targetPin: {
|
||||
type: String,
|
||||
attribute: "data-target-pin",
|
||||
reflect: true,
|
||||
},
|
||||
originatesFromInput: {
|
||||
type: Boolean,
|
||||
attribute: false,
|
||||
attribute: "data-from-input",
|
||||
converter: BooleanEntity.booleanConverter,
|
||||
reflect: true,
|
||||
},
|
||||
color: {
|
||||
type: LinearColorEntity,
|
||||
},
|
||||
svgPathD: {
|
||||
type: String,
|
||||
@@ -36,30 +62,58 @@ export default class LinkElement extends IFromToPositionedElement {
|
||||
}
|
||||
|
||||
/** @type {PinElement} */
|
||||
#source
|
||||
get source() {
|
||||
return this.#source
|
||||
#origin
|
||||
get origin() {
|
||||
return this.#origin
|
||||
}
|
||||
set source(pin) {
|
||||
set origin(pin) {
|
||||
this.#setPin(pin, false)
|
||||
}
|
||||
|
||||
/** @type {PinElement} */
|
||||
#destination
|
||||
get destination() {
|
||||
return this.#destination
|
||||
#target
|
||||
get target() {
|
||||
return this.#target
|
||||
}
|
||||
set destination(pin) {
|
||||
set target(pin) {
|
||||
this.#setPin(pin, true)
|
||||
}
|
||||
|
||||
/** @param {UEBNodeUpdateEvent} e */
|
||||
#nodeUpdateHandler = e => {
|
||||
if (this.#origin.nodeElement === e.target) {
|
||||
if (this.originNode != this.#origin.nodeElement.nodeTitle) {
|
||||
this.originNode = this.#origin.nodeElement.nodeTitle
|
||||
}
|
||||
this.setOriginLocation()
|
||||
} else if (this.#target.nodeElement === e.target) {
|
||||
if (this.targetNode != this.#target.nodeElement.nodeTitle) {
|
||||
this.targetNode = this.#target.nodeElement.nodeTitle
|
||||
}
|
||||
this.setTargetLocation()
|
||||
} else {
|
||||
throw new Error("Unexpected node update")
|
||||
}
|
||||
}
|
||||
/** @param {UEBNodeUpdateEvent} e */
|
||||
#pinUpdateHandler = e => {
|
||||
const colorReferencePin = this.getOutputPin(true)
|
||||
if (!this.color?.equals(colorReferencePin.color)) {
|
||||
this.color = colorReferencePin.color
|
||||
}
|
||||
}
|
||||
#nodeDeleteHandler = () => this.remove()
|
||||
/** @param {UEBDragEvent} e */
|
||||
#nodeDragSourceHandler = e => this.addSourceLocation(...e.detail.value)
|
||||
#nodeDragOriginHandler = e => this.addOriginLocation(...e.detail.value)
|
||||
/** @param {UEBDragEvent} e */
|
||||
#nodeDragDestinatonHandler = e => this.addDestinationLocation(...e.detail.value)
|
||||
#nodeReflowSourceHandler = e => this.setSourceLocation()
|
||||
#nodeReflowDestinatonHandler = e => this.setDestinationLocation()
|
||||
#nodeDragTargetHandler = e => this.addTargetLocation(...e.detail.value)
|
||||
#nodeReflowOriginHandler = e => {
|
||||
if (this.origin.isKnot()) {
|
||||
this.originatesFromInput = this.origin.isInputVisually()
|
||||
}
|
||||
this.setOriginLocation()
|
||||
}
|
||||
#nodeReflowTargetHandler = e => this.setTargetLocation()
|
||||
|
||||
/** @type {TemplateResult | nothing} */
|
||||
linkMessageIcon = nothing
|
||||
@@ -72,178 +126,197 @@ export default class LinkElement extends IFromToPositionedElement {
|
||||
constructor() {
|
||||
super()
|
||||
this.dragging = false
|
||||
this.originNode = ""
|
||||
this.originPin = ""
|
||||
this.targetNode = ""
|
||||
this.targetPin = ""
|
||||
this.originatesFromInput = false
|
||||
this.color = new LinearColorEntity()
|
||||
this.startPercentage = 0
|
||||
this.svgPathD = ""
|
||||
this.startPixels = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PinElement} source
|
||||
* @param {PinElement?} destination
|
||||
* @param {PinElement} origin
|
||||
* @param {PinElement?} target
|
||||
*/
|
||||
static newObject(source, destination) {
|
||||
static newObject(origin, target) {
|
||||
const result = new LinkElement()
|
||||
result.initialize(source, destination)
|
||||
result.initialize(origin, target)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PinElement} source
|
||||
* @param {PinElement?} destination
|
||||
* @param {PinElement} origin
|
||||
* @param {PinElement?} target
|
||||
*/
|
||||
// @ts-expect-error
|
||||
initialize(source, destination) {
|
||||
initialize(origin, target) {
|
||||
super.initialize({}, new LinkTemplate())
|
||||
if (source) {
|
||||
this.source = source
|
||||
if (!destination) {
|
||||
this.toX = this.fromX
|
||||
this.toY = this.fromY
|
||||
if (origin) {
|
||||
this.origin = origin
|
||||
if (!target) {
|
||||
this.targetX = this.originX
|
||||
this.targetY = this.originY
|
||||
}
|
||||
}
|
||||
if (destination) {
|
||||
this.destination = destination
|
||||
if (!source) {
|
||||
this.fromX = this.toX
|
||||
this.fromY = this.toY
|
||||
if (target) {
|
||||
this.target = target
|
||||
if (!origin) {
|
||||
this.originX = this.targetX
|
||||
this.originY = this.targetY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PinElement} pin
|
||||
* @param {Boolean} isDestinationPin
|
||||
* @param {Boolean} isTargetPin
|
||||
*/
|
||||
#setPin(pin, isDestinationPin) {
|
||||
const getCurrentPin = () => isDestinationPin ? this.destination : this.source
|
||||
#setPin(pin, isTargetPin) {
|
||||
const getCurrentPin = () => isTargetPin ? this.target : this.origin
|
||||
if (getCurrentPin() == pin) {
|
||||
return
|
||||
}
|
||||
if (getCurrentPin()) {
|
||||
const nodeElement = getCurrentPin().getNodeElement()
|
||||
nodeElement.removeEventListener(Configuration.nodeUpdateEventName, this.#nodeUpdateHandler)
|
||||
nodeElement.removeEventListener(Configuration.removeEventName, this.#nodeDeleteHandler)
|
||||
nodeElement.removeEventListener(
|
||||
Configuration.nodeDragEventName,
|
||||
isDestinationPin ? this.#nodeDragDestinatonHandler : this.#nodeDragSourceHandler
|
||||
)
|
||||
nodeElement.removeEventListener(
|
||||
Configuration.nodeReflowEventName,
|
||||
isDestinationPin ? this.#nodeReflowDestinatonHandler : this.#nodeReflowSourceHandler
|
||||
isTargetPin ? this.#nodeDragTargetHandler : this.#nodeDragOriginHandler
|
||||
)
|
||||
getCurrentPin().removeEventListener(Configuration.pinUpdateEventName, this.#pinUpdateHandler)
|
||||
this.#unlinkPins()
|
||||
}
|
||||
isDestinationPin
|
||||
? this.#destination = pin
|
||||
: this.#source = pin
|
||||
if (isTargetPin) {
|
||||
this.#target = pin
|
||||
this.targetNode = pin?.nodeElement.nodeTitle
|
||||
this.targetPin = pin?.pinId.toString()
|
||||
} else {
|
||||
this.#origin = pin
|
||||
this.originNode = pin?.nodeElement.nodeTitle
|
||||
this.originPin = pin?.pinId.toString()
|
||||
}
|
||||
if (getCurrentPin()) {
|
||||
const nodeElement = getCurrentPin().getNodeElement()
|
||||
nodeElement.addEventListener(Configuration.nodeUpdateEventName, this.#nodeUpdateHandler)
|
||||
nodeElement.addEventListener(Configuration.pinUpdateEventName, this.#pinUpdateHandler)
|
||||
nodeElement.addEventListener(Configuration.removeEventName, this.#nodeDeleteHandler)
|
||||
nodeElement.addEventListener(
|
||||
Configuration.nodeDragEventName,
|
||||
isDestinationPin ? this.#nodeDragDestinatonHandler : this.#nodeDragSourceHandler
|
||||
isTargetPin ? this.#nodeDragTargetHandler : this.#nodeDragOriginHandler
|
||||
)
|
||||
nodeElement.addEventListener(
|
||||
Configuration.nodeReflowEventName,
|
||||
isDestinationPin ? this.#nodeReflowDestinatonHandler : this.#nodeReflowSourceHandler
|
||||
)
|
||||
isDestinationPin
|
||||
? this.setDestinationLocation()
|
||||
: (this.setSourceLocation(), this.originatesFromInput = this.source.isInput())
|
||||
getCurrentPin().addEventListener(Configuration.pinUpdateEventName, this.#pinUpdateHandler)
|
||||
isTargetPin
|
||||
? this.setTargetLocation()
|
||||
: (this.setOriginLocation(), this.originatesFromInput = this.origin.isInputVisually())
|
||||
this.#linkPins()
|
||||
}
|
||||
this.color = this.getOutputPin(true)?.color
|
||||
}
|
||||
|
||||
#linkPins() {
|
||||
if (this.source && this.destination) {
|
||||
this.source.linkTo(this.destination)
|
||||
this.destination.linkTo(this.source)
|
||||
if (this.origin && this.target) {
|
||||
this.origin.linkTo(this.target)
|
||||
this.target.linkTo(this.origin)
|
||||
}
|
||||
}
|
||||
|
||||
#unlinkPins() {
|
||||
if (this.source && this.destination) {
|
||||
this.source.unlinkFrom(this.destination, false)
|
||||
this.destination.unlinkFrom(this.source, false)
|
||||
if (this.origin && this.target) {
|
||||
this.origin.unlinkFrom(this.target, false)
|
||||
this.target.unlinkFrom(this.origin, false)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
super.cleanup()
|
||||
this.#unlinkPins()
|
||||
this.source = null
|
||||
this.destination = null
|
||||
this.origin = null
|
||||
this.target = null
|
||||
}
|
||||
|
||||
/** @param {Coordinates} location */
|
||||
setSourceLocation(location = null, canPostpone = true) {
|
||||
setOriginLocation(location = null, canPostpone = true) {
|
||||
if (location == null) {
|
||||
const self = this
|
||||
if (canPostpone && (!this.hasUpdated || !this.source.hasUpdated)) {
|
||||
Promise.all([this.updateComplete, this.source.updateComplete])
|
||||
.then(() => self.setSourceLocation(null, false))
|
||||
if (canPostpone && (!this.hasUpdated || !this.origin.hasUpdated)) {
|
||||
Promise.all([this.updateComplete, this.origin.updateComplete])
|
||||
.then(() => self.setOriginLocation(null, false))
|
||||
return
|
||||
}
|
||||
location = this.source.template.getLinkLocation()
|
||||
location = this.origin.template.getLinkLocation()
|
||||
}
|
||||
const [x, y] = location
|
||||
this.fromX = x
|
||||
this.fromY = y
|
||||
this.originX = x
|
||||
this.originY = y
|
||||
}
|
||||
|
||||
/** @param {Coordinates} location */
|
||||
setDestinationLocation(location = null, canPostpone = true) {
|
||||
setTargetLocation(location = null, canPostpone = true) {
|
||||
if (location == null) {
|
||||
const self = this
|
||||
if (canPostpone && (!this.hasUpdated || !this.destination.hasUpdated)) {
|
||||
Promise.all([this.updateComplete, this.destination.updateComplete])
|
||||
.then(() => self.setDestinationLocation(null, false))
|
||||
if (canPostpone && (!this.hasUpdated || !this.target.hasUpdated)) {
|
||||
Promise.all([this.updateComplete, this.target.updateComplete])
|
||||
.then(() => self.setTargetLocation(null, false))
|
||||
return
|
||||
}
|
||||
location = this.destination.template.getLinkLocation()
|
||||
location = this.target.template.getLinkLocation()
|
||||
}
|
||||
this.toX = location[0]
|
||||
this.toY = location[1]
|
||||
this.targetX = location[0]
|
||||
this.targetY = location[1]
|
||||
}
|
||||
|
||||
getInputPin(getSomething = false) {
|
||||
if (this.source?.isInput()) {
|
||||
return this.source
|
||||
if (this.origin?.isInput()) {
|
||||
return this.origin
|
||||
}
|
||||
if (this.destination?.isInput()) {
|
||||
return this.destination
|
||||
if (this.target?.isInput()) {
|
||||
return this.target
|
||||
}
|
||||
if (getSomething) {
|
||||
return this.source ?? this.destination
|
||||
return this.origin ?? this.target
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {PinElement} pin */
|
||||
setInputPin(pin) {
|
||||
if (this.source?.isInput()) {
|
||||
this.source = pin
|
||||
if (this.origin?.isInput()) {
|
||||
this.origin = pin
|
||||
}
|
||||
this.destination = pin
|
||||
this.target = pin
|
||||
}
|
||||
|
||||
getOutputPin(getSomething = false) {
|
||||
if (this.source?.isOutput()) {
|
||||
return this.source
|
||||
if (this.origin?.isOutput()) {
|
||||
return this.origin
|
||||
}
|
||||
if (this.destination?.isOutput()) {
|
||||
return this.destination
|
||||
if (this.target?.isOutput()) {
|
||||
return this.target
|
||||
}
|
||||
if (getSomething) {
|
||||
return this.source ?? this.destination
|
||||
return this.origin ?? this.target
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {PinElement} pin */
|
||||
setOutputPin(pin) {
|
||||
if (this.destination?.isOutput()) {
|
||||
this.destination = pin
|
||||
if (this.target?.isOutput()) {
|
||||
this.target = pin
|
||||
}
|
||||
this.origin = pin
|
||||
}
|
||||
|
||||
/** @param {NodeElement} node */
|
||||
getOtherPin(node) {
|
||||
if (this.origin?.nodeElement === node) {
|
||||
return this.target
|
||||
}
|
||||
if (this.target?.nodeElement === node) {
|
||||
return this.origin
|
||||
}
|
||||
this.source = pin
|
||||
}
|
||||
|
||||
startDragging() {
|
||||
@@ -261,7 +334,7 @@ export default class LinkElement extends IFromToPositionedElement {
|
||||
|
||||
setMessageConvertType() {
|
||||
this.linkMessageIcon = SVGIcon.convert
|
||||
this.linkMessageText = html`Convert ${this.source.pinType} to ${this.destination.pinType}.`
|
||||
this.linkMessageText = html`Convert ${this.origin.pinType} to ${this.target.pinType}.`
|
||||
}
|
||||
|
||||
setMessageCorrect() {
|
||||
|
||||
37
js/element/NodeElement.js
Normal file → Executable file
37
js/element/NodeElement.js
Normal file → Executable file
@@ -101,13 +101,13 @@ export default class NodeElement extends ISelectableDraggableElement {
|
||||
|
||||
/** @param {String} name */
|
||||
#redirectLinksBeforeRename(name) {
|
||||
for (let sourcePinElement of this.getPinElements()) {
|
||||
for (let targetPinReference of sourcePinElement.getLinks()) {
|
||||
for (let originPinElement of this.getPinElements()) {
|
||||
for (let targetPinReference of originPinElement.getLinks()) {
|
||||
this.blueprint.getPin(targetPinReference).redirectLink(
|
||||
sourcePinElement,
|
||||
originPinElement,
|
||||
new PinReferenceEntity(
|
||||
new SymbolEntity(name),
|
||||
sourcePinElement.entity.PinId,
|
||||
originPinElement.entity.PinId,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -135,21 +135,14 @@ export default class NodeElement extends ISelectableDraggableElement {
|
||||
"Name",
|
||||
/** @param {InstanceType<typeof ObjectEntity.attributes.Name>} newName */
|
||||
newName => {
|
||||
this.#redirectLinksBeforeRename(newName.value)
|
||||
this.nodeTitle = newName.value
|
||||
this.#redirectLinksBeforeRename(newName?.toString())
|
||||
this.nodeTitle = newName?.toString()
|
||||
this.nodeDisplayName = nodeTitle(entity)
|
||||
this.acknowledgeUpdate()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async getUpdateComplete() {
|
||||
let result = await super.getUpdateComplete()
|
||||
for (const pin of this.getPinElements()) {
|
||||
result &&= await pin.updateComplete
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** @param {NodeElement} commentNode */
|
||||
bindToComment(commentNode) {
|
||||
if (commentNode != this && !this.boundComments.includes(commentNode)) {
|
||||
@@ -192,14 +185,14 @@ export default class NodeElement extends ISelectableDraggableElement {
|
||||
setNodeWidth(value) {
|
||||
this.entity.setNodeWidth(value)
|
||||
this.sizeX = value
|
||||
this.acknowledgeReflow()
|
||||
this.acknowledgeUpdate(true)
|
||||
}
|
||||
|
||||
/** @param {Number} value */
|
||||
setNodeHeight(value) {
|
||||
this.entity.setNodeHeight(value)
|
||||
this.sizeY = value
|
||||
this.acknowledgeReflow()
|
||||
this.acknowledgeUpdate(true)
|
||||
}
|
||||
|
||||
/** @param {IElement[]} nodesWhitelist */
|
||||
@@ -222,11 +215,13 @@ export default class NodeElement extends ISelectableDraggableElement {
|
||||
super.setLocation(x, y, acknowledge)
|
||||
}
|
||||
|
||||
acknowledgeReflow() {
|
||||
this.requestUpdate()
|
||||
this.updateComplete.then(() => this.computeSizes())
|
||||
let reflowEvent = new CustomEvent(Configuration.nodeReflowEventName)
|
||||
this.dispatchEvent(reflowEvent)
|
||||
acknowledgeUpdate(resize = false) {
|
||||
const event = new CustomEvent(Configuration.nodeUpdateEventName)
|
||||
if (resize) {
|
||||
this.requestUpdate()
|
||||
this.updateComplete.then(() => this.computeSizes())
|
||||
}
|
||||
this.dispatchEvent(event)
|
||||
}
|
||||
|
||||
setShowAdvancedPinDisplay(value) {
|
||||
|
||||
106
js/element/PinElement.js
Normal file → Executable file
106
js/element/PinElement.js
Normal file → Executable file
@@ -1,5 +1,5 @@
|
||||
import Configuration from "../Configuration.js"
|
||||
import pinTemplate from "../decoding/pinTemplate.js"
|
||||
import ArrayEntity from "../entity/ArrayEntity.js"
|
||||
import BooleanEntity from "../entity/BooleanEntity.js"
|
||||
import GuidEntity from "../entity/GuidEntity.js"
|
||||
import LinearColorEntity from "../entity/LinearColorEntity.js"
|
||||
@@ -44,7 +44,8 @@ export default class PinElement extends IElement {
|
||||
fromAttribute: (value, type) => value
|
||||
? LinearColorEntity.getLinearColorFromAnyFormat().parse(value)
|
||||
: null,
|
||||
toAttribute: (value, type) => value ? LinearColorEntity.printLinearColor(value) : null,
|
||||
/** @param {LinearColorEntity} value */
|
||||
toAttribute: (value, type) => value?.toString() ?? "",
|
||||
},
|
||||
attribute: "data-color",
|
||||
reflect: true,
|
||||
@@ -96,10 +97,11 @@ export default class PinElement extends IElement {
|
||||
this.connectable = !entity.bNotConnectable?.valueOf()
|
||||
super.initialize(entity, template)
|
||||
this.pinId = this.entity.PinId
|
||||
this.pinType = this.entity.getType()
|
||||
this.updateType()
|
||||
this.defaultValue = this.entity.getDefaultValue()
|
||||
this.color = PinElement.properties.color.converter.fromAttribute(this.getColor().toString())
|
||||
this.pinDirection = entity.isInput() ? "input" : entity.isOutput() ? "output" : "hidden"
|
||||
/** @type {LinearColorEntity} */
|
||||
this.color = PinElement.properties.color.converter.fromAttribute(this.entity.pinColor().toString())
|
||||
}
|
||||
|
||||
setup() {
|
||||
@@ -107,6 +109,15 @@ export default class PinElement extends IElement {
|
||||
this.nodeElement = this.closest("ueb-node")
|
||||
}
|
||||
|
||||
updateType() {
|
||||
this.pinType = this.entity.getType()
|
||||
const newColor = PinElement.properties.color.converter.fromAttribute(this.entity.pinColor().toString())
|
||||
if (!this.color?.equals(newColor)) {
|
||||
this.color = newColor
|
||||
this.acknowledgeUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
createPinReference() {
|
||||
return new PinReferenceEntity(new SymbolEntity(this.nodeElement.getNodeName()), this.getPinId())
|
||||
}
|
||||
@@ -123,21 +134,68 @@ export default class PinElement extends IElement {
|
||||
return this.entity.pinTitle()
|
||||
}
|
||||
|
||||
/** @return {CSSResult} */
|
||||
getColor() {
|
||||
return this.entity.pinColor()
|
||||
/** @param {PinElement} pin */
|
||||
#traverseKnots(pin) {
|
||||
while (pin?.isKnot()) {
|
||||
const pins = pin.nodeElement.getPinElements()
|
||||
pin = pin === pins[0] ? pins[1] : pins[0]
|
||||
pin = pin.isLinked ? this.blueprint.getPin(pin.getLinks()[0]) : null
|
||||
}
|
||||
return pin?.isKnot() ? undefined : pin
|
||||
}
|
||||
|
||||
isInput() {
|
||||
return this.entity.isInput()
|
||||
isInput(ignoreKnots = false) {
|
||||
/** @type {PinElement} */
|
||||
let result = this
|
||||
if (ignoreKnots) {
|
||||
return this.#traverseKnots(result)?.isInput()
|
||||
}
|
||||
return result.entity.isInput()
|
||||
}
|
||||
|
||||
isOutput() {
|
||||
return this.entity.isOutput()
|
||||
/** @returns {boolean} True when the pin is the input part of a knot that can switch direction */
|
||||
isInputLoosely() {
|
||||
return this.isInput(false) && this.isInput(true) === undefined
|
||||
}
|
||||
|
||||
getLinkLocation() {
|
||||
return this.template.getLinkLocation()
|
||||
/** @returns {boolean} True when the pin is input and if it is a knot it appears input */
|
||||
isInputVisually() {
|
||||
const template = /** @type {KnotNodeTemplate} */(this.nodeElement.template)
|
||||
const isKnot = this.isKnot()
|
||||
return isKnot && this.isInput() != template.switchDirectionsVisually
|
||||
|| !isKnot && this.isInput()
|
||||
}
|
||||
|
||||
isOutput(ignoreKnots = false) {
|
||||
/** @type {PinElement} */
|
||||
let result = this
|
||||
if (ignoreKnots) {
|
||||
return this.#traverseKnots(result)?.isOutput()
|
||||
}
|
||||
return result.entity.isOutput()
|
||||
}
|
||||
|
||||
/** @returns {boolean} True when the pin is the output part of a knot that can switch direction */
|
||||
isOutputLoosely() {
|
||||
return this.isOutput(false) && this.isOutput(true) === undefined
|
||||
}
|
||||
|
||||
/** @returns {boolean} True when the pin is output and if it is a knot it appears output */
|
||||
isOutputVisually() {
|
||||
const template = /** @type {KnotNodeTemplate} */(this.nodeElement.template)
|
||||
const isKnot = this.isKnot()
|
||||
return isKnot && this.isOutput() != template.switchDirectionsVisually
|
||||
|| !isKnot && this.isOutput()
|
||||
}
|
||||
|
||||
|
||||
/** @returns {value is InstanceType<PinElement<>>} */
|
||||
isKnot() {
|
||||
return this.nodeElement?.getType() == Configuration.paths.knot
|
||||
}
|
||||
|
||||
getLinkLocation(oppositeDirection = false) {
|
||||
return this.template.getLinkLocation(oppositeDirection)
|
||||
}
|
||||
|
||||
getNodeElement() {
|
||||
@@ -188,14 +246,17 @@ export default class PinElement extends IElement {
|
||||
const pinReference = this.createPinReference()
|
||||
if (
|
||||
this.isLinked
|
||||
&& this.isOutput()
|
||||
&& (this.pinType === "exec" || targetPinElement.pinType === "exec")
|
||||
&& !this.getLinks().some(ref => pinReference.equals(ref))) {
|
||||
&& this.entity.isExecution()
|
||||
&& this.isOutput(true)
|
||||
&& this.getLinks().some(ref => !pinReference.equals(ref))
|
||||
) {
|
||||
if (this.isKnot()) {
|
||||
|
||||
}
|
||||
this.unlinkFromAll()
|
||||
}
|
||||
if (this.entity.linkTo(targetPinElement.getNodeElement().getNodeName(), targetPinElement.entity)) {
|
||||
this.isLinked = this.entity.isLinked()
|
||||
this.nodeElement?.template.linksChanged()
|
||||
if (this.entity.recomputesNodeTitleOnChange) {
|
||||
this.nodeElement?.computeNodeDisplayName()
|
||||
}
|
||||
@@ -206,7 +267,6 @@ export default class PinElement extends IElement {
|
||||
unlinkFrom(targetPinElement, removeLink = true) {
|
||||
if (this.entity.unlinkFrom(targetPinElement.getNodeElement().getNodeName(), targetPinElement.entity)) {
|
||||
this.isLinked = this.entity.isLinked()
|
||||
this.nodeElement?.template.linksChanged()
|
||||
if (removeLink) {
|
||||
this.blueprint.getLink(this, targetPinElement)?.remove() // Might be called after the link is removed
|
||||
}
|
||||
@@ -217,11 +277,8 @@ export default class PinElement extends IElement {
|
||||
}
|
||||
|
||||
unlinkFromAll() {
|
||||
const isLinked = this.getLinks().length
|
||||
this.getLinks().map(ref => this.blueprint.getPin(ref)).forEach(pin => this.unlinkFrom(pin))
|
||||
if (isLinked) {
|
||||
this.nodeElement?.template.linksChanged()
|
||||
}
|
||||
const isLinked = false
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,4 +296,9 @@ export default class PinElement extends IElement {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
acknowledgeUpdate() {
|
||||
let event = new CustomEvent(Configuration.pinUpdateEventName)
|
||||
this.dispatchEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
12
js/element/SelectorElement.js
Normal file → Executable file
12
js/element/SelectorElement.js
Normal file → Executable file
@@ -37,16 +37,16 @@ export default class SelectorElement extends IFromToPositionedElement {
|
||||
/** @param {Coordinates} finalPosition */
|
||||
selectTo(finalPosition) {
|
||||
this.selectionModel.selectTo(finalPosition)
|
||||
this.toX = finalPosition[0]
|
||||
this.toY = finalPosition[1]
|
||||
this.targetX = finalPosition[0]
|
||||
this.targetY = finalPosition[1]
|
||||
}
|
||||
|
||||
endSelect() {
|
||||
this.blueprint.selecting = false
|
||||
this.selectionModel = null
|
||||
this.fromX = 0
|
||||
this.fromY = 0
|
||||
this.toX = 0
|
||||
this.toY = 0
|
||||
this.originX = 0
|
||||
this.originY = 0
|
||||
this.targetX = 0
|
||||
this.targetY = 0
|
||||
}
|
||||
}
|
||||
|
||||
0
js/element/WindowElement.js
Normal file → Executable file
0
js/element/WindowElement.js
Normal file → Executable file
0
js/element/defineElements.js
Normal file → Executable file
0
js/element/defineElements.js
Normal file → Executable file
0
js/entity/AlternativesEntity.js
Normal file → Executable file
0
js/entity/AlternativesEntity.js
Normal file → Executable file
3
js/entity/ArrayEntity.js
Normal file → Executable file
3
js/entity/ArrayEntity.js
Normal file → Executable file
@@ -33,8 +33,7 @@ export default class ArrayEntity extends IEntity {
|
||||
if ((trailing !== undefined) !== Self.trailing) {
|
||||
Self = Self.flagTrailing(trailing !== undefined)
|
||||
}
|
||||
const result = new Self(values)
|
||||
return result
|
||||
return new Self(values)
|
||||
}).label(`ArrayEntity of ${this.type?.className() ?? "unknown values"}`)
|
||||
}
|
||||
|
||||
|
||||
133
js/entity/BlueprintEntity.js
Normal file → Executable file
133
js/entity/BlueprintEntity.js
Normal file → Executable file
@@ -1,6 +1,7 @@
|
||||
import Configuration from "../Configuration.js"
|
||||
import Utility from "../Utility.js"
|
||||
import ObjectEntity from "./ObjectEntity.js"
|
||||
import ScriptVariableEntity from "./ScriptVariableEntity.js"
|
||||
|
||||
export default class BlueprintEntity extends ObjectEntity {
|
||||
|
||||
@@ -13,6 +14,18 @@ export default class BlueprintEntity extends ObjectEntity {
|
||||
return this.#objectEntities
|
||||
}
|
||||
|
||||
static attributes = {
|
||||
...super.attributes,
|
||||
ScriptVariables: super.attributes.ScriptVariables.asUniqueClass(true).withDefault(),
|
||||
}
|
||||
|
||||
constructor(...args) {
|
||||
super(...args)
|
||||
if (!this.Name) {
|
||||
this.Name = new (/** @type {typeof ObjectEntity} */(this.constructor).attributes.Name)("Blueprint")
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {ObjectEntity} entity */
|
||||
getHomonymObjectEntity(entity) {
|
||||
const name = entity.getObjectName()
|
||||
@@ -27,6 +40,16 @@ export default class BlueprintEntity extends ObjectEntity {
|
||||
return Configuration.nodeTitle(name, counter)
|
||||
}
|
||||
|
||||
/** @param {String} name */
|
||||
updateNameIndex(name) {
|
||||
const match = name.match(/(.+)_(\d+)$/)
|
||||
if (match) {
|
||||
name = match[1]
|
||||
const index = Number(match[2])
|
||||
this.#objectEntitiesNameCounter.set(name, Math.max(index, this.#objectEntitiesNameCounter.get(name) ?? 0))
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {ObjectEntity} entity */
|
||||
addObjectEntity(entity) {
|
||||
if (!this.#objectEntities.includes(entity)) {
|
||||
@@ -54,35 +77,121 @@ export default class BlueprintEntity extends ObjectEntity {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ObjectReferenceEntity} variable
|
||||
* @param {IEntity} entity
|
||||
*/
|
||||
renameScriptVariable(variable, entity) {
|
||||
const name = variable.getName()
|
||||
const newName = this.takeFreeName(name)
|
||||
{
|
||||
[true, false].forEach(v => {
|
||||
/** @type {ObjectEntity} */
|
||||
let object = this[Configuration.subObjectAttributeNameFromReference(variable, v)]
|
||||
object.Name.value = newName
|
||||
object.Name = object.Name
|
||||
})
|
||||
}
|
||||
variable.path.replace(name, newName)
|
||||
return newName
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ScriptVariableEntity} scriptVariableEntity
|
||||
* @returns {String}
|
||||
*/
|
||||
variableName(scriptVariableEntity) {
|
||||
return this[Configuration.subObjectAttributeNameFromReference(scriptVariableEntity.ScriptVariable, true)]
|
||||
?.["Variable"]
|
||||
?.["Name"]
|
||||
?.toString()
|
||||
}
|
||||
|
||||
/** @param {String} variableName */
|
||||
variableIndex(variableName) {
|
||||
let i = 0
|
||||
for (const v of this.ScriptVariables?.valueOf()) {
|
||||
if (variableName == this.variableName(v)) {
|
||||
return i
|
||||
}
|
||||
++i
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {ObjectEntity} entity */
|
||||
mergeWith(entity) {
|
||||
if (!entity.ScriptVariables || entity.ScriptVariables.length === 0) {
|
||||
if ((entity.ScriptVariables?.length ?? 0) === 0) {
|
||||
// The entity does not add new variables
|
||||
return this
|
||||
}
|
||||
if (!this.ScriptVariables || this.ScriptVariables.length === 0) {
|
||||
this.ScriptVariables = entity.ScriptVariables
|
||||
}
|
||||
const variableObjectNames = this.ScriptVariables.valueOf().map(v => v.ScriptVariable.getName())
|
||||
let scriptVariables = Utility.mergeArrays(
|
||||
this.ScriptVariables.valueOf(),
|
||||
entity.ScriptVariables.valueOf(),
|
||||
(l, r) => l.OriginalChangeId.value == r.OriginalChangeId.value
|
||||
(l, r) => this.variableName(l) == this.variableName(r),
|
||||
added => {
|
||||
let name = added.ScriptVariable.getName()
|
||||
if (variableObjectNames.includes(name)) {
|
||||
name = this.renameScriptVariable(added.ScriptVariable, entity)
|
||||
}
|
||||
this.updateNameIndex(name)
|
||||
}
|
||||
)
|
||||
if (scriptVariables.length === this.ScriptVariables.length) {
|
||||
// The entity does not add new variables
|
||||
return this
|
||||
}
|
||||
scriptVariables.reverse()
|
||||
const blueprintEntity = /** @type {typeof BlueprintEntity} */(this.constructor)
|
||||
const entries = scriptVariables.concat(scriptVariables).map((v, i) => {
|
||||
const name = Configuration.subObjectAttributeNameFromReference(v.ScriptVariable, i >= scriptVariables.length)
|
||||
return [
|
||||
name,
|
||||
this[name] ?? entity[name]
|
||||
]
|
||||
const name = Configuration.subObjectAttributeNameFromReference(
|
||||
v.ScriptVariable,
|
||||
i >= scriptVariables.length // First take all the small objects then all name only
|
||||
)
|
||||
const object = this[name] ?? entity[name]
|
||||
return object ? [name, object] : null
|
||||
})
|
||||
.filter(v => v)
|
||||
entries.push(
|
||||
...Object.entries(this).filter(([k, v]) =>
|
||||
!k.startsWith(Configuration.subObjectAttributeNamePrefix)
|
||||
&& k !== "ExportedNodes"
|
||||
)
|
||||
),
|
||||
["ScriptVariables", new (blueprintEntity.attributes.ScriptVariables)(scriptVariables.reverse())]
|
||||
)
|
||||
return new BlueprintEntity(Object.fromEntries(entries))
|
||||
const result = new BlueprintEntity(Object.fromEntries(entries))
|
||||
result.mirrorNameInExportPaths(entity.Name?.toString())
|
||||
result.#objectEntitiesNameCounter = this.#objectEntitiesNameCounter
|
||||
result.#objectEntities = this.#objectEntities
|
||||
return result
|
||||
}
|
||||
|
||||
/** @param {ObjectEntity[]} entities */
|
||||
getVariablesAttributesReferringTo(...entities) {
|
||||
let pins = new Set(...entities.flatMap(entity => entity.getPinEntities()).map(pin => pin.PinName.toString()))
|
||||
let attributes = this.ScriptVariables
|
||||
.valueOf()
|
||||
.map(v => {
|
||||
const keySimple = Configuration.subObjectAttributeNameFromReference(v.ScriptVariable, false)
|
||||
const keyFull = Configuration.subObjectAttributeNameFromReference(v.ScriptVariable, true)
|
||||
return {
|
||||
simple: [keySimple, this[keySimple]],
|
||||
full: [keyFull, this[keyFull]],
|
||||
variable: v,
|
||||
}
|
||||
})
|
||||
.filter(v => pins.has(v.full?.["Variable"]?.["Name"]))
|
||||
.reduce(
|
||||
(acc, cur) => {
|
||||
acc.simple.push([cur.simple[0], cur.simple[1]])
|
||||
acc.full.push([cur.full[0], cur.full[1]])
|
||||
acc.ScriptVariables.push(cur.variable)
|
||||
return acc
|
||||
},
|
||||
({ simple: [], full: [], ScriptVariables: [] })
|
||||
)
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
js/entity/ColorChannelEntity.js
Normal file → Executable file
0
js/entity/ColorChannelEntity.js
Normal file → Executable file
0
js/entity/ComputedTypeEntity.js
Normal file → Executable file
0
js/entity/ComputedTypeEntity.js
Normal file → Executable file
0
js/entity/FormatTextEntity.js
Normal file → Executable file
0
js/entity/FormatTextEntity.js
Normal file → Executable file
10
js/entity/IEntity.js
Normal file → Executable file
10
js/entity/IEntity.js
Normal file → Executable file
@@ -44,6 +44,7 @@ export default class IEntity {
|
||||
this.#keys = [... new Set(value)]
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
#lookbehind = /** @type {String} */(this.constructor.lookbehind)
|
||||
get lookbehind() {
|
||||
return this.#lookbehind.trim()
|
||||
@@ -145,9 +146,9 @@ export default class IEntity {
|
||||
* @this {T}
|
||||
* @returns {T}
|
||||
*/
|
||||
static asUniqueClass() {
|
||||
static asUniqueClass(alwaysCreate = false) {
|
||||
let result = this
|
||||
if (this.name.length) {
|
||||
if (this.name.length || alwaysCreate) {
|
||||
// @ts-expect-error
|
||||
result = (() => class extends this { })() // Comes from a lambda otherwise the class will have name "result"
|
||||
result.grammar = result.createGrammar() // Reassign grammar to capture the correct this from subclass
|
||||
@@ -429,4 +430,9 @@ export default class IEntity {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** @returns {IEntity | Boolean | Number | String | BigInt | (IEntity | Boolean | Number | String | BigInt)[]} */
|
||||
valueOf() {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
10
js/entity/InvariantTextEntity.js
Normal file → Executable file
10
js/entity/InvariantTextEntity.js
Normal file → Executable file
@@ -19,18 +19,22 @@ export default class InvariantTextEntity extends IEntity {
|
||||
P.reg(new RegExp(`${this.lookbehind}\\s*\\(`)),
|
||||
P.doubleQuotedString,
|
||||
P.reg(/\s*\)/)
|
||||
).map(([_0, value, _2]) => Number(value)),
|
||||
P.reg(new RegExp(this.lookbehind)).map(() => 0) // InvariantTextEntity can not have arguments
|
||||
).map(([_0, value, _2]) => value),
|
||||
P.reg(new RegExp(this.lookbehind)).map(() => "") // InvariantTextEntity can have no arguments
|
||||
)
|
||||
.map(value => new this(value))
|
||||
.label("InvariantTextEntity")
|
||||
}
|
||||
|
||||
doSerialize() {
|
||||
return this.lookbehind + "(" + this.value + ")"
|
||||
return this.lookbehind + '("' + this.value + '")'
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
return this.value
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.value
|
||||
}
|
||||
}
|
||||
|
||||
0
js/entity/KeyBindingEntity.js
Normal file → Executable file
0
js/entity/KeyBindingEntity.js
Normal file → Executable file
0
js/entity/LinearColorEntity.js
Normal file → Executable file
0
js/entity/LinearColorEntity.js
Normal file → Executable file
10
js/entity/MirroredEntity.js
Normal file → Executable file
10
js/entity/MirroredEntity.js
Normal file → Executable file
@@ -66,13 +66,13 @@ export default class MirroredEntity extends IEntity {
|
||||
return this.getter?.().equals(other)
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
this.valueOf = this.getter().valueOf.bind(this.getter())
|
||||
return this.valueOf()
|
||||
/** @returns {InstanceType<T>} */
|
||||
valueOf(arg) {
|
||||
// @ts-expect-error
|
||||
return this.getter(arg).valueOf()
|
||||
}
|
||||
|
||||
toString() {
|
||||
this.toString = this.getter().toString.bind(this.getter())
|
||||
return this.toString()
|
||||
return this.getter().toString()
|
||||
}
|
||||
}
|
||||
|
||||
0
js/entity/NullEntity.js
Normal file → Executable file
0
js/entity/NullEntity.js
Normal file → Executable file
@@ -47,7 +47,7 @@ export default class ObjectEntity extends IEntity {
|
||||
Class: ObjectReferenceEntity,
|
||||
Name: StringEntity,
|
||||
Archetype: ObjectReferenceEntity,
|
||||
ExportPath: ObjectReferenceEntity,
|
||||
ExportPath: MirroredEntity.of(ObjectReferenceEntity),
|
||||
ObjectRef: ObjectReferenceEntity,
|
||||
BlueprintElementType: ObjectReferenceEntity,
|
||||
BlueprintElementInstance: ObjectReferenceEntity,
|
||||
@@ -63,6 +63,7 @@ export default class ObjectEntity extends IEntity {
|
||||
bIsPureFunc: BooleanEntity,
|
||||
bIsConstFunc: BooleanEntity,
|
||||
bIsCaseSensitive: BooleanEntity,
|
||||
bDefaultsToPureFunc: BooleanEntity,
|
||||
VariableReference: VariableReferenceEntity,
|
||||
SelfContextInfo: SymbolEntity,
|
||||
DelegatePropertyName: StringEntity,
|
||||
@@ -107,14 +108,19 @@ export default class ObjectEntity extends IEntity {
|
||||
SizeX: MirroredEntity.of(IntegerEntity),
|
||||
SizeY: MirroredEntity.of(IntegerEntity),
|
||||
Text: MirroredEntity.of(StringEntity),
|
||||
ParameterName: StringEntity,
|
||||
ExpressionGUID: GuidEntity,
|
||||
MaterialExpressionEditorX: MirroredEntity.of(IntegerEntity),
|
||||
MaterialExpressionEditorY: MirroredEntity.of(IntegerEntity),
|
||||
MaterialExpressionGuid: GuidEntity,
|
||||
NodeTitle: StringEntity,
|
||||
NodeTitleColor: LinearColorEntity,
|
||||
PositionX: MirroredEntity.of(IntegerEntity),
|
||||
PositionY: MirroredEntity.of(IntegerEntity),
|
||||
SettingsInterface: ObjectReferenceEntity,
|
||||
PCGNode: ObjectReferenceEntity,
|
||||
SoundNode: ObjectReferenceEntity,
|
||||
SoundWaveAssetPtr: ObjectReferenceEntity,
|
||||
HiGenGridSize: SymbolEntity,
|
||||
Operation: SymbolEntity,
|
||||
NodePosX: IntegerEntity,
|
||||
@@ -136,10 +142,13 @@ export default class ObjectEntity extends IEntity {
|
||||
NodeGuid: GuidEntity,
|
||||
ErrorType: IntegerEntity,
|
||||
ErrorMsg: StringEntity,
|
||||
ScriptVariables: ArrayEntity.of(ScriptVariableEntity),
|
||||
ScriptVariables: ArrayEntity.flagInlined().of(ScriptVariableEntity),
|
||||
Node: MirroredEntity.of(ObjectReferenceEntity),
|
||||
ExportedNodes: StringEntity,
|
||||
CustomProperties: ArrayEntity.of(AlternativesEntity.accepting(PinEntity, UnknownPinEntity)).withDefault().flagSilent(),
|
||||
CustomProperties: ArrayEntity
|
||||
.of(AlternativesEntity.accepting(PinEntity, UnknownPinEntity))
|
||||
.withDefault()
|
||||
.flagSilent(),
|
||||
}
|
||||
static customPropertyGrammar = P.seq(
|
||||
P.reg(/CustomProperties\s+/),
|
||||
@@ -154,29 +163,28 @@ export default class ObjectEntity extends IEntity {
|
||||
Grammar.symbolQuoted.map(v => [v, true]),
|
||||
Grammar.symbol.map(v => [v, false]),
|
||||
),
|
||||
P.reg(new RegExp(String.raw`\s*\(\s*(\d+)\s*\)\s*\=\s*`), 1).map(Number)
|
||||
)
|
||||
.chain(
|
||||
/** @param {[[keyof ObjectEntity.attributes, Boolean], Number]} param */
|
||||
([[symbol, quoted], index]) =>
|
||||
(this.attributes[symbol]?.grammar ?? IEntity.unknownEntityGrammar).map(currentValue =>
|
||||
values => {
|
||||
if (values[symbol] === undefined) {
|
||||
let arrayEntity = ArrayEntity
|
||||
if (quoted != arrayEntity.quoted) {
|
||||
arrayEntity = arrayEntity.flagQuoted(quoted)
|
||||
}
|
||||
if (!arrayEntity.inlined) {
|
||||
arrayEntity = arrayEntity.flagInlined()
|
||||
}
|
||||
values[symbol] = new arrayEntity()
|
||||
P.reg(new RegExp(String.raw`\s*\(\s*(\d+)\s*\)\s*\=\s*`), 1).map(Number) // Number in parentheses then equal
|
||||
).chain(
|
||||
/** @param {[[keyof ObjectEntity.attributes, Boolean], Number]} param */
|
||||
([[symbol, quoted], index]) =>
|
||||
(this.attributes[symbol]?.grammar ?? IEntity.unknownEntityGrammar).map(currentValue =>
|
||||
values => {
|
||||
if (values[symbol] === undefined) {
|
||||
let arrayEntity = ArrayEntity
|
||||
if (quoted != arrayEntity.quoted) {
|
||||
arrayEntity = arrayEntity.flagQuoted(quoted)
|
||||
}
|
||||
/** @type {ArrayEntity} */
|
||||
const target = values[symbol]
|
||||
target.values[index] = currentValue
|
||||
if (!arrayEntity.inlined) {
|
||||
arrayEntity = arrayEntity.flagInlined()
|
||||
}
|
||||
values[symbol] = new arrayEntity()
|
||||
}
|
||||
)
|
||||
)
|
||||
/** @type {ArrayEntity} */
|
||||
const target = values[symbol]
|
||||
target.values[index] = currentValue
|
||||
}
|
||||
)
|
||||
)
|
||||
static grammar = this.createGrammar()
|
||||
static grammarMultipleObjects = P.seq(
|
||||
P.whitespaceOpt,
|
||||
@@ -202,20 +210,21 @@ export default class ObjectEntity extends IEntity {
|
||||
super(values)
|
||||
|
||||
// Attributes
|
||||
/** @type {ArrayEntity<typeof PinEntity | typeof UnknownPinEntity>} */ this.CustomProperties
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.AddedPins>} */ this.AddedPins
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.AdvancedPinDisplay>} */ this.AdvancedPinDisplay
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.Archetype>} */ this.Archetype
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.AxisKey>} */ this.AxisKey
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.bIsPureFunc>} */ this.bIsPureFunc
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.bDefaultsToPureFunc>} */ this.bDefaultsToPureFunc
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.BlueprintElementInstance>} */ this.BlueprintElementInstance
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.ConstA>} */ this.ConstA
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.ConstB>} */ this.ConstB
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.BlueprintElementType>} */ this.BlueprintElementType
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.Class>} */ this.Class
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.CommentColor>} */ this.CommentColor
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.ComponentPropertyName>} */ this.ComponentPropertyName
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.ConstA>} */ this.ConstA
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.ConstB>} */ this.ConstB
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.CustomFunctionName>} */ this.CustomFunctionName
|
||||
/** @type {ArrayEntity<typeof PinEntity | typeof UnknownPinEntity>} */ this.CustomProperties
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.DelegatePropertyName>} */ this.DelegatePropertyName
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.DelegateReference>} */ this.DelegateReference
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.EnabledState>} */ this.EnabledState
|
||||
@@ -254,9 +263,12 @@ export default class ObjectEntity extends IEntity {
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.Operation>} */ this.Operation
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.OpName>} */ this.OpName
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.OutputPins>} */ this.OutputPins
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.ParameterName>} */ this.ParameterName
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.PCGNode>} */ this.PCGNode
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.PinTags>} */ this.PinTags
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.SoundNode>} */ this.SoundNode
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.SoundWaveAssetPtr>} */ this.SoundWaveAssetPtr
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.PinNames>} */ this.PinNames
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.PinTags>} */ this.PinTags
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.PositionX>} */ this.PositionX
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.PositionY>} */ this.PositionY
|
||||
/** @type {InstanceType<typeof ObjectEntity.attributes.ProxyFactoryFunctionName>} */ this.ProxyFactoryFunctionName
|
||||
@@ -364,6 +376,7 @@ export default class ObjectEntity extends IEntity {
|
||||
? outputIndex++
|
||||
: i
|
||||
})
|
||||
this.mirrorNameInExportPaths()
|
||||
}
|
||||
|
||||
/** @returns {P<ObjectEntity>} */
|
||||
@@ -409,12 +422,46 @@ export default class ObjectEntity extends IEntity {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* Mirror then name part of the objects contained in this one in ExportPath
|
||||
*/
|
||||
mirrorNameInExportPaths(originalName = this.Name?.toString()) {
|
||||
if (!originalName) {
|
||||
return
|
||||
}
|
||||
const values = [this]
|
||||
for (let i = 0; i < values.length; ++i) {
|
||||
const value = values[i]
|
||||
if (value instanceof ObjectEntity) {
|
||||
values.push(...Object.values(value))
|
||||
if (!value.ExportPath?.valueOf().path.includes(originalName)) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
const mirroredEntity = /** @type {typeof ObjectEntity} */(value.constructor).attributes.ExportPath
|
||||
let originalExportPath = value.ExportPath
|
||||
value.ExportPath = new mirroredEntity(
|
||||
() => {
|
||||
const exportPath = originalExportPath.valueOf()
|
||||
return new (mirroredEntity.type)(
|
||||
exportPath.type,
|
||||
exportPath.path.replace(originalName, this.Name?.toString() ?? ""),
|
||||
exportPath.full
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {String} */
|
||||
#class
|
||||
getClass() {
|
||||
if (!this.#class) {
|
||||
this.#class = (this.Class?.path ? this.Class.path : this.Class?.type)
|
||||
?? this.ExportPath?.type
|
||||
?? this.ExportPath?.valueOf()?.type
|
||||
?? ""
|
||||
if (this.#class && !this.#class.startsWith("/")) {
|
||||
// Old path names did not start with /Script or /Engine, check tests/resources/LegacyNodes.js
|
||||
@@ -428,14 +475,18 @@ export default class ObjectEntity extends IEntity {
|
||||
}
|
||||
|
||||
getType() {
|
||||
let classValue = this.getClass()
|
||||
if (this.MacroGraphReference?.MacroGraph?.path) {
|
||||
return this.MacroGraphReference.MacroGraph.path
|
||||
const path = this.MacroGraphReference?.MacroGraph?.path
|
||||
if (path) {
|
||||
return path
|
||||
}
|
||||
if (this.MaterialExpression) {
|
||||
return this.MaterialExpression.type
|
||||
}
|
||||
return classValue
|
||||
let subobject = this.getSounCueSubobject()
|
||||
if (subobject) {
|
||||
return subobject.getClass()
|
||||
}
|
||||
return this.getClass()
|
||||
}
|
||||
|
||||
getObjectName(dropCounter = false) {
|
||||
@@ -555,24 +606,10 @@ export default class ObjectEntity extends IEntity {
|
||||
}
|
||||
|
||||
isMaterial() {
|
||||
|
||||
return this.getClass() === Configuration.paths.materialGraphNode
|
||||
// return [
|
||||
// Configuration.paths.materialExpressionConstant,
|
||||
// Configuration.paths.materialExpressionConstant2Vector,
|
||||
// Configuration.paths.materialExpressionConstant3Vector,
|
||||
// Configuration.paths.materialExpressionConstant4Vector,
|
||||
// Configuration.paths.materialExpressionLogarithm,
|
||||
// Configuration.paths.materialExpressionLogarithm10,
|
||||
// Configuration.paths.materialExpressionLogarithm2,
|
||||
// Configuration.paths.materialExpressionMaterialFunctionCall,
|
||||
// Configuration.paths.materialExpressionSquareRoot,
|
||||
// Configuration.paths.materialExpressionTextureCoordinate,
|
||||
// Configuration.paths.materialExpressionTextureSample,
|
||||
// Configuration.paths.materialGraphNode,
|
||||
// Configuration.paths.materialGraphNodeComment,
|
||||
// ]
|
||||
// .includes(this.getClass())
|
||||
const classValue = this.getClass()
|
||||
return classValue.startsWith("/Script/Engine.MaterialExpression")
|
||||
|| classValue.startsWith("/Script/InterchangeImport.MaterialExpression")
|
||||
|| classValue.startsWith("/Script/UnrealEd.MaterialGraph")
|
||||
}
|
||||
|
||||
/** @return {ObjectEntity} */
|
||||
@@ -584,14 +621,33 @@ export default class ObjectEntity extends IEntity {
|
||||
}
|
||||
|
||||
isPcg() {
|
||||
return this.getClass() === Configuration.paths.pcgEditorGraphNode
|
||||
|| this.getPcgSubobject() != null
|
||||
return this.getClass() == Configuration.paths.pcgEditorGraphNode || this.getPcgSubobject() != null
|
||||
}
|
||||
|
||||
isNiagara() {
|
||||
return this.Class && (this.Class.type ? this.Class.type : this.Class.path)?.startsWith("/Script/NiagaraEditor.")
|
||||
}
|
||||
|
||||
isSoundCue() {
|
||||
return this.getClass() == Configuration.paths.soundCueGraphNode
|
||||
}
|
||||
|
||||
getBlueprintType() {
|
||||
if (this.isMaterial()) {
|
||||
return "MATERIAL"
|
||||
}
|
||||
if (this.isNiagara()) {
|
||||
return "NIAGARA"
|
||||
}
|
||||
if (this.isPcg()) {
|
||||
return "PCG Graph"
|
||||
}
|
||||
if (this.isSoundCue()) {
|
||||
return "SOUND CUE"
|
||||
}
|
||||
return "BLUEPRINT"
|
||||
}
|
||||
|
||||
/** @return {ObjectEntity} */
|
||||
getPcgSubobject() {
|
||||
const node = this.PCGNode
|
||||
@@ -600,6 +656,14 @@ export default class ObjectEntity extends IEntity {
|
||||
: null
|
||||
}
|
||||
|
||||
/** @return {ObjectEntity} */
|
||||
getSounCueSubobject() {
|
||||
const node = this.SoundNode
|
||||
return node
|
||||
? this[Configuration.subObjectAttributeNameFromReference(node, true)]
|
||||
: null
|
||||
}
|
||||
|
||||
/** @return {ObjectEntity} */
|
||||
getSettingsObject() {
|
||||
const settings = this.SettingsInterface
|
||||
@@ -666,9 +730,10 @@ export default class ObjectEntity extends IEntity {
|
||||
attributeSeparator = Self.attributeSeparator,
|
||||
wrap = Self.wrap,
|
||||
) {
|
||||
const isSelfOverriden = Self !== this.constructor
|
||||
const deeperIndentation = indentation + Configuration.indentation
|
||||
const initial_trailing = this.trailing
|
||||
this.trailing = false
|
||||
this.trailing = true
|
||||
const content = super.doSerialize(insideString, deeperIndentation, Self, printKey, keySeparator, attributeSeparator, wrap)
|
||||
this.trailing = initial_trailing
|
||||
let result = indentation + "Begin Object"
|
||||
@@ -690,27 +755,27 @@ export default class ObjectEntity extends IEntity {
|
||||
? ` Archetype${keySeparator}${this.Archetype.serialize(insideString)}`
|
||||
: ""
|
||||
)
|
||||
+ ((this.ExportPath?.type || this.ExportPath?.path)
|
||||
// && Self.attributes.ExportPath.ignored !== true
|
||||
// && this.ExportPath.ignored !== true
|
||||
+ ((this.ExportPath?.valueOf()?.type || this.ExportPath?.valueOf()?.path)
|
||||
// && Self.attributes.ExportPath.valueOf().ignored !== true
|
||||
// && this.ExportPath.valueOf().ignored !== true
|
||||
? ` ExportPath${keySeparator}${this.ExportPath.serialize(insideString)}`
|
||||
: ""
|
||||
)
|
||||
+ (content ? attributeSeparator + content : "")
|
||||
+ attributeSeparator
|
||||
+ content
|
||||
+ (Self.attributes.CustomProperties.ignored !== true && this.CustomProperties.ignored !== true
|
||||
? this.getCustomproperties()
|
||||
.map(pin =>
|
||||
attributeSeparator
|
||||
+ deeperIndentation
|
||||
deeperIndentation
|
||||
+ printKey("CustomProperties ")
|
||||
+ pin.serialize(insideString)
|
||||
+ attributeSeparator
|
||||
)
|
||||
.join("")
|
||||
: ""
|
||||
)
|
||||
+ attributeSeparator
|
||||
+ indentation + "End Object"
|
||||
+ (this.trailing ? attributeSeparator : "")
|
||||
+ (isSelfOverriden && Self.trailing || this.trailing ? attributeSeparator : "")
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,6 @@ import IEntity from "./IEntity.js"
|
||||
|
||||
export default class ObjectReferenceEntity extends IEntity {
|
||||
|
||||
/** @protected */
|
||||
static _quotedParser = P.regArray(new RegExp(
|
||||
`'"(${Grammar.Regex.InsideString.source})"'`
|
||||
+ "|"
|
||||
+ `'(${Grammar.Regex.InsideSingleQuotedString.source})'`
|
||||
)).map(([_0, a, b]) => a ?? b)
|
||||
static typeReference = P.reg(
|
||||
// @ts-expect-error
|
||||
new RegExp(Grammar.Regex.Path.source + "|" + Grammar.symbol.getParser().regexp.source)
|
||||
@@ -31,29 +25,31 @@ export default class ObjectReferenceEntity extends IEntity {
|
||||
return this.#path
|
||||
}
|
||||
set path(value) {
|
||||
this.#name = ""
|
||||
this.#path = value
|
||||
}
|
||||
|
||||
#fullEscaped
|
||||
/** @type {String} */
|
||||
#full
|
||||
#serializer
|
||||
get full() {
|
||||
return this.#full
|
||||
return this.#serializer
|
||||
}
|
||||
set full(value) {
|
||||
this.#full = value
|
||||
this.#serializer = value
|
||||
}
|
||||
|
||||
#name = ""
|
||||
|
||||
constructor(type = "None", path = "", full = null) {
|
||||
/** @param {(t: String, p: String) => String} serializer */
|
||||
constructor(
|
||||
type = "None",
|
||||
path = "",
|
||||
serializer = type.includes("/") || path
|
||||
? (t, p) => `"${t + (p ? (`'${p}'`) : "")}"`
|
||||
: (t, p) => t) {
|
||||
super()
|
||||
this.#type = type
|
||||
this.#path = path
|
||||
this.#full = full ?? (
|
||||
this.type.includes("/") || this.path
|
||||
? `"${this.type + (this.path ? (`'${this.path}'`) : "")}"`
|
||||
: this.type
|
||||
)
|
||||
this.#serializer = serializer
|
||||
}
|
||||
|
||||
/** @returns {P<ObjectReferenceEntity>} */
|
||||
@@ -71,10 +67,21 @@ export default class ObjectReferenceEntity extends IEntity {
|
||||
new RegExp(
|
||||
// @ts-expect-error
|
||||
"(" + this.typeReference.getParser().regexp.source + ")"
|
||||
// @ts-expect-error
|
||||
+ "(?:" + this._quotedParser.getParser().parser.regexp.source + ")"
|
||||
+ "(?:"
|
||||
+ `'"(${Grammar.Regex.InsideString.source})"'`
|
||||
+ "|"
|
||||
+ `'(${Grammar.Regex.InsideSingleQuotedString.source})'`
|
||||
+ ")"
|
||||
)
|
||||
).map(([full, type, ...path]) => new this(type, path.find(v => v), full))
|
||||
).map(([full, type, fullQuotedPath, simpleQuotedPath]) => {
|
||||
let fullQuoted = fullQuotedPath ? true : false
|
||||
let quotes = fullQuoted ? [`'"`, `"'`] : ["'", "'"]
|
||||
return new this(
|
||||
type,
|
||||
fullQuoted ? fullQuotedPath : simpleQuotedPath,
|
||||
(t, p) => t + quotes[0] + p + quotes[1]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/** @returns {P<ObjectReferenceEntity>} */
|
||||
@@ -84,30 +91,34 @@ export default class ObjectReferenceEntity extends IEntity {
|
||||
'"(' + Grammar.Regex.InsideString.source + "?)"
|
||||
+ "(?:'(" + Grammar.Regex.InsideSingleQuotedString.source + `?)')?"`
|
||||
)
|
||||
).map(([full, type, path]) => new this(type, path, full))
|
||||
).map(([_0, type, path]) => new this(type, path, (t, p) => `"${t}${p ? `'${p}'` : ""}"`))
|
||||
}
|
||||
|
||||
/** @returns {P<ObjectReferenceEntity>} */
|
||||
static createTypeReferenceGrammar() {
|
||||
return this.typeReference.map(v => new this(v, "", v))
|
||||
return this.typeReference.map(v => new this(v, "", (t, p) => t))
|
||||
}
|
||||
|
||||
static createNoneInstance() {
|
||||
return new ObjectReferenceEntity("None", "", "None")
|
||||
return new this("None")
|
||||
}
|
||||
|
||||
getName(dropCounter = false) {
|
||||
return Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
|
||||
if (!this.#name) {
|
||||
if (!dropCounter) {
|
||||
return this.#name = Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
|
||||
}
|
||||
return Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
|
||||
}
|
||||
return this.#name
|
||||
}
|
||||
|
||||
doSerialize(insideString = false) {
|
||||
let result = this.full(this.type, this.path)
|
||||
if (insideString) {
|
||||
if (this.#fullEscaped === undefined) {
|
||||
this.#fullEscaped = Utility.escapeString(this.#full, false)
|
||||
}
|
||||
return this.#fullEscaped
|
||||
result = Utility.escapeString(result, false)
|
||||
}
|
||||
return this.full
|
||||
return result
|
||||
}
|
||||
|
||||
/** @param {IEntity} other */
|
||||
@@ -117,4 +128,8 @@ export default class ObjectReferenceEntity extends IEntity {
|
||||
}
|
||||
return this.type == other.type && this.path == other.path
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.full(this.type, this.path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import Vector2DEntity from "./Vector2DEntity.js"
|
||||
import Vector4DEntity from "./Vector4DEntity.js"
|
||||
import VectorEntity from "./VectorEntity.js"
|
||||
|
||||
const paths = Configuration.paths
|
||||
|
||||
/** @template {IEntity} T */
|
||||
export default class PinEntity extends IEntity {
|
||||
|
||||
@@ -43,27 +45,30 @@ export default class PinEntity extends IEntity {
|
||||
"byte": ByteEntity,
|
||||
"enum": EnumEntity,
|
||||
"exec": StringEntity,
|
||||
"float": NumberEntity,
|
||||
"int": IntegerEntity,
|
||||
"int64": Integer64Entity,
|
||||
"name": StringEntity,
|
||||
"real": NumberEntity,
|
||||
"string": StringEntity,
|
||||
[Configuration.paths.linearColor]: LinearColorEntity,
|
||||
[Configuration.paths.niagaraPosition]: VectorEntity,
|
||||
[Configuration.paths.rotator]: RotatorEntity,
|
||||
[Configuration.paths.vector]: VectorEntity,
|
||||
[Configuration.paths.vector2D]: Vector2DEntity,
|
||||
[Configuration.paths.vector4f]: Vector4DEntity,
|
||||
[paths.linearColor]: LinearColorEntity,
|
||||
[paths.niagaraBool]: BooleanEntity,
|
||||
[paths.niagaraFloat]: NumberEntity,
|
||||
[paths.niagaraPosition]: VectorEntity,
|
||||
[paths.rotator]: RotatorEntity,
|
||||
[paths.vector]: VectorEntity,
|
||||
[paths.vector2D]: Vector2DEntity,
|
||||
[paths.vector4f]: Vector4DEntity,
|
||||
}
|
||||
static #alternativeTypeEntityMap = {
|
||||
"enum": EnumDisplayValueEntity,
|
||||
"rg": RBSerializationVector2DEntity,
|
||||
[Configuration.paths.niagaraPosition]: SimpleSerializationVectorEntity.flagAllowShortSerialization(),
|
||||
[Configuration.paths.rotator]: SimpleSerializationRotatorEntity,
|
||||
[Configuration.paths.vector]: SimpleSerializationVectorEntity,
|
||||
[Configuration.paths.vector2D]: SimpleSerializationVector2DEntity,
|
||||
[Configuration.paths.vector3f]: SimpleSerializationVectorEntity,
|
||||
[Configuration.paths.vector4f]: SimpleSerializationVector4DEntity,
|
||||
[paths.niagaraPosition]: SimpleSerializationVectorEntity.flagAllowShortSerialization(),
|
||||
[paths.rotator]: SimpleSerializationRotatorEntity,
|
||||
[paths.vector]: SimpleSerializationVectorEntity,
|
||||
[paths.vector2D]: SimpleSerializationVector2DEntity,
|
||||
[paths.vector3f]: SimpleSerializationVectorEntity,
|
||||
[paths.vector4f]: SimpleSerializationVector4DEntity,
|
||||
}
|
||||
static attributes = {
|
||||
PinId: GuidEntity.withDefault(),
|
||||
@@ -170,9 +175,10 @@ export default class PinEntity extends IEntity {
|
||||
return new PinEntity(objectEntity)
|
||||
}
|
||||
|
||||
/** @returns {String} */
|
||||
getType() {
|
||||
const category = this.PinType.PinCategory?.toString().toLocaleLowerCase()
|
||||
if (category === "struct" || category === "class" || category === "object" || category === "type") {
|
||||
if (["struct", "class", "object", "type", "statictype"].includes(category)) {
|
||||
return this.PinType.PinSubCategoryObject?.path
|
||||
}
|
||||
if (this.isEnum()) {
|
||||
@@ -180,13 +186,9 @@ export default class PinEntity extends IEntity {
|
||||
}
|
||||
if (this.objectEntity?.isPcg()) {
|
||||
const pcgSuboject = this.objectEntity.getPcgSubobject()
|
||||
const pinObjectReference = this.isInput()
|
||||
? pcgSuboject.InputPins?.valueOf()[this.pinIndex]
|
||||
: pcgSuboject.OutputPins?.valueOf()[this.pinIndex]
|
||||
if (pinObjectReference) {
|
||||
/** @type {ObjectEntity} */
|
||||
const pinObject = pcgSuboject[Configuration.subObjectAttributeNameFromReference(pinObjectReference, true)]
|
||||
let allowedTypes = pinObject.Properties?.AllowedTypes?.toString() ?? ""
|
||||
const pinObject = this.getPinObject(pcgSuboject)
|
||||
if (pinObject) {
|
||||
let allowedTypes = pinObject["Properties"]?.AllowedTypes?.toString() ?? ""
|
||||
if (allowedTypes == "") {
|
||||
allowedTypes = this.PinType.PinCategory ?? ""
|
||||
if (allowedTypes == "") {
|
||||
@@ -195,8 +197,8 @@ export default class PinEntity extends IEntity {
|
||||
}
|
||||
if (allowedTypes) {
|
||||
if (
|
||||
pinObject.Properties.bAllowMultipleData?.valueOf() !== false
|
||||
&& pinObject.Properties.bAllowMultipleConnections?.valueOf() !== false
|
||||
pinObject["Properties"].bAllowMultipleData?.valueOf() !== false
|
||||
&& pinObject["Properties"].bAllowMultipleConnections?.valueOf() !== false
|
||||
) {
|
||||
allowedTypes += "[]"
|
||||
}
|
||||
@@ -212,9 +214,9 @@ export default class PinEntity extends IEntity {
|
||||
case "rg":
|
||||
return "rg"
|
||||
case "rgb":
|
||||
return Configuration.paths.vector
|
||||
return paths.vector
|
||||
case "rgba":
|
||||
return Configuration.paths.linearColor
|
||||
return paths.linearColor
|
||||
default:
|
||||
return subCategory
|
||||
}
|
||||
@@ -250,13 +252,14 @@ export default class PinEntity extends IEntity {
|
||||
|
||||
isEnum() {
|
||||
const type = this.PinType.PinSubCategoryObject?.type
|
||||
return type === Configuration.paths.enum
|
||||
|| type === Configuration.paths.userDefinedEnum
|
||||
return type === paths.enum
|
||||
|| type === paths.userDefinedEnum
|
||||
|| type?.toLowerCase() === "enum"
|
||||
}
|
||||
|
||||
isExecution() {
|
||||
return this.PinType.PinCategory.toString() === "exec"
|
||||
|| this.getType() === paths.niagaraParameterMap
|
||||
}
|
||||
|
||||
isHidden() {
|
||||
@@ -272,7 +275,7 @@ export default class PinEntity extends IEntity {
|
||||
}
|
||||
|
||||
isLinked() {
|
||||
return this.LinkedTo?.length > 0 ?? false
|
||||
return this.LinkedTo?.length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,6 +315,17 @@ export default class PinEntity extends IEntity {
|
||||
return false
|
||||
}
|
||||
|
||||
/** @param {ObjectEntity} pcgSuboject */
|
||||
getPinObject(pcgSuboject) {
|
||||
const pinObjectReference = this.isInput()
|
||||
? pcgSuboject.InputPins?.valueOf()[this.pinIndex]
|
||||
: pcgSuboject.OutputPins?.valueOf()[this.pinIndex]
|
||||
if (pinObjectReference) {
|
||||
/** @type {ObjectEntity} */
|
||||
return pcgSuboject[Configuration.subObjectAttributeNameFromReference(pinObjectReference, true)]
|
||||
}
|
||||
}
|
||||
|
||||
getSubCategory() {
|
||||
return this.PinType.PinSubCategoryObject?.path
|
||||
}
|
||||
|
||||
0
js/entity/RBSerializationVector2DEntity.js
Normal file → Executable file
0
js/entity/RBSerializationVector2DEntity.js
Normal file → Executable file
0
js/entity/RotatorEntity.js
Normal file → Executable file
0
js/entity/RotatorEntity.js
Normal file → Executable file
0
js/entity/ScriptVariableEntity.js
Normal file → Executable file
0
js/entity/ScriptVariableEntity.js
Normal file → Executable file
0
js/entity/SimpleSerializationRotatorEntity.js
Normal file → Executable file
0
js/entity/SimpleSerializationRotatorEntity.js
Normal file → Executable file
0
js/entity/SimpleSerializationVector2DEntity.js
Normal file → Executable file
0
js/entity/SimpleSerializationVector2DEntity.js
Normal file → Executable file
0
js/entity/SimpleSerializationVector4DEntity.js
Normal file → Executable file
0
js/entity/SimpleSerializationVector4DEntity.js
Normal file → Executable file
0
js/entity/SimpleSerializationVectorEntity.js
Normal file → Executable file
0
js/entity/SimpleSerializationVectorEntity.js
Normal file → Executable file
0
js/entity/SymbolEntity.js
Normal file → Executable file
0
js/entity/SymbolEntity.js
Normal file → Executable file
0
js/entity/TerminalTypeEntity.js
Normal file → Executable file
0
js/entity/TerminalTypeEntity.js
Normal file → Executable file
5
js/entity/UnknownKeysEntity.js
Normal file → Executable file
5
js/entity/UnknownKeysEntity.js
Normal file → Executable file
@@ -1,9 +1,14 @@
|
||||
import P from "parsernostrum"
|
||||
import Grammar from "../serialization/Grammar.js"
|
||||
import GuidEntity from "./GuidEntity.js"
|
||||
import IEntity from "./IEntity.js"
|
||||
|
||||
export default class UnknownKeysEntity extends IEntity {
|
||||
|
||||
static attributes = {
|
||||
...super.attributes,
|
||||
VariableGuid: GuidEntity,
|
||||
}
|
||||
static grammar = this.createGrammar()
|
||||
|
||||
static {
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class UnknownPinEntity extends PinEntity {
|
||||
static createGrammar() {
|
||||
return P.seq(
|
||||
// Lookbehind
|
||||
P.reg(new RegExp(`(${Grammar.Regex.Symbol.source}\\s*)\\(\\s*`), 1),
|
||||
P.reg(new RegExp(`(${Grammar.Regex.Symbol.source}\\s*)?\\(\\s*`), 1),
|
||||
Grammar.createAttributeGrammar(this).sepBy(Grammar.commaSeparation),
|
||||
P.reg(/\s*(?:,\s*)?\)/)
|
||||
).map(([lookbehind, attributes, _2]) => {
|
||||
|
||||
0
js/entity/Vector2DEntity.js
Normal file → Executable file
0
js/entity/Vector2DEntity.js
Normal file → Executable file
0
js/entity/Vector4DEntity.js
Normal file → Executable file
0
js/entity/Vector4DEntity.js
Normal file → Executable file
0
js/entity/VectorEntity.js
Normal file → Executable file
0
js/entity/VectorEntity.js
Normal file → Executable file
2
js/entity/objects/KnotEntity.js
Normal file → Executable file
2
js/entity/objects/KnotEntity.js
Normal file → Executable file
@@ -25,7 +25,7 @@ export default class KnotEntity extends ObjectEntity {
|
||||
inputPinEntity.copyTypeFrom(pinReferenceForType)
|
||||
outputPinEntity.copyTypeFrom(pinReferenceForType)
|
||||
}
|
||||
values["CustomProperties"] = new (ObjectEntity.attributes.CustomProperties)([inputPinEntity, outputPinEntity])
|
||||
values.CustomProperties = new (ObjectEntity.attributes.CustomProperties)([inputPinEntity, outputPinEntity])
|
||||
super(values)
|
||||
}
|
||||
}
|
||||
|
||||
45
js/entity/objects/NiagaraClipboardContent.js
Executable file
45
js/entity/objects/NiagaraClipboardContent.js
Executable file
@@ -0,0 +1,45 @@
|
||||
import Configuration from "../../Configuration.js"
|
||||
import ObjectEntity from "../ObjectEntity.js"
|
||||
import ObjectReferenceEntity from "../ObjectReferenceEntity.js"
|
||||
import StringEntity from "../StringEntity.js"
|
||||
|
||||
export default class NiagaraClipboardContent extends ObjectEntity {
|
||||
|
||||
/**
|
||||
* @param {BlueprintEntity} blueprint
|
||||
* @param {ObjectEntity[]} nodes
|
||||
*/
|
||||
constructor(blueprint, nodes) {
|
||||
const typePath = Configuration.paths.niagaraClipboardContent
|
||||
const name = blueprint.takeFreeName("NiagaraClipboardContent")
|
||||
const exportPath = `/Engine/Transient.${name}`
|
||||
/** @type {Set<Number>} */
|
||||
const variableIndexes = new Set()
|
||||
let exported = ""
|
||||
for (const node of nodes) {
|
||||
if (node.exported) {
|
||||
node.getPinEntities()
|
||||
.map(pin => blueprint.variableIndex(pin.PinName.toString()))
|
||||
.filter(v => v != null)
|
||||
.forEach(i => variableIndexes.add(i))
|
||||
exported += node.serialize()
|
||||
}
|
||||
}
|
||||
const scriptVariables = blueprint.ScriptVariables.valueOf().filter((v, i) => variableIndexes.has(i))
|
||||
const variableObjects = scriptVariables.concat(scriptVariables).map((v, i) => {
|
||||
const name = Configuration.subObjectAttributeNameFromReference(
|
||||
v.ScriptVariable,
|
||||
i >= scriptVariables.length // First take all the small objects then all name only
|
||||
)
|
||||
return [name, blueprint[name]]
|
||||
})
|
||||
super({
|
||||
Class: new ObjectReferenceEntity(typePath),
|
||||
Name: new StringEntity(name),
|
||||
...Object.fromEntries(variableObjects),
|
||||
ExportPath: new ObjectReferenceEntity(typePath, exportPath),
|
||||
ScriptVariables: new (NiagaraClipboardContent.attributes.ScriptVariables)(scriptVariables),
|
||||
ExportedNodes: new StringEntity(btoa(exported))
|
||||
})
|
||||
}
|
||||
}
|
||||
0
js/input/IInput.js
Normal file → Executable file
0
js/input/IInput.js
Normal file → Executable file
0
js/input/InputCombination.js
Normal file → Executable file
0
js/input/InputCombination.js
Normal file → Executable file
@@ -1,3 +1,4 @@
|
||||
import NiagaraClipboardContent from "../../entity/objects/NiagaraClipboardContent.js"
|
||||
import IInput from "../IInput.js"
|
||||
|
||||
/**
|
||||
@@ -28,20 +29,8 @@ export default class Copy extends IInput {
|
||||
window.removeEventListener("copy", this.#copyHandler)
|
||||
}
|
||||
|
||||
getSerializedText() {
|
||||
const allNodes = this.blueprint.getNodes(true).map(n => n.entity)
|
||||
const exported = allNodes.filter(n => n.exported).map(n => n.serialize())
|
||||
const result = allNodes.filter(n => !n.exported).map(n => n.serialize())
|
||||
if (exported.length) {
|
||||
this.blueprint.entity.ExportedNodes.value = btoa(exported.join(""))
|
||||
result.splice(0, 0, this.blueprint.entity.serialize(false))
|
||||
delete this.blueprint.entity.ExportedNodes
|
||||
}
|
||||
return result.join("")
|
||||
}
|
||||
|
||||
copied() {
|
||||
const value = this.getSerializedText()
|
||||
const value = this.blueprint.getSerializedText()
|
||||
navigator.clipboard.writeText(value)
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -33,13 +33,6 @@ export default class Cut extends IInput {
|
||||
window.removeEventListener("cut", this.#cutHandler)
|
||||
}
|
||||
|
||||
getSerializedText() {
|
||||
return this.blueprint
|
||||
.getNodes(true)
|
||||
.map(node => node.entity.serialize())
|
||||
.join("")
|
||||
}
|
||||
|
||||
cut() {
|
||||
this.blueprint.template.getCopyInputObject().copied()
|
||||
this.blueprint.removeGraphElement(...this.blueprint.getNodes(true))
|
||||
|
||||
0
js/input/keyboard/KeyboardEnableZoom.js
Normal file → Executable file
0
js/input/keyboard/KeyboardEnableZoom.js
Normal file → Executable file
0
js/input/keyboard/KeyboardShortcut.js
Normal file → Executable file
0
js/input/keyboard/KeyboardShortcut.js
Normal file → Executable file
0
js/input/mouse/IMouseClickDrag.js
Normal file → Executable file
0
js/input/mouse/IMouseClickDrag.js
Normal file → Executable file
0
js/input/mouse/IPointing.js
Normal file → Executable file
0
js/input/mouse/IPointing.js
Normal file → Executable file
0
js/input/mouse/MouseClick.js
Normal file → Executable file
0
js/input/mouse/MouseClick.js
Normal file → Executable file
@@ -14,21 +14,15 @@ export default class MouseCreateLink extends IMouseClickDrag {
|
||||
/** @type {NodeListOf<PinElement>} */
|
||||
#listenedPins
|
||||
|
||||
/** @type {PinElement} */
|
||||
#knotPin = null
|
||||
|
||||
/** @param {MouseEvent} e */
|
||||
#mouseenterHandler = e => {
|
||||
if (!this.enteredPin) {
|
||||
this.linkValid = false
|
||||
this.enteredPin = /** @type {PinElement} */(e.target)
|
||||
const a = this.link.source ?? this.target // Remember target might have change
|
||||
const a = this.link.origin ?? this.target // Remember target might have change
|
||||
const b = this.enteredPin
|
||||
const outputPin = a.isOutput() ? a : b
|
||||
if (
|
||||
a.nodeElement.getType() === Configuration.paths.knot
|
||||
|| b.nodeElement.getType() === Configuration.paths.knot
|
||||
) {
|
||||
if (a.isKnot() || b.isKnot()) {
|
||||
// A knot can be linked to any pin, it doesn't matter the type or input/output direction
|
||||
this.link.setMessageCorrect()
|
||||
this.linkValid = true
|
||||
@@ -83,9 +77,6 @@ export default class MouseCreateLink extends IMouseClickDrag {
|
||||
}
|
||||
|
||||
startDrag(location) {
|
||||
if (this.target.nodeElement.getType() == Configuration.paths.knot) {
|
||||
this.#knotPin = this.target
|
||||
}
|
||||
/** @type {LinkElement} */
|
||||
this.link = /** @type {LinkElementConstructor} */(ElementFactory.getConstructor("ueb-link"))
|
||||
.newObject(this.target, null)
|
||||
@@ -99,11 +90,11 @@ export default class MouseCreateLink extends IMouseClickDrag {
|
||||
}
|
||||
})
|
||||
this.link.startDragging()
|
||||
this.link.setDestinationLocation(location)
|
||||
this.link.setTargetLocation(location)
|
||||
}
|
||||
|
||||
dragTo(location, movement) {
|
||||
this.link.setDestinationLocation(location)
|
||||
this.link.setTargetLocation(location)
|
||||
}
|
||||
|
||||
endDrag() {
|
||||
@@ -113,28 +104,30 @@ export default class MouseCreateLink extends IMouseClickDrag {
|
||||
})
|
||||
this.#listenedPins = null
|
||||
if (this.enteredPin && this.linkValid) {
|
||||
const knot = this.enteredPin.isKnot()
|
||||
? this.enteredPin
|
||||
: this.link.origin.isKnot() ? this.link.origin : null
|
||||
// Knot can use wither the input or output (by default) part indifferently, check if a switch is needed
|
||||
if (this.#knotPin) {
|
||||
const otherPin = this.#knotPin !== this.link.source ? this.link.source : this.enteredPin
|
||||
if (knot) {
|
||||
const otherPin = knot !== this.link.origin ? this.link.origin : this.enteredPin
|
||||
// Knot pin direction correction
|
||||
if (this.#knotPin.isInput() && otherPin.isInput() || this.#knotPin.isOutput() && otherPin.isOutput()) {
|
||||
const oppositePin = /** @type {KnotPinTemplate} */(this.#knotPin.template).getOppositePin()
|
||||
if (this.#knotPin === this.link.source) {
|
||||
this.link.source = oppositePin
|
||||
if (knot.isInput() && otherPin.isInput() || knot.isOutput() && otherPin.isOutput()) {
|
||||
const oppositePin = /** @type {KnotPinTemplate} */(knot.template).getoppositePin()
|
||||
if (knot === this.link.origin) {
|
||||
this.link.origin = oppositePin
|
||||
} else {
|
||||
this.enteredPin = oppositePin
|
||||
}
|
||||
}
|
||||
} else if (this.enteredPin.nodeElement.getType() === Configuration.paths.knot) {
|
||||
this.#knotPin = this.enteredPin
|
||||
if (this.link.source.isOutput()) {
|
||||
// Knot uses by default the output pin, let's switch to keep it coherent with the source node we have
|
||||
this.enteredPin = /** @type {KnotPinTemplate} */(this.enteredPin.template).getOppositePin()
|
||||
} else if (this.enteredPin.isKnot()) {
|
||||
if (this.link.origin.isOutput()) {
|
||||
// Knot uses by default the output pin, let's switch to keep it coherent with the origin node we have
|
||||
this.enteredPin = /** @type {KnotPinTemplate} */(this.enteredPin.template).getoppositePin()
|
||||
}
|
||||
}
|
||||
if (!this.link.source.getLinks().find(ref => ref.equals(this.enteredPin.createPinReference()))) {
|
||||
if (!this.link.origin.getLinks().find(ref => ref.equals(this.enteredPin.createPinReference()))) {
|
||||
this.blueprint.addGraphElement(this.link)
|
||||
this.link.destination = this.enteredPin
|
||||
this.link.target = this.enteredPin
|
||||
} else {
|
||||
this.link.remove()
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user