Merge remote-tracking branch 'origin/v2' into v2-ui
This commit is contained in:
commit
be4d8d458e
|
@ -72,8 +72,7 @@ const SideBarRight: React.FC = () => {
|
|||
<div className="sidebar-actions-container">
|
||||
{/* {activeModule === "builder" && ( */}
|
||||
<div
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "properties" ? "active" : ""
|
||||
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setSubModule("properties")}
|
||||
>
|
||||
|
@ -83,24 +82,21 @@ const SideBarRight: React.FC = () => {
|
|||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<div
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "mechanics" ? "active" : ""
|
||||
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setSubModule("mechanics")}
|
||||
>
|
||||
<MechanicsIcon isActive={subModule === "mechanics"} />
|
||||
</div>
|
||||
<div
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "simulations" ? "active" : ""
|
||||
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setSubModule("simulations")}
|
||||
>
|
||||
<SimulationIcon isActive={subModule === "simulations"} />
|
||||
</div>
|
||||
<div
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "analysis" ? "active" : ""
|
||||
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setSubModule("analysis")}
|
||||
>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useRef, useState } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import {
|
||||
AddIcon,
|
||||
ArrowIcon,
|
||||
|
@ -7,65 +7,81 @@ import {
|
|||
} from "../../../icons/ExportCommonIcons";
|
||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||
import { handleResize } from "../../../../functions/handleResizePannel";
|
||||
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||
import { generateUUID } from "three/src/math/MathUtils";
|
||||
|
||||
interface Path {
|
||||
pathName: string; // Represents the name of the path
|
||||
Children: string[]; // Represents the list of child points
|
||||
interface Event {
|
||||
pathName: string;
|
||||
}
|
||||
|
||||
interface DropListProps {
|
||||
val: Path; // Use the Path interface for the val prop
|
||||
interface ListProps {
|
||||
val: Event;
|
||||
}
|
||||
|
||||
const DropList: React.FC<DropListProps> = ({ val }) => {
|
||||
const [openDrop, setOpenDrop] = useState(false);
|
||||
const List: React.FC<ListProps> = ({ val }) => {
|
||||
return (
|
||||
<div className="process-container">
|
||||
<div
|
||||
className="value"
|
||||
onClick={() => {
|
||||
setOpenDrop(!openDrop);
|
||||
}}
|
||||
>
|
||||
<div className="value">
|
||||
{val.pathName}
|
||||
<div className={`arrow-container${openDrop ? " active" : ""}`}>
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</div>
|
||||
{val.Children && openDrop && (
|
||||
<div className="children-drop">
|
||||
{val.Children.map((child, index) => (
|
||||
<div key={index} className="value">
|
||||
{child}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Simulations: React.FC = () => {
|
||||
const productsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [productsList, setProductsList] = useState<string[]>([]);
|
||||
const [selectedItem, setSelectedItem] = useState<string>();
|
||||
const { products, addProduct, removeProduct, renameProduct } = useProductStore();
|
||||
const { selectedProduct, setSelectedProduct } = useSelectedProduct();
|
||||
|
||||
const handleAddAction = () => {
|
||||
setProductsList([...productsList, `Product ${productsList.length + 1}`]);
|
||||
useEffect(() => {
|
||||
if (products.length > 0 && selectedProduct.productId === '' && selectedProduct.productName === '') {
|
||||
setSelectedProduct(products[0].productId, products[0].productName);
|
||||
}
|
||||
}, [products, selectedProduct]);
|
||||
|
||||
const handleAddProduct = () => {
|
||||
addProduct(`Product ${products.length + 1}`, generateUUID());
|
||||
};
|
||||
|
||||
const handleRemoveAction = (index: number) => {
|
||||
setProductsList(productsList.filter((_, i) => i !== index));
|
||||
if (selectedItem === productsList[index]) {
|
||||
setSelectedItem("");
|
||||
const handleRemoveProduct = (productId: string) => {
|
||||
const currentIndex = products.findIndex(p => p.productId === productId);
|
||||
const isSelected = selectedProduct.productId === productId;
|
||||
|
||||
const updatedProducts = products.filter(p => p.productId !== productId);
|
||||
|
||||
if (isSelected) {
|
||||
if (updatedProducts.length > 0) {
|
||||
let newSelectedIndex = currentIndex;
|
||||
if (currentIndex >= updatedProducts.length) {
|
||||
newSelectedIndex = updatedProducts.length - 1;
|
||||
}
|
||||
setSelectedProduct(
|
||||
updatedProducts[newSelectedIndex].productId,
|
||||
updatedProducts[newSelectedIndex].productName
|
||||
);
|
||||
} else {
|
||||
setSelectedProduct('', '');
|
||||
}
|
||||
}
|
||||
|
||||
removeProduct(productId);
|
||||
};
|
||||
|
||||
const handleRenameProduct = (productId: string, newName: string) => {
|
||||
renameProduct(productId, newName);
|
||||
if (selectedProduct.productId === productId) {
|
||||
setSelectedProduct(productId, newName);
|
||||
}
|
||||
};
|
||||
|
||||
const Value = [
|
||||
{ pathName: "Path 1", Children: ["Point 1", "Point 2"] },
|
||||
{ pathName: "Path 2", Children: ["Point 1", "Point 2"] },
|
||||
{ pathName: "Path 3", Children: ["Point 1", "Point 2"] },
|
||||
];
|
||||
const selectedProductData = products.find(
|
||||
(product) => product.productId === selectedProduct.productId
|
||||
);
|
||||
|
||||
const events: Event[] = selectedProductData?.eventsData.map((event, index) => ({
|
||||
pathName: `${event.modelName} - ${event.type} #${index + 1}`,
|
||||
})) || [];
|
||||
|
||||
return (
|
||||
<div className="simulations-container">
|
||||
|
@ -74,7 +90,7 @@ const Simulations: React.FC = () => {
|
|||
<div className="actions">
|
||||
<div className="header">
|
||||
<div className="header-value">Products</div>
|
||||
<div className="add-button" onClick={handleAddAction}>
|
||||
<div className="add-button" onClick={handleAddProduct}>
|
||||
<AddIcon /> Add
|
||||
</div>
|
||||
</div>
|
||||
|
@ -84,26 +100,34 @@ const Simulations: React.FC = () => {
|
|||
style={{ height: "120px" }}
|
||||
>
|
||||
<div className="list-container">
|
||||
{productsList.map((action, index) => (
|
||||
{products.map((product, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`list-item ${
|
||||
selectedItem === action ? "active" : ""
|
||||
}`}
|
||||
key={product.productId}
|
||||
className={`list-item ${selectedProduct.productId === product.productId ? "active" : ""}`}
|
||||
>
|
||||
<div
|
||||
className="value"
|
||||
onClick={() => setSelectedItem(action)}
|
||||
onClick={() => setSelectedProduct(product.productId, product.productName)}
|
||||
>
|
||||
<input type="radio" name="products" id="products" />
|
||||
<RenameInput value={action} />
|
||||
<input
|
||||
type="radio"
|
||||
name="products"
|
||||
checked={selectedProduct.productId === product.productId}
|
||||
readOnly
|
||||
/>
|
||||
<RenameInput
|
||||
value={product.productName}
|
||||
onRename={(newName) => handleRenameProduct(product.productId, newName)}
|
||||
/>
|
||||
</div>
|
||||
{products.length > 1 && (
|
||||
<div
|
||||
className="remove-button"
|
||||
onClick={() => handleRemoveAction(index)}
|
||||
onClick={() => handleRemoveProduct(product.productId)}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -116,24 +140,25 @@ const Simulations: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="simulation-process">
|
||||
<div className="collapse-header-container">
|
||||
<div className="header">Operations</div>
|
||||
<div className="header">Events</div>
|
||||
<div className="arrow-container">
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</div>
|
||||
{Value.map((val, index) => (
|
||||
<DropList key={index} val={val} />
|
||||
{events.map((event, index) => (
|
||||
<List key={index} val={event} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="compare-simulations-container">
|
||||
<div className="compare-simulations-header">
|
||||
Need to Compare Layout?
|
||||
</div>
|
||||
<div className="content">
|
||||
Click <span>'Compare'</span> to review and analyze the layout
|
||||
differences between them.
|
||||
Click <span>'Compare'</span> to review and analyze the layout differences between them.
|
||||
</div>
|
||||
<div className="input">
|
||||
<input type="button" value={"Compare"} className="submit" />
|
||||
|
|
|
@ -8,10 +8,12 @@ import * as Types from "../../../types/world/worldTypes";
|
|||
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
||||
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
||||
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||
import PointsCalculator from '../../simulation/events/points/functions/pointsCalculator';
|
||||
|
||||
async function loadInitialFloorItems(
|
||||
itemsGroup: Types.RefGroup,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
addEvent: (event: EventsSchema) => void,
|
||||
): Promise<void> {
|
||||
if (!itemsGroup.current) return;
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
|
@ -70,7 +72,7 @@ async function loadInitialFloorItems(
|
|||
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||
if (cachedModel) {
|
||||
// console.log(`[Cache] Fetching ${item.modelname}`);
|
||||
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems);
|
||||
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, addEvent);
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||
return;
|
||||
|
@ -85,7 +87,7 @@ async function loadInitialFloorItems(
|
|||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(item.modelfileID!, gltf);
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, addEvent);
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||
},
|
||||
|
@ -106,7 +108,7 @@ async function loadInitialFloorItems(
|
|||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||
await storeGLTF(item.modelfileID!, modelBlob);
|
||||
THREE.Cache.add(item.modelfileID!, gltf);
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, addEvent);
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||
},
|
||||
|
@ -148,8 +150,9 @@ function processLoadedModel(
|
|||
item: Types.FloorItemType,
|
||||
itemsGroup: Types.RefGroup,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
addEvent: (event: EventsSchema) => void,
|
||||
) {
|
||||
const model = gltf;
|
||||
const model = gltf.clone();
|
||||
model.uuid = item.modeluuid;
|
||||
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||
model.userData = { name: item.modelname, modelId: item.modelfileID, modeluuid: item.modeluuid };
|
||||
|
@ -182,6 +185,243 @@ function processLoadedModel(
|
|||
},
|
||||
]);
|
||||
|
||||
if (item.modelfileID === "a1ee92554935007b10b3eb05") {
|
||||
const data = PointsCalculator(
|
||||
'Vehicle',
|
||||
gltf.clone(),
|
||||
new THREE.Vector3(...model.rotation)
|
||||
);
|
||||
|
||||
if (!data || !data.points) return;
|
||||
|
||||
const vehicleEvent: VehicleEventSchema = {
|
||||
modelUuid: item.modeluuid,
|
||||
modelName: item.modelname,
|
||||
position: item.position,
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "vehicle",
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||
rotation: [0, 0, 0],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Vehicle Action",
|
||||
actionType: "travel",
|
||||
material: null,
|
||||
unLoadDuration: 5,
|
||||
loadCapacity: 10,
|
||||
pickUpPoint: null,
|
||||
unLoadPoint: null,
|
||||
triggers: []
|
||||
}
|
||||
}
|
||||
};
|
||||
addEvent(vehicleEvent);
|
||||
} else if (item.modelfileID === "7dc04e36882e4debbc1a8e3d") {
|
||||
const data = PointsCalculator(
|
||||
'Conveyor',
|
||||
gltf.clone(),
|
||||
new THREE.Vector3(...model.rotation)
|
||||
);
|
||||
|
||||
if (!data || !data.points) return;
|
||||
|
||||
const ConveyorEvent: ConveyorEventSchema = {
|
||||
modelUuid: item.modeluuid,
|
||||
modelName: item.modelname,
|
||||
position: item.position,
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "transfer",
|
||||
speed: 1,
|
||||
points: data.points.map((point: THREE.Vector3, index: number) => ({
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [point.x, point.y, point.z],
|
||||
rotation: [0, 0, 0],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: `Action ${index}`,
|
||||
actionType: 'default',
|
||||
material: 'inherit',
|
||||
delay: 0,
|
||||
spawnInterval: 5,
|
||||
spawnCount: 1,
|
||||
triggers: []
|
||||
}
|
||||
}))
|
||||
};
|
||||
addEvent(ConveyorEvent);
|
||||
} else if (item.modelfileID === "7dc04e36882e4debbc1a8e3d") {
|
||||
// const data = PointsCalculator(
|
||||
// 'Conveyor',
|
||||
// gltf.clone(),
|
||||
// new THREE.Vector3(...model.rotation)
|
||||
// );
|
||||
|
||||
// if (!data || !data.points) return;
|
||||
|
||||
// const points: ConveyorPointSchema[] = data.points.map((point: THREE.Vector3, index: number) => {
|
||||
// const actionUuid = THREE.MathUtils.generateUUID();
|
||||
// return {
|
||||
// uuid: THREE.MathUtils.generateUUID(),
|
||||
// position: [point.x, point.y, point.z],
|
||||
// rotation: [0, 0, 0],
|
||||
// action: {
|
||||
// actionUuid,
|
||||
// actionName: `Action ${index}`,
|
||||
// actionType: 'default',
|
||||
// material: 'inherit',
|
||||
// delay: 0,
|
||||
// spawnInterval: 5,
|
||||
// spawnCount: 1,
|
||||
// triggers: []
|
||||
// }
|
||||
// };
|
||||
// });
|
||||
|
||||
// points.forEach((point, index) => {
|
||||
// if (index < points.length - 1) {
|
||||
// const nextPoint = points[index + 1];
|
||||
// point.action.triggers.push({
|
||||
// triggerUuid: THREE.MathUtils.generateUUID(),
|
||||
// triggerName: `Trigger 1`,
|
||||
// triggerType: "onComplete",
|
||||
// delay: 0,
|
||||
// triggeredAsset: {
|
||||
// triggeredModel: {
|
||||
// modelName: item.modelname,
|
||||
// modelUuid: item.modeluuid
|
||||
// },
|
||||
// triggeredPoint: {
|
||||
// pointName: `Point ${index + 1}`,
|
||||
// pointUuid: nextPoint.uuid
|
||||
// },
|
||||
// triggeredAction: {
|
||||
// actionName: nextPoint.action.actionName,
|
||||
// actionUuid: nextPoint.action.actionUuid
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// const ConveyorEvent: ConveyorEventSchema = {
|
||||
// modelUuid: item.modeluuid,
|
||||
// modelName: item.modelname,
|
||||
// position: item.position,
|
||||
// rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
// state: "idle",
|
||||
// type: "transfer",
|
||||
// speed: 1,
|
||||
// points
|
||||
// };
|
||||
// addEvent(ConveyorEvent);
|
||||
} else if (item.modelfileID === "7dc04e36882e4debbc1a8e3d") {
|
||||
const data = PointsCalculator(
|
||||
'Conveyor',
|
||||
gltf.clone(),
|
||||
new THREE.Vector3(...model.rotation)
|
||||
);
|
||||
|
||||
if (!data || !data.points) return;
|
||||
|
||||
const ConveyorEvent: ConveyorEventSchema = {
|
||||
modelUuid: item.modeluuid,
|
||||
modelName: item.modelname,
|
||||
position: item.position,
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "transfer",
|
||||
speed: 1,
|
||||
points: data.points.map((point: THREE.Vector3, index: number) => ({
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [point.x, point.y, point.z],
|
||||
rotation: [0, 0, 0],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: `Action ${index}`,
|
||||
actionType: 'default',
|
||||
material: 'inherit',
|
||||
delay: 0,
|
||||
spawnInterval: 5,
|
||||
spawnCount: 1,
|
||||
triggers: []
|
||||
}
|
||||
}))
|
||||
};
|
||||
addEvent(ConveyorEvent);
|
||||
} else if (item.modelfileID === "29dee78715ad5b853f5c346d") {
|
||||
const data = PointsCalculator(
|
||||
'StaticMachine',
|
||||
gltf.clone(),
|
||||
new THREE.Vector3(...model.rotation)
|
||||
);
|
||||
|
||||
if (!data || !data.points) return;
|
||||
|
||||
const machineEvent: MachineEventSchema = {
|
||||
modelUuid: item.modeluuid,
|
||||
modelName: item.modelname,
|
||||
position: item.position,
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "machine",
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||
rotation: [0, 0, 0],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Process Action",
|
||||
actionType: "process",
|
||||
processTime: 10,
|
||||
swapMaterial: "material-id",
|
||||
triggers: []
|
||||
}
|
||||
}
|
||||
};
|
||||
addEvent(machineEvent);
|
||||
} else if (item.modelfileID === "52e6681fbb743a890d96c914") {
|
||||
const data = PointsCalculator(
|
||||
'ArmBot',
|
||||
gltf.clone(),
|
||||
new THREE.Vector3(...model.rotation)
|
||||
);
|
||||
|
||||
if (!data || !data.points) return;
|
||||
|
||||
const roboticArmEvent: RoboticArmEventSchema = {
|
||||
modelUuid: item.modeluuid,
|
||||
modelName: item.modelname,
|
||||
position: item.position,
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "roboticArm",
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||
rotation: [0, 0, 0],
|
||||
actions: [
|
||||
{
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Pick and Place",
|
||||
actionType: "pickAndPlace",
|
||||
process: {
|
||||
startPoint: [0, 0, 0],
|
||||
endPoint: [0, 0, 0]
|
||||
},
|
||||
triggers: []
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
addEvent(roboticArmEvent);
|
||||
}
|
||||
|
||||
gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' });
|
||||
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' });
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
|
|||
import { Socket } from 'socket.io-client';
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||
import PointsCalculator from '../../../simulation/events/points/pointsCalculator';
|
||||
import PointsCalculator from '../../../simulation/events/points/functions/pointsCalculator';
|
||||
|
||||
async function addAssetModel(
|
||||
raycaster: THREE.Raycaster,
|
||||
|
@ -266,7 +266,7 @@ async function handleModelLoad(
|
|||
}
|
||||
};
|
||||
addEvent(roboticArmEvent);
|
||||
} else if (selectedItem.type === "Machine") {
|
||||
} else if (selectedItem.type === "StaticMachine") {
|
||||
const machineEvent: MachineEventSchema = {
|
||||
modelUuid: newFloorItem.modeluuid,
|
||||
modelName: newFloorItem.modelname,
|
||||
|
|
|
@ -75,7 +75,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
|||
gltfLoaderWorker.postMessage({ floorItems: data });
|
||||
} else {
|
||||
gltfLoaderWorker.postMessage({ floorItems: [] });
|
||||
loadInitialFloorItems(itemsGroup, setFloorItems);
|
||||
loadInitialFloorItems(itemsGroup, setFloorItems, addEvent);
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
|
@ -94,7 +94,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
|||
updateLoadingProgress(progress);
|
||||
|
||||
if (loadedAssets === totalAssets) {
|
||||
loadInitialFloorItems(itemsGroup, setFloorItems);
|
||||
loadInitialFloorItems(itemsGroup, setFloorItems, addEvent);
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,11 +9,13 @@ import {
|
|||
import * as Types from "../../../types/world/worldTypes";
|
||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||
import { useEffect } from "react";
|
||||
import { useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
|
||||
|
||||
export default function PostProcessing() {
|
||||
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
||||
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
|
||||
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const { selectedEventSphere } = useSelectedEventSphere();
|
||||
|
||||
function flattenChildren(children: any[]) {
|
||||
const allChildren: any[] = [];
|
||||
|
@ -85,6 +87,21 @@ export default function PostProcessing() {
|
|||
xRay={true}
|
||||
/>
|
||||
)}
|
||||
{selectedEventSphere && (
|
||||
<Outline
|
||||
selection={[selectedEventSphere]}
|
||||
selectionLayer={10}
|
||||
width={1000}
|
||||
blendFunction={BlendFunction.ALPHA}
|
||||
edgeStrength={10}
|
||||
resolutionScale={2}
|
||||
pulseSpeed={0}
|
||||
visibleEdgeColor={0x6f42c1}
|
||||
hiddenEdgeColor={0x6f42c1}
|
||||
blur={true}
|
||||
xRay={true}
|
||||
/>
|
||||
)}
|
||||
</EffectComposer>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -4,20 +4,20 @@ import { useEventsStore } from '../../../../../store/simulation/useEventsStore';
|
|||
import useModuleStore from '../../../../../store/useModuleStore';
|
||||
import { TransformControls } from '@react-three/drei';
|
||||
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
|
||||
import { useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore';
|
||||
|
||||
function PointsCreator() {
|
||||
const { events, updatePoint, getPointByUuid } = useEventsStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
const transformRef = useRef<any>(null);
|
||||
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
||||
const [selectedPoint, setSelectedPoint] = useState<THREE.Mesh | null>(null);
|
||||
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
|
||||
const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere } = useSelectedEventSphere();
|
||||
|
||||
useEffect(() => {
|
||||
setTransformMode(null);
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(e);
|
||||
if (!selectedPoint) return;
|
||||
if (!selectedEventSphere) return;
|
||||
if (keyCombination === "G") {
|
||||
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
|
||||
}
|
||||
|
@ -28,13 +28,13 @@ function PointsCreator() {
|
|||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [selectedPoint]);
|
||||
}, [selectedEventSphere]);
|
||||
|
||||
const updatePointToState = (selectedPoint: THREE.Mesh) => {
|
||||
let point = JSON.parse(JSON.stringify(getPointByUuid(selectedPoint.userData.modelUuid, selectedPoint.userData.pointUuid)));
|
||||
const updatePointToState = (selectedEventSphere: THREE.Mesh) => {
|
||||
let point = JSON.parse(JSON.stringify(getPointByUuid(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid)));
|
||||
if (point) {
|
||||
point.position = [selectedPoint.position.x, selectedPoint.position.y, selectedPoint.position.z];
|
||||
updatePoint(selectedPoint.userData.modelUuid, selectedPoint.userData.pointUuid, point)
|
||||
point.position = [selectedEventSphere.position.x, selectedEventSphere.position.y, selectedEventSphere.position.z];
|
||||
updatePoint(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid, point)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,10 +53,11 @@ function PointsCreator() {
|
|||
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedPoint(sphereRefs.current[point.uuid]);
|
||||
setSelectedEventSphere(sphereRefs.current[point.uuid]);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setSelectedPoint(null);
|
||||
clearSelectedEventSphere();
|
||||
setTransformMode(null);
|
||||
}}
|
||||
key={`${i}-${j}`}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
|
@ -76,10 +77,11 @@ function PointsCreator() {
|
|||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedPoint(sphereRefs.current[event.point.uuid]);
|
||||
setSelectedEventSphere(sphereRefs.current[event.point.uuid]);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setSelectedPoint(null);
|
||||
clearSelectedEventSphere();
|
||||
setTransformMode(null);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
||||
|
@ -97,10 +99,11 @@ function PointsCreator() {
|
|||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedPoint(sphereRefs.current[event.point.uuid]);
|
||||
setSelectedEventSphere(sphereRefs.current[event.point.uuid]);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setSelectedPoint(null);
|
||||
clearSelectedEventSphere();
|
||||
setTransformMode(null);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
||||
|
@ -118,10 +121,11 @@ function PointsCreator() {
|
|||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedPoint(sphereRefs.current[event.point.uuid]);
|
||||
setSelectedEventSphere(sphereRefs.current[event.point.uuid]);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setSelectedPoint(null);
|
||||
clearSelectedEventSphere();
|
||||
setTransformMode(null);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
||||
|
@ -136,8 +140,8 @@ function PointsCreator() {
|
|||
}
|
||||
})}
|
||||
</group>
|
||||
{(selectedPoint && transformMode) &&
|
||||
<TransformControls ref={transformRef} object={selectedPoint} mode={transformMode} onMouseUp={(e) => { updatePointToState(selectedPoint) }} />
|
||||
{(selectedEventSphere && transformMode) &&
|
||||
<TransformControls ref={transformRef} object={selectedEventSphere} mode={transformMode} onMouseUp={(e) => { updatePointToState(selectedEventSphere) }} />
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as THREE from 'three';
|
||||
import { Group } from '../../../../types/world/worldTypes';
|
||||
import { Group } from '../../../../../types/world/worldTypes';
|
||||
|
||||
function PointsCalculator(
|
||||
type: string,
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
|
||||
function MachineInstance() {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MachineInstance
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react'
|
||||
import MachineInstance from './machineInstance/machineInstance'
|
||||
|
||||
function MachineInstances() {
|
||||
return (
|
||||
<>
|
||||
|
||||
<MachineInstance />
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MachineInstances
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react'
|
||||
import MachineInstances from './instances/machineInstances'
|
||||
|
||||
function Machine() {
|
||||
return (
|
||||
<>
|
||||
|
||||
<MachineInstances />
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Machine
|
|
@ -0,0 +1,20 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import { useProductStore } from '../../../store/simulation/useProductStore'
|
||||
import * as THREE from 'three';
|
||||
|
||||
function Products() {
|
||||
const { products, addProduct } = useProductStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (products.length === 0) {
|
||||
addProduct('Product 1', THREE.MathUtils.generateUUID());
|
||||
}
|
||||
}, [products])
|
||||
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react'
|
||||
import RoboticArmInstances from './instances/roboticArmInstances';
|
||||
import IkInstances from './instances/ikInstances';
|
||||
|
||||
function RoboticArm() {
|
||||
return (
|
||||
|
|
|
@ -5,13 +5,18 @@ import Vehicles from './vehicle/vehicles';
|
|||
import Points from './events/points/points';
|
||||
import Conveyor from './conveyor/conveyor';
|
||||
import RoboticArm from './roboticArm/roboticArm';
|
||||
import Materials from './materials/materials';
|
||||
import Machine from './machine/machine';
|
||||
import StorageUnit from './storageUnit/storageUnit';
|
||||
import Simulator from './simulator/simulator';
|
||||
import Products from './products/products';
|
||||
|
||||
function Simulation() {
|
||||
const { events } = useEventsStore();
|
||||
const { products } = useProductStore();
|
||||
|
||||
useEffect(() => {
|
||||
// console.log('events: ', events);
|
||||
console.log('events: ', events);
|
||||
}, [events])
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -23,11 +28,21 @@ function Simulation() {
|
|||
|
||||
<Points />
|
||||
|
||||
<Products />
|
||||
|
||||
<Materials />
|
||||
|
||||
<Conveyor />
|
||||
|
||||
<Vehicles />
|
||||
|
||||
<RoboticArm />
|
||||
|
||||
<Conveyor />
|
||||
<Machine />
|
||||
|
||||
<StorageUnit />
|
||||
|
||||
<Simulator />
|
||||
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react'
|
||||
|
||||
function Simulator() {
|
||||
return (
|
||||
<>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Simulator
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
|
||||
function storageUnitInstance() {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default storageUnitInstance
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react'
|
||||
import StorageUnitInstance from './storageUnitInstance/storageUnitInstance'
|
||||
|
||||
function StorageUnitInstances() {
|
||||
return (
|
||||
<>
|
||||
|
||||
<StorageUnitInstance />
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default StorageUnitInstances
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react'
|
||||
import StorageUnitInstances from './instances/storageUnitInstances'
|
||||
|
||||
function StorageUnit() {
|
||||
return (
|
||||
<>
|
||||
|
||||
<StorageUnitInstances />
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default StorageUnit
|
|
@ -17,6 +17,9 @@ interface ArmBotStore {
|
|||
addAction: (modelUuid: string, action: RoboticArmPointSchema['actions'][number]) => void;
|
||||
removeAction: (modelUuid: string, actionUuid: string) => void;
|
||||
|
||||
updateStartPoint: (modelUuid: string, actionUuid: string, startPoint: [number, number, number] | null) => void;
|
||||
updateEndPoint: (modelUuid: string, actionUuid: string, endPoint: [number, number, number] | null) => void;
|
||||
|
||||
setArmBotActive: (modelUuid: string, isActive: boolean) => void;
|
||||
|
||||
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
||||
|
@ -106,6 +109,30 @@ export const useArmBotStore = create<ArmBotStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
updateStartPoint: (modelUuid, actionUuid, startPoint) => {
|
||||
set((state) => {
|
||||
const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
|
||||
if (armBot) {
|
||||
const action = armBot.point.actions.find(a => a.actionUuid === actionUuid);
|
||||
if (action) {
|
||||
action.process.startPoint = startPoint;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateEndPoint: (modelUuid, actionUuid, endPoint) => {
|
||||
set((state) => {
|
||||
const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
|
||||
if (armBot) {
|
||||
const action = armBot.point.actions.find(a => a.actionUuid === actionUuid);
|
||||
if (action) {
|
||||
action.process.endPoint = endPoint;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setArmBotActive: (modelUuid, isActive) => {
|
||||
set((state) => {
|
||||
const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import { create } from 'zustand';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
|
||||
type MaterialsStore = {
|
||||
materials: MaterialsSchema;
|
||||
|
||||
addMaterial: (material: MaterialSchema) => void;
|
||||
removeMaterial: (materialId: string) => void;
|
||||
updateMaterial: (materialId: string, updates: Partial<MaterialSchema>) => void;
|
||||
|
||||
setStartTime: (materialId: string, startTime: string) => void;
|
||||
setEndTime: (materialId: string, endTime: string) => void;
|
||||
setCost: (materialId: string, cost: number) => void;
|
||||
setWeight: (materialId: string, weight: number) => void;
|
||||
|
||||
getMaterialById: (materialId: string) => MaterialSchema | undefined;
|
||||
};
|
||||
|
||||
export const useMaterialStore = create<MaterialsStore>()(
|
||||
immer((set, get) => ({
|
||||
materials: [],
|
||||
|
||||
addMaterial: (material) => {
|
||||
set((state) => {
|
||||
state.materials.push(material);
|
||||
});
|
||||
},
|
||||
|
||||
removeMaterial: (materialId) => {
|
||||
set((state) => {
|
||||
state.materials = state.materials.filter(m => m.materialId !== materialId);
|
||||
});
|
||||
},
|
||||
|
||||
updateMaterial: (materialId, updates) => {
|
||||
set((state) => {
|
||||
const material = state.materials.find(m => m.materialId === materialId);
|
||||
if (material) {
|
||||
Object.assign(material, updates);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setStartTime: (materialId, startTime) => {
|
||||
set((state) => {
|
||||
const material = state.materials.find(m => m.materialId === materialId);
|
||||
if (material) material.startTime = startTime;
|
||||
});
|
||||
},
|
||||
|
||||
setEndTime: (materialId, endTime) => {
|
||||
set((state) => {
|
||||
const material = state.materials.find(m => m.materialId === materialId);
|
||||
if (material) material.endTime = endTime;
|
||||
});
|
||||
},
|
||||
|
||||
setCost: (materialId, cost) => {
|
||||
set((state) => {
|
||||
const material = state.materials.find(m => m.materialId === materialId);
|
||||
if (material) material.cost = cost;
|
||||
});
|
||||
},
|
||||
|
||||
setWeight: (materialId, weight) => {
|
||||
set((state) => {
|
||||
const material = state.materials.find(m => m.materialId === materialId);
|
||||
if (material) material.weight = weight;
|
||||
});
|
||||
},
|
||||
|
||||
getMaterialById: (materialId) => {
|
||||
return get().materials.find(m => m.materialId === materialId);
|
||||
},
|
||||
}))
|
||||
);
|
|
@ -48,6 +48,11 @@ type ProductsStore = {
|
|||
updates: Partial<TriggerSchema>
|
||||
) => void;
|
||||
|
||||
// Renaming functions
|
||||
renameProduct: (productId: string, newName: string) => void;
|
||||
renameAction: (actionUuid: string, newName: string) => void;
|
||||
renameTrigger: (triggerUuid: string, newName: string) => void;
|
||||
|
||||
// Helper functions
|
||||
getProductById: (productId: string) => { productName: string; productId: string; eventsData: EventsSchema[] } | undefined;
|
||||
};
|
||||
|
@ -331,6 +336,84 @@ export const useProductStore = create<ProductsStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
// Renaming functions
|
||||
renameProduct: (productId, newName) => {
|
||||
set((state) => {
|
||||
const product = state.products.find(p => p.productId === productId);
|
||||
if (product) {
|
||||
product.productName = newName;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
renameAction: (actionUuid, newName) => {
|
||||
set((state) => {
|
||||
for (const product of state.products) {
|
||||
for (const event of product.eventsData) {
|
||||
if ('points' in event) {
|
||||
for (const point of (event as ConveyorEventSchema).points) {
|
||||
if (point.action && point.action.actionUuid === actionUuid) {
|
||||
point.action.actionName = newName;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if ('point' in event) {
|
||||
const point = (event as any).point;
|
||||
if ('action' in point && point.action.actionUuid === actionUuid) {
|
||||
point.action.actionName = newName;
|
||||
return;
|
||||
} else if ('actions' in point) {
|
||||
const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
|
||||
if (action) {
|
||||
action.actionName = newName;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
renameTrigger: (triggerUuid, newName) => {
|
||||
set((state) => {
|
||||
for (const product of state.products) {
|
||||
for (const event of product.eventsData) {
|
||||
if ('points' in event) {
|
||||
for (const point of (event as ConveyorEventSchema).points) {
|
||||
if (point.action && 'triggers' in point.action) {
|
||||
const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid);
|
||||
if (trigger) {
|
||||
trigger.triggerName = newName;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ('point' in event) {
|
||||
const point = (event as any).point;
|
||||
if ('action' in point && 'triggers' in point.action) {
|
||||
const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
|
||||
if (trigger) {
|
||||
trigger.triggerName = newName;
|
||||
return;
|
||||
}
|
||||
} else if ('actions' in point) {
|
||||
for (const action of point.actions) {
|
||||
if ('triggers' in action) {
|
||||
const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
|
||||
if (trigger) {
|
||||
trigger.triggerName = newName;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Helper functions
|
||||
getProductById: (productId) => {
|
||||
return get().products.find(p => p.productId === productId);
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import { create } from 'zustand';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import * as THREE from 'three';
|
||||
|
||||
interface SelectedEventSphereState {
|
||||
selectedEventSphere: THREE.Mesh | null;
|
||||
setSelectedEventSphere: (mesh: THREE.Mesh | null) => void;
|
||||
clearSelectedEventSphere: () => void;
|
||||
}
|
||||
|
||||
export const useSelectedEventSphere = create<SelectedEventSphereState>()(
|
||||
immer((set) => ({
|
||||
selectedEventSphere: null,
|
||||
setSelectedEventSphere: (mesh) => {
|
||||
set((state) => {
|
||||
state.selectedEventSphere = mesh;
|
||||
});
|
||||
},
|
||||
clearSelectedEventSphere: () => {
|
||||
set((state) => {
|
||||
state.selectedEventSphere = null;
|
||||
});
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
interface SelectedProductState {
|
||||
selectedProduct: { productId: string; productName: string };
|
||||
setSelectedProduct: (productId: string, productName: string) => void;
|
||||
clearSelectedProduct: () => void;
|
||||
}
|
||||
|
||||
export const useSelectedProduct = create<SelectedProductState>()(
|
||||
immer((set) => ({
|
||||
selectedProduct: { productId: '', productName: '' },
|
||||
setSelectedProduct: (productId, productName) => {
|
||||
set((state) => {
|
||||
state.selectedProduct.productId = productId;
|
||||
state.selectedProduct.productName = productName;
|
||||
});
|
||||
},
|
||||
clearSelectedProduct: () => {
|
||||
set((state) => {
|
||||
state.selectedProduct.productId = '';
|
||||
state.selectedProduct.productName = '';
|
||||
});
|
||||
},
|
||||
}))
|
||||
);
|
|
@ -169,3 +169,16 @@ interface StorageUnitStatus extends StorageEventSchema {
|
|||
activeTime: number;
|
||||
currentLoad: number;
|
||||
}
|
||||
|
||||
interface MaterialSchema {
|
||||
materialId: string;
|
||||
materialName: string;
|
||||
materialType: string;
|
||||
isActive: boolean;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
cost?: number;
|
||||
weight?: number;
|
||||
}
|
||||
|
||||
type MaterialsSchema = MaterialSchema[];
|
Loading…
Reference in New Issue