-
Notifications
You must be signed in to change notification settings - Fork 3
Fix and improve gradient validation #415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2011799
eeb6f5d
d2263ba
a3730ed
8a15190
95d16a4
b8d7b69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. merging gradient constants, classes, and settings here from archived |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,25 @@ class PlaceMethod(str, Enum): | |
| SPHERES_SST = "spheresSST" | ||
|
|
||
|
|
||
| class CoordinateSystem(str, Enum): | ||
| LEFT = "left" | ||
| RIGHT = "right" | ||
|
|
||
|
|
||
| # 3-element float array - used for 3D vectors, colors, etc. | ||
| ThreeFloatArray = List[float] | ||
|
|
||
|
|
||
| # GRADIENT CLASSES | ||
| class GradientMode(str, Enum): | ||
| X = "X" | ||
| Y = "Y" | ||
| Z = "Z" | ||
| VECTOR = "vector" | ||
| RADIAL = "radial" | ||
| SURFACE = "surface" | ||
|
|
||
|
|
||
| class WeightMode(str, Enum): | ||
| LINEAR = "linear" | ||
| SQUARE = "square" | ||
|
|
@@ -51,22 +70,67 @@ class PickMode(str, Enum): | |
| REG = "reg" | ||
|
|
||
|
|
||
| class GradientMode(str, Enum): | ||
| X = "x" | ||
| Y = "y" | ||
| Z = "z" | ||
| VECTOR = "vector" | ||
| RADIAL = "radial" | ||
| SURFACE = "surface" | ||
| class ModeOptions(str, Enum): | ||
| """ | ||
| All available options for individual modes | ||
| """ | ||
|
|
||
| direction = "direction" | ||
| center = "center" | ||
| radius = "radius" | ||
| gblob = "gblob" | ||
| object = "object" | ||
| scale_distance_between = "scale_distance_between" | ||
|
|
||
| class CoordinateSystem(str, Enum): | ||
| LEFT = "left" | ||
| RIGHT = "right" | ||
|
|
||
| class InvertOptions(str, Enum): | ||
| """ | ||
| All available options for individual invert modes | ||
| """ | ||
|
|
||
| weight = "weight" | ||
| distance = "distance" | ||
|
|
||
|
|
||
| class WeightModeOptions(str, Enum): | ||
| """ | ||
| All available options for individual weight modes | ||
| """ | ||
|
|
||
| power = "power" | ||
| decay_length = "decay_length" | ||
|
|
||
| # 3-element float array - used for 3D vectors, colors, etc. | ||
| ThreeFloatArray = List[float] | ||
|
|
||
| REQUIRED_MODE_OPTIONS = { | ||
| GradientMode.VECTOR: [ModeOptions.direction], | ||
| GradientMode.SURFACE: [ModeOptions.object], | ||
| } | ||
|
|
||
|
|
||
| DIRECTION_MAP = { | ||
| GradientMode.X: [1, 0, 0], | ||
| GradientMode.Y: [0, 1, 0], | ||
| GradientMode.Z: [0, 0, 1], | ||
| } | ||
|
|
||
|
|
||
| REQUIRED_WEIGHT_MODE_OPTIONS = { | ||
| WeightMode.POWER: [WeightModeOptions.power], | ||
| WeightMode.EXPONENTIAL: [WeightModeOptions.decay_length], | ||
| } | ||
|
|
||
|
|
||
| # default gradient settings for v2.0 to v2.1 migration | ||
| DEFAULT_GRADIENT_MODE_SETTINGS = { | ||
| "mode": "X", | ||
| "weight_mode": "linear", | ||
| "pick_mode": "linear", | ||
| "description": "Linear gradient in the X direction", | ||
| "reversed": False, | ||
| "invert": None, | ||
| "mode_settings": {}, | ||
| "weight_mode_settings": {}, | ||
| } | ||
|
|
||
|
|
||
| class WeightModeSettings(BaseModel): | ||
|
|
@@ -89,46 +153,68 @@ class RecipeGradient(BaseModel): | |
| pick_mode: PickMode = Field(PickMode.LINEAR) | ||
| weight_mode: Optional[WeightMode] = None | ||
| reversed: Optional[bool] = None | ||
| invert: Optional[bool] = None | ||
| invert: Optional[InvertOptions] = None | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed invert type to be |
||
| weight_mode_settings: Optional[WeightModeSettings] = None | ||
| mode_settings: Optional[GradientModeSettings] = None | ||
|
|
||
| @model_validator(mode="after") | ||
| def validate_mode_requirements(self): | ||
| """Validate that required `mode_settings` exist for modes that need them""" | ||
| # surface mode requires mode_settings with object | ||
| if self.mode == GradientMode.SURFACE: | ||
| if not self.mode_settings: | ||
| raise ValueError("Surface gradient mode requires 'mode_settings' field") | ||
| if ( | ||
| not hasattr(self.mode_settings, "object") | ||
| or not self.mode_settings.object | ||
| ): | ||
| raise ValueError( | ||
| "Surface gradient mode requires 'object' in mode_settings" | ||
| ) | ||
| required_options = REQUIRED_MODE_OPTIONS.get(self.mode) | ||
|
|
||
| # vector mode requires mode_settings with direction | ||
| elif self.mode == GradientMode.VECTOR: | ||
| if required_options: | ||
| if not self.mode_settings: | ||
| raise ValueError("Vector gradient mode requires 'mode_settings' field") | ||
| if ( | ||
| not hasattr(self.mode_settings, "direction") | ||
| or not self.mode_settings.direction | ||
| ): | ||
| raise ValueError( | ||
| "Vector gradient mode requires 'direction' in mode_settings" | ||
| f"{self.mode.value} gradient mode requires 'mode_settings' field" | ||
| ) | ||
|
|
||
| # validate that direction vector is not zero (only for vector mode) | ||
| import math | ||
| for option in required_options: | ||
| option_name = option.value if hasattr(option, "value") else option | ||
| if ( | ||
| not hasattr(self.mode_settings, option_name) | ||
| or getattr(self.mode_settings, option_name) is None | ||
| ): | ||
| raise ValueError( | ||
| f"{self.mode.value} gradient mode requires '{option_name}' in mode_settings" | ||
| ) | ||
|
|
||
| # vector mode direction must be non-zero | ||
| if self.mode == GradientMode.VECTOR and self.mode_settings: | ||
| if self.mode_settings.direction: | ||
| import math | ||
|
|
||
| magnitude = math.sqrt(sum(x**2 for x in self.mode_settings.direction)) | ||
| if magnitude == 0: | ||
| raise ValueError( | ||
| "Vector gradient mode requires a non-zero direction vector" | ||
| ) | ||
|
|
||
| return self | ||
|
|
||
| @model_validator(mode="after") | ||
| def validate_weight_mode_requirements(self): | ||
| """Validate that required `weight_mode_settings` exist for weight modes that need them""" | ||
| if self.weight_mode is None: | ||
| return self | ||
|
|
||
| required_options = REQUIRED_WEIGHT_MODE_OPTIONS.get(self.weight_mode) | ||
|
|
||
| magnitude = math.sqrt(sum(x**2 for x in self.mode_settings.direction)) | ||
| if magnitude == 0: | ||
| if required_options: | ||
| if not self.weight_mode_settings: | ||
| raise ValueError( | ||
| "Vector gradient mode requires a non-zero direction vector" | ||
| f"{self.weight_mode.value} weight mode requires 'weight_mode_settings' field" | ||
| ) | ||
|
|
||
| for option in required_options: | ||
| option_name = option.value if hasattr(option, "value") else option | ||
| if ( | ||
| not hasattr(self.weight_mode_settings, option_name) | ||
| or getattr(self.weight_mode_settings, option_name) is None | ||
| ): | ||
| raise ValueError( | ||
| f"{self.weight_mode.value} weight mode requires '{option_name}' in weight_mode_settings" | ||
| ) | ||
|
|
||
| return self | ||
|
|
||
|
|
||
|
|
@@ -206,7 +292,7 @@ class RecipeObject(BaseModel): | |
| # Standard format: "gradient_name" | ||
| # Multiple gradients: ["gradient1", "gradient2"] | ||
| # Unnested Firebase: {"name": "gradient_name", "mode": "surface", ...} | ||
| # Converted Firebase list: [{"name": "grad1", "mode": "x"}, {"name": "grad2", "mode": "y"}] | ||
| # Converted Firebase list: [{"name": "grad1", "mode": "X"}, {"name": "grad2", "mode": "Y"}] | ||
| gradient: Optional[ | ||
| Union[str, List[str], "RecipeGradient", List["RecipeGradient"]] | ||
| ] = None | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to still have this test for gradient data with the new validation schema, but that can be a separate PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moving the constant to recipe model to keep things centralized