Skip to content

Commit

Permalink
add 3D garden environment
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielburnworth committed Jun 28, 2024
1 parent 1bc2663 commit 748cbf0
Show file tree
Hide file tree
Showing 22 changed files with 274 additions and 64 deletions.
2 changes: 2 additions & 0 deletions app/controllers/dashboard_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class DashboardController < ApplicationController
:terminal,
:tos_update,
:try_farmbot,
:promo,
]

OUTPUT_URL = "/" + File.join("assets", "parcel") # <= served from public/ dir
Expand All @@ -32,6 +33,7 @@ class DashboardController < ApplicationController
tos_update: "/tos_update/index.tsx",
demo: "/demo/index.tsx",
try_farmbot: "/try_farmbot/index.tsx",
promo: "/promo/index.tsx",
os_download: "/os_download/index.tsx",
featured: "/featured/index.tsx",
terminal: "/terminal/index.tsx",
Expand Down
6 changes: 6 additions & 0 deletions app/views/dashboard/promo.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<% # Intentionally blank %>
<style>
body {
margin: 0;
}
</style>
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@

get "/demo" => "dashboard#demo", as: :demo_main
get "/try_farmbot" => "dashboard#try_farmbot", as: :try_farmbot_main
get "/promo" => "dashboard#promo", as: :promo_main
get "/os" => "dashboard#os_download", as: :os_download
get "/featured" => "dashboard#featured", as: :featured
get "/password_reset/*token" => "dashboard#password_reset", as: :password_reset
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class AddThreeDGardenToWebAppConfig < ActiveRecord::Migration[6.1]
def up
add_column :web_app_configs, :three_d_garden, :boolean, default: false
end

def down
remove_column :web_app_configs, :three_d_garden
end
end
6 changes: 4 additions & 2 deletions db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2036,7 +2036,8 @@ CREATE TABLE public.web_app_configs (
show_uncropped_camera_view_area boolean DEFAULT false,
default_plant_depth integer DEFAULT 5,
show_missed_step_plot boolean DEFAULT false,
enable_3d_electronics_box_top boolean DEFAULT true
enable_3d_electronics_box_top boolean DEFAULT true,
three_d_garden boolean DEFAULT false
);


Expand Down Expand Up @@ -3987,6 +3988,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20240118204046'),
('20240202171922'),
('20240207234421'),
('20240405171128');
('20240405171128'),
('20240625195838');


1 change: 1 addition & 0 deletions frontend/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2166,6 +2166,7 @@ export enum DeviceSetting {
showReadingsMapLayer = `Show Readings Map Layer`,
showMoisture = `Moisture`,
showMoistureInterpolationMapLayer = `Show Moisture Interpolation Map Layer`,
show3DMap = `3D Map`,

// Controls
invertJogButtonXAxis = `X Axis`,
Expand Down
7 changes: 7 additions & 0 deletions frontend/css/farm_designer/farm_designer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1201,3 +1201,10 @@
}
}
}

.three-d-garden {
position: relative;
height: 100vh;
width: 100vw;
cursor: grab;
}
8 changes: 8 additions & 0 deletions frontend/farm_designer/__tests__/index_test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ describe("<FarmDesigner />", () => {
p.designer.openedSavedGarden = 1;
const wrapper = mount(<FarmDesigner {...p} />);
expect(wrapper.text().toLowerCase()).toContain("viewing saved garden");
expect(wrapper.html()).not.toContain("three-d-garden");
});

it("toggles setting", () => {
Expand Down Expand Up @@ -157,6 +158,13 @@ describe("<FarmDesigner />", () => {
const i = new FarmDesigner(p);
expect(i.getBotOriginQuadrant()).toEqual(1);
});

it("renders 3D garden", () => {
const p = fakeProps();
p.getConfigValue = () => true;
const wrapper = mount(<FarmDesigner {...p} />);
expect(wrapper.html()).toContain("three-d-garden");
});
});

