From 465f707561e87c73d32171d18ff09f024517d778 Mon Sep 17 00:00:00 2001 From: Taylor Kimmett Date: Mon, 25 May 2026 00:52:25 -0400 Subject: [PATCH 1/4] Export POE2 passive tree images --- RePoE/model/characters.py | 1 + RePoE/parser/poe2/ascendancies.py | 13 +++++++-- RePoE/parser/poe2/characters.py | 46 +++++++++++++++++-------------- RePoE/parser/poe2/passives.py | 25 +++++++++++++---- 4 files changed, 57 insertions(+), 28 deletions(-) diff --git a/RePoE/model/characters.py b/RePoE/model/characters.py index 7c49ca87b..54d5bc018 100644 --- a/RePoE/model/characters.py +++ b/RePoE/model/characters.py @@ -40,6 +40,7 @@ class CharactersSchemaElement(BaseModel): extra="forbid", ) base_stats: BaseStats + passive_tree_image: Optional[str] = None integer_id: int metadata_id: MetadataID name: str diff --git a/RePoE/parser/poe2/ascendancies.py b/RePoE/parser/poe2/ascendancies.py index 3f16de4fa..6f15e54e5 100644 --- a/RePoE/parser/poe2/ascendancies.py +++ b/RePoE/parser/poe2/ascendancies.py @@ -6,7 +6,7 @@ from RePoE.parser import Parser_Module from RePoE.parser.modules.flavour import flavour from RePoE.parser.poe2.passives import uiart, passive -from RePoE.parser.util import call_with_default_args, write_any_json +from RePoE.parser.util import call_with_default_args, export_image, write_any_json COLS = { "ClassNo": "class_number", @@ -27,6 +27,7 @@ class ascendancies(Parser_Module): def write(self) -> None: + self.should_export_images = self.language == "English" self.relational_reader["AscendancyPassiveSkillOverrides.dat64"].build_index("AscendancyToOverrideFor") write_any_json( {asc["Id"]: self.process(asc) for asc in self.relational_reader["Ascendancy.dat64"]}, @@ -38,12 +39,20 @@ def process(self, row): tf = self.get_cache(TranslationFileCache)["passive_skill_stat_descriptions.txt"] data = {v: row[k] for k, v in COLS.items()} + if self.should_export_images and data["passive_tree_image"]: + export_image(data["passive_tree_image"], self.data_path, self.file_system) data["art"] = uiart(row["UIArt"]) if row["BaseAscendancy"]: data["passive_overrides"] = [ { "from_hash": o["SkillToOverride"]["PassiveSkillGraphId"], - "to_passive": passive(o["Override"], tf, self.language), + "to_passive": passive( + o["Override"], + tf, + self.language, + self.data_path if self.should_export_images else None, + self.file_system if self.should_export_images else None, + ), } for o in self.relational_reader["AscendancyPassiveSkillOverrides.dat64"].index[ "AscendancyToOverrideFor" diff --git a/RePoE/parser/poe2/characters.py b/RePoE/parser/poe2/characters.py index 3b3f11db4..2225106bb 100644 --- a/RePoE/parser/poe2/characters.py +++ b/RePoE/parser/poe2/characters.py @@ -1,32 +1,36 @@ -from RePoE.parser.util import write_json, call_with_default_args +from RePoE.parser.util import call_with_default_args, export_image, write_json from RePoE.parser import Parser_Module class characters(Parser_Module): def write(self): root = [] + should_export_images = self.language == "English" for row in self.relational_reader["Characters.dat64"]: - root.append( - { - "metadata_id": row["Id"], - "integer_id": row["IntegerId"], - "name": row["Name"], - "description": row["Description"], - "base_stats": { - "life": row["BaseMaxLife"], - "mana": row["BaseMaxMana"], - "strength": row["BaseStrength"], - "dexterity": row["BaseDexterity"], - "intelligence": row["BaseIntelligence"], - "unarmed": { - "attack_time": row["WeaponSpeed"], - "min_physical_damage": row["MinDamage"], - "max_physical_damage": row["MaxDamage"], - "range": row["MaxAttackDistance"], - }, + character = { + "metadata_id": row["Id"], + "integer_id": row["IntegerId"], + "name": row["Name"], + "description": row["Description"], + "base_stats": { + "life": row["BaseMaxLife"], + "mana": row["BaseMaxMana"], + "strength": row["BaseStrength"], + "dexterity": row["BaseDexterity"], + "intelligence": row["BaseIntelligence"], + "unarmed": { + "attack_time": row["WeaponSpeed"], + "min_physical_damage": row["MinDamage"], + "max_physical_damage": row["MaxDamage"], + "range": row["MaxAttackDistance"], }, - } - ) + }, + } + if row["PassiveTreeImage"]: + character["passive_tree_image"] = row["PassiveTreeImage"] + if should_export_images: + export_image(row["PassiveTreeImage"], self.data_path, self.file_system) + root.append(character) write_json(root, self.data_path, "characters") diff --git a/RePoE/parser/poe2/passives.py b/RePoE/parser/poe2/passives.py index b3534b52f..cd40b37dd 100644 --- a/RePoE/parser/poe2/passives.py +++ b/RePoE/parser/poe2/passives.py @@ -4,7 +4,7 @@ from PyPoE.poe.file.translations import TranslationFileCache from RePoE.parser import Parser_Module -from RePoE.parser.util import call_with_default_args, write_any_json +from RePoE.parser.util import call_with_default_args, export_image, write_any_json PASSIVE_COLS = { @@ -34,7 +34,7 @@ } -def passive(row, translation_file, lang): +def passive(row, translation_file, lang, data_path=None, file_system=None): result = {k: row[col] for col, k in PASSIVE_COLS.items()} if row["PassiveSkillBuffs"]: result["buff_definitions"] = [buff["BuffDefinitionsKey"]["Id"] for buff in row["PassiveSkillBuffsKeys"]] @@ -42,6 +42,8 @@ def passive(row, translation_file, lang): result["ascendancy"] = row["Ascendancy"]["Id"] if row["Icon_DDSFile"]: result["icon"] = row["Icon_DDSFile"] + if row["Ascendancy"] and data_path and file_system: + export_image(row["Icon_DDSFile"], data_path, file_system) if row["AtlasSubTree"]: result["atlas_subtree"] = row["AtlasSubTree"]["Id"] if row["GrantedSkill"]: @@ -95,6 +97,7 @@ def frame_art(row): class passives(Parser_Module): def write(self) -> None: + should_export_images = self.language == "English" all_passives = self.relational_reader["PassiveSkills.dat64"] if "PassiveSkillGraphId" not in all_passives.index: all_passives.build_index("PassiveSkillGraphId") @@ -106,7 +109,13 @@ def write(self) -> None: nodes = {} for p in psg.root_passives: if p not in nodes: - nodes[p] = passive(self.index[p], tf, self.language) + nodes[p] = passive( + self.index[p], + tf, + self.language, + self.data_path if should_export_images else None, + self.file_system if should_export_images else None, + ) groups = [] for group in psg.groups: groups.append( @@ -128,12 +137,18 @@ def write(self) -> None: ) for node in group.nodes: if node.passive_skill not in nodes: - nodes[node.passive_skill] = passive(self.index[node.passive_skill], tf, self.language) + nodes[node.passive_skill] = passive( + self.index[node.passive_skill], + tf, + self.language, + self.data_path if should_export_images else None, + self.file_system if should_export_images else None, + ) write_any_json( { "title": tree["Name"]["Text"], "roots": psg.root_passives, - "skills_per_orbit": psg.skills_per_orbit, + # "skills_per_orbit": psg.skills_per_orbit, "orbit_radii": [0, 82, 162, 335, 493, 662, 846, 251, 1080, 1332], "groups": groups, "passives": nodes, From d3720e551268fad43ab81e7f9f6afda9a5c5bbba Mon Sep 17 00:00:00 2001 From: Taylor Kimmett Date: Mon, 25 May 2026 17:38:15 -0400 Subject: [PATCH 2/4] Remove dead POE2 passive export code --- RePoE/parser/poe2/passives.py | 1 - 1 file changed, 1 deletion(-) diff --git a/RePoE/parser/poe2/passives.py b/RePoE/parser/poe2/passives.py index cd40b37dd..02db2e6b2 100644 --- a/RePoE/parser/poe2/passives.py +++ b/RePoE/parser/poe2/passives.py @@ -148,7 +148,6 @@ def write(self) -> None: { "title": tree["Name"]["Text"], "roots": psg.root_passives, - # "skills_per_orbit": psg.skills_per_orbit, "orbit_radii": [0, 82, 162, 335, 493, 662, 846, 251, 1080, 1332], "groups": groups, "passives": nodes, From 56b6521d2af889360954f10a2cd24906c9cdff95 Mon Sep 17 00:00:00 2001 From: Taylor Kimmett Date: Mon, 25 May 2026 17:49:22 -0400 Subject: [PATCH 3/4] Revise POE2 passive image export changes --- RePoE/parser/poe2/ascendancies.py | 13 ++----------- RePoE/parser/poe2/characters.py | 5 +---- RePoE/parser/poe2/passives.py | 31 ++++++++++++------------------- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/RePoE/parser/poe2/ascendancies.py b/RePoE/parser/poe2/ascendancies.py index 6f15e54e5..3f16de4fa 100644 --- a/RePoE/parser/poe2/ascendancies.py +++ b/RePoE/parser/poe2/ascendancies.py @@ -6,7 +6,7 @@ from RePoE.parser import Parser_Module from RePoE.parser.modules.flavour import flavour from RePoE.parser.poe2.passives import uiart, passive -from RePoE.parser.util import call_with_default_args, export_image, write_any_json +from RePoE.parser.util import call_with_default_args, write_any_json COLS = { "ClassNo": "class_number", @@ -27,7 +27,6 @@ class ascendancies(Parser_Module): def write(self) -> None: - self.should_export_images = self.language == "English" self.relational_reader["AscendancyPassiveSkillOverrides.dat64"].build_index("AscendancyToOverrideFor") write_any_json( {asc["Id"]: self.process(asc) for asc in self.relational_reader["Ascendancy.dat64"]}, @@ -39,20 +38,12 @@ def process(self, row): tf = self.get_cache(TranslationFileCache)["passive_skill_stat_descriptions.txt"] data = {v: row[k] for k, v in COLS.items()} - if self.should_export_images and data["passive_tree_image"]: - export_image(data["passive_tree_image"], self.data_path, self.file_system) data["art"] = uiart(row["UIArt"]) if row["BaseAscendancy"]: data["passive_overrides"] = [ { "from_hash": o["SkillToOverride"]["PassiveSkillGraphId"], - "to_passive": passive( - o["Override"], - tf, - self.language, - self.data_path if self.should_export_images else None, - self.file_system if self.should_export_images else None, - ), + "to_passive": passive(o["Override"], tf, self.language), } for o in self.relational_reader["AscendancyPassiveSkillOverrides.dat64"].index[ "AscendancyToOverrideFor" diff --git a/RePoE/parser/poe2/characters.py b/RePoE/parser/poe2/characters.py index 2225106bb..b766a5ff7 100644 --- a/RePoE/parser/poe2/characters.py +++ b/RePoE/parser/poe2/characters.py @@ -1,11 +1,10 @@ -from RePoE.parser.util import call_with_default_args, export_image, write_json +from RePoE.parser.util import call_with_default_args, write_json from RePoE.parser import Parser_Module class characters(Parser_Module): def write(self): root = [] - should_export_images = self.language == "English" for row in self.relational_reader["Characters.dat64"]: character = { "metadata_id": row["Id"], @@ -28,8 +27,6 @@ def write(self): } if row["PassiveTreeImage"]: character["passive_tree_image"] = row["PassiveTreeImage"] - if should_export_images: - export_image(row["PassiveTreeImage"], self.data_path, self.file_system) root.append(character) write_json(root, self.data_path, "characters") diff --git a/RePoE/parser/poe2/passives.py b/RePoE/parser/poe2/passives.py index 02db2e6b2..7b0c3e9ac 100644 --- a/RePoE/parser/poe2/passives.py +++ b/RePoE/parser/poe2/passives.py @@ -4,7 +4,7 @@ from PyPoE.poe.file.translations import TranslationFileCache from RePoE.parser import Parser_Module -from RePoE.parser.util import call_with_default_args, export_image, write_any_json +from RePoE.parser.util import call_with_default_args, write_any_json PASSIVE_COLS = { @@ -34,7 +34,7 @@ } -def passive(row, translation_file, lang, data_path=None, file_system=None): +def passive(row, translation_file, lang): result = {k: row[col] for col, k in PASSIVE_COLS.items()} if row["PassiveSkillBuffs"]: result["buff_definitions"] = [buff["BuffDefinitionsKey"]["Id"] for buff in row["PassiveSkillBuffsKeys"]] @@ -42,8 +42,6 @@ def passive(row, translation_file, lang, data_path=None, file_system=None): result["ascendancy"] = row["Ascendancy"]["Id"] if row["Icon_DDSFile"]: result["icon"] = row["Icon_DDSFile"] - if row["Ascendancy"] and data_path and file_system: - export_image(row["Icon_DDSFile"], data_path, file_system) if row["AtlasSubTree"]: result["atlas_subtree"] = row["AtlasSubTree"]["Id"] if row["GrantedSkill"]: @@ -97,7 +95,6 @@ def frame_art(row): class passives(Parser_Module): def write(self) -> None: - should_export_images = self.language == "English" all_passives = self.relational_reader["PassiveSkills.dat64"] if "PassiveSkillGraphId" not in all_passives.index: all_passives.build_index("PassiveSkillGraphId") @@ -109,13 +106,7 @@ def write(self) -> None: nodes = {} for p in psg.root_passives: if p not in nodes: - nodes[p] = passive( - self.index[p], - tf, - self.language, - self.data_path if should_export_images else None, - self.file_system if should_export_images else None, - ) + nodes[p] = passive(self.index[p], tf, self.language) groups = [] for group in psg.groups: groups.append( @@ -137,17 +128,12 @@ def write(self) -> None: ) for node in group.nodes: if node.passive_skill not in nodes: - nodes[node.passive_skill] = passive( - self.index[node.passive_skill], - tf, - self.language, - self.data_path if should_export_images else None, - self.file_system if should_export_images else None, - ) + nodes[node.passive_skill] = passive(self.index[node.passive_skill], tf, self.language) write_any_json( { "title": tree["Name"]["Text"], "roots": psg.root_passives, + "skills_per_orbit": self.skills_per_orbit(psg), "orbit_radii": [0, 82, 162, 335, 493, 662, 846, 251, 1080, 1332], "groups": groups, "passives": nodes, @@ -162,6 +148,13 @@ def psg(self, filename): psg.read(self.file_system.get_file(filename + ".psg")) return psg + def skills_per_orbit(self, psg): + skills_per_orbit = {} + for group in psg.groups: + for node in group.nodes: + skills_per_orbit[node.radius] = max(skills_per_orbit.get(node.radius, 0), node.position + 1) + return [skills_per_orbit[i] for i in sorted(skills_per_orbit)] + if __name__ == "__main__": call_with_default_args(passives) From 03ec09c15c9e504f279a2848c4cb787bd0e3904c Mon Sep 17 00:00:00 2001 From: Taylor Kimmett Date: Fri, 29 May 2026 14:55:22 -0400 Subject: [PATCH 4/4] remove image from character schema --- RePoE/model/characters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/RePoE/model/characters.py b/RePoE/model/characters.py index 54d5bc018..7c49ca87b 100644 --- a/RePoE/model/characters.py +++ b/RePoE/model/characters.py @@ -40,7 +40,6 @@ class CharactersSchemaElement(BaseModel): extra="forbid", ) base_stats: BaseStats - passive_tree_image: Optional[str] = None integer_id: int metadata_id: MetadataID name: str