wip
This commit is contained in:
BIN
dist/text_texture_generator_v1.0.0.zip
vendored
BIN
dist/text_texture_generator_v1.0.0.zip
vendored
Binary file not shown.
BIN
dist/text_texture_generator_v1.0.0_free.zip
vendored
BIN
dist/text_texture_generator_v1.0.0_free.zip
vendored
Binary file not shown.
Binary file not shown.
@@ -1,19 +1,19 @@
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import StringProperty
|
||||
from ..utils.constants import has_multiline_text, get_version_limits
|
||||
from ..utils.constants import has_multiline_text
|
||||
|
||||
|
||||
class TEXT_TEXTURE_OT_edit_multiline_text(Operator):
|
||||
"""Open a text editor for multiline input"""
|
||||
"""Open a text editor for single-line input"""
|
||||
bl_idname = "text_texture.edit_multiline_text"
|
||||
bl_label = "Edit Multiline Text"
|
||||
bl_description = "Open a text editor for multiline input"
|
||||
bl_label = "Edit Text"
|
||||
bl_description = "Open a text editor for single-line input"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
temp_text: StringProperty(
|
||||
name="Text",
|
||||
description="Text content being edited",
|
||||
description="Single line text only - multiline not supported",
|
||||
default="",
|
||||
maxlen=4096
|
||||
)
|
||||
@@ -27,40 +27,59 @@ class TEXT_TEXTURE_OT_edit_multiline_text(Operator):
|
||||
"""Copy current text to temp storage and open dialog"""
|
||||
props = context.scene.text_texture_props
|
||||
self.temp_text = props.text
|
||||
return context.window_manager.invoke_props_dialog(self, width=600)
|
||||
return context.window_manager.invoke_props_dialog(self, width=700)
|
||||
|
||||
def draw(self, context):
|
||||
"""Draw the dialog UI"""
|
||||
"""Draw the dialog UI with single-line input"""
|
||||
layout = self.layout
|
||||
|
||||
limits = get_version_limits()
|
||||
max_lines = limits.get("max_text_lines", 50)
|
||||
max_chars = limits.get("max_text_length", 4096)
|
||||
|
||||
current_lines = self.temp_text.count('\n') + 1 if self.temp_text else 0
|
||||
max_chars = 4096
|
||||
current_chars = len(self.temp_text)
|
||||
|
||||
# Main column
|
||||
col = layout.column()
|
||||
col.prop(self, "temp_text", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.label(text=f"Lines: {current_lines}/{max_lines} | Chars: {current_chars}/{max_chars}")
|
||||
col.label(text="Edit Text (Single Line Only):", icon='EDITMODE_HLT')
|
||||
|
||||
# Make the text input larger
|
||||
text_row = col.row()
|
||||
text_row.scale_y = 2.0
|
||||
text_row.prop(self, "temp_text", text="")
|
||||
|
||||
# Warning box
|
||||
col.separator()
|
||||
info_box = col.box()
|
||||
info_box.scale_y = 0.8
|
||||
info_box.label(text="⚠️ Single Line Only:", icon='ERROR')
|
||||
row = info_box.row()
|
||||
row.label(text="• Multiline text is not supported")
|
||||
|
||||
# Statistics
|
||||
col.separator()
|
||||
stats_row = col.row()
|
||||
stats_row.label(text=f"📝 Characters: {current_chars}/{max_chars}", icon='FONT_DATA')
|
||||
|
||||
# Warnings
|
||||
if current_chars > max_chars:
|
||||
col.separator()
|
||||
warning_box = col.box()
|
||||
warning_box.alert = True
|
||||
warning_box.label(text=f"⚠ Exceeds character limit! Will be trimmed to {max_chars} chars", icon='ERROR')
|
||||
|
||||
def execute(self, context):
|
||||
"""Save temp text back to main property with limits enforced"""
|
||||
"""Save temp text back to main property - reject if contains newlines"""
|
||||
props = context.scene.text_texture_props
|
||||
|
||||
limits = get_version_limits()
|
||||
max_lines = limits.get("max_text_lines", 50)
|
||||
max_chars = limits.get("max_text_length", 4096)
|
||||
# Check for newlines and reject
|
||||
if '\n' in self.temp_text:
|
||||
self.report({'ERROR'}, "Multiline text not supported. Please use single line only.")
|
||||
return {'CANCELLED'}
|
||||
|
||||
max_chars = 4096
|
||||
|
||||
# Enforce character limit
|
||||
text = self.temp_text[:max_chars]
|
||||
|
||||
lines = text.split('\n')
|
||||
if len(lines) > max_lines:
|
||||
lines = lines[:max_lines]
|
||||
text = '\n'.join(lines)
|
||||
|
||||
props.text = text
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
Binary file not shown.
@@ -5,6 +5,7 @@ Tests verify:
|
||||
- Operator class exists and is properly structured
|
||||
- PRO-only access control via poll()
|
||||
- Text processing and limit enforcement
|
||||
- Single-line validation (rejects newlines)
|
||||
- Proper Blender operator contract
|
||||
"""
|
||||
|
||||
@@ -53,13 +54,13 @@ class TestModalTextEditorOperator:
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = "Line 1\nLine 2\nLine 3"
|
||||
mock_context.scene.text_texture_props.text = "Single line text"
|
||||
mock_context.window_manager.invoke_props_dialog.return_value = {'RUNNING_MODAL'}
|
||||
mock_event = Mock()
|
||||
|
||||
result = operator.invoke(mock_context, mock_event)
|
||||
|
||||
assert operator.temp_text == "Line 1\nLine 2\nLine 3"
|
||||
assert operator.temp_text == "Single line text"
|
||||
assert result == {'RUNNING_MODAL'}
|
||||
mock_context.window_manager.invoke_props_dialog.assert_called_once()
|
||||
|
||||
@@ -68,84 +69,83 @@ class TestModalTextEditorOperator:
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
operator.temp_text = "Edited Line 1\nEdited Line 2"
|
||||
operator.temp_text = "Edited single line"
|
||||
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = "Old text"
|
||||
|
||||
with patch('src.operators.text_editor_ops.get_version_limits', return_value={
|
||||
'max_text_lines': 50,
|
||||
'max_text_length': 4096
|
||||
}):
|
||||
result = operator.execute(mock_context)
|
||||
|
||||
assert result == {'FINISHED'}
|
||||
assert mock_context.scene.text_texture_props.text == "Edited Line 1\nEdited Line 2"
|
||||
assert mock_context.scene.text_texture_props.text == "Edited single line"
|
||||
|
||||
def test_execute_enforces_character_limit(self):
|
||||
"""execute() must truncate text exceeding max_text_length."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
# Create text exceeding 100 char limit
|
||||
operator.temp_text = "x" * 150
|
||||
# Create text exceeding 4096 char limit
|
||||
operator.temp_text = "x" * 5000
|
||||
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = ""
|
||||
|
||||
with patch('src.operators.text_editor_ops.get_version_limits', return_value={
|
||||
'max_text_lines': 50,
|
||||
'max_text_length': 100
|
||||
}):
|
||||
operator.execute(mock_context)
|
||||
|
||||
saved_text = mock_context.scene.text_texture_props.text
|
||||
assert len(saved_text) == 100, f"Expected 100 chars, got {len(saved_text)}"
|
||||
assert len(saved_text) == 4096, f"Expected 4096 chars, got {len(saved_text)}"
|
||||
|
||||
def test_execute_enforces_line_limit(self):
|
||||
"""execute() must truncate lines exceeding max_text_lines."""
|
||||
def test_execute_rejects_text_with_newlines(self):
|
||||
"""execute() must reject text containing newline characters."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
# Create 10 lines when limit is 5
|
||||
operator.temp_text = "\n".join([f"Line {i}" for i in range(10)])
|
||||
operator.temp_text = "Line 1\nLine 2"
|
||||
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = ""
|
||||
mock_context.scene.text_texture_props.text = "Old text"
|
||||
|
||||
with patch('src.operators.text_editor_ops.get_version_limits', return_value={
|
||||
'max_text_lines': 5,
|
||||
'max_text_length': 4096
|
||||
}):
|
||||
operator.execute(mock_context)
|
||||
result = operator.execute(mock_context)
|
||||
|
||||
saved_text = mock_context.scene.text_texture_props.text
|
||||
line_count = saved_text.count('\n') + 1
|
||||
assert line_count == 5, f"Expected 5 lines, got {line_count}"
|
||||
assert saved_text == "Line 0\nLine 1\nLine 2\nLine 3\nLine 4"
|
||||
# Should return CANCELLED
|
||||
assert result == {'CANCELLED'}
|
||||
# Should not modify the original text
|
||||
assert mock_context.scene.text_texture_props.text == "Old text"
|
||||
# Should report an error
|
||||
operator.report.assert_called_once_with(
|
||||
{'ERROR'},
|
||||
"Multiline text not supported. Please use single line only."
|
||||
)
|
||||
|
||||
def test_execute_handles_both_limits_simultaneously(self):
|
||||
"""execute() must enforce both character and line limits correctly."""
|
||||
def test_execute_rejects_multiple_newlines(self):
|
||||
"""execute() must reject text with multiple newline characters."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
# Create 10 lines of 20 chars each (200 chars total)
|
||||
operator.temp_text = "\n".join([f"Line {i:015d}" for i in range(10)])
|
||||
operator.temp_text = "Line 1\nLine 2\nLine 3\nLine 4"
|
||||
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = ""
|
||||
mock_context.scene.text_texture_props.text = "Old text"
|
||||
|
||||
with patch('src.operators.text_editor_ops.get_version_limits', return_value={
|
||||
'max_text_lines': 5,
|
||||
'max_text_length': 150
|
||||
}):
|
||||
operator.execute(mock_context)
|
||||
result = operator.execute(mock_context)
|
||||
|
||||
saved_text = mock_context.scene.text_texture_props.text
|
||||
# Should truncate to 150 chars first, then check lines
|
||||
assert len(saved_text) <= 150
|
||||
line_count = saved_text.count('\n') + 1
|
||||
assert line_count <= 5
|
||||
assert result == {'CANCELLED'}
|
||||
assert mock_context.scene.text_texture_props.text == "Old text"
|
||||
|
||||
def test_execute_accepts_text_without_newlines(self):
|
||||
"""execute() must accept and save text without newlines."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
operator.temp_text = "This is a single line with no newlines"
|
||||
|
||||
mock_context = Mock()
|
||||
mock_context.scene.text_texture_props.text = "Old text"
|
||||
|
||||
result = operator.execute(mock_context)
|
||||
|
||||
assert result == {'FINISHED'}
|
||||
assert mock_context.scene.text_texture_props.text == "This is a single line with no newlines"
|
||||
|
||||
def test_operator_has_temp_text_property(self):
|
||||
"""Operator must have StringProperty named temp_text."""
|
||||
@@ -179,3 +179,49 @@ class TestModalTextEditorOperator:
|
||||
|
||||
assert hasattr(text_editor_ops, 'unregister')
|
||||
assert callable(text_editor_ops.unregister)
|
||||
|
||||
|
||||
class TestSingleLineUIBehavior:
|
||||
"""Test suite for single-line text UI display and statistics."""
|
||||
|
||||
def test_draw_calculates_character_count(self):
|
||||
"""draw() must count characters correctly."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
mock_context = Mock()
|
||||
|
||||
operator.temp_text = "Single line text"
|
||||
operator.draw(mock_context)
|
||||
|
||||
actual_chars = len(operator.temp_text)
|
||||
expected_chars = 16
|
||||
assert actual_chars == expected_chars, f"Expected {expected_chars} chars, got {actual_chars}"
|
||||
|
||||
def test_draw_handles_empty_text(self):
|
||||
"""draw() must handle empty text without errors."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
mock_context = Mock()
|
||||
|
||||
operator.temp_text = ""
|
||||
operator.draw(mock_context)
|
||||
|
||||
char_count = len(operator.temp_text)
|
||||
assert char_count == 0, f"Empty text should have 0 characters, got {char_count}"
|
||||
|
||||
def test_draw_handles_long_single_line(self):
|
||||
"""draw() must handle long single-line text correctly."""
|
||||
from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text
|
||||
|
||||
operator = TEXT_TEXTURE_OT_edit_multiline_text()
|
||||
mock_context = Mock()
|
||||
|
||||
# Create a very long single line
|
||||
long_text = "x" * 5000
|
||||
operator.temp_text = long_text
|
||||
operator.draw(mock_context)
|
||||
|
||||
# Should track all characters
|
||||
assert len(operator.temp_text) == 5000
|
||||
Reference in New Issue
Block a user