Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions apple_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import sys

from pyhelios import Context, PlantArchitecture, Visualizer
from pyhelios.types import RGBcolor, vec3


def build_apple_tree(plantarch, position=None, age_days=365.0, build_parameters=None):
"""Load the apple library model and build one tree instance.

Args:
plantarch: PlantArchitecture instance bound to a Context.
position: Base of the trunk (default: origin).
age_days: Plant age in days. Older = larger / more developed.
build_parameters: Optional training overrides, e.g.
{'trunk_height': 0.8, 'num_scaffolds': 4, 'scaffold_angle': 40}

Returns:
plant_id for the created tree.
"""
if position is None:
position = vec3(0, 0, 0)

if build_parameters and build_parameters.get("trunk_height", 0.8) > 0.8:
raise ValueError(
"The built-in apple model requires trunk_height <= 0.80 m "
"(20 nodes at 0.04 m per internode)."
)

plantarch.loadPlantModelFromLibrary("apple")
plant_id = plantarch.buildPlantInstanceFromLibrary(
base_position=position,
age=age_days,
build_parameters=build_parameters,
)
return plant_id


def visualize_trees(context, plantarch, plant_ids):
"""Open one interactive window showing all built trees together."""
all_uuids = []
for plant_id in plant_ids:
uuids = plantarch.getAllPlantUUIDs(plant_id)
if not uuids:
raise RuntimeError(f"Plant {plant_id} has no geometry to visualize")
all_uuids.extend(uuids)

if not all_uuids:
raise RuntimeError("No tree geometry to visualize")

# Fit the camera to the combined scene bounding box
x_bounds, y_bounds, z_bounds = context.getDomainBoundingBox(all_uuids)
center = vec3(
0.5 * (x_bounds.x + x_bounds.y),
0.5 * (y_bounds.x + y_bounds.y),
0.5 * (z_bounds.x + z_bounds.y),
)
extent = max(
x_bounds.y - x_bounds.x,
y_bounds.y - y_bounds.x,
z_bounds.y - z_bounds.x,
1.0,
)

with Visualizer(width=1000, height=800) as visualizer:
visualizer.buildContextGeometry(context, uuids=all_uuids)
visualizer.setCameraPosition(
position=vec3(
center.x + 1.8 * extent,
center.y + 1.8 * extent,
center.z + 0.6 * extent,
),
lookAt=center,
)
visualizer.setBackgroundColor(RGBcolor(0.70, 0.85, 1.0))
visualizer.setLightingModel("phong_shadowed")
visualizer.plotInteractive()


if __name__ == "__main__":
# Age is the main "size" control for library plants (days).
# Try ~90 for a young tree, ~365 for a year-old tree.
AGE_DAYS = [720.0] * 10

POSITIONS = [
vec3(0, 0, 0),
vec3(1.5, 0, 0),
vec3(3, 0, 0),
vec3(9, 0, 0),
vec3(12, 0, 0),
vec3(15, 0, 0),
vec3(18, 0, 0),
vec3(21, 0, 0),
vec3(24, 0, 0),
vec3(27, 0, 0),
]

# Optional apple training-system overrides (omit to use defaults):
BUILD_PARAMS_LIST = [
{"trunk_height": 0.72, "num_scaffolds": 4, "scaffold_angle": 33},
{"trunk_height": 0.78, "num_scaffolds": 6, "scaffold_angle": 47},
{"trunk_height": 0.79, "num_scaffolds": 5, "scaffold_angle": 39},
{"trunk_height": 0.74, "num_scaffolds": 4, "scaffold_angle": 44},
{"trunk_height": 0.70, "num_scaffolds": 6, "scaffold_angle": 31},
{"trunk_height": 0.80, "num_scaffolds": 5, "scaffold_angle": 50},
{"trunk_height": 0.76, "num_scaffolds": 4, "scaffold_angle": 36},
{"trunk_height": 0.77, "num_scaffolds": 5, "scaffold_angle": 42},
{"trunk_height": 0.71, "num_scaffolds": 6, "scaffold_angle": 35},
{"trunk_height": 0.74, "num_scaffolds": 5, "scaffold_angle": 48},
]

with Context() as context:
with PlantArchitecture(context) as plantarch:
plantarch.loadPlantModelFromLibrary("apple")

