diff --git a/src/bin/3dView/Data/BuildCableData.ts b/src/bin/3dView/Data/BuildCableData.ts
index 0b0ed03..4b34fb9 100644
--- a/src/bin/3dView/Data/BuildCableData.ts
+++ b/src/bin/3dView/Data/BuildCableData.ts
@@ -172,8 +172,9 @@ export function BuildCableData(bin: Bin, dims: BinDims): CableData[] {
distributed.forEach(orbit => {
const count = orbit.assignedCount ?? 0;
- // const topY = getTopY(orbit.radius, dims); //this is if we want the cables to start at the roof
- const topY = dims.sidewallHeight/2 //this will start the cables at the top of the sidewall
+ const topY = getTopY(orbit.radius, dims); //this is if we want the cables to start at the roof
+ //const topY = dims.sidewallHeight/2 //this will start the cables at the top of the sidewall
+
const bottomY = getBottomY(orbit.radius, dims);
if (orbit.orbit === 0 && count > 0) {
diff --git a/src/bin/3dView/Scene/Bin3dView.tsx b/src/bin/3dView/Scene/Bin3dView.tsx
index e866904..f8107f4 100644
--- a/src/bin/3dView/Scene/Bin3dView.tsx
+++ b/src/bin/3dView/Scene/Bin3dView.tsx
@@ -92,6 +92,7 @@ export default function Bin3dView(props: Props) {
dims.diameter,
dims.sidewallHeight,
dims.hopperHeight,
+ dims.roofHeight,
fillPercent
);
}, [isCableInventory, fillPercent, dims]);
diff --git a/src/bin/3dView/Systems/Heatmap/MoistureHeatmap.tsx b/src/bin/3dView/Systems/Heatmap/MoistureHeatmap.tsx
index 12ea832..4756c4a 100644
--- a/src/bin/3dView/Systems/Heatmap/MoistureHeatmap.tsx
+++ b/src/bin/3dView/Systems/Heatmap/MoistureHeatmap.tsx
@@ -133,11 +133,18 @@ export default function MoistureHeatMap({ binDimensions, targetMoisture, nodes,
const binRadius = binDimensions.diameter / 2;
const sidewallHeight = binDimensions.sidewallHeight;
const hopperHeight = binDimensions.hopperHeight ?? 0;
+ const roofHeight = binDimensions.roofHeight ?? 0;
const sidewallBaseY = -sidewallHeight / 2;
+ const sidewallTopY = sidewallHeight / 2;
const hopperTipY = sidewallBaseY - hopperHeight;
+ const roofTipY = sidewallTopY + roofHeight;
const maxRadiusAtY = (y: number) => {
+ if (y > sidewallTopY) {
+ if (roofHeight <= 0 || y >= roofTipY) return 0;
+ return binRadius * (1 - (y - sidewallTopY) / roofHeight);
+ }
if (y >= sidewallBaseY) return binRadius;
if (hopperHeight <= 0 || y <= hopperTipY) return 0;
return binRadius * ((y - hopperTipY) / hopperHeight);
@@ -191,7 +198,7 @@ export default function MoistureHeatMap({ binDimensions, targetMoisture, nodes,
const getSurfaceY = (x: number, z: number): number => {
if (surfaceAnchors.length > 0) {
const raw = idwSurfaceY(x, z, surfaceAnchors, SURFACE_IDW_POWER);
- return Math.max(grainFloorY, Math.min(sidewallHeight / 2, raw));
+ return Math.max(grainFloorY, Math.min(roofTipY, raw));
}
return flatMaxY ?? sidewallBaseY;
};
@@ -415,6 +422,8 @@ export default function MoistureHeatMap({ binDimensions, targetMoisture, nodes,
binRadius,
targetMoisture,
hopperHeight,
+ roofHeight,
+ roofTipY,
flatMaxY,
flatMinY,
]);
diff --git a/src/bin/3dView/Systems/Heatmap/TempHeatMap.tsx b/src/bin/3dView/Systems/Heatmap/TempHeatMap.tsx
index b631aea..1b25f0a 100644
--- a/src/bin/3dView/Systems/Heatmap/TempHeatMap.tsx
+++ b/src/bin/3dView/Systems/Heatmap/TempHeatMap.tsx
@@ -133,13 +133,23 @@ export default function TempHeatMap({ binDimensions, targetTemp, nodes, flatMaxY
const binRadius = binDimensions.diameter / 2;
const sidewallHeight = binDimensions.sidewallHeight;
const hopperHeight = binDimensions.hopperHeight ?? 0;
+ const roofHeight = binDimensions.roofHeight ?? 0;
const upperThreshold = targetTemp;
const sidewallBaseY = -sidewallHeight / 2;
+ const sidewallTopY = sidewallHeight / 2;
const hopperTipY = sidewallBaseY - hopperHeight;
-
+ const roofTipY = sidewallTopY + roofHeight;
+
const maxRadiusAtY = (y: number) => {
+ // Inside the roof cone — tapers from binRadius at sidewallTopY to 0 at roofTipY
+ if (y > sidewallTopY) {
+ if (roofHeight <= 0 || y >= roofTipY) return 0;
+ return binRadius * (1 - (y - sidewallTopY) / roofHeight);
+ }
+ // Straight sidewall
if (y >= sidewallBaseY) return binRadius;
+ // Inside the hopper cone — tapers from binRadius at sidewallBaseY to 0 at hopperTipY
if (hopperHeight <= 0 || y <= hopperTipY) return 0;
return binRadius * ((y - hopperTipY) / hopperHeight);
};
@@ -192,7 +202,7 @@ export default function TempHeatMap({ binDimensions, targetTemp, nodes, flatMaxY
const getSurfaceY = (x: number, z: number): number => {
if (surfaceAnchors.length > 0) {
const raw = idwSurfaceY(x, z, surfaceAnchors, SURFACE_IDW_POWER);
- return Math.max(grainFloorY, Math.min(sidewallHeight / 2, raw));
+ return Math.max(grainFloorY, Math.min(roofTipY, raw));
}
return flatMaxY ?? sidewallBaseY;
};
@@ -416,6 +426,8 @@ export default function TempHeatMap({ binDimensions, targetTemp, nodes, flatMaxY
binRadius,
upperThreshold,
hopperHeight,
+ roofHeight,
+ roofTipY,
flatMaxY,
flatMinY,
]);
@@ -455,5 +467,4 @@ export default function TempHeatMap({ binDimensions, targetTemp, nodes, flatMaxY
);
-}
-
+}
\ No newline at end of file
diff --git a/src/bin/3dView/utils/grainFillBounds.ts b/src/bin/3dView/utils/grainFillBounds.ts
index 698fcb0..d1b6fef 100644
--- a/src/bin/3dView/utils/grainFillBounds.ts
+++ b/src/bin/3dView/utils/grainFillBounds.ts
@@ -1,3 +1,5 @@
+import { ConeVolume, CylinderVolume } from "common/TrigFunctions";
+
/**
* Y bounds of filled grain volume — matches GrainFillFlat / GrainCableFill fallback volume math.
*/
@@ -6,28 +8,40 @@ export interface GrainYBounds {
maxY: number;
}
+// Grain never physically fills all the way to the roof tip — cap roof fill
+// at this fraction of roofHeight so there is always visible breathing room.
+const MAX_ROOF_FILL_FRACTION = 0.75;
+
export function grainYBoundsFromFillPercent(
diameter: number,
sidewallHeight: number,
hopperHeight: number,
+ roofHeight: number,
fillPercent: number
): GrainYBounds {
- const radius = diameter / 2;
+ const radius = diameter / 2;
const sidewallBaseY = -sidewallHeight / 2;
- const hopperTipY = sidewallBaseY - hopperHeight;
+ const hopperTipY = sidewallBaseY - hopperHeight;
+ const roofBaseY = sidewallHeight / 2;
if (fillPercent <= 0) {
return { minY: sidewallBaseY, maxY: sidewallBaseY };
}
- const cylinderVolume = Math.PI * radius * radius * sidewallHeight;
- const hopperVolume =
- hopperHeight > 0 ? (1 / 3) * Math.PI * radius * radius * hopperHeight : 0;
- const totalVolume = cylinderVolume + hopperVolume;
+ const cylinderVolume = CylinderVolume(radius, sidewallHeight);
+ const hopperVolume = hopperHeight > 0 ? ConeVolume(radius, hopperHeight) : 0;
+ const roofVolume = roofHeight > 0 ? ConeVolume(radius, roofHeight) : 0;
+
+ // Volume of the usable roof portion (up to MAX_ROOF_FILL_FRACTION of height).
+ // A cone fills as h³ so the capped volume fraction = MAX_ROOF_FILL_FRACTION³.
+ const roofVolumeCapped = roofVolume * Math.pow(MAX_ROOF_FILL_FRACTION, 3);
+
+ const totalVolume = cylinderVolume + hopperVolume + roofVolumeCapped;
const filledVolume = totalVolume * fillPercent;
+ // Fill the hopper first
if (hopperHeight > 0 && filledVolume <= hopperVolume) {
- const ratio = filledVolume / hopperVolume;
+ const ratio = filledVolume / hopperVolume;
const hopperFillHeight = hopperHeight * Math.pow(ratio, 1 / 3);
return {
minY: hopperTipY,
@@ -35,15 +49,23 @@ export function grainYBoundsFromFillPercent(
};
}
- const hopperFillHeight = hopperHeight;
- const remainingVolume = filledVolume - hopperVolume;
- const cylinderFillHeight = Math.max(
- 0,
- remainingVolume / (Math.PI * radius * radius)
- );
+ // Then fill the cylinder
+ const volumeAfterHopper = filledVolume - hopperVolume;
+ if (volumeAfterHopper <= cylinderVolume) {
+ const cylinderFillHeight = volumeAfterHopper / (Math.PI * radius * radius);
+ return {
+ minY: hopperHeight > 0 ? hopperTipY : sidewallBaseY,
+ maxY: sidewallBaseY + cylinderFillHeight,
+ };
+ }
+
+ // Finally fill into the roof cone, capped at MAX_ROOF_FILL_FRACTION
+ const roofFilledVolume = volumeAfterHopper - cylinderVolume;
+ const roofRatio = Math.min(1, roofFilledVolume / roofVolumeCapped);
+ const roofFillHeight = roofHeight * MAX_ROOF_FILL_FRACTION * Math.pow(roofRatio, 1 / 3);
return {
minY: hopperHeight > 0 ? hopperTipY : sidewallBaseY,
- maxY: sidewallBaseY + cylinderFillHeight,
+ maxY: roofBaseY + roofFillHeight,
};
-}
+}
\ No newline at end of file
diff --git a/src/bin/BinPlayer.tsx b/src/bin/BinPlayer.tsx
index 9fcd85c..95aabb3 100644
--- a/src/bin/BinPlayer.tsx
+++ b/src/bin/BinPlayer.tsx
@@ -513,8 +513,8 @@ const dateSelector = () => {
{currentCheckpointTime ? currentCheckpointTime.format("MMM D, YYYY h:mm A") : moment().format("MMM D, YYYY h:mm A")}
- {startDate.format("MMM D, YYYY")}
- {endDate.format("MMM D, YYYY")}
+ {startDate.format("MMM D, YYYY")}
+ {endDate.format("MMM D, YYYY")}
{/* date range selector */}
diff --git a/src/bin/BinTableExpanded.tsx b/src/bin/BinTableExpanded.tsx
index cf1def6..ecb7baa 100644
--- a/src/bin/BinTableExpanded.tsx
+++ b/src/bin/BinTableExpanded.tsx
@@ -81,8 +81,8 @@ export default function BinTableExpanded(props: Props) {
const inGrain = nodeIndex < cable.topNode
if (inGrain) {
- if (raw > targetTemp + 5) color = warning
- else if (raw > targetTemp) color = caution
+ if (raw > targetTemp + 10) color = warning
+ else if (raw > targetTemp + 5) color = caution
else color = inBounds
}
@@ -102,7 +102,7 @@ export default function BinTableExpanded(props: Props) {
const inGrain = nodeIndex < cable.topNode
if (inGrain) {
if (raw > targetMoisture + 1.5) color = warning
- else if (raw > targetMoisture) color = caution
+ else if (raw > targetMoisture + 0.5) color = caution
else color = inBounds
}
return { display: raw.toFixed(2) + "%", color }
@@ -196,15 +196,15 @@ export default function BinTableExpanded(props: Props) {
- On or below target
+ On target
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+10 °F" : "+5 °C"} above target
+ {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~10 °F" : "+5 °C"} above
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+20 °F" : "+10 °C"} above target
+ {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~20 °F" : "+10 °C"} above
diff --git a/src/bin/binSummary/BinSummary.tsx b/src/bin/binSummary/BinSummary.tsx
index 61a71ea..f868712 100644
--- a/src/bin/binSummary/BinSummary.tsx
+++ b/src/bin/binSummary/BinSummary.tsx
@@ -390,6 +390,7 @@ export default function BinSummary(props: Props){
setOpenGrainDialog(false)} permissions={permissions} updateBin={updateBinCallback}/>
{loading ? : isMobile ? inventorySummaryMobile() : inventorySummaryDesktop()}
+ {!isMobile &&
{loading ?
@@ -406,8 +407,7 @@ export default function BinSummary(props: Props){
binPrefs={binPrefs}
updateBinCallback={updateBinCallback}
/>
- {/* only show the player on desktop */}
- {setBin && !isMobile &&
+ {setBin &&
@@ -415,6 +415,7 @@ export default function BinSummary(props: Props){
}
+ }
{loading ?
@@ -428,7 +429,12 @@ export default function BinSummary(props: Props){
linkedComponents={componentMap}
permissions={permissions}
cables={cables}
- plenums={plenums}/>
+ plenums={plenums}
+ fans={fans}
+ heaters={heaters}
+ binPrefs={binPrefs}
+ componentMap={componentMap}
+ setBin={setBin}/>
}
diff --git a/src/bin/binSummary/components/binDetails.tsx b/src/bin/binSummary/components/binDetails.tsx
index 008eed0..1e42eee 100644
--- a/src/bin/binSummary/components/binDetails.tsx
+++ b/src/bin/binSummary/components/binDetails.tsx
@@ -11,6 +11,9 @@ import BinAnalysisGraphs from "./binAnalysisGraphs";
import { GrainCable } from "models/GrainCable";
import { Plenum } from "models/Plenum";
import { Controller } from "models/Controller";
+import { useMobile } from "hooks";
+import Bin3dVisualizer from "bin/bin3dVisualizer";
+import BinPlayer from "bin/BinPlayer";
interface Props {
bin: Bin
@@ -21,6 +24,12 @@ interface Props {
permissions: pond.Permission[]
cables: GrainCable[]
plenums: Plenum[]
+ showBinTab?: boolean
+ fans?: Controller[]
+ heaters?: Controller[]
+ componentMap: Map
+ binPrefs?: Map
+ setBin?: React.Dispatch>;
}
interface TabPanelProps {
@@ -51,8 +60,9 @@ function TabPanelMine(props: TabPanelProps) {
}
export default function BinDetails (props: Props) {
- const {bin, updateBinCallback, linkedComponents, componentDevices, devices, permissions, cables, plenums} = props
- const [currentTab, setCurrentTab] = useState(0)
+ const {bin, updateBinCallback, linkedComponents, componentDevices, devices, permissions, cables, plenums, showBinTab, fans, heaters, componentMap, binPrefs, setBin} = props
+ const isMobile = useMobile()
+ const [currentTab, setCurrentTab] = useState((isMobile || showBinTab) ? "bin" : "table")
//this is where we will have the tabs and tab panels for the table view, alerts, controls, and analysis
@@ -65,22 +75,48 @@ export default function BinDetails (props: Props) {
textColor="primary"
variant="fullWidth"
sx={{ flexShrink: 0 }}>
-
-
-
-
+ {isMobile && }
+
+
+
+
-
+ {(isMobile || showBinTab) &&
+
+
+ {setBin &&
+
+
+
+ }
+
+ }
+
+ {setBin && isMobile &&
+
+
+
+ }
-
+
-
+
-
+
diff --git a/src/bin/binTableView.tsx b/src/bin/binTableView.tsx
index ff3fdb0..33fcf6a 100644
--- a/src/bin/binTableView.tsx
+++ b/src/bin/binTableView.tsx
@@ -23,7 +23,6 @@ const useStyles = makeStyles((theme: Theme) => ({
headerCellStyle: {
fontWeight: 650,
backgroundColor: darken(theme.palette.background.paper, 0.2),
- fontSize: 15,
textAlign: "center",
borderTop: "1px solid black",
borderLeft: "1px solid black",
@@ -32,6 +31,18 @@ const useStyles = makeStyles((theme: Theme) => ({
borderRight: "1px solid black",
}
},
+ mobileHeaderCellStyle: {
+ fontWeight: 650,
+ backgroundColor: darken(theme.palette.background.paper, 0.2),
+ textAlign: "center",
+ borderTop: "1px solid black",
+ borderLeft: "1px solid black",
+ borderBottom: "1px solid black",
+ "&:last-child": {
+ borderRight: "1px solid black",
+ },
+ padding: 2
+ },
cellStyle: {
padding: 2,
fontWeight: 650,
@@ -164,9 +175,9 @@ export default function BinTableView(props: Props) {
if(level.grainTemps.length > 0){
lvlTemp = avg(level.grainTemps)
//if the average temp is 5 degrees above the bins threshold
- if(lvlTemp > (bin.targetTemp() + 5)){
+ if(lvlTemp > (bin.targetTemp() + 10)){
tempColour = warning
- }else if(lvlTemp > bin.targetTemp()){
+ }else if(lvlTemp > bin.targetTemp() + 5){
//if the average temp is greater than 5 degrees over
tempColour = caution
}else {
@@ -179,7 +190,7 @@ export default function BinTableView(props: Props) {
if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
lvlTemp = celsiusToFahrenheit(lvlTemp)
}
- tempDisplay = lvlTemp.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")
+ tempDisplay = lvlTemp.toFixed(2)
}
//determine the moisture to show
@@ -187,8 +198,7 @@ export default function BinTableView(props: Props) {
lvlMoisture = avg(level.grainMoistures)
if(lvlMoisture > (bin.targetMoisture() + 1.5)){
moistureColour = warning
- }else if(lvlMoisture > bin.targetMoisture()){
- //if the average temp is greater than 5 degrees over
+ }else if(lvlMoisture > bin.targetMoisture() + 0.5){
moistureColour = caution
}else {
moistureColour = inBounds
@@ -222,11 +232,11 @@ export default function BinTableView(props: Props) {
}
}
- const tempThreshDisplay = () => {
+ const tempThreshDisplay = (hideUnit?: boolean) => {
if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
- return tempTarget.toFixed(2) + " °F"
+ return tempTarget.toFixed(2) + (hideUnit ? "" : " °F")
}
- return tempTarget.toFixed(2) + " °C"
+ return tempTarget.toFixed(2) + (hideUnit ? "" : " °C")
}
const LegendSwatch = ({ color }: { color: string }) => (
@@ -287,7 +297,7 @@ export default function BinTableView(props: Props) {
return (
-
+
{expandedTableDialog()}
{/* Level Table */}
@@ -311,80 +321,80 @@ export default function BinTableView(props: Props) {
-
-
-
-
-
- Targets
-
-
-
-
-
-
- {enterTemp ?
- updateTempEntry()
- :
-
- {tempThreshDisplay()}
-
- }
- {enterTemp ?
- {
- setEnterTemp(false)
- //and update the bin with the new temp target
- update()
- }}
- >
-
-
- :
- {
- setEnterTemp(true)
- }}>
-
-
- }
-
-
-
-
-
- {enterMoisture ?
- updateMoistureEntry()
- :
-
- {bin.targetMoisture()}%
-
- }
- {enterMoisture ?
- {
- setEnterMoisture(false)
- //and update the bin with the new temp target
- update()
- }}
- >
-
-
- :
- {
- setEnterMoisture(true)
- }}>
-
-
- }
-
-
-
+
+ {/* "Targets" cell — hide on mobile when either field is being edited */}
+ {!(isMobile && (enterTemp || enterMoisture)) && (
+
+
+
+ {!isMobile &&
+
+ Targets
+
+ }
+
+
+ )}
+
+ {/* Temperature cell */}
+ {!(isMobile && enterMoisture) && (
+
+
+
+ {enterTemp ? updateTempEntry() : (
+
+ {tempThreshDisplay(isMobile)}
+
+ )}
+ {enterTemp ? (
+ { setEnterTemp(false); update(); }}>
+
+
+ ) : (
+ setEnterTemp(true)}>
+
+
+ )}
+
+
+ )}
+ {/* Moisture cell — hide on mobile when temperature is being edited */}
+ {!(isMobile && enterTemp) && (
+
+
+
+ {enterMoisture ? updateMoistureEntry() : (
+
+ {bin.targetMoisture()}%
+
+ )}
+ {enterMoisture ? (
+ { setEnterMoisture(false); update(); }}>
+
+
+ ) : (
+ setEnterMoisture(true)}>
+
+
+ )}
+
+
+ )}
+
- Level
- Temperature
- Moisture
+ Level
+
+ {isMobile ? "T" : "Temperature"}{(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " (°F)" : " (°C)")}
+
+ EMC (%)
@@ -402,18 +412,17 @@ export default function BinTableView(props: Props) {
- On or below target
+ On target
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+10 °F" : "+5 °C"} above target
+ {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~10 °F" : "+5 °C"} (0.5%) above
- {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+20 °F" : "+10 °C"} above target
+ {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~20 °F" : "+10 °C"} (1.5%) above
-
)
}
\ No newline at end of file