NOTICE: All information contained herein is, and remains
the property of TechnoCore.
The intellectual and technical concepts contained
herein are proprietary to TechnoCore and dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from TechnoCore.
The ObjPalette class, located in factory.core, is designed to manage color palettes within the Axion framework. It provides functionalities to read, create, and manipulate color palettes, including extracting colors from images and generating visual swatches.
ObjPalette inherits from ObjImage, giving it access to a wide range of image manipulation capabilities.
__init__(self, DB=0)Initializes a new ObjPalette instance.
DB (optional): A database connection object.read(self, palette_name: str = "")Reads a color palette from the def_colour_palette table in the database.
palette_name (str): The name of the palette to read.get_color(self, color_name: str, default_value: str = "") -> strReturns a specific color value from the currently loaded palette.
color_name (str): The name of the color to retrieve (e.g., 'primary', 'secondary').default_value (str, optional): A default value to return if the color is not found.str: The hex code of the color.save(self, palette_name: str)Saves the current color palette to the def_colour_palette table.
palette_name (str): The name to save the palette under.extract_from_image(self, image_path: str, palette_name: str, num_colors: int = 6) -> listExtracts the dominant colors from an image, saves them as a new palette, and generates a swatch.
image_path (str): The path to the image file.palette_name (str): The name to assign to the new palette.num_colors (int, optional): The number of colors to extract. Defaults to 6.list: A list of the extracted hex color codes.are_complementary(self, color1_hex: str, color2_hex: str) -> boolChecks if two hex colors are complementary.
color1_hex (str): The first color in hex format.color2_hex (str): The second color in hex format.bool: True if the colors are complementary, False otherwise.load_json_to_db(self)Loads color palettes from resource.config/tableau_palettes.json into the def_colour_palette table.
normalize_palettes(self)Normalizes all palettes in the database by filling in missing color fields from a 'REFERENCE' palette and standardizing the palette names.
swatches(self, palette_name: str = None, combine: bool = True)Generates PNG swatches for color palettes. Before generating, it calls normalize_palettes.
palette_name (str, optional): The name of a specific palette to generate a swatch for. If None, swatches are generated for all palettes.combine (bool, optional): If True, a combined image of all swatches will be generated.combine_swatches(self)Combines all individual swatch PNGs into a single horizontally arranged PNG.
display_cli_palette(self, palette: dict)Displays a color palette in the CLI with colorized output.
palette (dict): A dictionary representing the color palette.get_mermaid_theme(self) -> strReturns a Mermaid theme name based on the palette background luminance. Used to auto-select a readable Mermaid diagram theme that matches the active palette.
str: "dark" if the background is dark, "neutral" if light.Algorithm: Reads the palette background colour (falls back to the dark key if background is not set). Converts the hex colour to RGB and calculates relative luminance using the formula 0.2126*R + 0.7152*G + 0.0722*B (values normalised to 0-1). Returns "dark" when luminance is below 0.5, otherwise "neutral".
This method is also available on all ObjData subclasses via ObjDataBrandingMixin.get_mermaid_theme(), which delegates to ObjPalette.
get_print_palette(self) -> dictLoads the PRINT palette from def_colour_palette for use in PDF and print output. Ensures printed documents use high-contrast, paper-friendly colours regardless of the active screen palette.
dict: A palette dictionary with standard colour keys (background, text, primary, secondary, etc.).Lookup: Queries def_colour_palette where PaletteName='PRINT' and Package='ALL'. If no PRINT palette exists in the database, returns hardcoded light defaults (white background, dark text, blue primary) to guarantee readability.
This method is also available on all ObjData subclasses via ObjDataBrandingMixin.get_print_palette(), which delegates to ObjPalette.
The def_colour_palette table includes two columns for package-level font configuration:
| Column | Purpose |
|---|---|
Font |
CSS font-family stack (e.g. "Nunito", "Helvetica Neue", sans-serif) |
FontUrl |
Google Fonts CDN link for loading the font (e.g. https://fonts.googleapis.com/css2?family=Nunito) |
These are read alongside the colour values when a palette is loaded, allowing each package to define its own typography alongside its colour scheme.
The ObjPalette module can be run from the command line to perform various tasks.
get-palette-nameDisplays the current configured palette in a CLI table.
python3 factory.core/ObjPalette.py get-palette-name
load-palettesLoads palettes from the JSON configuration file into the database.
python3 factory.core/ObjPalette.py load-palettes
normalize-palettesNormalizes all palettes in the database.
python3 factory.core/ObjPalette.py normalize-palettes
create-swatchesGenerates PNG swatches for all palettes and a combined image.
python3 factory.core/ObjPalette.py create-swatches
--name <palette_name>: Generate a swatch for a specific palette.--no-combine: Do not generate the combined all_palettes.png image.extract-paletteExtracts a palette from an image, saves it, and generates a swatch.
python3 factory.core/ObjPalette.py extract-palette <image_path> <palette_name>
python3 factory.core/ObjPalette.py extract-palette resource.images/package.base/technocore_logo.png technocore_theme
get_palette_font)| Role | Palette column | Falls back to |
|---|---|---|
primary |
FontHeading |
body font |
secondary |
FontSecondary |
body font |
body |
Font |
built-in default |
mono |
— | ui-monospace, … |
url |
FontUrl |
CDN default |
Roles are FontTypeEnum values in factory.core/ObjEnum.py; config
<package>.fonts.<role> overrides the DB palette.
Why secondary falls back to the body font, not Arial (added
2026-08-08): secondary previously had no mapping at all, so every caller
got the bare Arial, Helvetica default. A brand-set page therefore
rendered unbranded with nothing logged anywhere — which is how a "wrong
body font" complaint arises with no one having done anything wrong. The
fallback is now the package's own body font, so the worst case is a
correct-but-undifferentiated face rather than a foreign one.
PRIMARY KEY (Package, Palette_name, Mode). It was (Palette_name)
alone, which forced palette names to be globally unique across every
package — two clubs could not both hold a "brand" palette, and one
package could not hold a LIGHT and a DARK row under one name.
Mode is char(16) NOT NULL DEFAULT '' (it must be non-null to sit in a
key). '' behaves exactly as NULL did for lookups, because the mode
query is an equality match (WHERE Mode = '<mode>'), never IS NULL.
Existing installs need the migration in
ObjPreflight.check_palette_key; create_tables_from_yaml adds columns
but never rebuilds a key.
⚠️ Widening the key changed seeding behaviour. The old single-column
key was silently acting as the stock-palette seeder's dedupe: a stock
palette could exist only once globally. Now it exists once per package,
so the JSON seed populates the library into every package (a one-time
redistribution — seed_from_yaml uses INSERT IGNORE, so re-seeding
never overwrites a row someone has since edited).
Stock rows are all Mode='', so they can never win a light/dark lookup.
With no name given, get_palette() walks four steps:
<package>.palette.name — authoritative, and versiondef_package.DefaultPalletetechnocore fallbackStep 4 is the hazard. PIPELINE had no config name, a NULL
DefaultPallete and no palette called pipeline, so it fell to step 4
and picked reference — a raw Bootswatch swatch that had been left
Active='Y'. A reference swatch is not a UI palette:
reference gave |
what it should be | |
|---|---|---|
Danger |
#3fb618 — green |
red |
Warning |
#373a3c — the page background |
amber |
Info |
#f8f9fa — near-white |
blue |
Background / Panel / Text / Muted / Border |
all NULL | set |
Nothing errors. The site rendered near-white text on near-white panels
and drew failed builds in green, and a purpose-built pipeline_dark
palette sat in the same table unused. Fixed 2026-08-12 by naming it in
config.yaml and clearing Active on the swatch.
So: name the palette in config for every package that has one, and
never leave a stock/reference swatch Active='Y' — active is what makes
it reachable by the guess.
⚠️ config.yaml is gitignored, so a name set there does not travel
to another instance. Set def_package.DefaultPallete to the same value
as well — it rides with the database and is step 2 of the same walk.
PIPELINE carries both.
_get_palette_by_mode matches on Mode + Package only — the
palette name is not consulted — and falls through to the resolution
above when there is no row for the mode. So a package with only a dark
palette serves that palette to mode="light" callers too, and
ObjReportBuild.render_email then puts text: #E6EDF7 onto email
templates whose backgrounds are a hardcoded #ffffff. PIPELINE
therefore carries pipeline_light (Mode='LIGHT') alongside
pipeline_dark.