# Include fruit in collision detection and steer growth away from
# overlaps (fruit is excluded by default) so apples on neighboring
# trees/branches don't grow into each other.
plantarch.setCollisionRelevantOrgans(
include_internodes=True,
include_leaves=True,
include_fruit=True,
)
plantarch.enableSoftCollisionAvoidance(enable_fruit_collision=True)

plant_ids = []

for i in range(3):
plant_id_i = build_apple_tree(
plantarch,
position=POSITIONS[i],
age_days=AGE_DAYS[i],
build_parameters=BUILD_PARAMS_LIST[i],
)
plant_ids.append(plant_id_i)

object_ids = plantarch.getAllPlantObjectIDs(plant_id_i)
uuids = plantarch.getAllPlantUUIDs(plant_id_i)
x_bounds, y_bounds, z_bounds = context.getDomainBoundingBox(uuids)
print(f"Apple tree plant_id={plant_id_i}")
print(f" objects: {len(object_ids)}")
print(f" primitives: {len(uuids)}")
print(f" age: {AGE_DAYS[i]} days")
print(f" extent: x={x_bounds.y - x_bounds.x:.3f}m, y={y_bounds.y - y_bounds.x:.3f}m, z={z_bounds.y - z_bounds.x:.3f}m")

if "--no-visualization" not in sys.argv:
visualize_trees(context, plantarch, plant_ids)
76 changes: 76 additions & 0 deletions apple_tree_cameras.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import os
import sys

from apple_tree import build_apple_tree
from pyhelios import Context, PlantArchitecture, Visualizer
from pyhelios.types import RGBcolor, vec3

# Gaussian-splat capture rig: all 3 cameras sit at the same horizontal
# distance in front of the tree (same x, same y offset) and differ only in
# elevation (z) -- one above the tree looking down, one level with the
# tree's center, one low near the ground looking up. The same rig is
# reused for every tree, just re-centered on that tree's position/height.
CAMERA_DISTANCE = 2.5 # front offset from the tree, in meters, same for all rigs

CAMERA_RIGS = [
{"name": "above", "height_frac": 1.15},
{"name": "level", "height_frac": 0.5},
{"name": "below", "height_frac": 0.05},
]

OUTPUT_DIR = "renders"


def camera_position_for_tree(context, plantarch, plant_id, base_position, rig):
"""Compute a camera position/lookAt in front of one tree for a given rig."""
uuids = plantarch.getAllPlantUUIDs(plant_id)
x_bounds, y_bounds, z_bounds = context.getDomainBoundingBox(uuids)
tree_height = z_bounds.y - z_bounds.x

look_at = vec3(base_position.x, base_position.y, z_bounds.x + 0.5 * tree_height)
position = vec3(
base_position.x,
base_position.y - CAMERA_DISTANCE,
z_bounds.x + rig["height_frac"] * tree_height,
)
return position, look_at


if __name__ == "__main__":
AGE_DAYS = 365.0
POSITIONS = [vec3(0, 0, 0), vec3(3, 0, 0), vec3(6, 0, 0)]

os.makedirs(OUTPUT_DIR, exist_ok=True)

with Context() as context:
with PlantArchitecture(context) as plantarch:
plant_ids = []
for i, position in enumerate(POSITIONS):
plant_id = build_apple_tree(plantarch, position=position, age_days=AGE_DAYS)
plant_ids.append(plant_id)
print(f"Built apple tree plant_id={plant_id} at {position}")

all_uuids = []
for plant_id in plant_ids:
all_uuids.extend(plantarch.getAllPlantUUIDs(plant_id))

with Visualizer(width=1000, height=800, headless=True) as visualizer:
visualizer.buildContextGeometry(context, uuids=all_uuids)
visualizer.setBackgroundColor(RGBcolor(0.70, 0.85, 1.0))
visualizer.setLightingModel("phong_shadowed")

for plant_id, position in zip(plant_ids, POSITIONS):
for rig in CAMERA_RIGS:
camera_pos, look_at = camera_position_for_tree(
context, plantarch, plant_id, position, rig
)
visualizer.setCameraPosition(position=camera_pos, lookAt=look_at)
visualizer.plotUpdate()

filename = os.path.join(
OUTPUT_DIR, f"tree{plant_id}_{rig['name']}.png"
)
visualizer.printWindow(filename)
print(f" Saved {filename}")

print(f"\nDone. Images written to {OUTPUT_DIR}/")