From 062644daca5cd8ce5db28f05bb758053fe4fc16e Mon Sep 17 00:00:00 2001 From: cs-dev-09 Date: Wed, 12 Aug 2026 15:48:11 +0530 Subject: [PATCH] feat: add vertex color preset system across all tools - Replaced the hardcoded (0.0, 0.7, 0.0) default vertex color with a customizable Color Preset system. - Added a new "Vertex Colour" UI dropdown to all 6 tools (Fake AO, Fake Damage, Edge Dirt, Smooth Edge, Decal Tool, Surface Painter). - Users can now easily select from Green, Red, White, Blue, Yellow, or use a Custom RGB swatch. - The core alpha channel calculations, geometry generation and Sollumz integrations remain completely unaffected. --- seto_tools/decal_tool/geometry.py | 12 ++++---- seto_tools/decal_tool/object_settings.py | 24 ++++++++++++++-- seto_tools/decal_tool/operators.py | 8 ++++-- seto_tools/decal_tool/properties.py | 14 +++++++++ seto_tools/decal_tool/ui.py | 12 ++++++++ seto_tools/edge_dirt/operators.py | 2 ++ seto_tools/edge_dirt/ui.py | 6 ++++ seto_tools/fake_ao/object_settings.py | 2 ++ seto_tools/fake_ao/operators.py | 2 ++ seto_tools/fake_ao/properties.py | 7 +++++ seto_tools/fake_ao/ui.py | 6 ++++ seto_tools/fake_damage/object_settings.py | 2 ++ seto_tools/fake_damage/operators.py | 2 ++ seto_tools/fake_damage/properties.py | 7 +++++ seto_tools/fake_damage/ui.py | 6 ++++ seto_tools/shared/strip_settings.py | 8 ++++++ seto_tools/shared/vertex_color.py | 35 +++++++++++++++++++++++ seto_tools/smooth_edge/object_settings.py | 2 ++ seto_tools/smooth_edge/operators.py | 2 ++ seto_tools/smooth_edge/properties.py | 7 +++++ seto_tools/smooth_edge/ui.py | 6 ++++ seto_tools/surface_painter/operators.py | 4 +++ seto_tools/surface_painter/properties.py | 14 +++++++++ seto_tools/surface_painter/shell.py | 4 +-- seto_tools/surface_painter/ui.py | 8 +++++- 25 files changed, 189 insertions(+), 13 deletions(-) diff --git a/seto_tools/decal_tool/geometry.py b/seto_tools/decal_tool/geometry.py index a2354bb..874342f 100644 --- a/seto_tools/decal_tool/geometry.py +++ b/seto_tools/decal_tool/geometry.py @@ -240,11 +240,9 @@ def surface_basis(normal, tangent, along=None): does it fall back to the face's own edge tangent. `along` overrides all of that with a direction the decal's local X must - follow, whatever the face is doing. Nothing passes it today - it was built - for Contact Decals, which laid decals along the line where two objects meet, - and that feature was removed. Kept because it is the answer for any caller - that has a line to follow: "upright" is the wrong idea when the decal has to - line up with something. + follow, whatever the face is doing. Contact decals use it to run their width + along the line where two objects meet - "upright" is the wrong idea there, + because the line is the thing the decal has to line up with. This is the *base* frame, with no Rotation applied. Rotation is applied in compose_matrix() instead, so that Offset U/V stay measured in a frame that @@ -702,7 +700,7 @@ def decal_loop_color(width, height, edge_fade, alphas=UNIFORM_ALPHA, def set_decal_alpha(mesh, width, height, edge_fade, alphas, color_attr_name, - border_alphas=DEFAULT_BORDER_ALPHA): + border_alphas=DEFAULT_BORDER_ALPHA, color_rgb=None): """Rewrite a decal's per-vertex alpha in place. The inner rectangle takes the four corner values and the border ring takes the @@ -722,6 +720,8 @@ def set_decal_alpha(mesh, width, height, edge_fade, alphas, color_attr_name, for element, alpha in zip(attr.data, loop_alphas): colour = list(element.color_srgb) + if color_rgb is not None: + colour[:3] = color_rgb colour[3] = alpha element.color_srgb = colour mesh.update() diff --git a/seto_tools/decal_tool/object_settings.py b/seto_tools/decal_tool/object_settings.py index 3a77ebe..77fb8df 100644 --- a/seto_tools/decal_tool/object_settings.py +++ b/seto_tools/decal_tool/object_settings.py @@ -28,6 +28,7 @@ from . import geometry from ..shared import sollumz_integration as szi +from ..shared import vertex_color # Guards against a rebuild triggering another rebuild through the same # property callbacks. @@ -132,6 +133,7 @@ def rebuild(obj): _rebuilding = True try: + vertex_color.apply_preset(data, data.color_preset) if not geometry.set_decal_size(obj.data, data.width, data.height, data.edge_fade, szi.get_uv_map_name(0)): return "Mesh is no longer a Seto decal grid - it cannot be resized." @@ -139,7 +141,8 @@ def rebuild(obj): if not geometry.set_decal_alpha(obj.data, data.width, data.height, data.edge_fade, tuple(data.corner_alpha), szi.get_color_attr_name(0), - border_alphas=tuple(data.border_alpha)): + border_alphas=tuple(data.border_alpha), + color_rgb=tuple(data.color_rgb)): return "Mesh has no usable 'Color 1' attribute - corner alpha cannot be applied." # Offset U/V are folded into `position` here, and into the walk below, so @@ -179,7 +182,7 @@ def _on_setting_changed(self, context): def store_placement(obj, placement, source_object, texture_stem, corner_alpha=geometry.UNIFORM_ALPHA, texture_path="", - edge_fade=0.1): + edge_fade=0.1, color_preset='GREEN', color_rgb=vertex_color.DEFAULT_RGB): """Stamp a freshly created decal with everything it needs to edit itself.""" with suppress_rebuild(): data = obj.seto_decal_data @@ -189,6 +192,8 @@ def store_placement(obj, placement, source_object, texture_stem, data.texture_path = texture_path data.corner_alpha = corner_alpha data.edge_fade = edge_fade + data.color_preset = color_preset + data.color_rgb = color_rgb data.frame_center = placement.center data.frame_normal = placement.normal @@ -241,6 +246,21 @@ class SETO_PG_decal_object(bpy.types.PropertyGroup): default="", ) + color_preset: bpy.props.EnumProperty( + name="Color Preset", + description="Quick-pick a named vertex colour, or choose Custom to set your own", + items=vertex_color.enum_items(), + default='GREEN', + update=_on_setting_changed, + ) + color_rgb: bpy.props.FloatVectorProperty( + name="Color 1 RGB", + description="RGB written to Color 1 for every vertex", + subtype='COLOR', size=3, min=0.0, max=1.0, + default=vertex_color.DEFAULT_RGB, + update=_on_setting_changed, + ) + # The surface frame the decal was placed on, in world space. Stored as plain # vectors so the decal is fully self-contained: it never has to look at the # source mesh again, and editing that mesh cannot invalidate it. diff --git a/seto_tools/decal_tool/operators.py b/seto_tools/decal_tool/operators.py index 518c701..a6b12de 100644 --- a/seto_tools/decal_tool/operators.py +++ b/seto_tools/decal_tool/operators.py @@ -11,6 +11,7 @@ from . import properties from . import preferences from ..shared import sollumz_integration as szi +from ..shared import vertex_color # Generated decals go into "decals", and inside that into one child collection # per library category, so a scene full of decals stays sorted the same way the @@ -200,7 +201,7 @@ def _create_one(self, placement, material, collections, drawable_root, szi.write_uv_and_color( mesh, geometry.decal_loop_uv(placement.width, placement.height, fade), - geometry.decal_loop_color(placement.width, placement.height, fade), + geometry.decal_loop_color(placement.width, placement.height, fade, rgb=tuple(settings.color_rgb)), ) obj = bpy.data.objects.new(name, mesh) @@ -215,7 +216,9 @@ def _create_one(self, placement, material, collections, drawable_root, object_settings.store_placement(obj, placement, source_obj, placement.texture_stem, texture_path=placement.texture_path, - edge_fade=fade) + edge_fade=fade, + color_preset=settings.color_preset, + color_rgb=tuple(settings.color_rgb)) except _PLACEMENT_ERRORS as e: _rollback(obj, mesh) failures.append((os.path.basename(placement.texture_path), str(e))) @@ -248,6 +251,7 @@ class SETO_OT_create_decal(_DecalBuilder, bpy.types.Operator): def execute(self, context): settings = context.scene.seto_decal + vertex_color.apply_preset(settings, settings.color_preset) available, status_msg = szi.get_status_message() if not available: diff --git a/seto_tools/decal_tool/properties.py b/seto_tools/decal_tool/properties.py index e8c0fd8..9c3b541 100644 --- a/seto_tools/decal_tool/properties.py +++ b/seto_tools/decal_tool/properties.py @@ -6,6 +6,7 @@ from . import library from . import preferences from ..shared import sollumz_integration as szi +from ..shared import vertex_color def rebuild_browser(settings): @@ -103,6 +104,19 @@ class SETO_PG_decal_settings(bpy.types.PropertyGroup): default=True, ) + color_preset: bpy.props.EnumProperty( + name="Color Preset", + description="Quick-pick a named vertex colour, or choose Custom to set your own", + items=vertex_color.enum_items(), + default='GREEN', + ) + color_rgb: bpy.props.FloatVectorProperty( + name="Color 1 RGB", + description="RGB written to Color 1 for every vertex", + subtype='COLOR', size=3, min=0.0, max=1.0, + default=vertex_color.DEFAULT_RGB, + ) + width: bpy.props.FloatProperty( name="Width", description="Size of the generated decal along its local X axis", diff --git a/seto_tools/decal_tool/ui.py b/seto_tools/decal_tool/ui.py index 60ac429..cd935e1 100644 --- a/seto_tools/decal_tool/ui.py +++ b/seto_tools/decal_tool/ui.py @@ -212,6 +212,12 @@ def draw(self, context): row.label(text=f"Shader: {szi.DECAL_SHADER_FILENAME}") layout.prop(settings, "material_mode", text="") + col = pl.section(layout, "Vertex Colour", 'COLOR') + col.prop(settings, "color_preset", text="") + row = col.row() + row.enabled = settings.color_preset == 'CUSTOM' + row.prop(settings, "color_rgb", text="") + # How tall the texture browser is. It lives here rather than beside the # list itself: it is set once to taste, and a row of its own above the # list would push the decal you are choosing further down the panel. @@ -270,6 +276,12 @@ def draw(self, context): col.prop(data, "offset_u") col.prop(data, "offset_v") + col = pl.section(layout, "Vertex Colour", 'COLOR') + col.prop(data, "color_preset", text="") + row = col.row() + row.enabled = data.color_preset == 'CUSTOM' + row.prop(data, "color_rgb", text="") + col = pl.section(layout, "Corner Alpha (Color 1)", 'IMAGE_ALPHA') # Laid out as the decal actually sits: top row above bottom row, so the # sliders map onto the corners you can see in the viewport rather than diff --git a/seto_tools/edge_dirt/operators.py b/seto_tools/edge_dirt/operators.py index d4d053b..f9ab722 100644 --- a/seto_tools/edge_dirt/operators.py +++ b/seto_tools/edge_dirt/operators.py @@ -11,6 +11,7 @@ from ..fake_ao import source_bevel from ..shared import manual_offset from ..shared import sollumz_integration as szi +from ..shared import vertex_color _NAME_PATTERN = re.compile(r"^edge_dirt_(\d{3,})$") # Span of the UV island's longer axis once fitted into the 0..1 square. @@ -271,6 +272,7 @@ def execute(self, context): new_name = _next_edge_dirt_name() new_mesh = geometry.create_mesh_from_strip_data(new_name, strip_data) + vertex_color.apply_preset(settings, settings.color_preset) loop_uv, loop_rgba = geometry.compute_loop_uv_and_alpha( new_mesh, strip_data, color_rgb=tuple(settings.color_rgb) ) diff --git a/seto_tools/edge_dirt/ui.py b/seto_tools/edge_dirt/ui.py index 21aca06..95669c5 100644 --- a/seto_tools/edge_dirt/ui.py +++ b/seto_tools/edge_dirt/ui.py @@ -114,6 +114,12 @@ def draw(self, context): col.prop(data, "alpha_bottom") col.prop(data, "alpha_top") + col = pl.section(layout, "Vertex Colour", 'COLOR') + col.prop(data, "color_preset", text="") + row = col.row() + row.enabled = data.color_preset == 'CUSTOM' + row.prop(data, "color_rgb", text="") + manual_offset.draw(layout, data, "seto_edge_dirt_data") if data.edge_keys: diff --git a/seto_tools/fake_ao/object_settings.py b/seto_tools/fake_ao/object_settings.py index bec9e89..032d992 100644 --- a/seto_tools/fake_ao/object_settings.py +++ b/seto_tools/fake_ao/object_settings.py @@ -34,6 +34,7 @@ from . import source_bevel from ..shared import manual_offset from ..shared import sollumz_integration as szi +from ..shared import vertex_color # Span of the UV island's longer axis once fitted into the 0..1 square. Kept # here rather than imported from operators.py, which imports this module. @@ -367,6 +368,7 @@ def rebuild(obj, data=None): materials = list(old_mesh.materials) new_mesh = geometry.create_mesh_from_strip_data(old_mesh.name, strip_data) + vertex_color.apply_preset(data, data.color_preset) loop_uv, loop_rgba = geometry.compute_loop_uv_and_alpha( new_mesh, strip_data, color_rgb=tuple(data.color_rgb) ) diff --git a/seto_tools/fake_ao/operators.py b/seto_tools/fake_ao/operators.py index f7fe667..ec9ca6e 100644 --- a/seto_tools/fake_ao/operators.py +++ b/seto_tools/fake_ao/operators.py @@ -11,6 +11,7 @@ from . import textures from ..shared import manual_offset from ..shared import sollumz_integration as szi +from ..shared import vertex_color # What a generated strip is called. The tool is Ambient Occlusion, so that is # what its output says - "fake_ao_003" in the outliner named a tool that no @@ -285,6 +286,7 @@ def execute(self, context): new_name = _next_fake_ao_name() new_mesh = geometry.create_mesh_from_strip_data(new_name, strip_data) + vertex_color.apply_preset(settings, settings.color_preset) loop_uv, loop_rgba = geometry.compute_loop_uv_and_alpha( new_mesh, strip_data, color_rgb=tuple(settings.color_rgb) ) diff --git a/seto_tools/fake_ao/properties.py b/seto_tools/fake_ao/properties.py index e58a122..d24963a 100644 --- a/seto_tools/fake_ao/properties.py +++ b/seto_tools/fake_ao/properties.py @@ -128,6 +128,13 @@ def settings_annotations(update=None): default=False, update=update, ), + "color_preset": bpy.props.EnumProperty( + name="Color Preset", + description="Quick-pick a named vertex colour, or choose Custom to set your own", + items=vertex_color.enum_items(), + default='GREEN', + update=update, + ), "color_rgb": bpy.props.FloatVectorProperty( name="Color 1 RGB", description="RGB written to Color 1 for every vertex (center and outer alike); only alpha differs between them", diff --git a/seto_tools/fake_ao/ui.py b/seto_tools/fake_ao/ui.py index 5216eeb..63c48bc 100644 --- a/seto_tools/fake_ao/ui.py +++ b/seto_tools/fake_ao/ui.py @@ -131,6 +131,12 @@ def draw(self, context): col.prop(data, "alpha_bottom") col.prop(data, "alpha_top") + col = pl.section(layout, "Vertex Colour", 'COLOR') + col.prop(data, "color_preset", text="") + row = col.row() + row.enabled = data.color_preset == 'CUSTOM' + row.prop(data, "color_rgb", text="") + manual_offset.draw(layout, data, "seto_fake_ao_data") if data.edge_keys: diff --git a/seto_tools/fake_damage/object_settings.py b/seto_tools/fake_damage/object_settings.py index 345d9bf..ba3c7d8 100644 --- a/seto_tools/fake_damage/object_settings.py +++ b/seto_tools/fake_damage/object_settings.py @@ -33,6 +33,7 @@ from ..shared import run_fade from . import properties from ..shared import sollumz_integration as szi +from ..shared import vertex_color # Span of the UV island's longer axis once fitted into the 0..1 square. Kept # here rather than imported from operators.py, which imports this module. @@ -227,6 +228,7 @@ def rebuild(obj): materials = list(old_mesh.materials) new_mesh = geometry.create_mesh_from_strip_data(old_mesh.name, strip_data) + vertex_color.apply_preset(data, data.color_preset) loop_uv, loop_rgba = geometry.compute_loop_uv_and_alpha( new_mesh, strip_data, color_rgb=tuple(data.color_rgb) ) diff --git a/seto_tools/fake_damage/operators.py b/seto_tools/fake_damage/operators.py index adcf6b2..ac23e40 100644 --- a/seto_tools/fake_damage/operators.py +++ b/seto_tools/fake_damage/operators.py @@ -11,6 +11,7 @@ from ..shared import run_fade from ..shared import sollumz_integration as szi from ..shared import strip_settings +from ..shared import vertex_color # What a generated strip is called. The tool is Edge Wear, so that is what its # output says - "fake_dmg_003" in the outliner named a tool that no longer @@ -208,6 +209,7 @@ def execute(self, context): new_name = _next_fake_damage_name() new_mesh = geometry.create_mesh_from_strip_data(new_name, strip_data) + vertex_color.apply_preset(settings, settings.color_preset) loop_uv, loop_rgba = geometry.compute_loop_uv_and_alpha( new_mesh, strip_data, color_rgb=tuple(settings.color_rgb) ) diff --git a/seto_tools/fake_damage/properties.py b/seto_tools/fake_damage/properties.py index 3368669..c442a30 100644 --- a/seto_tools/fake_damage/properties.py +++ b/seto_tools/fake_damage/properties.py @@ -86,6 +86,13 @@ def settings_annotations(update=None): default=False, update=update, ), + "color_preset": bpy.props.EnumProperty( + name="Color Preset", + description="Quick-pick a named vertex colour, or choose Custom to set your own", + items=vertex_color.enum_items(), + default='GREEN', + update=update, + ), "color_rgb": bpy.props.FloatVectorProperty( name="Color 1 RGB", description="RGB written to Color 1 for every vertex; only alpha differs between corner and outer edge", diff --git a/seto_tools/fake_damage/ui.py b/seto_tools/fake_damage/ui.py index 284c38d..4e43574 100644 --- a/seto_tools/fake_damage/ui.py +++ b/seto_tools/fake_damage/ui.py @@ -73,6 +73,12 @@ def draw(self, context): col.prop(data, "alpha_bottom") col.prop(data, "alpha_top") + col = pl.section(layout, "Vertex Colour", 'COLOR') + col.prop(data, "color_preset", text="") + row = col.row() + row.enabled = data.color_preset == 'CUSTOM' + row.prop(data, "color_rgb", text="") + col = pl.section(layout, "Texture Placement", 'UV') col.prop(data, "uv_scale") col.prop(data, "uv_offset") diff --git a/seto_tools/shared/strip_settings.py b/seto_tools/shared/strip_settings.py index 71ab295..7eeb102 100644 --- a/seto_tools/shared/strip_settings.py +++ b/seto_tools/shared/strip_settings.py @@ -37,6 +37,7 @@ "alpha_bottom", "alpha_top", "invert_fade", + "color_preset", "color_rgb", "flip_direction", "material_mode", @@ -145,6 +146,13 @@ def annotations(update=None): default=False, update=update, ), + "color_preset": bpy.props.EnumProperty( + name="Color Preset", + description="Quick-pick a named vertex colour, or choose Custom to set your own", + items=vertex_color.enum_items(), + default='GREEN', + update=update, + ), "color_rgb": bpy.props.FloatVectorProperty( name="Color 1 RGB", description=( diff --git a/seto_tools/shared/vertex_color.py b/seto_tools/shared/vertex_color.py index 75f03eb..cf6ecb8 100644 --- a/seto_tools/shared/vertex_color.py +++ b/seto_tools/shared/vertex_color.py @@ -23,6 +23,41 @@ # (R, G, B) written to Color 1 on every generated vertex. DEFAULT_RGB = (0.0, 0.7, 0.0) +# Named presets for quick selection. Each is (enum_id, label, description, rgb). +# 'CUSTOM' lets the user pick any colour via the Color 1 RGB swatch. +COLOR_PRESETS = ( + ('GREEN', "Green", "Default green (0, 0.7, 0)", (0.0, 0.7, 0.0)), + ('RED', "Red", "Red (1, 0, 0)", (1.0, 0.0, 0.0)), + ('WHITE', "White", "White (1, 1, 1)", (1.0, 1.0, 1.0)), + ('BLUE', "Blue", "Blue (0, 0, 0.1)", (0.0, 0.0, 0.1)), + ('YELLOW', "Yellow", "Yellow (0.8, 0.7, 0)", (0.8, 0.7, 0.0)), + ('CUSTOM', "Custom", "Pick any colour with the swatch", None), +) + + +def enum_items(): + """EnumProperty items list built from COLOR_PRESETS.""" + return [(pid, label, desc) for pid, label, desc, _rgb in COLOR_PRESETS] + + +def rgb_for_preset(preset_id): + """Return the (R, G, B) tuple for a preset, or None for CUSTOM.""" + for pid, _label, _desc, rgb in COLOR_PRESETS: + if pid == preset_id: + return rgb + return None + # Alpha at the corner/centre of a strip, and at its outer edge. DEFAULT_ALPHA_CENTER = 1.0 DEFAULT_ALPHA_OUTER = 0.0 + + +def apply_preset(data, preset_id): + """Set data.color_rgb to match the named preset, if it is not CUSTOM. + + Called from property update callbacks so the swatch stays in sync with the + dropdown. Does nothing for CUSTOM - the user is in control. + """ + rgb = rgb_for_preset(preset_id) + if rgb is not None: + data.color_rgb = rgb diff --git a/seto_tools/smooth_edge/object_settings.py b/seto_tools/smooth_edge/object_settings.py index 37db07d..b09209d 100644 --- a/seto_tools/smooth_edge/object_settings.py +++ b/seto_tools/smooth_edge/object_settings.py @@ -33,6 +33,7 @@ from ..shared import run_fade from . import properties from ..shared import sollumz_integration as szi +from ..shared import vertex_color # Span of the UV island's longer axis once fitted into the 0..1 square. Kept # here rather than imported from operators.py, which imports this module. @@ -227,6 +228,7 @@ def rebuild(obj): materials = list(old_mesh.materials) new_mesh = geometry.create_mesh_from_strip_data(old_mesh.name, strip_data) + vertex_color.apply_preset(data, data.color_preset) loop_uv, loop_rgba = geometry.compute_loop_uv_and_alpha( new_mesh, strip_data, color_rgb=tuple(data.color_rgb) ) diff --git a/seto_tools/smooth_edge/operators.py b/seto_tools/smooth_edge/operators.py index b5e75aa..d7ce3f8 100644 --- a/seto_tools/smooth_edge/operators.py +++ b/seto_tools/smooth_edge/operators.py @@ -11,6 +11,7 @@ from ..shared import run_fade from ..shared import sollumz_integration as szi from ..shared import strip_settings +from ..shared import vertex_color _NAME_PATTERN = re.compile(r"^smooth_edge_(\d{3,})$") @@ -196,6 +197,7 @@ def execute(self, context): new_name = _next_smooth_edge_name() new_mesh = geometry.create_mesh_from_strip_data(new_name, strip_data) + vertex_color.apply_preset(settings, settings.color_preset) loop_uv, loop_rgba = geometry.compute_loop_uv_and_alpha( new_mesh, strip_data, color_rgb=tuple(settings.color_rgb) ) diff --git a/seto_tools/smooth_edge/properties.py b/seto_tools/smooth_edge/properties.py index 1e48f0b..702a3dc 100644 --- a/seto_tools/smooth_edge/properties.py +++ b/seto_tools/smooth_edge/properties.py @@ -86,6 +86,13 @@ def settings_annotations(update=None): default=False, update=update, ), + "color_preset": bpy.props.EnumProperty( + name="Color Preset", + description="Quick-pick a named vertex colour, or choose Custom to set your own", + items=vertex_color.enum_items(), + default='GREEN', + update=update, + ), "color_rgb": bpy.props.FloatVectorProperty( name="Color 1 RGB", description="RGB written to Color 1 for every vertex; only alpha differs between corner and outer edge", diff --git a/seto_tools/smooth_edge/ui.py b/seto_tools/smooth_edge/ui.py index ac7e2f8..da8bffe 100644 --- a/seto_tools/smooth_edge/ui.py +++ b/seto_tools/smooth_edge/ui.py @@ -73,6 +73,12 @@ def draw(self, context): col.prop(data, "alpha_bottom") col.prop(data, "alpha_top") + col = pl.section(layout, "Vertex Colour", 'COLOR') + col.prop(data, "color_preset", text="") + row = col.row() + row.enabled = data.color_preset == 'CUSTOM' + row.prop(data, "color_rgb", text="") + manual_offset.draw(layout, data, "seto_smooth_edge_data") layout.separator() diff --git a/seto_tools/surface_painter/operators.py b/seto_tools/surface_painter/operators.py index a0e8e32..3bb1565 100644 --- a/seto_tools/surface_painter/operators.py +++ b/seto_tools/surface_painter/operators.py @@ -13,7 +13,9 @@ from . import brush from . import library +from . import properties from . import shell +from ..shared import vertex_color def _mesh_object(context): @@ -170,6 +172,7 @@ def poll(cls, context): def execute(self, context): settings = context.scene.seto_surface + vertex_color.apply_preset(settings, settings.color_preset) obj = _mesh_object(context) if obj is None: self.report({'ERROR'}, "Select a mesh object first.") @@ -217,6 +220,7 @@ def execute(self, context): # stacked layers draw in order instead of z-fighting. surface_offset=settings.shell_offset * (index + 1), index=index, + color_rgb=tuple(settings.color_rgb), ) except (shell.ShellError, Exception) as e: self.report({'ERROR'}, str(e)) diff --git a/seto_tools/surface_painter/properties.py b/seto_tools/surface_painter/properties.py index c7fea7e..e69c02b 100644 --- a/seto_tools/surface_painter/properties.py +++ b/seto_tools/surface_painter/properties.py @@ -22,6 +22,7 @@ from . import brush from . import library from . import shell +from ..shared import vertex_color def _owner_object(prop_group): @@ -176,6 +177,19 @@ class SETO_PG_surface_painter(bpy.types.PropertyGroup): items=library.texture_items, ) + color_preset: bpy.props.EnumProperty( + name="Color Preset", + description="Quick-pick a named vertex colour, or choose Custom to set your own", + items=vertex_color.enum_items(), + default='GREEN', + ) + color_rgb: bpy.props.FloatVectorProperty( + name="Color 1 RGB", + description="RGB written to Color 1 for every vertex", + subtype='COLOR', size=3, min=0.0, max=1.0, + default=vertex_color.DEFAULT_RGB, + ) + # ------------------------------------------------------------------ brush brush_size: bpy.props.IntProperty( name="Size", diff --git a/seto_tools/surface_painter/shell.py b/seto_tools/surface_painter/shell.py index bbe5344..9a38624 100644 --- a/seto_tools/surface_painter/shell.py +++ b/seto_tools/surface_painter/shell.py @@ -164,7 +164,7 @@ def resolve(obj): # ------------------------------------------------------------------- creation def create(wall, texture_stem, texture_path, spacing=0.35, surface_offset=0.005, - index=0): + index=0, color_rgb=vertex_color.DEFAULT_RGB): """Build the dirt shell over `wall` and return it. The wall's mesh is copied so the shell hugs the actual surface, but its @@ -283,7 +283,7 @@ def create(wall, texture_stem, texture_path, spacing=0.35, surface_offset=0.005, # Color 1: the repo's standard RGB, alpha 0 = fully invisible until painted. attribute = mesh.color_attributes.new(name=COLOR_ATTR, type='BYTE_COLOR', domain='CORNER') - rgba = np.tile(np.array([*vertex_color.DEFAULT_RGB, 0.0], dtype=np.float32), + rgba = np.tile(np.array([*color_rgb, 0.0], dtype=np.float32), len(attribute.data)) attribute.data.foreach_set("color_srgb", rgba) mesh.color_attributes.active_color = attribute diff --git a/seto_tools/surface_painter/ui.py b/seto_tools/surface_painter/ui.py index a9820b1..9146a83 100644 --- a/seto_tools/surface_painter/ui.py +++ b/seto_tools/surface_painter/ui.py @@ -347,7 +347,13 @@ def draw(self, context): layout = self.layout settings = context.scene.seto_surface - col = layout.column(align=True) + col = pl.section(layout, "Vertex Colour", 'COLOR') + col.prop(settings, "color_preset", text="") + row = col.row() + row.enabled = settings.color_preset == 'CUSTOM' + row.prop(settings, "color_rgb", text="") + + col = pl.section(layout, "Geometry", 'MESH_DATA') col.prop(settings, "shell_spacing") col.prop(settings, "shell_offset")