258 lines
8.7 KiB
Python
258 lines
8.7 KiB
Python
"""Migration and conversion utilities for Cable Normalizer.
|
|
|
|
Provides operators to:
|
|
- Convert the selected object to a cable (adds 'Cable' GeoNode)
|
|
- Batch-migrate all legacy cables at once
|
|
- Migrate text/font settings from the Text Texture Generator addon
|
|
"""
|
|
import bpy
|
|
from . import utils
|
|
|
|
|
|
# Mapping from kabeck.blend scene names to their correct TTG label text.
|
|
# Derived from scanning all source .blend files' text_texture_props.
|
|
# Only entries where scene name differs from the actual label text need
|
|
# to be listed; for all others the scene name IS the label text.
|
|
_TTG_LABEL_TEXT = {
|
|
'N2XSF2Y': 'N2XS(F)2Y',
|
|
'NA2XSF2Y': 'NA2XS(F)2Y',
|
|
'NA2XSFL2Y': 'NA2XS(FL)2Y',
|
|
'NA2XY': '(N)A2XY',
|
|
}
|
|
|
|
|
|
def _get_ttg_data_for_scene(scene):
|
|
"""Extract text and font from Text Texture Generator properties.
|
|
|
|
Checks both the scene's own text_texture_props and, if the object
|
|
is linked, attempts to read from the source library's scene.
|
|
|
|
Returns dict with keys: text, font_path (or None if TTG not found).
|
|
"""
|
|
if not hasattr(scene, 'text_texture_props'):
|
|
return None
|
|
|
|
tp = scene.text_texture_props
|
|
text = tp.prepend_text + tp.text + tp.append_text
|
|
font_path = ''
|
|
|
|
if tp.use_custom_font and tp.custom_font_path:
|
|
font_path = tp.custom_font_path
|
|
elif tp.font_path and tp.font_path != 'default':
|
|
font_path = tp.font_path
|
|
|
|
return {'text': text, 'font_path': font_path}
|
|
|
|
|
|
def _find_ttg_font_from_libraries():
|
|
"""Scan linked library scenes for Text Texture Generator font settings.
|
|
|
|
Since the kabeck.blend links objects from source blends (NYY.blend etc.),
|
|
the TTG settings live in those source files. We can't open them during
|
|
migration, but the linked data may carry scene references.
|
|
|
|
As a pragmatic fallback, we check if any scene in the current file
|
|
has non-empty TTG settings. If not, we look for commonly used fonts
|
|
on disk based on what we know from the source blend analysis.
|
|
"""
|
|
# First: Check all scenes in current file for TTG data
|
|
for scn in bpy.data.scenes:
|
|
data = _get_ttg_data_for_scene(scn)
|
|
if data and data['font_path']:
|
|
return data['font_path']
|
|
|
|
# Fallback: Check known font path from the source blends
|
|
# (all source blends use "Windows Dots.ttf" per analysis)
|
|
import os
|
|
known_font = "/Volumes/Alpha SSD/Fonts/Windows Dots.ttf"
|
|
if os.path.exists(known_font):
|
|
return known_font
|
|
|
|
return ''
|
|
|
|
|
|
def _find_ttg_prefix():
|
|
"""Find the PREPEND composition text from TTG overlays.
|
|
|
|
The TTG composition system stores prepend text as overlay components
|
|
with placement_mode='PREPEND'. All source blends use 'Kabeck' as
|
|
the prefix, set as a TEXT overlay component.
|
|
"""
|
|
# Check all scenes for TTG overlay data
|
|
for scn in bpy.data.scenes:
|
|
if not hasattr(scn, 'text_texture_props'):
|
|
continue
|
|
tp = scn.text_texture_props
|
|
for ov in tp.image_overlays:
|
|
if ov.component_type == 'TEXT' and ov.placement_mode == 'PREPEND':
|
|
if ov.text_content:
|
|
return ov.text_content
|
|
|
|
# Fallback: known prefix from source blend analysis
|
|
return 'Kabeck'
|
|
|
|
def _add_cable_node(obj, label_text=''):
|
|
"""Add a Cable node modifier to an object and populate defaults.
|
|
|
|
The Cable node group is created once and shared across all cables.
|
|
This is effectively 'linked' behavior — all cables reference the
|
|
same node group definition.
|
|
"""
|
|
cable_ng = utils.ensure_cable_node_group()
|
|
|
|
# Avoid duplicates
|
|
if utils.is_cable(obj):
|
|
return None
|
|
|
|
mod = obj.modifiers.new(name=utils.CABLE_NODE_NAME, type='NODES')
|
|
mod.node_group = cable_ng
|
|
|
|
# Auto-detect label text with priority chain:
|
|
# 1. TTG mapping table (known mismatches)
|
|
# 2. TTG scene properties (prepend + text + append)
|
|
# 3. Scene name as fallback
|
|
if not label_text:
|
|
for scn in bpy.data.scenes:
|
|
if obj.name in scn.objects:
|
|
scene_name = scn.name
|
|
# Priority 1: Known TTG label text mapping
|
|
if scene_name in _TTG_LABEL_TEXT:
|
|
label_text = _TTG_LABEL_TEXT[scene_name]
|
|
else:
|
|
# Priority 2: TTG properties on the scene
|
|
ttg = _get_ttg_data_for_scene(scn)
|
|
if ttg and ttg['text']:
|
|
label_text = ttg['text']
|
|
else:
|
|
# Priority 3: Scene name
|
|
label_text = scene_name
|
|
break
|
|
|
|
# Read current diameter from legacy structure
|
|
try:
|
|
params = utils.get_cable_params(obj)
|
|
outer_diam = utils.compute_outer_diameter(params)
|
|
current_diam = outer_diam * obj.scale[0]
|
|
except ValueError:
|
|
current_diam = 13.8
|
|
|
|
# Populate sockets
|
|
for item in cable_ng.interface.items_tree:
|
|
if item.item_type != 'SOCKET' or item.in_out != 'INPUT':
|
|
continue
|
|
if item.name == 'Label Text':
|
|
mod[item.identifier] = label_text
|
|
elif item.name == 'Target Diameter':
|
|
mod[item.identifier] = current_diam
|
|
|
|
return mod
|
|
|
|
|
|
class KABECK_OT_convert_to_cable(bpy.types.Operator):
|
|
"""Convert the selected object to a cable by adding the Cable node."""
|
|
bl_idname = "kabeck.convert_to_cable"
|
|
bl_label = "Convert to Cable"
|
|
bl_description = (
|
|
"Adds the 'Cable' Geometry Node to the selected object, "
|
|
"marking it as a cable and enabling per-cable label settings"
|
|
)
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
obj = context.active_object
|
|
return obj is not None and obj.type == 'MESH' and not utils.is_cable(obj)
|
|
|
|
def execute(self, context):
|
|
obj = context.active_object
|
|
|
|
mod = _add_cable_node(obj)
|
|
if mod is None:
|
|
self.report({'WARNING'}, f"'{obj.name}' is already a cable")
|
|
return {'CANCELLED'}
|
|
|
|
self.report({'INFO'}, f"Converted '{obj.name}' to cable")
|
|
return {'FINISHED'}
|
|
|
|
|
|
class KABECK_OT_migrate_cables(bpy.types.Operator):
|
|
"""Batch-convert legacy cables and update existing Cable nodes.
|
|
|
|
Migrates Text Texture Generator settings:
|
|
- Label Text from TTG text/prepend/append fields (or mapping table)
|
|
- Font path set globally from TTG custom_font_path
|
|
|
|
Can be re-run to update Label Text on already-migrated cables.
|
|
"""
|
|
bl_idname = "kabeck.migrate_cables"
|
|
bl_label = "Migrate / Update Cable Labels"
|
|
bl_description = (
|
|
"Add or update 'Cable' GeoNode on all cables, "
|
|
"migrate text/font from Text Texture Generator"
|
|
)
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
migrated = 0
|
|
updated = 0
|
|
|
|
# Migrate font setting globally from TTG
|
|
ttg_font = _find_ttg_font_from_libraries()
|
|
if ttg_font:
|
|
context.scene.normalizer_font_path = ttg_font
|
|
|
|
# Migrate prefix from TTG composition overlays
|
|
ttg_prefix = _find_ttg_prefix()
|
|
if ttg_prefix:
|
|
context.scene.normalizer_label_prefix = ttg_prefix
|
|
|
|
for obj in bpy.data.objects:
|
|
if utils.is_cable(obj):
|
|
# Already has Cable node → update Label Text
|
|
self._update_label_text(obj)
|
|
updated += 1
|
|
continue
|
|
if not utils.is_legacy_cable(obj):
|
|
continue
|
|
|
|
_add_cable_node(obj)
|
|
migrated += 1
|
|
|
|
font_msg = f", font: {ttg_font}" if ttg_font else ""
|
|
self.report(
|
|
{'INFO'},
|
|
f"Migrated {migrated}, updated {updated} cables{font_msg}",
|
|
)
|
|
return {'FINISHED'}
|
|
|
|
def _update_label_text(self, obj):
|
|
"""Update the Label Text on an existing Cable node from TTG data."""
|
|
mod = utils._get_cable_modifier(obj)
|
|
if mod is None:
|
|
return
|
|
|
|
# Resolve the correct label text
|
|
label_text = ''
|
|
for scn in bpy.data.scenes:
|
|
if obj.name in scn.objects:
|
|
scene_name = scn.name
|
|
if scene_name in _TTG_LABEL_TEXT:
|
|
label_text = _TTG_LABEL_TEXT[scene_name]
|
|
else:
|
|
ttg = _get_ttg_data_for_scene(scn)
|
|
if ttg and ttg['text']:
|
|
label_text = ttg['text']
|
|
else:
|
|
label_text = scene_name
|
|
break
|
|
|
|
if not label_text:
|
|
return
|
|
|
|
# Write to the Cable node socket
|
|
cable_ng = mod.node_group
|
|
for item in cable_ng.interface.items_tree:
|
|
if item.item_type == 'SOCKET' and item.in_out == 'INPUT' and item.name == 'Label Text':
|
|
mod[item.identifier] = label_text
|
|
break
|