Skip to content

Glowing led decks in sim.#45

Merged
amacati merged 8 commits intolearnsyslab:devfrom
yufei4hua:dev
Nov 21, 2025
Merged

Glowing led decks in sim.#45
amacati merged 8 commits intolearnsyslab:devfrom
yufei4hua:dev

Conversation

@yufei4hua
Copy link
Copy Markdown
Collaborator

  1. Add led deck geom and corresponding materials to cf21B_500.xml
  2. Implement change_material method under sim/visualize.py to change certain materials' rgba & emission values.
  3. Input sim, specify mat_name, drone_ids, rgba, and emission. Effects are long term valid.
  4. Add example script for rendering led decks, with top/bottom led activated for drones with even/odd ids. BUT requires drone_model package.
    p.s. User can dim the scene by specifying their own scene.xml when creating the Sim.

Copy link
Copy Markdown
Collaborator

@amacati amacati left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a cool functionality, but there are several things that need to change.

For every new addition to the sim that is relatively confined, let's also try to add tests to make sure it works and doesn't break with future iterations.

For these kind of tests, please also check if it does not succeed for problematic inputs. See e.g. https://docs.pytest.org/en/7.1.x/how-to/assert.html#assertions-about-expected-exceptions

Comment thread crazyflow/sim/visualize.py Outdated
raise ValueError(f"drone_ids must be in range [0, {sim.n_drones - 1}], got {drone_ids}")

if rgba is not None:
rgba = np.asarray(rgba, dtype=float)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function signature already sais we require a numpy array. Or is the cast because we need dtype to be float64?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

Comment thread crazyflow/sim/visualize.py Outdated
Comment on lines +95 to +99
try:
# this returns itself if rgba is already the right shape
rgba = np.broadcast_to(rgba, (len(drone_ids), 4)).copy()
except Exception:
raise ValueError(f"rgba must be shape (4,) or ({len(drone_ids)}, 4), got {rgba.shape}")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the try block? If this fails, just let it fail. Also, try to avoid bare Exceptions at all cost. They can be extremely confusing for users. Anything could go wrong, but they wouldn't see any of it because the bare except just swallows the error.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.
The np.broadcast_to() will report:
"ValueError: operands could not be broadcast together with remapped shapes [original->remapped]: (3,4) and requested shape (13,4)"
if shape is incorrect, which is also clear.

Comment thread crazyflow/sim/visualize.py Outdated
rgba = np.broadcast_to(rgba, (len(drone_ids), 4)).copy()
except Exception:
raise ValueError(f"rgba must be shape (4,) or ({len(drone_ids)}, 4), got {rgba.shape}")
rgba = np.clip(rgba, 0.0, 1.0)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the clip? Can't mujoco deal with [2 0 0 0]?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we don't. Removed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, I found that mujoco can also handle emission<0, so I removed "emission = np.maximum(emission, 0.0)" as well.

Comment thread crazyflow/sim/visualize.py Outdated
rgba = np.clip(rgba, 0.0, 1.0)

if emission is not None:
emission = np.asarray(emission, dtype=float)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing. Do we need the cast?

Comment thread crazyflow/sim/visualize.py Outdated

if emission is not None:
emission = np.asarray(emission, dtype=float)
try:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just fail fast, it's okay to error in the function.

Comment thread crazyflow/sim/visualize.py Outdated

mj_model = sim.mj_model

for i, id in enumerate(drone_ids):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use id, it's a built-in function that you are shadowing (https://docs.python.org/3/library/functions.html#id).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to drone_id

Comment thread crazyflow/sim/visualize.py Outdated
Comment on lines +120 to +124
if rgba is not None:
mj_model.mat_rgba[mat_id, :] = rgba[i]

if emission is not None:
mj_model.mat_emission[mat_id] = emission[i]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we broadcast this? I.e. create an index of the drones and then set mj_model.mat_rgba[mat_id, drone_ids] = rgba?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can.
However, we still need to loop over the drone_ids to find the corresponding mat_id.
I changed it to mj_model.mat_rgba[mat_ids, :] = rgba

