Initial commit
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
@tool
|
||||
extends RefCounted
|
||||
class_name CghConfig
|
||||
## Central config + exact After-Effects-addon color palette.
|
||||
## All colors copied 1:1 from the AE/PPRO addon spec so the Godot panel looks identical.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Backend endpoints (override via OS env for dev, like the desktop addons)
|
||||
# ---------------------------------------------------------------------------
|
||||
const ADDON_SLUG := "godot"
|
||||
const ADDON_VERSION := "1.0.0"
|
||||
|
||||
# Distribution channel tag (build-time). Lets PostHog split installs by where the
|
||||
# user got the addon (website / godot_asset_library / adobe_exchange / …).
|
||||
# Resolution order: env CGHEVEN_INSTALL_SOURCE -> bundled install_source.txt
|
||||
# (res://addons/cgheven/install_source.txt, dropped in per-channel builds) -> default.
|
||||
const INSTALL_SOURCE_DEFAULT := "website"
|
||||
static func install_source() -> String:
|
||||
var e := OS.get_environment("CGHEVEN_INSTALL_SOURCE").strip_edges()
|
||||
if e != "":
|
||||
return e
|
||||
var p := "res://addons/cgheven/install_source.txt"
|
||||
if FileAccess.file_exists(p):
|
||||
var f := FileAccess.open(p, FileAccess.READ)
|
||||
if f:
|
||||
var t := f.get_as_text().strip_edges()
|
||||
f.close()
|
||||
if t != "":
|
||||
return t
|
||||
return INSTALL_SOURCE_DEFAULT
|
||||
|
||||
static func api_base() -> String:
|
||||
var e := OS.get_environment("CGHEVEN_API_BASE")
|
||||
return e if e != "" else "https://api.cgheven.com"
|
||||
|
||||
static func web_base() -> String:
|
||||
var e := OS.get_environment("CGHEVEN_WEB_BASE")
|
||||
return e if e != "" else "https://cgheven.com"
|
||||
|
||||
# Asset listing goes through the same gating proxy the other addons use.
|
||||
static func public_assets_url() -> String:
|
||||
return api_base() + "/api/proxy/public/assets" # guest browsing
|
||||
static func authed_assets_url() -> String:
|
||||
return api_base() + "/api/proxy/assets" # logged-in (Bearer)
|
||||
# Category tree (top categories + subcategories) — same proxy, for the live
|
||||
# subcategory dropdown.
|
||||
static func public_categories_url() -> String:
|
||||
return api_base() + "/api/proxy/public/categories"
|
||||
static func authed_categories_url() -> String:
|
||||
return api_base() + "/api/proxy/categories"
|
||||
# BlenderKit-exact broker: the addon opens our website's /addon-login page, which logs
|
||||
# the user in and redirects to the loopback with a short ?code=. addon.start_login()
|
||||
# builds the full URL (it has the port/state/machine). Loopback path mirrors Blender.
|
||||
static func login_base_url() -> String:
|
||||
return web_base() + "/addon-login"
|
||||
static func loopback_redirect(port: int) -> String:
|
||||
return "http://127.0.0.1:%d/consumer/exchange/" % port
|
||||
# Same endpoint the Blender/AE addons redeem their handoff code at.
|
||||
static func addon_exchange_url() -> String:
|
||||
return api_base() + "/api/logto/addon-exchange"
|
||||
static func addon_activate_url() -> String:
|
||||
return api_base() + "/api/addon-license/activate"
|
||||
static func heartbeat_url() -> String:
|
||||
return api_base() + "/api/addon-updates/heartbeat"
|
||||
# Analytics goes through the backend (same as every desktop addon) so the PostHog
|
||||
# key stays server-side — the addon never ships a key.
|
||||
static func analytics_track_url() -> String:
|
||||
return api_base() + "/api/analytics/track"
|
||||
static func check_update_url() -> String:
|
||||
return api_base() + "/api/addon-updates/check"
|
||||
static func pricing_url() -> String:
|
||||
return web_base() + "/pricing"
|
||||
static func discord_url() -> String:
|
||||
return "https://discord.gg/cgheven"
|
||||
|
||||
# PostHog (analytics) — same project as the desktop addons.
|
||||
static func posthog_host() -> String:
|
||||
return "https://us.i.posthog.com"
|
||||
static func posthog_key() -> String:
|
||||
var e := OS.get_environment("CGHEVEN_POSTHOG_KEY")
|
||||
return e if e != "" else "" # filled at build/ship time
|
||||
|
||||
# Loopback OAuth ports (avoid 62485 = BlenderKit, like the Blender addon)
|
||||
const LOOPBACK_PORTS := [62490, 62491, 62492]
|
||||
|
||||
# Locked-file marker the backend sends instead of a real CDN url.
|
||||
const LOCK_PREFIX := "CGHLOCKED::"
|
||||
|
||||
# Local data lives under user:// (cross-project, per-user, always writable).
|
||||
const DATA_DIR := "user://cgheven"
|
||||
const CACHE_DIR := "user://cgheven/cache"
|
||||
const THUMB_DIR := "user://cgheven/thumbs"
|
||||
const DOWNLOAD_DIR := "user://cgheven/downloads"
|
||||
const PREFS_FILE := "user://cgheven/prefs.json"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# User prefs (persisted JSON) — e.g. a custom download folder the user picks.
|
||||
# ---------------------------------------------------------------------------
|
||||
static func _read_prefs() -> Dictionary:
|
||||
if not FileAccess.file_exists(PREFS_FILE):
|
||||
return {}
|
||||
var f := FileAccess.open(PREFS_FILE, FileAccess.READ)
|
||||
if f == null:
|
||||
return {}
|
||||
var txt := f.get_as_text()
|
||||
f.close()
|
||||
var d = JSON.parse_string(txt)
|
||||
return d if d is Dictionary else {}
|
||||
|
||||
static func _write_prefs(d: Dictionary) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(DATA_DIR)
|
||||
var f := FileAccess.open(PREFS_FILE, FileAccess.WRITE)
|
||||
if f:
|
||||
f.store_string(JSON.stringify(d))
|
||||
f.close()
|
||||
|
||||
# Where the user chose to save downloads ("" = use the default shared folder).
|
||||
static func custom_download_dir() -> String:
|
||||
return str(_read_prefs().get("download_dir", ""))
|
||||
|
||||
static func set_custom_download_dir(p: String) -> void:
|
||||
var d := _read_prefs()
|
||||
d["download_dir"] = p
|
||||
_write_prefs(d)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# COLORS — exact hex from the After Effects addon (addon-ui-spec)
|
||||
# ---------------------------------------------------------------------------
|
||||
const C_BG := Color("141416")
|
||||
const C_BG2 := Color("1a1a1f")
|
||||
const C_CARD := Color("1e1e24")
|
||||
const C_CARD_HOVER := Color("252530")
|
||||
const C_INPUT := Color("1a1a20")
|
||||
const C_NAV := Color("111114")
|
||||
const C_ACCENT := Color("5b7cff")
|
||||
const C_ACCENT_HOVER:= Color("6e8dff")
|
||||
const C_ACCENT_DIM := Color(0.357, 0.486, 1.0, 0.14) # rgba(91,124,255,.14)
|
||||
const C_TEXT1 := Color("f0f0f5")
|
||||
const C_TEXT2 := Color("8a8a9a")
|
||||
const C_TEXT3 := Color("505060")
|
||||
const C_BORDER := Color(1, 1, 1, 0.07) # rgba(255,255,255,.07)
|
||||
const C_BORDER_HOVER:= Color(1, 1, 1, 0.13) # rgba(255,255,255,.13)
|
||||
const C_DROPDOWN := Color("1c1c22") # popover bg --bgd
|
||||
const C_OK := Color("34c759")
|
||||
const C_ERR := Color("ff453a")
|
||||
const C_WARN := Color("ff9f0a")
|
||||
# CGHEVEN brand gradient: #f97316 -> #ef4444 (StyleBoxFlat has no gradient,
|
||||
# so the Upgrade/Login buttons use a solid mid-orange that reads the same).
|
||||
const C_BRAND_A := Color("f97316")
|
||||
const C_BRAND_B := Color("ef4444")
|
||||
const C_BRAND := Color("f4502c") # mid of the gradient
|
||||
const C_BRAND_HOVER := Color("e0431f")
|
||||
const C_RES_BADGE := Color(0.039, 0.039, 0.071, 0.82) # rgba(10,10,18,.82)
|
||||
const C_FOOTER_PILL := Color(0.5, 0.5, 0.5, 0.07) # rgba(128,128,128,.07)
|
||||
|
||||
# Radii / spacing (AE spec)
|
||||
const RADIUS_SM := 4
|
||||
const RADIUS_MD := 6
|
||||
const RADIUS_LG := 8 # card radius
|
||||
const RADIUS_XL := 10
|
||||
const GRID_GAP := 7
|
||||
|
||||
# Category pills. Real backend slugs (from the Blender addon):
|
||||
# vfx, 3d-models, hdri, flipbooks, vdbs, shaders, tutorials
|
||||
# Godot can usefully consume only: 3d-models, hdri, flipbooks.
|
||||
# vdbs / vfx(video) / tutorials / shaders are Blender-only and intentionally NOT listed.
|
||||
const CATEGORIES := [
|
||||
{"label": "All", "slug": ""},
|
||||
{"label": "3D Models", "slug": "3d-models"},
|
||||
{"label": "HDRI", "slug": "hdri"},
|
||||
{"label": "Flipbooks", "slug": "flipbooks"},
|
||||
]
|
||||
|
||||
# Subcategories per parent slug (from the Blender addon _PARENT_MAP).
|
||||
# Each subcategory is itself a backend category Slug; selecting one filters by it.
|
||||
const SUBCATEGORIES := {
|
||||
"3d-models": [
|
||||
{"label": "Furniture", "slug": "furniture"},
|
||||
{"label": "Props", "slug": "props"},
|
||||
{"label": "Weapons", "slug": "weapons"},
|
||||
],
|
||||
"hdri": [
|
||||
{"label": "Space", "slug": "space-hdr"},
|
||||
],
|
||||
"flipbooks": [
|
||||
{"label": "Fire", "slug": "fire-flipbooks"},
|
||||
{"label": "Magic Effects", "slug": "magic-effects-flipbooks"},
|
||||
{"label": "Muzzle Flashes","slug": "muzzle-flashes-flipbooks"},
|
||||
{"label": "Tornado FX", "slug": "tornado-fx-flipbooks"},
|
||||
],
|
||||
"shaders": [],
|
||||
}
|
||||
|
||||
# Sort options (matches AE/Blender)
|
||||
const SORTS := [
|
||||
{"label": "Newest", "param": "createdAt:desc"},
|
||||
{"label": "Oldest", "param": "createdAt:asc"},
|
||||
{"label": "A -> Z", "param": "Title:asc"},
|
||||
{"label": "Z -> A", "param": "Title:desc"},
|
||||
]
|
||||
|
||||
# File extensions Godot can import, grouped by purpose.
|
||||
const EXT_3D := ["glb", "gltf", "fbx"]
|
||||
const EXT_HDRI := ["exr", "hdr"]
|
||||
const EXT_IMAGE := ["png", "jpg", "jpeg", "webp"] # flipbook sheets / textures
|
||||
const EXT_ARCHIVE := ["zip"]
|
||||
# Resolution tokens recognised in filenames (index 0 = lowest = free fallback)
|
||||
const RES_ORDER := ["1k", "2k", "4k", "8k", "16k"]
|
||||
|
||||
# Discord invite (Godot community — placeholder, set real link before ship)
|
||||
const DISCORD := "https://discord.gg/cgheven"
|
||||
|
||||
static func ensure_dirs() -> void:
|
||||
for d in [DATA_DIR, CACHE_DIR, THUMB_DIR, DOWNLOAD_DIR]:
|
||||
DirAccess.make_dir_recursive_absolute(d)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cross-addon "file scanner" — shared CGHEVEN download folders on the OS.
|
||||
# A file downloaded by AE (~/Downloads/CGHEVEN), Blender (~/Documents/CGHEVEN)
|
||||
# or this addon should show the ✓ "downloaded" tick everywhere. We scan all of
|
||||
# these by filename so the tick is shared across every CGHEVEN addon.
|
||||
# ---------------------------------------------------------------------------
|
||||
static func os_home() -> String:
|
||||
var h := OS.get_environment("USERPROFILE") # Windows
|
||||
if h == "":
|
||||
h = OS.get_environment("HOME") # macOS / Linux
|
||||
return h
|
||||
|
||||
static func shared_download_dirs() -> Array:
|
||||
var home := os_home()
|
||||
var dirs := []
|
||||
var custom := custom_download_dir()
|
||||
if custom != "":
|
||||
dirs.append(custom) # user-chosen folder
|
||||
if home != "":
|
||||
dirs.append(home.path_join("Downloads").path_join("CGHEVEN")) # AE / PPRO default
|
||||
dirs.append(home.path_join("Documents").path_join("CGHEVEN")) # Blender default
|
||||
dirs.append(ProjectSettings.globalize_path(DOWNLOAD_DIR)) # this addon
|
||||
return dirs
|
||||
|
||||
# Where THIS addon saves downloads — a user-chosen folder if set, else the shared
|
||||
# AE folder so the other CGHEVEN addons see the files too.
|
||||
static func primary_download_dir() -> String:
|
||||
var custom := custom_download_dir()
|
||||
if custom != "":
|
||||
return custom
|
||||
var home := os_home()
|
||||
if home != "":
|
||||
return home.path_join("Downloads").path_join("CGHEVEN")
|
||||
return ProjectSettings.globalize_path(DOWNLOAD_DIR)
|
||||
|
||||
# Sanitize a CDN filename the same way the Blender addon does (cross-addon match).
|
||||
static func safe_download_name(filename: String) -> String:
|
||||
var f := filename.uri_decode()
|
||||
for ch in ["<", ">", ":", "\"", "/", "\\", "|", "?", "*"]:
|
||||
f = f.replace(ch, "_")
|
||||
return f
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Responsive grid columns — exact AE breakpoints (panel width -> column count).
|
||||
# ---------------------------------------------------------------------------
|
||||
static func responsive_columns(width: float) -> int:
|
||||
# Default narrow dock shows 2 large AE-style cards; grows on wider panels.
|
||||
if width < 520.0: return 2
|
||||
if width < 780.0: return 3
|
||||
if width < 1040.0: return 4
|
||||
if width < 1300.0: return 5
|
||||
if width < 1560.0: return 6
|
||||
return 7
|
||||
Reference in New Issue
Block a user