import { modelService } from "./models.js"; export const samplers = [ "DPM++ 2M Karras", "Euler A", "DDIM", "PLMS", "DPM++ SDE Karras", "UniPC", "LCM", "Euler A Substep", "DPM++ SDE Substep", "TCD", "Euler A Trailing", "DPM++ SDE Trailing", "DPM++ 2M AYS", "Euler A AYS", "DPM++ SDE AYS", "DPM++ 2M Trailing", "DDIM Trailing", "UniPC Trailing", "UniPC AYS", ]; export const seedModes = ["Legacy", "TorchCpuCompatible", "ScaleAlike", "NvidiaGpuCompatible"]; // From flux-auto-workflow.js export function calcShift(h, w) { const step1 = (h * w) / 256; const step2 = (1.15 - 0.5) / (4096 - 256); const step3 = (step1 - 256) * step2; const step4 = step3 + 0.5; const result = Math.exp(step4); return Math.round(result * 100) / 100; } /** @typedef {"v1" | "v2" | "kandinsky2.1" | "sdxl_base_v0.9" | "sdxl_refiner_v0.9" | "ssd_1b" | "svd_i2v"} _Models_A */ /** @typedef {"wurstchen_v3.0_stage_c" | "wurstchen_v3.0_stage_b" | "sd3" | "pixart" | "auraflow" | "flux1" } _Models_B */ /** @typedef {"sd3_large" | "hunyuan_video" | "wan_v2.1_1.3b" | "wan_v2.1_14b" | "hidream_i1" | "qwen_image" } _Models_C */ /** @typedef {_Models_A | _Models_B | _Models_C | "Other"} ModelVersion */ /** @typedef {Record} ModelMap */ /** @type ModelMap */ const numFramesMaxMap = { "wan_v2.1_1.3b": 129, "wan_v2.1_14b": 129, hunyuan_video: 201, svd_i2v: 25 }; const numFramesDefMap = { "wan_v2.1_1.3b": 81, "wan_v2.1_14b": 81, hunyuan_video: 129, svd_i2v: 14 }; /** [ config.fbs name, comfy widget name, the node it belongs to, the property name in DT's json config] */ // prettier-ignore /** @type {[fbs: string, comfy: string, nodeType: string, json: string, type: string, defaultValue: unknown, ...unknown][]} */ export const propertyData = [ ['start_width', 'width', 'DrawThingsSampler', 'width', 'int', 512, 128, 2048, 64, 'roundTo64'], ['start_height', 'height', 'DrawThingsSampler', 'height', 'int', 512, 128, 2048, 64, 'roundTo64'], ['seed', 'seed', 'DrawThingsSampler', 'seed', 'int', -1, -1, null, 1, 'modulo=4294967295'], ['steps', 'steps', 'DrawThingsSampler', 'steps', 'int', 16, 1, 150, 1], ['guidance_scale', 'cfg', 'DrawThingsSampler', 'guidanceScale', 'float', 5, 0, 50, 0.1], ['strength', 'strength', 'DrawThingsSampler', 'strength', 'float', 1, 0, 1, 0.01], ['model', 'model', 'DrawThingsSampler', 'model', 'DT_MODEL', null], ['sampler', 'sampler_name', 'DrawThingsSampler', 'sampler', 'index', 0, samplers], ['batch_count', 'batch_count', 'DrawThingsSampler', 'batchCount', 'int', 1, 1, 4, 1], ['batch_size', 'batch_size', 'DrawThingsSampler', 'batchSize', 'int', 1, 1, 1, 1], ['hires_fix', 'high_res_fix', 'DrawThingsSampler', 'hiresFix', 'bool', false], ['hires_fix_start_width', 'high_res_fix_start_width', 'DrawThingsSampler', 'hiresFixWidth', 'int', 512, 128, 2048, 64, 'roundTo64,ifTrue=hiresFix'], ['hires_fix_start_height', 'high_res_fix_start_height', 'DrawThingsSampler', 'hiresFixHeight', 'int', 512, 128, 2048, 64, 'roundTo64,ifTrue=hiresFix'], ['hires_fix_strength', 'high_res_fix_strength', 'DrawThingsSampler', 'hiresFixStrength', 'float', 0.7, 0, 1, 0.01, 'ifTrue=hiresFix'], ['image_guidance_scale', 'image_guidance_scale', 'DrawThingsSampler', 'imageGuidanceScale', 'float', 5, 0, 50, 0.1], ['seed_mode', 'seed_mode', 'DrawThingsSampler', 'seedMode', 'index', 2, seedModes], ['clip_skip', 'clip_skip', 'DrawThingsSampler', 'clipSkip', 'int', 1, 1, 23, 1], ['controls', null, 'DrawThingsControlNet', 'controls'], ['loras', null, 'DrawThingsLoRA', 'loras'], ['mask_blur', 'mask_blur', 'DrawThingsSampler', 'maskBlur', 'float', 2.5, 0, 15, 0.1], ['face_restoration', null, null, 'faceRestoration'], ['decode_with_attention', null, null], ['hires_fix_decode_with_attention', null, null], ['clip_weight', null, null], ['negative_prompt_for_image_prior', null, null], ['image_prior_steps', null, null], ['original_image_height', null, null, 'originalImageHeight'], ['original_image_width', null, null, 'originalImageWidth'], ['crop_top', null, null, 'cropTop'], ['crop_left', null, null, 'cropLeft'], ['target_image_height', null, null, 'targetImageHeight'], ['target_image_width', null, null, 'targetImageWidth'], ['aesthetic_score', null, null, 'aestheticScore'], ['negative_aesthetic_score', null, null, 'negativeAestheticScore'], ['zero_negative_prompt', null, null, 'zeroNegativePrompt'], ['negative_original_image_height', null, null, 'negativeOriginalImageHeight'], ['negative_original_image_width', null, null, 'negativeOriginalImageWidth'], ['name', null, null, null], ['fps_id', 'fps', 'DrawThingsSampler', 'fps', 'int', 12, 1, 30, 1], ['motion_bucket_id', 'motion_scale', 'DrawThingsSampler', 'motionScale', 'int', 127, 0, 255, 1], ['cond_aug', 'guiding_frame_noise', 'DrawThingsSampler', 'guidingFrameNoise', 'float', 0.02, 0, 1, 0.01], ['start_frame_cfg', 'start_frame_guidance', 'DrawThingsSampler', 'startFrameGuidance', 'float', 1, 0, 15, 0.1], ['num_frames', 'num_frames', 'DrawThingsSampler', 'numFrames', 'int', numFramesDefMap, 1, numFramesDefMap, 1], ['mask_blur_outset', 'mask_blur_outset', 'DrawThingsSampler', 'maskBlurOutset', 'float', 0, -100, 100, 0.1], ['sharpness', 'sharpness', 'DrawThingsSampler', 'sharpness', 'float', 0, 0, 30, 0.1], ['shift', 'shift', 'DrawThingsSampler', 'shift', 'float', 1, 0, 16, 0.01], ['stage_2_steps', null, null, 'stage2Steps'], ['stage_2_cfg', null, null, 'stage2Guidance'], ['stage_2_shift', null, null, 'stage2Shift'], ['tiled_decoding', 'tiled_decoding', 'DrawThingsSampler', 'tiledDecoding', 'bool', false], ['decoding_tile_width', 'decoding_tile_width', 'DrawThingsSampler', 'decodingTileWidth', 'int', 512, 128, 2048, 64, 'roundTo64,ifTrue=tiledDecoding'], ['decoding_tile_height', 'decoding_tile_height', 'DrawThingsSampler', 'decodingTileHeight', 'int', 512, 128, 2048, 64, 'roundTo64,ifTrue=tiledDecoding'], ['decoding_tile_overlap', 'decoding_tile_overlap', 'DrawThingsSampler', 'decodingTileOverlap', 'int', 512, 64, 1024, 64, 'roundTo64,ifTrue=tiledDecoding'], ['stochastic_sampling_gamma', 'stochastic_sampling_gamma', 'DrawThingsSampler', 'stochasticSamplingGamma', 'float', 0.3, 0, 1, 0.01], ['preserve_original_after_inpaint', 'preserve_original', 'DrawThingsSampler', 'preserveOriginalAfterInpaint', 'bool', true], ['tiled_diffusion', 'tiled_diffusion', 'DrawThingsSampler', 'tiledDiffusion', 'bool', false], ['diffusion_tile_width', 'diffusion_tile_width', 'DrawThingsSampler', 'diffusionTileWidth', 'int', 512, 128, 2048, 64, 'roundTo64,ifTrue=tiledDiffusion'], ['diffusion_tile_height', 'diffusion_tile_height', 'DrawThingsSampler', 'diffusionTileHeight', 'int', 512, 128, 2048, 64, 'roundTo64,ifTrue=tiledDiffusion'], ['diffusion_tile_overlap', 'diffusion_tile_overlap', 'DrawThingsSampler', 'diffusionTileOverlap', 'int', 512, 64, 1024, 64, 'roundTo64,ifTrue=tiledDiffusion'], ['t5_text_encoder', null, null, 't5TextEncoder'], ['separate_clip_l', 'separate_clip_l', 'DrawThingsSampler', 'separateClipL', 'bool', false], ['clip_l_text', 'clip_l_text', 'DrawThingsSampler', null, 'string', "", 'ifTrue=separateClipL'], ['separate_open_clip_g', 'separate_open_clip_g', 'DrawThingsSampler', 'separateOpenClipG', 'bool', false], ['open_clip_g_text', 'open_clip_g_text', 'DrawThingsSampler', null, 'string', "", 'ifTrue=separateOpenClipG'], ['speed_up_with_guidance_embed', 'speed_up', 'DrawThingsSampler', 'speedUpWithGuidanceEmbed', 'bool', true], ['guidance_embed', 'guidance_embed', 'DrawThingsSampler', 'guidanceEmbed', 'float', 4.5, 0, 50, 0.1, 'ifFalse=speedUpWithGuidanceEmbed'], ['resolution_dependent_shift', 'res_dpt_shift', 'DrawThingsSampler', 'resolutionDependentShift', 'bool', true], ['tea_cache_start', 'tea_cache_start', 'DrawThingsSampler', 'teaCacheStart', 'int', 5, 0, 'ref=steps', 1, 'ifTrue=teaCache'], ['tea_cache_end', 'tea_cache_end', 'DrawThingsSampler', 'teaCacheEnd', 'int', 'ref=steps', 0, 'ref=steps', 1, 'ifTrue=teaCache'], ['tea_cache_threshold', 'tea_cache_threshold', 'DrawThingsSampler', 'teaCacheThreshold', 'float', 0.3, 0, 1, 0.01, 'ifTrue=teaCache'], ['tea_cache', 'tea_cache', 'DrawThingsSampler', 'teaCache', 'bool', false], ['separate_t5', null, null, 'separateT5'], ['t5_text', null, null, null], ['tea_cache_max_skip_steps', 'tea_cache_max_skip_steps', 'DrawThingsSampler', 'teaCacheMaxSkipSteps', 'int', 3, 1, 50, 1, 'ifTrue=teaCache'], // causal_inference_enabled is implied by causal_inference>0 ['causal_inference_enabled', null, null, null], ['causal_inference', 'causal_inference', 'DrawThingsSampler', 'causalInference', 'int', 0, 0, 129, 4, 'ifPos=set(causalInference, true),causInfConvert'], ['causal_inference_pad', 'causal_inference_pad', 'DrawThingsSampler', 'causalInferencePad', 'int', 0, 0, 129, 4, 'causInfConvert'], // in upscaler node ['upscaler', 'upscaler', 'DrawThingsUpscaler', 'upscaler'], ['upscaler_scale_factor', 'upscaler_scale_factor', 'DrawThingsUpscaler', 'upscalerScaleFactor'], // in refiner node ['refiner_model', 'refiner_model', 'DrawThingsRefiner', 'refinerModel'], ['refiner_start', 'refiner_start', 'DrawThingsRefiner', 'refinerStart'], ["cfg_zero_star", "cfg_zero_star", "DrawThingsSampler", "cfgZeroStar", "bool", false], ["cfg_zero_star_init_steps", "cfg_zero_star_init_steps", "DrawThingsSampler", "cfgZeroInitSteps", "int", 0, 0, "ref=steps", 1, "ifTrue=cfgZeroStar"], ] // prettier-ignore-end /** @typedef {{ fbs: string, python: string, node: string, json: string }} DTProperty */ function roundBy64(value) { return Math.round(value / 64) * 64; } /** @type {Record void>} */ const importers = { model: async (k, v, w, n, c) => { await modelService.updateNodes(); const matchingOption = w?.options?.values?.find((ov) => ov.value?.file === v); if (matchingOption) w.value = matchingOption; }, start_width: (k, v, w) => { if (w) w.value = roundBy64(v); }, start_height: (k, v, w) => { if (w) w.value = roundBy64(v); }, sampler: (k, v, w) => { w.value = samplers[v]; }, seed: (k, v, w, n) => { if (typeof v === "number" && v >= 0) { w.value = v; // Originally was setting to "fixed" if the config has a specific seed // but then I realised copying a config from draw things *always* has a seed // const controlWidget = n.widgets.find((w) => w.name === "control_after_generate") // if (controlWidget) controlWidget.value = "fixed" } }, hires_fix_start_width: (k, v, w) => { if (w) w.value = roundBy64(v); }, hires_fix_start_height: (k, v, w) => { if (w) w.value = roundBy64(v); }, seed_mode: (k, v, w) => { if (w && seedModes[v]) w.value = seedModes[v]; }, decoding_tile_width: (k, v, w) => { if (w) w.value = roundBy64(v); }, decoding_tile_height: (k, v, w) => { if (w) w.value = roundBy64(v); }, decoding_tile_overlap: (k, v, w) => { if (w) w.value = roundBy64(v); }, diffusion_tile_width: (k, v, w) => { if (w) w.value = roundBy64(v); }, diffusion_tile_height: (k, v, w) => { if (w) w.value = roundBy64(v); }, diffusion_tile_overlap: (k, v, w) => { if (w) w.value = roundBy64(v); }, guidance_embed: (k, v, w, n, c) => { if (w) w.value = v; }, resolution_dependent_shift: (k, v, w, n, c) => { if (w) w.value = v; if (v) { const shiftWidget = n.widgets.find((w) => w.name === "shift"); const width = c.width || n.widgets.find((w) => w.name === "width")?.value; const height = c.height || n.widgets.find((w) => w.name === "height")?.value; if (shiftWidget && width && height) shiftWidget.value = calcShift(width, height); } }, causal_inference: function (k, v, w, n, c) { if (Number.isFinite(v)) w.value = v * 4 - 3; else w.value = this.defaultValue; }, causal_inference_pad: (k, v, w, n, c) => { if (w && typeof v === "number") w.value = v * 4; }, }; const exporters = { // start_width: {}, // start_height: {}, model: async (w, n, c) => { if (w && w.value && w.value.value) c.model = w.value.value.file ?? w.value.value.name; }, sampler: (w, n, c) => { if (w && typeof w.value === "string") c.sampler = samplers.indexOf(w.value); }, // hires_fix_start_width: {}, // hires_fix_start_height: {}, seed_mode: (w, n, c) => { if (w && typeof w.value === "string") c.seed_mode = seedModes.indexOf(w.value); }, // decoding_tile_width: {}, // decoding_tile_height: {}, // decoding_tile_overlap: {}, // diffusion_tile_width: {}, // diffusion_tile_height: {}, // diffusion_tile_overlap: {}, // guidance_embed: (w, n, c) => { // }, // resolution_dependent_shift: (w, n, c) => { // }, // shift: (w, n, c) => { // }, causal_inference: (w, n, c) => { if (w && typeof w.value === "number") c.causal_inference = Math.floor((w.value + 3) / 4); }, causal_inference: (w, n, c) => { if (w && typeof w.value === "number") c.causal_inference_pad = Math.floor(w.value / 4); }, }; class DTProperty { constructor(fbs, python, node, json, type, defaultValue, ...rest) { this.fbs = fbs; this.python = python; this.node = node; this.json = json; this.type = type; this.defaultValue = defaultValue; if (type === "int" || type === "float") { this.min = rest[0]; this.max = rest[1]; this.step = rest[2]; this.spec = rest[3]; } if (type === "bool") { this.spec = rest[0]; } if (type === "index") { this.values = rest[0]; this.spec = rest[1]; } if (type === "string") { this.spec = rest[0]; } } customImport = undefined; customExport = undefined; async import(jsonKey, jsonValue, widget, node, config) { if (!widget || !node) return; if (this.customImport) await this.customImport(jsonKey, jsonValue, widget, node, config); else { widget.value = jsonValue ?? this.defaultValue; } } async export(widget, node, config) { if (this.customExport) await this.customExport(widget, node, config); else { if (this.json && widget && widget.value !== undefined) config[this.json] = widget.value; } } coerce(value) { if (this.type === "int" || this.type === "float") { if (typeof value !== "number") return this.defaultValue || 0; if (Number.isFinite(this.min) && value < this.min) return this.min; if (Number.isFinite(this.max) && value > this.max) return this.max; return value; } if (this.type === "bool") { if (typeof value !== "boolean") return this.defaultValue || false; return value; } if (this.type === "string") { if (typeof value !== "string") return this.defaultValue || ""; return value; } if (this.type === "index") { if (typeof value === "number" && value >= 0 && value < this.values.length) return this.values[value]; if (typeof value === "string" && this.values.includes(value)) return value; return this.values[this.defaultValue]; } return value; } } /** @type {DTProperty[]} */ export const properties = propertyData.map(([fbs, ...rest]) => { const prop = new DTProperty(fbs, ...rest); prop.customImport = importers[fbs]; prop.customExport = exporters[fbs]; return prop; }); export const dtProperties = propertyData .filter((pd) => !pd.slice(4).includes(null)) .map(([fbs, comfy, nodeType, json, type, defaultValue,...rest]) => { return new DTProperty({ fbs, comfy, json, type, defaultValue }); }); export function findPropertyJson(name) { return properties.find((p) => p.json === name); } export function findPropertyPython(name) { return properties.find((p) => p.python === name); }