options.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import json
  2. import sys
  3. import gradio as gr
  4. from modules import errors
  5. from modules.shared_cmd_options import cmd_opts
  6. class OptionInfo:
  7. def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after='', infotext=None, restrict_api=False):
  8. self.default = default
  9. self.label = label
  10. self.component = component
  11. self.component_args = component_args
  12. self.onchange = onchange
  13. self.section = section
  14. self.refresh = refresh
  15. self.do_not_save = False
  16. self.comment_before = comment_before
  17. """HTML text that will be added after label in UI"""
  18. self.comment_after = comment_after
  19. """HTML text that will be added before label in UI"""
  20. self.infotext = infotext
  21. self.restrict_api = restrict_api
  22. """If True, the setting will not be accessible via API"""
  23. def link(self, label, url):
  24. self.comment_before += f"[<a href='{url}' target='_blank'>{label}</a>]"
  25. return self
  26. def js(self, label, js_func):
  27. self.comment_before += f"[<a onclick='{js_func}(); return false'>{label}</a>]"
  28. return self
  29. def info(self, info):
  30. self.comment_after += f"<span class='info'>({info})</span>"
  31. return self
  32. def html(self, html):
  33. self.comment_after += html
  34. return self
  35. def needs_restart(self):
  36. self.comment_after += " <span class='info'>(requires restart)</span>"
  37. return self
  38. def needs_reload_ui(self):
  39. self.comment_after += " <span class='info'>(requires Reload UI)</span>"
  40. return self
  41. class OptionHTML(OptionInfo):
  42. def __init__(self, text):
  43. super().__init__(str(text).strip(), label='', component=lambda **kwargs: gr.HTML(elem_classes="settings-info", **kwargs))
  44. self.do_not_save = True
  45. def options_section(section_identifier, options_dict):
  46. for v in options_dict.values():
  47. v.section = section_identifier
  48. return options_dict
  49. options_builtin_fields = {"data_labels", "data", "restricted_opts", "typemap"}
  50. class Options:
  51. typemap = {int: float}
  52. def __init__(self, data_labels: dict[str, OptionInfo], restricted_opts):
  53. self.data_labels = data_labels
  54. self.data = {k: v.default for k, v in self.data_labels.items()}
  55. self.restricted_opts = restricted_opts
  56. def __setattr__(self, key, value):
  57. if key in options_builtin_fields:
  58. return super(Options, self).__setattr__(key, value)
  59. if self.data is not None:
  60. if key in self.data or key in self.data_labels:
  61. assert not cmd_opts.freeze_settings, "changing settings is disabled"
  62. info = self.data_labels.get(key, None)
  63. if info.do_not_save:
  64. return
  65. comp_args = info.component_args if info else None
  66. if isinstance(comp_args, dict) and comp_args.get('visible', True) is False:
  67. raise RuntimeError(f"not possible to set {key} because it is restricted")
  68. if cmd_opts.hide_ui_dir_config and key in self.restricted_opts:
  69. raise RuntimeError(f"not possible to set {key} because it is restricted")
  70. self.data[key] = value
  71. return
  72. return super(Options, self).__setattr__(key, value)
  73. def __getattr__(self, item):
  74. if item in options_builtin_fields:
  75. return super(Options, self).__getattribute__(item)
  76. if self.data is not None:
  77. if item in self.data:
  78. return self.data[item]
  79. if item in self.data_labels:
  80. return self.data_labels[item].default
  81. return super(Options, self).__getattribute__(item)
  82. def set(self, key, value, is_api=False, run_callbacks=True):
  83. """sets an option and calls its onchange callback, returning True if the option changed and False otherwise"""
  84. oldval = self.data.get(key, None)
  85. if oldval == value:
  86. return False
  87. option = self.data_labels[key]
  88. if option.do_not_save:
  89. return False
  90. if is_api and option.restrict_api:
  91. return False
  92. try:
  93. setattr(self, key, value)
  94. except RuntimeError:
  95. return False
  96. if run_callbacks and option.onchange is not None:
  97. try:
  98. option.onchange()
  99. except Exception as e:
  100. errors.display(e, f"changing setting {key} to {value}")
  101. setattr(self, key, oldval)
  102. return False
  103. return True
  104. def get_default(self, key):
  105. """returns the default value for the key"""
  106. data_label = self.data_labels.get(key)
  107. if data_label is None:
  108. return None
  109. return data_label.default
  110. def save(self, filename):
  111. assert not cmd_opts.freeze_settings, "saving settings is disabled"
  112. with open(filename, "w", encoding="utf8") as file:
  113. json.dump(self.data, file, indent=4)
  114. def same_type(self, x, y):
  115. if x is None or y is None:
  116. return True
  117. type_x = self.typemap.get(type(x), type(x))
  118. type_y = self.typemap.get(type(y), type(y))
  119. return type_x == type_y
  120. def load(self, filename):
  121. with open(filename, "r", encoding="utf8") as file:
  122. self.data = json.load(file)
  123. # 1.6.0 VAE defaults
  124. if self.data.get('sd_vae_as_default') is not None and self.data.get('sd_vae_overrides_per_model_preferences') is None:
  125. self.data['sd_vae_overrides_per_model_preferences'] = not self.data.get('sd_vae_as_default')
  126. # 1.1.1 quicksettings list migration
  127. if self.data.get('quicksettings') is not None and self.data.get('quicksettings_list') is None:
  128. self.data['quicksettings_list'] = [i.strip() for i in self.data.get('quicksettings').split(',')]
  129. # 1.4.0 ui_reorder
  130. if isinstance(self.data.get('ui_reorder'), str) and self.data.get('ui_reorder') and "ui_reorder_list" not in self.data:
  131. self.data['ui_reorder_list'] = [i.strip() for i in self.data.get('ui_reorder').split(',')]
  132. bad_settings = 0
  133. for k, v in self.data.items():
  134. info = self.data_labels.get(k, None)
  135. if info is not None and not self.same_type(info.default, v):
  136. print(f"Warning: bad setting value: {k}: {v} ({type(v).__name__}; expected {type(info.default).__name__})", file=sys.stderr)
  137. bad_settings += 1
  138. if bad_settings > 0:
  139. print(f"The program is likely to not work with bad settings.\nSettings file: {filename}\nEither fix the file, or delete it and restart.", file=sys.stderr)
  140. def onchange(self, key, func, call=True):
  141. item = self.data_labels.get(key)
  142. item.onchange = func
  143. if call:
  144. func()
  145. def dumpjson(self):
  146. d = {k: self.data.get(k, v.default) for k, v in self.data_labels.items()}
  147. d["_comments_before"] = {k: v.comment_before for k, v in self.data_labels.items() if v.comment_before is not None}
  148. d["_comments_after"] = {k: v.comment_after for k, v in self.data_labels.items() if v.comment_after is not None}
  149. return json.dumps(d)
  150. def add_option(self, key, info):
  151. self.data_labels[key] = info
  152. def reorder(self):
  153. """reorder settings so that all items related to section always go together"""
  154. section_ids = {}
  155. settings_items = self.data_labels.items()
  156. for _, item in settings_items:
  157. if item.section not in section_ids:
  158. section_ids[item.section] = len(section_ids)
  159. self.data_labels = dict(sorted(settings_items, key=lambda x: section_ids[x[1].section]))
  160. def cast_value(self, key, value):
  161. """casts an arbitrary to the same type as this setting's value with key
  162. Example: cast_value("eta_noise_seed_delta", "12") -> returns 12 (an int rather than str)
  163. """
  164. if value is None:
  165. return None
  166. default_value = self.data_labels[key].default
  167. if default_value is None:
  168. default_value = getattr(self, key, None)
  169. if default_value is None:
  170. return None
  171. expected_type = type(default_value)
  172. if expected_type == bool and value == "False":
  173. value = False
  174. else:
  175. value = expected_type(value)
  176. return value