{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# About\n",
    "\n",
    "This is a notebook for finding and copying Textures form extracted game assets to named images for the item database. \n",
    "\n",
    "## Why not a script?\n",
    "\n",
    "because depending on what extractor you use and the whims of the developers all this could use some serious tweaking every run. The notebook lets you run things in stages and inspect what your working with."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "database = {}\n",
    "\n",
    "with open(\"data/database.json\", \"r\") as f:\n",
    "    database = json.load(f)\n",
    "\n",
    "db = database[\"db\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Item Database Pulled in"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path \n",
    "\n",
    "# Location were https://github.com/SeriousCache/UABE has extracted all Texture2D assets\n",
    "# Change as necessary\n",
    "datapath = Path(r\"E:\\Games\\SteamLibrary\\steamapps\\common\\Stationeers\\Stationpedia\\exported_textures\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Change this Datapath to point to the extracted textures"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "# Pull in a list of all found textures\n",
    "images = list(datapath.glob(\"*.png\"))\n",
    "names = [image.name  for image in images]\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Finding matches\n",
    "\n",
    "This next section loops through all the item names and collects all the candidate textures. Then, through a series of rules, attempts to narrow down the choices to 1 texture."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "image_candidates = {}\n",
    "\n",
    "def filter_candidates(candidates):\n",
    "    max_match_len = 0\n",
    "    filtered_matches = []\n",
    "\n",
    "    # go for longest match\n",
    "    for can in candidates:\n",
    "        name, match, mapping = can\n",
    "        match_len = len(match)\n",
    "        if match_len > max_match_len:\n",
    "            max_match_len = match_len\n",
    "            filtered_matches = [(name, match, mapping)]\n",
    "        elif match_len == max_match_len:\n",
    "            filtered_matches.append((name, match, mapping))\n",
    "\n",
    "    # choose better matches\n",
    "    if len(filtered_matches) > 1:\n",
    "        better_matches = []\n",
    "        for can in filtered_matches:\n",
    "            name, match, mapping = can\n",
    "            if mapping.startswith(\"Item\") and mapping in name:\n",
    "                better_matches.append((name, match, mapping))\n",
    "            elif mapping.startswith(\"Structure\") and mapping in name:\n",
    "                better_matches.append((name, match, mapping))\n",
    "        if len(better_matches) > 0:\n",
    "            filtered_matches = better_matches\n",
    "\n",
    "    #exclude build states if we have non build states\n",
    "    if len(filtered_matches) > 1:\n",
    "        non_build_state = []\n",
    "        for can in filtered_matches:\n",
    "            name, match, mapping = can\n",
    "            if \"BuildState\" not in name:\n",
    "                non_build_state.append((name, match, mapping))\n",
    "        if len(non_build_state) > 0:\n",
    "            filtered_matches = non_build_state\n",
    "\n",
    "    #prefer matches without extra tags\n",
    "    if len(filtered_matches) > 1:\n",
    "        direct = []\n",
    "        for can in filtered_matches:\n",
    "            name, match, mapping = can\n",
    "            if f\"{match}-\" in name:\n",
    "                direct.append((name, match, mapping))\n",
    "        if len(direct) > 0:\n",
    "            filtered_matches = direct\n",
    "    \n",
    "    #filter to unique filenames\n",
    "    if len(filtered_matches) > 1:\n",
    "        unique_names = []\n",
    "        unique_matches = []\n",
    "        for can in filtered_matches:\n",
    "            name, match, mapping = can\n",
    "            if name not in unique_names:\n",
    "                unique_names.append(name)\n",
    "                unique_matches.append((name, match, mapping))\n",
    "        filtered_matches = unique_matches\n",
    "\n",
    "    #prefer not worse matches\n",
    "    if len(filtered_matches) > 1:\n",
    "        not_worse = []\n",
    "        for can in filtered_matches:\n",
    "            name, match, mapping = can\n",
    "            if name.startswith(\"Item\") and not mapping.startswith(\"Item\"):\n",
    "                continue\n",
    "            elif name.startswith(\"Structure\") and not mapping.startswith(\"Structure\"):\n",
    "                continue\n",
    "            elif name.startswith(\"Kit\") and not mapping.startswith(\"Kit\"):\n",
    "                continue\n",
    "            elif not name.startswith(match):\n",
    "                continue\n",
    "            not_worse.append((name, match, mapping))\n",
    "        if len(not_worse) > 0:\n",
    "            filtered_matches = not_worse\n",
    "\n",
    "    #if we have colored variants take White\n",
    "    if len(filtered_matches) > 1:\n",
    "        for can in filtered_matches:\n",
    "            name, match, mapping = can\n",
    "            if f\"_White\" in name:\n",
    "                return [name]\n",
    "\n",
    "    return [name for name, _, _ in filtered_matches]\n",
    "\n",
    "for entry in db.values():\n",
    "    candidates = []\n",
    "    for name in names:\n",
    "        if entry[\"name\"] in name:\n",
    "            candidates.append((name, entry[\"name\"], entry[\"name\"]))\n",
    "        if entry[\"name\"].removeprefix(\"Item\") in name:\n",
    "            candidates.append((name, entry[\"name\"].removeprefix(\"Item\"), entry[\"name\"]))\n",
    "        if entry[\"name\"].removeprefix(\"Structure\") in name:\n",
    "            candidates.append((name, entry[\"name\"].removeprefix(\"Structure\"), entry[\"name\"]))\n",
    "    image_candidates[entry[\"name\"]] = filter_candidates(candidates)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some Items end up with no match but these items are often subtypes of an item that will have a match"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# rematch items to super structure?\n",
    "for name in image_candidates.keys():\n",
    "    for other in image_candidates.keys():\n",
    "        if name != other and name in other:\n",
    "            if len(image_candidates[name]) > 0 and len(image_candidates[other]) == 0:\n",
    "                image_candidates[other] = image_candidates[name]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Prepare out List of file copies. at this point a few items will never have a match. and one or two will have two choices but those choices will be arbitrary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ItemBiomass []\n",
      "StructureBlocker []\n",
      "CartridgePlantAnalyser []\n",
      "StructureElevatorLevelIndustrial []\n",
      "ItemPlantEndothermic_Creative []\n",
      "Flag_ODA_10m []\n",
      "Flag_ODA_4m []\n",
      "Flag_ODA_6m []\n",
      "Flag_ODA_8m []\n",
      "ItemHorticultureBelt []\n",
      "ItemKitLiquidRegulator []\n",
      "ItemKitPortablesConnector []\n",
      "Landingpad_GasConnectorInwardPiece []\n",
      "Landingpad_LiquidConnectorInwardPiece []\n",
      "ItemMushroom ['ItemMushroom-resources.assets-3022.png', 'ItemMushroom-resources.assets-9304.png']\n",
      "StructurePlinth []\n",
      "ItemPlantThermogenic_Creative []\n"
     ]
    }
   ],
   "source": [
    "to_copy = []\n",
    "for name, candidates in image_candidates.items():\n",
    "    if len(candidates) != 1:\n",
    "        print(name, candidates)\n",
    "        if len(candidates) > 1:\n",
    "            #take first as fallback\n",
    "            to_copy.append((name, candidates[0]))\n",
    "    else:\n",
    "        # print(name, candidates)\n",
    "        to_copy.append((name, candidates[0]))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1223 of 1223 | 100.00%    \n",
      "Done\n"
     ]
    }
   ],
   "source": [
    "import shutil\n",
    "\n",
    "destpath = Path(\"img/stationpedia\")\n",
    "total_files = len(to_copy)\n",
    "\n",
    "count = 0\n",
    "print ( f\"{count} of {total_files} | { count / total_files * 100}\", end=\"\\r\")\n",
    "for name, file in to_copy:\n",
    "    source = datapath / file\n",
    "    dest = destpath / f\"{name}.png\"\n",
    "    shutil.copy(source, dest)\n",
    "    count += 1\n",
    "    print ( f\"{count} of {total_files} | { (count / total_files) * 100 :.2f}%    \",  end=\"\\r\")\n",
    "print()\n",
    "print(\"Done\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
