diff --git a/src/bin/3dView/Data/BuildNodeData.ts b/src/bin/3dView/Data/BuildNodeData.ts
index 4103ce5..fd3f5dd 100644
--- a/src/bin/3dView/Data/BuildNodeData.ts
+++ b/src/bin/3dView/Data/BuildNodeData.ts
@@ -21,7 +21,7 @@ export interface NodeData {
}
export function BuildNodeData(cables: CableData[]): NodeData[] {
- const nodeRadius = 20
+ const nodeRadius = 15
let nodeData: NodeData[] = []
cables.forEach((cable, cableIndex) => {
diff --git a/src/bin/3dView/Scene/Bin3dView.tsx b/src/bin/3dView/Scene/Bin3dView.tsx
index 41ef6d6..cc2ae26 100644
--- a/src/bin/3dView/Scene/Bin3dView.tsx
+++ b/src/bin/3dView/Scene/Bin3dView.tsx
@@ -14,6 +14,7 @@ import { pond } from "protobuf-ts/pond";
import GrainCableFill from "../Systems/Inventory/GrainCableFill";
import TempHeatMap from "../Systems/Heatmap/TempHeatMap";
import NodePointCloud from "../Systems/Heatmap/NodePointCloud";
+import { Box } from "@mui/material";
interface Props {
bin: Bin
@@ -35,8 +36,6 @@ interface Props {
export default function Bin3dView(props: Props) {
const { bin, scale = 100, fillPercent, nodeClick, showTempHeatmap, showGrain, showHotspots, showResetButton, showMoistureHeatmap, showTemp, showMoisture } = props
- const binCenter = useMemo(() => new Vector3(0, 0, 0), []);
-
// Compute the initial camera radius so the full bin fits in frame.
// Uses the perspective camera FOV (default 75°) with a padding factor.
// Both the diameter and total height are considered so neither is clipped.
@@ -129,7 +128,6 @@ export default function Bin3dView(props: Props) {
cableData={cableData}
nodeData={nodeData}
bin={bin}
- binCenter={binCenter}
onNodeClick={(node, cable) => {
if (nodeClick) nodeClick(node, cable)
}}
diff --git a/src/bin/3dView/Systems/Cables/BinCables.tsx b/src/bin/3dView/Systems/Cables/BinCables.tsx
index 7480e46..1901178 100644
--- a/src/bin/3dView/Systems/Cables/BinCables.tsx
+++ b/src/bin/3dView/Systems/Cables/BinCables.tsx
@@ -1,15 +1,13 @@
import GrainCable from "./GrainCable";
-import { Vector3 } from "three";
import { Bin } from "models";
import React from "react";
import { CableData } from "../../Data/BuildCableData";
import { NodeData } from "../../Data/BuildNodeData";
interface Props {
- cableData: CableData[]
- nodeData: NodeData[]
- bin: Bin
- binCenter: Vector3,
+ cableData: CableData[],
+ nodeData: NodeData[],
+ bin: Bin,
renderOrder?: number,
displayTemp?: boolean,
displayMoisture?: boolean,
@@ -17,7 +15,7 @@ interface Props {
}
export default function BinCables(props: Props){
- const {bin, binCenter, cableData, nodeData, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
+ const {bin, cableData, nodeData, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
return (
{cableData.map((cable, i) => {
@@ -29,7 +27,6 @@ export default function BinCables(props: Props){
cable={cable}
nodes={cableNodes}
bin={bin}
- binCenter={binCenter}
displayTemp={displayTemp}
displayMoisture={displayMoisture}
onNodeClick={(node) => {
diff --git a/src/bin/3dView/Systems/Cables/CableNode.tsx b/src/bin/3dView/Systems/Cables/CableNode.tsx
index 99c5824..ecaaf18 100644
--- a/src/bin/3dView/Systems/Cables/CableNode.tsx
+++ b/src/bin/3dView/Systems/Cables/CableNode.tsx
@@ -1,21 +1,18 @@
import Sphere, { SphereGeometry } from "3dModels/Shapes/3D/Sphere"
import { useFrame, useThree } from "@react-three/fiber"
-import { Billboard, Text } from "@react-three/drei"
-import React, { useMemo, useState } from "react"
+import { Text } from "@react-three/drei"
+import React, { useMemo } from "react"
import { Euler, Group, Vector3 } from "three"
-import { describeMeasurement } from "pbHelpers/MeasurementDescriber"
-import { quack } from "protobuf-ts/quack"
+import { green, grey, orange, red } from "@mui/material/colors";
import { useGlobalState } from "providers"
import { pond } from "protobuf-ts/pond"
import Ring, { RingGeometry } from "3dModels/Shapes/3D/Ring"
import { NodeData } from "../../Data/BuildNodeData"
-import { TempToColour } from "bin/3dView/utils/tempToColour"
interface Props {
node: NodeData
- binCenter: Vector3
- lowerThreshold: number
upperThreshold: number,
+ targetMoisture: number,
renderOrder?: number,
displayTemp?: boolean,
displayMoisture?: boolean,
@@ -23,33 +20,31 @@ interface Props {
}
export default function CableNode(props: Props) {
- const { node, binCenter, lowerThreshold, upperThreshold, onNodeClick, renderOrder, displayTemp, displayMoisture } = props
+ const { node, upperThreshold, targetMoisture, onNodeClick, renderOrder, displayTemp, displayMoisture } = props
const { camera } = useThree();
const [{ user }] = useGlobalState();
// const [showLabel, setShowLabel] = useState(false);
- const labelGroupRef = React.useRef(null);
- const ROW_HEIGHT = 35;
- const PADDING = 20;
+ const ROW_HEIGHT = 45;
+ const PADDING = 15;
+ const LABEL_WIDTH = 220;
+ const flagRef = React.useRef(null);
- //this can be used to control label visibility through the cameras zoom level
- // useFrame(() => {
- // const distance = camera.position.distanceTo(binCenter)
-
- // const SHOW_DISTANCE = 15;
- // const HIDE_DISTANCE = 16;
-
- // if (!showLabel && distance < SHOW_DISTANCE) {
- // setShowLabel(true);
- // } else if (showLabel && distance > HIDE_DISTANCE) {
- // setShowLabel(false);
- // }
-
- // const scale = distance * 0.07;
-
- // if (labelGroupRef.current) {
- // labelGroupRef.current.scale.set(scale, scale, 1);
- // }
- // });
+ useFrame(() => {
+ if (!flagRef.current || !showLabel) return;
+
+ // Get camera's right direction in world space
+ const cameraRight = new Vector3();
+ camera.getWorldDirection(cameraRight); // forward
+ const up = new Vector3(0, 1, 0);
+ cameraRight.crossVectors(cameraRight, up).normalize(); // right = forward × up
+
+ const pos = node.position.clone();
+ pos.addScaledVector(cameraRight, LABEL_WIDTH / 2); // offset right in screen space
+ pos.addScaledVector(camera.position.clone().sub(node.position).normalize(), 1); // nudge toward camera
+
+ flagRef.current.position.copy(pos);
+ flagRef.current.quaternion.copy(camera.quaternion);
+ });
const tempDisplay = useMemo(() => {
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
@@ -63,7 +58,7 @@ export default function CableNode(props: Props) {
if (node.excluded) {
if (!anyToggleOn) return [];
- return [{ text: "Excluded", color: "red" }];
+ return [{ text: "Excluded", color: grey[700] }];
}
const r: { text: string; color: string }[] = [];
@@ -72,7 +67,9 @@ export default function CableNode(props: Props) {
if (displayTemp && node.celcius !== 0) {
r.push({
text: tempDisplay,
- color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour()
+ // color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour()
+ //will be green when below threshold, orange when it goeas above but by less than 5 degrees, and red if more than 5 over
+ color: node.celcius > upperThreshold ? node.celcius > upperThreshold + 5 ? red[700] : orange[800]: green[800]
});
}
@@ -81,21 +78,25 @@ export default function CableNode(props: Props) {
if (node.moisture && node.moisture > 0) {
r.push({
text: `${node.moisture.toFixed(2)}% EMC`,
- color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour()
+ // color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour()
+ color: node.moisture > targetMoisture ? node.moisture > targetMoisture + 0.5 ? red[700] : orange[800]: green[800]
});
- } else if (node.humidity && node.humidity > 0) {
- r.push({
- text: `${node.humidity.toFixed(2)}%`,
- color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT).colour()
- });
- }
+ }
+ //apparently we dont want to display humidity at all
+ // else if (node.humidity && node.humidity > 0) {
+ // r.push({
+ // text: `${node.humidity.toFixed(2)}%`,
+ // // color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT).colour()
+ // color: "black"
+ // });
+ // }
}
const hasData = r.length > 0;
// Only show top node if there's actual data
if (node.topNode && hasData) {
- r.unshift({ text: "Top Node", color: "yellow" });
+ r.unshift({ text: "Top Node", color: grey[700] });
}
return r;
@@ -117,12 +118,8 @@ export default function CableNode(props: Props) {
const topNodeRotation = React.useMemo(() => new Euler(-Math.PI / 2, 0, 0), []);
- const labelPosition = node.position.clone();
const labelHeight = rows.length * ROW_HEIGHT + PADDING;
- const dir = camera.position.clone().sub(node.position).normalize();
- labelPosition.add(dir.multiplyScalar(1));
-
const getRowY = (index: number) => {
const totalHeight = rows.length * ROW_HEIGHT;
return totalHeight / 2 - (index * ROW_HEIGHT) - ROW_HEIGHT / 2;
@@ -130,9 +127,7 @@ export default function CableNode(props: Props) {
const nodeColour = () => {
if (node.excluded) return "grey";
- if (!node.inGrain) return "white";
- const c = TempToColour(node, lowerThreshold, upperThreshold, "node");
- return c !== null ? c.getStyle() : "white";
+ return "white";
};
const handleClick = (e: any) => {
@@ -144,49 +139,46 @@ export default function CableNode(props: Props) {
return (
- {(!node.inGrain || !showLabel) &&
-
-
- {node.topNode && (
-
- )}
-
- }
+
+
+ {node.topNode && (
+
+ )}
+
{node.inGrain && showLabel &&
-
-
-
-
-
-
- {rows.map((row, i) => (
-
- {row.text}
-
- ))}
-
-
- }
+
+
+
+
+
+ {rows.map((row, i) => (
+
+ {row.text}
+
+ ))}
+
+}
)
}
\ No newline at end of file
diff --git a/src/bin/3dView/Systems/Cables/GrainCable.tsx b/src/bin/3dView/Systems/Cables/GrainCable.tsx
index 73add6a..7c3aa3b 100644
--- a/src/bin/3dView/Systems/Cables/GrainCable.tsx
+++ b/src/bin/3dView/Systems/Cables/GrainCable.tsx
@@ -7,17 +7,16 @@ import { CableData } from "../../Data/BuildCableData"
import { NodeData } from "../../Data/BuildNodeData"
interface Props {
- cable: CableData
- nodes: NodeData[]
- bin: Bin
- binCenter: Vector3,
+ cable: CableData,
+ nodes: NodeData[],
+ bin: Bin,
renderOrder?: number,
displayTemp?: boolean,
- displayMoisture?: boolean
+ displayMoisture?: boolean,
onNodeClick?: (node: NodeData) => void
}
export default function GrainCable(props: Props) {
- const {cable, nodes, bin, binCenter, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
+ const {cable, nodes, bin, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
const calculateHeight = () => {
return cable.topPosition.distanceTo(cable.bottomPosition)
@@ -44,7 +43,7 @@ export default function GrainCable(props: Props) {
{nodes.map((node, i) => (
-
+
))}
)
diff --git a/src/bin/binSummary/BinSummary.tsx b/src/bin/binSummary/BinSummary.tsx
index a4777e7..737f125 100644
--- a/src/bin/binSummary/BinSummary.tsx
+++ b/src/bin/binSummary/BinSummary.tsx
@@ -47,12 +47,12 @@ export default function BinSummary(props: Props){
-
+
{bin3dView()}
-
+
{tableView()}
diff --git a/src/bin/binSummary/components/bin3dVisualizer.tsx b/src/bin/binSummary/components/bin3dVisualizer.tsx
index 62cf814..5da0a3e 100644
--- a/src/bin/binSummary/components/bin3dVisualizer.tsx
+++ b/src/bin/binSummary/components/bin3dVisualizer.tsx
@@ -1,6 +1,7 @@
-import { Box, Checkbox, Grid2, FormControlLabel } from "@mui/material";
+import { Box, Checkbox, FormControlLabel, List, ListItem } from "@mui/material";
import Bin3dView from "bin/3dView/Scene/Bin3dView";
import { Bin } from "models";
+import React from "react";
import { useState } from "react";
interface Props {
@@ -19,13 +20,13 @@ export default function bin3dVisualizer(props: Props){
return 0
}
-
- return (
-
-
-
- {
+ return (
+
+
+
+
-
+
+
-
+
+
-
+
+
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ )
+ }
+
+ const binViewer = () => {
+ return (
+
+ )
+ }
+
+ return (
+
+
+ {binViewer()}
+
+ {controlsOverlay()}
+
+
)
}
\ No newline at end of file
diff --git a/src/bin/binSummary/components/binTableView.tsx b/src/bin/binSummary/components/binTableView.tsx
index 42d4fb9..daa76ff 100644
--- a/src/bin/binSummary/components/binTableView.tsx
+++ b/src/bin/binSummary/components/binTableView.tsx
@@ -1,37 +1,165 @@
-import { Box } from "@mui/material";
+import { Box, Table, TableBody, TableCell, TableHead, TableRow } from "@mui/material";
+import { green, grey, orange, red } from "@mui/material/colors";
import { Bin } from "models";
+import { pond } from "protobuf-ts/pond";
+import { useGlobalState } from "providers";
+import React from "react";
import { useMemo } from "react";
+import { avg, celsiusToFahrenheit } from "utils";
interface Props {
bin: Bin
}
-interface LevelAvg {
- temp: number
- moisture: number
- inGrain: boolean
-
+interface BinLevel {
+ grainTemps: number[]
+ airTemps: number[]
+ grainMoistures: number[]
+ airMoistures: number[]
}
export default function BinTableView(props: Props) {
const {bin} = props
+ const [{user}] = useGlobalState()
- const cableLevelAverages = useMemo(() => {
+ const binLevels = useMemo(() => {
const cables = bin.status.grainCables
- let levels: LevelAvg[] = []
- console.log(cables)
+ let levels: BinLevel[] = []
//note for future self, the first item in the measurement arrays is the lowest node on the cable
//the zones we are creating here will match with the node number because most cables will either have the same number of nodes or only have a difference of 1 or 2
//example: cable 1 has 5 nodes cable 2 has 7 nodes, the first five nodes of the cables will match up and average together for each zone
//and then zones 6 and 7 will be made using only cable 2 because cable 1 doesn't have them
cables.forEach(cable => {
-
+ cable.celcius.forEach((c, index) => {
+ //remember index will be 0 based but the top node starts at 1 for the lowest cable
+ let inGrain = index < cable.topNode
+ if (levels[index]){
+ if(cable.excludedNodes.includes(index+1)) return
+ if(inGrain){
+ levels[index].grainTemps.push(c)
+ //need to check that the cable not only has the moisture mutation on it, but also that it reads humidity
+ //cables that dont read humidity return an array of 0 values so if the average of all of the values is 0 then it is a temp only cable
+ if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
+ //based on all the other checks and the fact that the indexes between the array should match this should never be 0 but put a guard here just in case
+ //also note that we are only doing moisture and not humidity, it seems like users dont care about humidity
+ levels[index].grainMoistures.push(cable.moisture[index] ?? 0)
+ }
+ }else{
+ levels[index].airTemps.push(c)
+ //need to check that the cable not only has the moisture mutation on it, but also that it reads humidity
+ //cables that dont read humidity return an array of 0 values so if the average of all of the values is 0 then it is a temp only cable
+ if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
+ //based on all the other checks and the fact that the indexes between the array should match this should never be 0 but put a guard here just in case
+ levels[index].airMoistures.push(cable.moisture[index] ?? 0)
+ }
+ }
+ }else{
+ let newlevel: BinLevel = {
+ grainTemps: [],
+ grainMoistures: [],
+ airTemps: [],
+ airMoistures: []
+ }
+ if(!cable.excludedNodes.includes(index)){
+ if(inGrain){
+ newlevel.grainTemps.push(c)
+ if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
+ newlevel.grainMoistures.push(cable.moisture[index] ?? 0)
+ }
+ }else{
+ newlevel.airTemps.push(c)
+ if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
+ newlevel.airMoistures.push(cable.moisture[index] ?? 0)
+ }
+ }
+ }
+ levels.push(newlevel)
+ }
+
+ })
})
+ //flip the levels so that the highest nodes are on the top
+ levels.reverse()
return levels
},[bin])
+ const levelDisplay = (level: BinLevel) => {
+ let tempDisplay = "--"
+ let moistureDisplay = "--"
+ let lvlTemp
+ let lvlMoisture
+ let tempColour: string = grey[600]
+ let moistureColour: string = grey[600]
+
+ //determine the temp
+ if(level.grainTemps.length > 0){
+ lvlTemp = avg(level.grainTemps)
+ //if the average temp is 5 degrees above the bins threshold
+ if(lvlTemp > (bin.upperTempThreshold() + 5)){
+ tempColour = red[700]
+ }else if(lvlTemp > bin.upperTempThreshold()){
+ //if the average temp is greater than 5 degrees over
+ tempColour = orange[600]
+ }else {
+ tempColour = green[300]
+ }
+ }else if (level.airTemps.length > 0){
+ lvlTemp = avg(level.airTemps)
+ }
+ if (lvlTemp){
+ if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
+ lvlTemp = celsiusToFahrenheit(lvlTemp)
+ }
+ tempDisplay = lvlTemp.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " F" : " C")
+ }
+
+ //determine the moisture to show
+ if(level.grainMoistures.length > 0){
+ lvlMoisture = avg(level.grainMoistures)
+ if(lvlMoisture > (bin.targetMoisture() + 1.5)){
+ moistureColour = red[700]
+ }else if(lvlMoisture > bin.targetMoisture()){
+ //if the average temp is greater than 5 degrees over
+ moistureColour = orange[600]
+ }else {
+ moistureColour = green[300]
+ }
+ }else if (level.airMoistures.length > 0){
+ lvlMoisture = avg(level.airMoistures)
+ }
+ if(lvlMoisture){
+ moistureDisplay = lvlMoisture.toFixed(2) + "%"
+ }
+ return (
+
+ {tempDisplay}
+ {moistureDisplay}
+
+ )
+ }
+
+
+
return (
- Table View
+
+
+
+
+ Level
+ Temperature
+ Moisture
+
+
+
+ {binLevels.map((level, i) => (
+
+ {binLevels.length - i}
+ {levelDisplay(level)}
+
+ ))}
+
+
+
)
}
\ No newline at end of file
diff --git a/src/models/Bin.ts b/src/models/Bin.ts
index 475958c..6aa2389 100644
--- a/src/models/Bin.ts
+++ b/src/models/Bin.ts
@@ -292,4 +292,8 @@ export class Bin {
public lowerTempThreshold(): number {
return this.settings.lowTemp
}
+
+ public targetMoisture(): number {
+ return this.settings.inventory?.targetMoisture ?? 0
+ }
}