Skip to content

Conversation

@momchil-flex
Copy link
Collaborator

@momchil-flex momchil-flex commented Jan 28, 2026

trimesh was not being lazy because of TrimeshType. That was an easy fix.

The bulk of the changes were needed to make matplotlib lazy. This doesn't look too bad, what do you think @yaugenst ?

On my PC import time dropped from 1.9s to 1.2s.

Greptile Overview

Greptile Summary

This PR implements lazy imports for trimesh and matplotlib to reduce initial import overhead for the tidy3d package.

Key Changes:

  • Fixed trimesh lazy loading by moving TrimeshType import check from module-level to usage-time
  • Implemented comprehensive lazy loading for matplotlib across visualization components
  • Changed arrow_style from module-level constant to lazy-loading function
  • Added _ensure_tidy3d_style() mechanism to defer matplotlib style configuration until first plot
  • Moved all matplotlib imports to either TYPE_CHECKING blocks (for type hints) or function-level imports
  • Added comprehensive test suite using subprocess isolation to verify lazy import behavior

Impact:
The changes successfully defer heavy optional dependencies until they're actually needed, improving import performance for users who don't require visualization features.

Confidence Score: 4/5

  • This PR is safe to merge with one minor style improvement suggested
  • The implementation is well-structured and comprehensive, with proper TYPE_CHECKING usage and function-level imports. Tests validate the behavior. Minor inefficiency in is_valid_color that could be optimized but doesn't affect correctness.
  • Check tidy3d/components/viz/visualization_spec.py for the redundant import optimization

Important Files Changed

Filename Overview
tests/test_package/test_lazy_imports.py New comprehensive test suite for lazy imports using subprocess isolation
tidy3d/components/types/base.py Changed matplotlib Axes import to TYPE_CHECKING pattern for lazy loading
tidy3d/components/viz/init.py Added lazy style application mechanism to defer matplotlib initialization
tidy3d/components/viz/styles.py Converted arrow_style from module-level constant to lazy-loading function
tidy3d/components/viz/visualization_spec.py Made is_color_like import lazy but imports it twice per validation call

Sequence Diagram

sequenceDiagram
    participant User
    participant Tidy3d
    participant VizModule as viz/__init__.py
    participant MakeAx as make_ax()
    participant EnsureStyle as _ensure_tidy3d_style()
    participant Matplotlib

    User->>Tidy3d: import tidy3d
    Note over Tidy3d,VizModule: No matplotlib import at this stage
    Tidy3d-->>User: Module loaded (fast)
    
    User->>Tidy3d: sim.plot(z=0)
    Tidy3d->>MakeAx: ax is None, create one
    MakeAx->>EnsureStyle: Check if style applied
    
    alt First plot call
        EnsureStyle->>Matplotlib: import matplotlib
        EnsureStyle->>Matplotlib: apply_tidy3d_params()
        Note over EnsureStyle: Set _tidy3d_style_applied = True
    else Subsequent calls
        Note over EnsureStyle: Style already applied, skip
    end
    
    MakeAx->>Matplotlib: plt.subplots()
    Matplotlib-->>MakeAx: ax created
    MakeAx-->>Tidy3d: Return ax
    Tidy3d->>Matplotlib: Render plot
    Matplotlib-->>User: Display plot
Loading

Note

Improves import performance by lazily loading heavy optional deps and tightening import checks.

  • Defers matplotlib imports across plotting code by moving to function-level and TYPE_CHECKING blocks; adds _ensure_tidy3d_style() and converts arrow_style to a lazy factory
  • Lazifies trimesh usage by removing eager TrimeshType exposure and related imports from __all__
  • Updates packaging.check_import() to use importlib.util.find_spec to check availability without importing
  • Adjusts plotting paths (Scene, Simulation, EME, unstructured triangular, geometry, viz utils/styles/descartes) to import matplotlib on demand and handle fallbacks when unavailable
  • Makes color validation lazy in visualization_spec and avoids importing Axes at runtime in types
  • Adds tests/test_package/test_lazy_imports.py to verify no top-level imports of matplotlib, scipy, trimesh, vtk and that plotting triggers matplotlib

Written by Cursor Bugbot for commit 0514703. This will update automatically on new commits. Configure here.

@momchil-flex momchil-flex marked this pull request as ready for review January 28, 2026 12:32
@momchil-flex momchil-flex changed the title fix: making trimesh and matplotlib lazy FXC-5153: making trimesh and matplotlib lazy Jan 28, 2026
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 30 to 33
else:
if is_color_like is not None and not is_color_like(value):
from matplotlib.colors import is_color_like

if not is_color_like(value):
Copy link

Choose a reason for hiding this comment

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

redundant import - is_color_like is imported twice per validation call (once on line 18 during initialization, again here on line 31)

Suggested change
else:
if is_color_like is not None and not is_color_like(value):
from matplotlib.colors import is_color_like
if not is_color_like(value):
if not MATPLOTLIB_IMPORTED:
log.warning(
"matplotlib was not successfully imported, but is required "
"to validate colors in the VisualizationSpec. The specified colors "
"have not been validated."
)
return value
from matplotlib.colors import is_color_like
if not is_color_like(value):
raise ValueError(f"{value} is not a valid plotting color")
Prompt To Fix With AI
This is a comment left during a code review.
Path: tidy3d/components/viz/visualization_spec.py
Line: 30:33

Comment:
redundant import - `is_color_like` is imported twice per validation call (once on line 18 during initialization, again here on line 31)

```suggestion
    if not MATPLOTLIB_IMPORTED:
        log.warning(
            "matplotlib was not successfully imported, but is required "
            "to validate colors in the VisualizationSpec. The specified colors "
            "have not been validated."
        )
        return value
    
    from matplotlib.colors import is_color_like

    if not is_color_like(value):
        raise ValueError(f"{value} is not a valid plotting color")
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to make imports of trimesh and matplotlib lazy to reduce the initial import time of tidy3d. The bulk of the changes focus on deferring matplotlib imports until they are actually needed for plotting operations.

Changes:

  • Converted matplotlib imports from module-level to lazy imports using TYPE_CHECKING for type hints and runtime imports within functions
  • Changed arrow_style from a module-level constant to a lazy-loading function
  • Deferred apply_tidy3d_params() call to only execute when plotting functions are first used via _ensure_tidy3d_style()
  • Attempted to make trimesh lazy by removing TrimeshType from exports (incomplete implementation)

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tidy3d/components/viz/visualization_spec.py Made is_color_like import lazy with deferred check on first use
tidy3d/components/viz/styles.py Converted arrow_style from module constant to lazy-loading function
tidy3d/components/viz/descartes.py Moved matplotlib.patches and matplotlib.path imports inside functions
tidy3d/components/viz/axes_utils.py Added _ensure_tidy3d_style() call to make_ax() for deferred style application
tidy3d/components/viz/init.py Deferred apply_tidy3d_params() and added _ensure_tidy3d_style() mechanism
tidy3d/components/types/base.py Used TYPE_CHECKING for Axes import to avoid runtime matplotlib import
tidy3d/components/types/init.py Removed TrimeshType from exports (breaks existing usage)
tidy3d/components/scene.py Used TYPE_CHECKING for matplotlib imports and updated type hints to use pipe operator
tidy3d/components/simulation.py Moved matplotlib imports inside plotting methods
tidy3d/components/eme/simulation.py Moved matplotlib imports inside plotting methods
tidy3d/components/geometry/base.py Moved matplotlib imports inside methods and updated arrow_style to function call
tidy3d/components/data/unstructured/triangular.py Used TYPE_CHECKING for Triangulation import and moved pyplot import
tidy3d/components/tcad/simulation/heat_charge.py Moved colormaps import inside conditional block where used
tests/test_package/test_lazy_imports.py Added comprehensive tests for lazy imports of matplotlib, scipy, trimesh, vtk, and networkx

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"TensorReal",
"TrackFreq",
"TrimeshType",
"Undefined",
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

TrimeshType was removed from the exported types but it's not clear if it was actually being used by external code. If TrimeshType was part of the public API (it's in all), removing it could be a breaking change. Consider deprecating it first or ensuring it's not used elsewhere.

Copilot uses AI. Check for mistakes.
xyz,
)
from tidy3d.components.types.third_party import TrimeshType
from tidy3d.components.types.utils import _add_schema
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The check_import function in tidy3d.packaging.py actually imports the module via import_module, which means that when third_party.py uses check_import("trimesh"), it will import trimesh at module load time. This defeats the purpose of making trimesh lazy. The third_party.py file should also be updated to use TYPE_CHECKING pattern, or mesh.py should import trimesh.Trimesh directly instead of using TrimeshType.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@yaugenst-flex yaugenst-flex left a comment

Choose a reason for hiding this comment

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

Overall looks like a good direction, cleaner overall than before. Some comments to iron out.

Comment on lines +118 to +120
import matplotlib
matplotlib.use('Agg') # Use non-interactive backend for testing
ax = sim.plot(z=0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This doesn't actually prove plotting triggers the import because matplotlib is imported explicitly before the plotting call?

_arrow_style = None


def arrow_style():
Copy link
Collaborator

Choose a reason for hiding this comment

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

This changed from value to function and is sort of a public-ish function since it's exposed in viz/__init__.py. Could just not do that instead, I doubt anyone uses it..

Comment on lines +102 to +104
from tidy3d.components.viz import _ensure_tidy3d_style

_ensure_tidy3d_style()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe this is on purpose, but it constitutes a behavioral change. This means that the matplotlib style is no longer applied at import time and only happens when this function is used, meaning if a user passes an existing ax, our style won't be applied. This behavior might actually be better though, not sure.

tidycomplex,
xyz,
)
from tidy3d.components.types.third_party import TrimeshType
Copy link
Collaborator

Choose a reason for hiding this comment

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

Technically a breaking change...

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

return True
except ImportError:
return False
return find_spec(module_name) is not None
Copy link

Choose a reason for hiding this comment

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

Missing import error handling after check_import behavior change

Medium Severity

The change from import_module() to find_spec() in check_import removes the validation that a module can actually be imported. The old implementation caught ImportError if the import failed. Now, check_import("trimesh") returns True if the spec exists, but third_party.py then calls import trimesh without try/except. If trimesh is installed but broken (missing dependencies, etc.), this causes an unhandled ImportError crash where the old code would gracefully fall back to TrimeshType = Any.

Fix in Cursor Fix in Web

Copy link
Contributor

@marcorudolphflex marcorudolphflex left a comment

Choose a reason for hiding this comment

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

LGTM overall. I do not really have something to add but I wouldn't really mind the theoretical user-facing changes for these minor cases too much. Would also fix the testing issue pointed out by @yaugenst-flex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants