Skip to content

Conversation

yaugenst-flex
Copy link
Collaborator

@yaugenst-flex yaugenst-flex commented Jul 23, 2025

#2655 was merged accidentally so this is back for after the 2.9 release. refer to comments in that PR, the changes are identical

Greptile Summary

This PR adds support for imperial length units ('mil' and 'in') to the plot_length_units functionality in Tidy3D. The change addresses the need for users working with PCB designs and semiconductor layouts who prefer to visualize geometries in imperial units.

The implementation involves four key components:

  1. Type Definition: Expands the LengthUnit Literal type in types.py to include 'mil' and 'in' alongside existing metric units
  2. Unit Conversion: Adds the 'mil' conversion factor (1.0 / 25.4) to the UnitScaling dictionary in constants.py, complementing the existing 'in' unit
  3. Smart Tick Placement: Introduces a custom UnitAwareLocator class in axes_utils.py that intelligently positions matplotlib ticks for imperial units, addressing the issue where default tick placement produces awkward decimal values due to the conversion from micrometers
  4. Test Coverage: Adds a test case for plotting with 'mil' units to ensure the functionality works correctly

The feature integrates seamlessly with Tidy3D's existing plotting infrastructure. The custom locator is only applied to imperial units ('mil' and 'in'), preserving existing behavior for metric units. This ensures users see intuitive tick values like 0.1, 0.2, 0.5 mil instead of awkward decimals when plotting in imperial units.

According to the PR description, this is a re-implementation of changes from PR #2655 that were accidentally merged and then reverted for the 2.9 release.

Confidence score: 4.5/5

  • This PR appears safe to merge with well-implemented imperial unit support that follows established patterns
  • The implementation is mathematically correct and includes proper test coverage with smart fallback mechanisms
  • The axes_utils.py file needs careful review due to its complex tick placement logic and potential edge cases in the range extension and fallback mechanisms

@yaugenst-flex yaugenst-flex self-assigned this Jul 23, 2025
@yaugenst-flex yaugenst-flex linked an issue Jul 23, 2025 that may be closed by this pull request
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 Bot Settings | Greptile

Copy link
Contributor

github-actions bot commented Jul 23, 2025

Diff Coverage

Diff: origin/develop...HEAD, staged and unstaged changes

  • tidy3d/components/types.py (100%)
  • tidy3d/components/viz/axes_utils.py (38.3%): Missing lines 29-30,34,38-39,42-43,45,47,50-51,53-56,61,64-65,70-71,73,76-77,80-81,84,91,94-95

Summary

  • Total: 48 lines
  • Missing: 29 lines
  • Coverage: 39%

tidy3d/components/viz/axes_utils.py

Lines 25-88

  25             super().__init__()
  26             self.scale_factor = scale_factor
  27 
  28         def __call__(self):
! 29             vmin, vmax = self.axis.get_view_interval()
! 30             return self.tick_values(vmin, vmax)
  31 
  32         def view_limits(self, vmin, vmax):
  33             """Override to prevent matplotlib from adjusting our limits."""
! 34             return vmin, vmax
  35 
  36         def tick_values(self, vmin, vmax):
  37             # convert the view range to the target unit
! 38             vmin_unit = vmin * self.scale_factor
! 39             vmax_unit = vmax * self.scale_factor
  40 
  41             # tolerance for floating point comparisons in target unit
! 42             unit_range = vmax_unit - vmin_unit
! 43             unit_tol = unit_range * 1e-8
  44 
! 45             locator = ticker.MaxNLocator(nbins=11, prune=None, min_n_ticks=2)
  46 
! 47             ticks_unit = locator.tick_values(vmin_unit, vmax_unit)
  48 
  49             # ensure we have ticks that cover the full range
! 50             if len(ticks_unit) > 0:
! 51                 if ticks_unit[0] > vmin_unit + unit_tol or ticks_unit[-1] < vmax_unit - unit_tol:
  52                     # try with fewer bins to get better coverage
! 53                     for n in [10, 9, 8, 7, 6, 5]:
! 54                         locator = ticker.MaxNLocator(nbins=n, prune=None, min_n_ticks=2)
! 55                         ticks_unit = locator.tick_values(vmin_unit, vmax_unit)
! 56                         if (
  57                             len(ticks_unit) >= 3
  58                             and ticks_unit[0] <= vmin_unit + unit_tol
  59                             and ticks_unit[-1] >= vmax_unit - unit_tol
  60                         ):
! 61                             break
  62 
  63                 # if still no good coverage, manually ensure edge coverage
! 64                 if len(ticks_unit) > 0:
! 65                     if (
  66                         ticks_unit[0] > vmin_unit + unit_tol
  67                         or ticks_unit[-1] < vmax_unit - unit_tol
  68                     ):
  69                         # find a reasonable step size from existing ticks
! 70                         if len(ticks_unit) > 1:
! 71                             step = ticks_unit[1] - ticks_unit[0]
  72                         else:
! 73                             step = unit_range / 5
  74 
  75                         # extend the range to ensure coverage
! 76                         extended_min = vmin_unit - step
! 77                         extended_max = vmax_unit + step
  78 
  79                         # try one more time with extended range
! 80                         locator = ticker.MaxNLocator(nbins=8, prune=None, min_n_ticks=2)
! 81                         ticks_unit = locator.tick_values(extended_min, extended_max)
  82 
  83                         # filter to reasonable bounds around the original range
! 84                         ticks_unit = [
  85                             t
  86                             for t in ticks_unit
  87                             if t >= vmin_unit - step / 2 and t <= vmax_unit + step / 2
  88                         ]

Lines 87-99

  87                             if t >= vmin_unit - step / 2 and t <= vmax_unit + step / 2
  88                         ]
  89 
  90             # convert the nice ticks back to the original data unit (micrometers)
! 91             ticks_um = ticks_unit / self.scale_factor
  92 
  93             # filter to ensure ticks are within bounds (with small tolerance)
! 94             eps = (vmax - vmin) * 1e-8
! 95             return [tick for tick in ticks_um if vmin - eps <= tick <= vmax + eps]
  96 
  97     return UnitAwareLocator
  98 

@yaugenst-flex yaugenst-flex force-pushed the yaugenst-flex/units2 branch 3 times, most recently from ed946ef to a5a77fe Compare August 6, 2025 12:16
@yaugenst-flex yaugenst-flex added this pull request to the merge queue Aug 7, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to no response for status checks Aug 7, 2025
@yaugenst-flex yaugenst-flex added this pull request to the merge queue Aug 8, 2025
Merged via the queue into develop with commit 7aac129 Aug 8, 2025
30 of 31 checks passed
@yaugenst-flex yaugenst-flex deleted the yaugenst-flex/units2 branch August 8, 2025 07:46
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.

More options for 'plot_length_units' and results caching
2 participants