describe("getDefaultAxisLength()", () => {
Expand Down
13 changes: 13 additions & 0 deletions frontend/farm_designer/__tests__/three_d_garden_map_test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from "react";
import { mount } from "enzyme";
import { ThreeDGardenMapProps, ThreeDGardenMap } from "../three_d_garden_map";

describe("<ThreeDGardenMap />", () => {
const fakeProps = (): ThreeDGardenMapProps => ({
});

it("renders", () => {
const wrapper = mount(<ThreeDGardenMap {...fakeProps()} />);
expect(wrapper.html()).toContain("three-d-garden");
});
});
126 changes: 65 additions & 61 deletions frontend/farm_designer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { SavedGardenHUD } from "../saved_gardens/saved_gardens";
import { calculateImageAgeInfo } from "../photos/photo_filter_settings/util";
import { Xyz } from "farmbot";
import { ProfileViewer } from "./map/profile";
import { ThreeDGardenMap } from "./three_d_garden_map";

export const getDefaultAxisLength =
(getConfigValue: GetWebAppConfigValue): Record<Xyz, number> => {
Expand Down Expand Up @@ -192,72 +193,75 @@ export class RawFarmDesigner
{this.props.children || React.createElement(Plants)}
</div>

<div
className={`farm-designer-map ${this.mapPanelClassName}`}
style={{
transform: `scale(${zoom_level})`,
transformOrigin: `${mapPadding.left}px ${mapPadding.top}px`,
height: `calc(${100 / zoom_level}% + ${padHeightOffset}px)`
}}>
<GardenMap
showPoints={show_points}
showPlants={show_plants}
showWeeds={show_weeds}
showSpread={show_spread}
showFarmbot={show_farmbot}
showImages={show_images}
showZones={show_zones}
showSensorReadings={show_sensor_readings}
selectedPlant={this.props.selectedPlant}
crops={this.props.crops}
{this.props.getConfigValue(BooleanSetting.three_d_garden)
? <ThreeDGardenMap />
: <div
className={`farm-designer-map ${this.mapPanelClassName}`}
style={{
transform: `scale(${zoom_level})`,
transformOrigin: `${mapPadding.left}px ${mapPadding.top}px`,
height: `calc(${100 / zoom_level}% + ${padHeightOffset}px)`
}}>
<GardenMap
showPoints={show_points}
showPlants={show_plants}
showWeeds={show_weeds}
showSpread={show_spread}
showFarmbot={show_farmbot}
showImages={show_images}
showZones={show_zones}
showSensorReadings={show_sensor_readings}
selectedPlant={this.props.selectedPlant}
crops={this.props.crops}
designer={this.props.designer}
plants={this.props.plants}
genericPoints={this.props.genericPoints}
weeds={this.props.weeds}
allPoints={this.props.allPoints}
toolSlots={this.props.toolSlots}
botLocationData={this.props.botLocationData}
botSize={this.props.botSize}
stopAtHome={stopAtHome}
hoveredPlant={this.props.hoveredPlant}
zoomLvl={zoom_level}
mapTransformProps={this.mapTransformProps}
gridOffset={gridOffset}
peripheralValues={this.props.peripheralValues}
eStopStatus={this.props.eStopStatus}
latestImages={this.props.latestImages}
cameraCalibrationData={this.props.cameraCalibrationData}
getConfigValue={this.props.getConfigValue}
sensorReadings={this.props.sensorReadings}
timeSettings={this.props.timeSettings}
sensors={this.props.sensors}
groups={this.props.groups}
logs={this.props.logs}
deviceTarget={this.props.deviceTarget}
mountedToolInfo={this.props.mountedToolInfo}
visualizedSequenceBody={this.props.visualizedSequenceBody}
farmwareEnvs={this.props.farmwareEnvs}
curves={this.props.curves}
dispatch={this.props.dispatch} />
</div>}

{this.props.designer.openedSavedGarden &&
<SavedGardenHUD dispatch={this.props.dispatch} />}

{!this.props.getConfigValue(BooleanSetting.three_d_garden) &&
<ProfileViewer
getConfigValue={this.props.getConfigValue}
dispatch={this.props.dispatch}
designer={this.props.designer}
plants={this.props.plants}
genericPoints={this.props.genericPoints}
weeds={this.props.weeds}
allPoints={this.props.allPoints}
toolSlots={this.props.toolSlots}
botLocationData={this.props.botLocationData}
botSize={this.props.botSize}
stopAtHome={stopAtHome}
hoveredPlant={this.props.hoveredPlant}
zoomLvl={zoom_level}
mapTransformProps={this.mapTransformProps}
gridOffset={gridOffset}
botLocationData={this.props.botLocationData}
peripheralValues={this.props.peripheralValues}
eStopStatus={this.props.eStopStatus}
latestImages={this.props.latestImages}
cameraCalibrationData={this.props.cameraCalibrationData}
getConfigValue={this.props.getConfigValue}
sensorReadings={this.props.sensorReadings}
timeSettings={this.props.timeSettings}
sensors={this.props.sensors}
groups={this.props.groups}
logs={this.props.logs}
deviceTarget={this.props.deviceTarget}
negativeZ={!!this.props.botMcuParams.movement_home_up_z}
sourceFbosConfig={this.props.sourceFbosConfig}
mountedToolInfo={this.props.mountedToolInfo}
visualizedSequenceBody={this.props.visualizedSequenceBody}
tools={this.props.tools}
farmwareEnvs={this.props.farmwareEnvs}
curves={this.props.curves}
dispatch={this.props.dispatch} />
</div>

{this.props.designer.openedSavedGarden &&
<SavedGardenHUD dispatch={this.props.dispatch} />}

<ProfileViewer
getConfigValue={this.props.getConfigValue}
dispatch={this.props.dispatch}
designer={this.props.designer}
botSize={this.props.botSize}
botLocationData={this.props.botLocationData}
peripheralValues={this.props.peripheralValues}
negativeZ={!!this.props.botMcuParams.movement_home_up_z}
sourceFbosConfig={this.props.sourceFbosConfig}
mountedToolInfo={this.props.mountedToolInfo}
tools={this.props.tools}
farmwareEnvs={this.props.farmwareEnvs}
mapTransformProps={this.mapTransformProps}
allPoints={this.props.allPoints} />
mapTransformProps={this.mapTransformProps}
allPoints={this.props.allPoints} />}
</div>;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ jest.mock("../../../../config_storage/actions", () => ({
setWebAppConfigValue: jest.fn(),
}));

let mockDev = false;
jest.mock("../../../../settings/dev/dev_support", () => ({
DevSettings: { futureFeaturesEnabled: () => mockDev }
}));

import React from "react";
import { shallow, mount } from "enzyme";
import {
Expand Down Expand Up @@ -63,6 +68,7 @@ describe("<GardenMapLegend />", () => {
expect(wrapper.html()).toContain("filter");
expect(wrapper.html()).toContain("extras");
expect(wrapper.html()).not.toContain("-100");
expect(wrapper.text().toLowerCase()).not.toContain("3d map");
});

it("renders with readings", () => {
Expand All @@ -77,6 +83,16 @@ describe("<GardenMapLegend />", () => {
wrapper.find(".fb-toggle-button").last().simulate("click");
expect(wrapper.html()).toContain("-100");
});

it("renders 3D map toggle", () => {
mockDev = true;
const p = fakeProps();
const wrapper = mount(<GardenMapLegend {...p} />);
expect(wrapper.text().toLowerCase()).toContain("3d map");
wrapper.find(".fb-layer-toggle").last().simulate("click");
expect(setWebAppConfigValue).toHaveBeenCalledWith(
BooleanSetting.three_d_garden, true);
});
});

describe("<ZoomControls />", () => {
Expand Down
9 changes: 9 additions & 0 deletions frontend/farm_designer/map/legend/garden_map_legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getModifiedClassName } from "../../../settings/default_values";
import { Position } from "@blueprintjs/core";
import { MapSizeInputs } from "../../map_size_setting";
import { OriginSelector } from "../../../settings/farm_designer_settings";
import { DevSettings } from "../../../settings/dev/dev_support";

export const ZoomControls = ({ zoom, getConfigValue }: {
zoom: (value: number) => () => void,
Expand Down Expand Up @@ -115,6 +116,7 @@ export const FarmbotSubMenu = (props: SettingsSubMenuProps) =>
const LayerToggles = (props: GardenMapLegendProps) => {
const { toggle, getConfigValue, dispatch } = props;
const subMenuProps = { dispatch, getConfigValue };
const threeDGarden = !!props.getConfigValue(BooleanSetting.three_d_garden);
return <div className="toggle-buttons">
<LayerToggle
settingName={BooleanSetting.show_plants}
Expand Down Expand Up @@ -196,6 +198,13 @@ const LayerToggles = (props: GardenMapLegendProps) => {
value={props.showMoistureInterpolationMap}
label={DeviceSetting.showMoisture}
onClick={toggle(BooleanSetting.show_moisture_interpolation_map)} />}
{DevSettings.futureFeaturesEnabled() &&
<LayerToggle
settingName={BooleanSetting.three_d_garden}
value={threeDGarden}
label={DeviceSetting.show3DMap}
onClick={() => dispatch(setWebAppConfigValue(
BooleanSetting.three_d_garden, !threeDGarden))} />}
</div>;
};

Expand Down
1 change: 1 addition & 0 deletions frontend/farm_designer/map/legend/layer_toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function LayerToggle(props: LayerToggleProps) {
const classNames = [
"fb-button",
"fb-toggle-button",
"fb-layer-toggle",
value ? "green" : "red",
getModifiedClassName(props.settingName),
].join(" ");
Expand Down
10 changes: 10 additions & 0 deletions frontend/farm_designer/three_d_garden_map.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from "react";
import { ThreeDGarden } from "../three_d_garden";

export interface ThreeDGardenMapProps {
}

export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => {
props;
return <ThreeDGarden />;
};
11 changes: 11 additions & 0 deletions frontend/promo/__tests__/index_test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
jest.mock("../../util/page", () => ({ entryPoint: jest.fn() }));

import { entryPoint } from "../../util";
import { Promo } from "../promo";

describe("Promo loader", () => {
it("calls entryPoint", async () => {
await import("../index");
expect(entryPoint).toHaveBeenCalledWith(Promo);
});
});
11 changes: 11 additions & 0 deletions frontend/promo/__tests__/promo_test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";
import { shallow } from "enzyme";
import { Promo } from "../promo";

describe("<Promo />", () => {
it("renders", () => {
console.error = jest.fn();
const wrapper = shallow(<Promo />);
expect(wrapper.html()).toContain("three-d-garden");
});
});
4 changes: 4 additions & 0 deletions frontend/promo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { entryPoint } from "../util";
import { Promo } from "./promo";

entryPoint(Promo);
5 changes: 5 additions & 0 deletions frontend/promo/promo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from "react";
import { ThreeDGarden } from "../three_d_garden";

export const Promo = () =>
<ThreeDGarden />;
3 changes: 2 additions & 1 deletion frontend/session_keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
StringConfigKey as WebAppStringConfigKey,
} from "farmbot/dist/resources/configs/web_app";

type WebAppBooleanConfigKeyAll = WebAppBooleanConfigKey;
type WebAppBooleanConfigKeyAll = WebAppBooleanConfigKey | "three_d_garden";
type WebAppNumberConfigKeyAll = WebAppNumberConfigKey;
type WebAppStringConfigKeyAll = WebAppStringConfigKey;

Expand Down Expand Up @@ -49,6 +49,7 @@ export const BooleanSetting: BooleanSettings = {
crop_images: "crop_images",
clip_image_layer: "clip_image_layer",
highlight_modified_settings: "highlight_modified_settings",
three_d_garden: "three_d_garden" as WebAppBooleanConfigKey,

/** Sequence settings */
confirm_step_deletion: "confirm_step_deletion",
Expand Down
Loading

0 comments on commit 748cbf0

Please sign in to comment.