if emission is not None:
mj_model.mat_emission[mat_id] = emission[i]


Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for this function! Even if we are not rendering in the tests, we should at least try to see if this errors as expected and succeeds for the correct input.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added 2 small tests under test_sim.py
One mainly to make sure all the drone models have "led_top" and "led_bot" in xml file.
One to test the error output of the function.

Comment thread examples/led_deck.py Outdated
Comment on lines +10 to +18
n_worlds, n_drones = 1, 25
sim = Sim(
n_worlds=n_worlds,
n_drones=n_drones,
drone_model="cf21B_500",
control=Control.state,
physics=Physics.so_rpy,
device="cpu",
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The basic scripts made these kwargs explicit to show users the options we provide with the sim. In specialized examples such as this one, we can skip this.

Suggested change
n_worlds, n_drones = 1, 25
sim = Sim(
n_worlds=n_worlds,
n_drones=n_drones,
drone_model="cf21B_500",
control=Control.state,
physics=Physics.so_rpy,
device="cpu",
)
sim = Sim(n_drones=25, drone_model="cf21B_500")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified.

Comment thread crazyflow/sim/visualize.py Outdated
rgba: NDArray | None = None,
emission: NDArray | None = None,
):
"""Change the material of all drones matching the mask.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a mask, but the drone_ids, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, corrected.

Comment thread examples/led_deck.py Outdated

def main():
"""Spawn 25 drones in one world and activate led decks."""
sim = Sim(n_drones=25, drone_model="cf21B_500", control=Control.state)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use the new ones, they look great. When I commented on removing the defaults I was referring to repeating stuff that is already set by default. I.e. n_worlds is 1 by default, so there is no need to repeat it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understood. I was trying to align with other examples.
I reverted this in the latest commit.

Use new drones in example.
@yufei4hua
Copy link
Copy Markdown
Collaborator Author

Thanks a lot for your reviews! I learnt a lot from that.
Please review the tests I wrote for this method. I'm not sure if this is the way to do it.

Copy link
Copy Markdown
Collaborator

@amacati amacati left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested some very minor changes, otherwise this looks good.

Comment thread crazyflow/sim/visualize.py Outdated
raise ValueError(f"drone_ids must be in range [0, {sim.n_drones - 1}], got {drone_ids}")

if rgba is not None:
# this returns itself if rgba is already the right shape
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# this returns itself if rgba is already the right shape

Comment thread crazyflow/sim/visualize.py Outdated
mat_ids.append(mat_id)

if rgba is not None:
mj_model.mat_rgba[mat_ids, :] = rgba
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is [mat_ids, :] necessary?

Suggested change
mj_model.mat_rgba[mat_ids, :] = rgba
mj_model.mat_rgba[mat_ids] = rgba

Comment thread tests/unit/test_sim.py Outdated
sim, mat_name="bad_mat", drone_ids=drone_ids, rgba=rgba, emission=emission
)

with pytest.raises(ValueError, match=r"drone_ids must be 1D array"):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need the r-string. But please check.

Suggested change
with pytest.raises(ValueError, match=r"drone_ids must be 1D array"):
with pytest.raises(ValueError, match="drone_ids must be 1D array"):

Comment thread tests/unit/test_sim.py
emission=emission,
)

with pytest.raises(ValueError, match=r"drone_ids must be in range \[0, 1\]"):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, but again, please check.

Suggested change
with pytest.raises(ValueError, match=r"drone_ids must be in range \[0, 1\]"):
with pytest.raises(ValueError, match="drone_ids must be in range [0, 1]"):

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked up, so "r" s needed when "[]" appears in a regular expression.
I'll leave it for the second check.

@amacati
Copy link
Copy Markdown
Collaborator

amacati commented Nov 20, 2025

Thanks a lot for your reviews! I learnt a lot from that. Please review the tests I wrote for this method. I'm not sure if this is the way to do it.

Yes, that was what I had in mind. Thanks for adding those.

@amacati amacati self-assigned this Nov 20, 2025
@amacati amacati added the enhancement New feature or request label Nov 20, 2025
@yufei4hua
Copy link
Copy Markdown
Collaborator Author

I applied the minor suggestions. Sorry for the delay.

Copy link
Copy Markdown
Collaborator

@amacati amacati left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@amacati amacati merged commit 057cd6c into learnsyslab:dev Nov 21, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants