commit
feb1887a7d
17 changed files with 270 additions and 89 deletions
|
|
@ -1 +1 @@
|
||||||
1.1.0
|
1.1.0+build.1
|
||||||
|
|
@ -5,4 +5,3 @@ if __name__ == "__main__":
|
||||||
currentVersion = versioning.get_version()
|
currentVersion = versioning.get_version()
|
||||||
nextVersion = currentVersion.bump_minor()
|
nextVersion = currentVersion.bump_minor()
|
||||||
versioning.save_version(nextVersion)
|
versioning.save_version(nextVersion)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ classes = (
|
||||||
BakeToIDMapPanel,
|
BakeToIDMapPanel,
|
||||||
BakeToIDInfoPanel,
|
BakeToIDInfoPanel,
|
||||||
BakeToIDOptionsPanel,
|
BakeToIDOptionsPanel,
|
||||||
BakeToIDAdvancedMenu,
|
|
||||||
BakeToIDProperties,
|
BakeToIDProperties,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ import math
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from .. types import (get_source, get_target)
|
from ..types.colors import get_color
|
||||||
|
from .. types.sources import get_source
|
||||||
|
from .. types.targets import get_target
|
||||||
|
|
||||||
|
|
||||||
class BakeToIDMapOperator(bpy.types.Operator):
|
class BakeToIDMapOperator(bpy.types.Operator):
|
||||||
|
|
@ -27,27 +29,44 @@ class BakeToIDMapOperator(bpy.types.Operator):
|
||||||
def paint_id_mask(self, context, props):
|
def paint_id_mask(self, context, props):
|
||||||
source = get_source(props.source)
|
source = get_source(props.source)
|
||||||
|
|
||||||
targets = source.get_targets(context)
|
targets = self.get_targets(context, source, props)
|
||||||
|
|
||||||
if len(targets) < 1:
|
if len(targets) < 1:
|
||||||
return
|
return
|
||||||
|
|
||||||
totalTargets = len(targets)
|
color = get_color(props.colors)
|
||||||
colors = []
|
colors = color.get_colors(props)
|
||||||
|
|
||||||
total_hues = props.adv_total_hues
|
|
||||||
total_satuations = props.adv_total_satuations
|
|
||||||
total_brightnesses = props.adv_total_brightnesses
|
|
||||||
|
|
||||||
satuations_break_point = math.pow(total_brightnesses, total_hues)
|
|
||||||
|
|
||||||
for i in range(totalTargets):
|
|
||||||
h = (i / total_hues) % 1
|
|
||||||
l = (math.ceil(i / total_hues) % total_brightnesses) / total_brightnesses
|
|
||||||
s = math.ceil(i / satuations_break_point) / total_satuations
|
|
||||||
|
|
||||||
colors.append(colorsys.hls_to_rgb(h, l, s))
|
|
||||||
|
|
||||||
target = get_target(props.target)
|
target = get_target(props.target)
|
||||||
|
self.paint_targets(props, target, targets, colors)
|
||||||
|
if 'after_painting' in dir(source):
|
||||||
|
source.after_painting(context, props)
|
||||||
|
|
||||||
|
def get_targets(self, context, source, props):
|
||||||
|
if props.selection_mode == 'SINGLE':
|
||||||
|
return source.get_targets([context.active_object])
|
||||||
|
|
||||||
|
if props.selection_mode == 'MULTIPLE_COMBINED':
|
||||||
|
return source.get_targets(context.selected_objects)
|
||||||
|
|
||||||
|
if props.selection_mode == 'MULTIPLE_SEPARATE':
|
||||||
|
result = []
|
||||||
|
for obj in context.selected_objects:
|
||||||
|
if obj.type != 'MESH':
|
||||||
|
continue
|
||||||
|
|
||||||
|
obj_targets = source.get_targets([obj])
|
||||||
|
result.append(obj_targets)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
raise ValueError('Invalid selection_mode')
|
||||||
|
|
||||||
|
def paint_targets(self, props, target, targets, colors):
|
||||||
|
if props.selection_mode == 'MULTIPLE_SEPARATE':
|
||||||
|
for targetList in targets:
|
||||||
|
target.paint_targets(props,targetList, colors)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
target.paint_targets(props, targets, colors)
|
target.paint_targets(props, targets, colors)
|
||||||
source.after_painting(context, props)
|
|
||||||
|
|
@ -13,4 +13,19 @@ class BakeToIDMapPanel(bpy.types.Panel):
|
||||||
layout.use_property_split = True
|
layout.use_property_split = True
|
||||||
layout.use_property_decorate = False # No animation.
|
layout.use_property_decorate = False # No animation.
|
||||||
|
|
||||||
self.layout.operator("object.bake_to_id_map", text="Bake")
|
props = context.scene.bake_to_id_props
|
||||||
|
|
||||||
|
operator_row = layout.row()
|
||||||
|
operator_row.operator("object.bake_to_id_map", text="Bake")
|
||||||
|
operator_row.enabled = self.check_if_props_valid(context, props)
|
||||||
|
|
||||||
|
layout.prop(props, "selection_mode")
|
||||||
|
|
||||||
|
def check_if_props_valid(self, context, props):
|
||||||
|
if (props.selection_mode == "SINGLE" and context.active_object is None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (props.selection_mode != "SINGLE" and len(context.selected_objects) < 1):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
@ -21,4 +21,4 @@ class BakeToIDAdvancedMenu(bpy.types.Panel):
|
||||||
layout.prop(props, "adv_total_satuations")
|
layout.prop(props, "adv_total_satuations")
|
||||||
layout.prop(props, "adv_total_brightnesses")
|
layout.prop(props, "adv_total_brightnesses")
|
||||||
layout.label(text="Max ID-count: " + str(
|
layout.label(text="Max ID-count: " + str(
|
||||||
math.pow(math.pow(props.adv_total_hues, props.adv_total_satuations), props.adv_total_brightnesses)))
|
))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from .. operators.bake_to_id_map import BakeToIDMapOperator
|
from ..types.colors import get_color
|
||||||
from ..types import get_source
|
from .. types.sources import get_source
|
||||||
|
|
||||||
|
|
||||||
class BakeToIDInfoPanel(bpy.types.Panel):
|
class BakeToIDInfoPanel(bpy.types.Panel):
|
||||||
|
|
@ -19,5 +19,31 @@ class BakeToIDInfoPanel(bpy.types.Panel):
|
||||||
|
|
||||||
source = get_source(props.source)
|
source = get_source(props.source)
|
||||||
|
|
||||||
layout.label(text="Selected Object-Count: " + str(len(context.selected_objects)))
|
if props.selection_mode != 'SINGLE':
|
||||||
layout.label(text="Estimated ID-Count: " + str(source.estimate_ids(context, props)))
|
layout.label(text="Selected Object-Count: " + str(len(context.selected_objects)))
|
||||||
|
|
||||||
|
if props.selection_mode == 'SINGLE':
|
||||||
|
layout.label(text="ID-Total: " + str(source.estimate_ids([context.active_object])))
|
||||||
|
|
||||||
|
if props.selection_mode == 'MULTIPLE_SEPARATE':
|
||||||
|
total = 0
|
||||||
|
count = 0
|
||||||
|
for obj in context.selected_objects:
|
||||||
|
if (obj.type != 'MESH'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
total += source.estimate_ids([obj])
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
layout.label(text="Estimated ID-Total: " + str(total))
|
||||||
|
try:
|
||||||
|
layout.label(text="Estimated ID-Average: " + str(total / count))
|
||||||
|
except ZeroDivisionError:
|
||||||
|
layout.label(text="Estimated ID-Average: 0")
|
||||||
|
|
||||||
|
if props.selection_mode == 'MULTIPLE_COMBINED':
|
||||||
|
layout.label(text="ID-Total: " + str(source.estimate_ids(context.selected_objects)))
|
||||||
|
|
||||||
|
color = get_color(props.colors)
|
||||||
|
|
||||||
|
layout.label(text="Colors available: " + str(color.get_count(props)))
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import textwrap
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from src.types import get_source, get_target
|
from .. types.sources import get_source
|
||||||
|
from .. types.targets import get_target
|
||||||
|
|
||||||
|
|
||||||
class BakeToIDOptionsPanel(bpy.types.Panel):
|
class BakeToIDOptionsPanel(bpy.types.Panel):
|
||||||
|
|
@ -15,19 +16,28 @@ class BakeToIDOptionsPanel(bpy.types.Panel):
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.use_property_decorate = False # No animation.
|
||||||
|
|
||||||
props = context.scene.bake_to_id_props
|
props = context.scene.bake_to_id_props
|
||||||
|
|
||||||
layout.prop(props, "source")
|
layout.prop(props, "source")
|
||||||
source = get_source(props.source)
|
source = get_source(props.source)
|
||||||
source_settings_box = layout.box()
|
|
||||||
for setting in source.connected_properties:
|
if len(source.connected_properties) > 0:
|
||||||
source_settings_box.prop(props, setting)
|
source_settings_box = layout.box()
|
||||||
|
for setting in source.connected_properties:
|
||||||
|
source_settings_box.prop(props, setting)
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
|
|
||||||
layout.prop(props, "target")
|
layout.prop(props, "target")
|
||||||
target = get_target(props.target)
|
target = get_target(props.target)
|
||||||
target_settings_box = layout.box()
|
if len(target.connected_properties) > 0:
|
||||||
for setting in target.connected_properties:
|
target_settings_box = layout.box()
|
||||||
target_settings_box.prop(props, setting)
|
for setting in target.connected_properties:
|
||||||
|
target_settings_box.prop(props, setting)
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
layout.prop(props, "colors")
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,30 @@
|
||||||
from bpy.types import (PropertyGroup)
|
from bpy.types import (PropertyGroup)
|
||||||
from bpy.props import (EnumProperty, BoolProperty, IntProperty, StringProperty)
|
from bpy.props import (EnumProperty, BoolProperty, IntProperty, StringProperty)
|
||||||
|
|
||||||
from src.types import get_source_enum, get_targets_enum
|
from src.types.colors import get_color_enum
|
||||||
|
from src.types.sources import get_source_enum
|
||||||
|
from src.types.targets import get_targets_enum
|
||||||
|
|
||||||
|
|
||||||
class BakeToIDProperties(PropertyGroup):
|
class BakeToIDProperties(PropertyGroup):
|
||||||
|
selection_mode: EnumProperty(
|
||||||
|
items=[
|
||||||
|
("SINGLE", "Only active element", "Only use the currently selected element", 0),
|
||||||
|
("MULTIPLE_SEPARATE", "Use Selection, separate", "It performs the transfer for every selected element, but each get there own ids", 1),
|
||||||
|
("MULTIPLE_COMBINED", "Use Selection, combined", "It performs the transfer for every selected element, combining the ids", 2)
|
||||||
|
],
|
||||||
|
name="Selection Mode",
|
||||||
|
description="Specifies how the 3D View-Selection is gonna be used.",
|
||||||
|
default="MULTIPLE_SEPARATE"
|
||||||
|
)
|
||||||
|
|
||||||
source: EnumProperty(
|
source: EnumProperty(
|
||||||
items=get_source_enum(),
|
items=get_source_enum(),
|
||||||
name="Source",
|
name="Source",
|
||||||
description="From where should the IDs be taken",
|
description="From where should the IDs be taken",
|
||||||
default = "MATERIAL_INDEX"
|
default="MATERIAL_INDEX"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target: EnumProperty(
|
target: EnumProperty(
|
||||||
items=get_targets_enum(),
|
items=get_targets_enum(),
|
||||||
name="Target",
|
name="Target",
|
||||||
|
|
@ -19,7 +32,14 @@ class BakeToIDProperties(PropertyGroup):
|
||||||
default=get_targets_enum()[0][0]
|
default=get_targets_enum()[0][0]
|
||||||
)
|
)
|
||||||
|
|
||||||
source_materials_remove_all : BoolProperty(
|
colors: EnumProperty(
|
||||||
|
items=get_color_enum(),
|
||||||
|
name="Color Source",
|
||||||
|
description="From where to take the colors",
|
||||||
|
default=get_color_enum()[0][0]
|
||||||
|
)
|
||||||
|
|
||||||
|
source_materials_remove_all: BoolProperty(
|
||||||
name="Remove all source materials",
|
name="Remove all source materials",
|
||||||
default=False,
|
default=False,
|
||||||
description="Removes every material except the first one."
|
description="Removes every material except the first one."
|
||||||
|
|
@ -29,6 +49,11 @@ class BakeToIDProperties(PropertyGroup):
|
||||||
name="Color Attribute",
|
name="Color Attribute",
|
||||||
default="ID_MASK",
|
default="ID_MASK",
|
||||||
)
|
)
|
||||||
|
target_vertex_color_override_attribute: BoolProperty(
|
||||||
|
name="Override Color Attribute",
|
||||||
|
default=True,
|
||||||
|
description="If set true, the attribute will be deleted and recreated, if it already exists. If set false, the data will just be overwritten."
|
||||||
|
)
|
||||||
|
|
||||||
adv_total_hues: IntProperty(
|
adv_total_hues: IntProperty(
|
||||||
name="Total Hues",
|
name="Total Hues",
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
from . sources import material_index as source_mat_index
|
|
||||||
|
|
||||||
from . targets import vertex_colors as target_vertex_colors
|
|
||||||
|
|
||||||
_sources = [
|
|
||||||
source_mat_index
|
|
||||||
]
|
|
||||||
|
|
||||||
_targets = [
|
|
||||||
target_vertex_colors
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_source(id):
|
|
||||||
for source in _sources:
|
|
||||||
if source.source_id == id:
|
|
||||||
return source
|
|
||||||
|
|
||||||
raise Exception("Source not found: " + id)
|
|
||||||
|
|
||||||
def get_target(id):
|
|
||||||
for target in _targets:
|
|
||||||
if target.target_id == id:
|
|
||||||
return target
|
|
||||||
|
|
||||||
raise Exception("Target not found: " + id)
|
|
||||||
|
|
||||||
def get_source_enum():
|
|
||||||
enumList = []
|
|
||||||
i = 0
|
|
||||||
for source in _sources:
|
|
||||||
enumList.append((source.source_id, source.name, source.description, i))
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
return enumList
|
|
||||||
|
|
||||||
def get_targets_enum():
|
|
||||||
enumList = []
|
|
||||||
i = 0
|
|
||||||
for target in _targets:
|
|
||||||
enumList.append((target.target_id, target.name, target.description, i))
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
return enumList
|
|
||||||
24
src/types/colors/__init__.py
Normal file
24
src/types/colors/__init__.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
from . import generated
|
||||||
|
|
||||||
|
_colors = [
|
||||||
|
generated
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_color(id):
|
||||||
|
for color in _colors:
|
||||||
|
if color.color_id == id:
|
||||||
|
return color
|
||||||
|
|
||||||
|
raise Exception("Source not found: " + id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_color_enum():
|
||||||
|
enum_list = []
|
||||||
|
i = 0
|
||||||
|
for color in _colors:
|
||||||
|
enum_list.append((color.color_id, color.name, color.description, i))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return enum_list
|
||||||
|
|
||||||
32
src/types/colors/generated.py
Normal file
32
src/types/colors/generated.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import colorsys
|
||||||
|
import math
|
||||||
|
|
||||||
|
color_id = 'GENERATED'
|
||||||
|
name = 'Generated'
|
||||||
|
description = 'The colors get generated on-the-fly'
|
||||||
|
|
||||||
|
MAX_BRIGHTNESS_STEPS = 8
|
||||||
|
MAX_SATURATION_STEPS = 8
|
||||||
|
MAX_HUES_STEPS = 8
|
||||||
|
|
||||||
|
def get_colors(props):
|
||||||
|
return gen_colors()
|
||||||
|
|
||||||
|
|
||||||
|
def get_count(props):
|
||||||
|
return len(gen_colors())
|
||||||
|
|
||||||
|
|
||||||
|
def gen_colors():
|
||||||
|
colors = []
|
||||||
|
for brightnessStep in range(0, MAX_BRIGHTNESS_STEPS):
|
||||||
|
v = 1 - (brightnessStep / MAX_BRIGHTNESS_STEPS)
|
||||||
|
for satuationStep in range(0, MAX_SATURATION_STEPS):
|
||||||
|
s = 1 - (satuationStep / MAX_SATURATION_STEPS)
|
||||||
|
for hueStep in range(0, MAX_HUES_STEPS):
|
||||||
|
h = hueStep / MAX_HUES_STEPS
|
||||||
|
|
||||||
|
color = colorsys.hsv_to_rgb(h, s, v)
|
||||||
|
colors.append(color)
|
||||||
|
|
||||||
|
return colors
|
||||||
25
src/types/sources/__init__.py
Normal file
25
src/types/sources/__init__.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
from . import material_index
|
||||||
|
from . import object
|
||||||
|
|
||||||
|
_sources = [
|
||||||
|
material_index,
|
||||||
|
object
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_source(id):
|
||||||
|
for source in _sources:
|
||||||
|
if source.source_id == id:
|
||||||
|
return source
|
||||||
|
|
||||||
|
raise Exception("Source not found: " + id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_source_enum():
|
||||||
|
enum_list = []
|
||||||
|
i = 0
|
||||||
|
for source in _sources:
|
||||||
|
enum_list.append((source.source_id, source.name, source.description, i))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return enum_list
|
||||||
|
|
||||||
|
|
@ -6,9 +6,9 @@ connected_properties = [
|
||||||
'source_materials_remove_all'
|
'source_materials_remove_all'
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_targets(context):
|
def get_targets(objects):
|
||||||
targets = []
|
targets = []
|
||||||
for obj in context.selected_objects:
|
for obj in objects:
|
||||||
if not obj.material_slots: continue
|
if not obj.material_slots: continue
|
||||||
|
|
||||||
mesh = obj.data
|
mesh = obj.data
|
||||||
|
|
@ -33,9 +33,9 @@ def after_painting(context, props):
|
||||||
for obj in context.selected_objects:
|
for obj in context.selected_objects:
|
||||||
obj.data.materials.clear()
|
obj.data.materials.clear()
|
||||||
|
|
||||||
def estimate_ids(context, props):
|
def estimate_ids(objects):
|
||||||
count = 0
|
count = 0
|
||||||
for obj in context.selected_objects:
|
for obj in objects:
|
||||||
count += len(obj.material_slots)
|
count += len(obj.material_slots)
|
||||||
|
|
||||||
return count
|
return count
|
||||||
|
|
|
||||||
16
src/types/sources/object.py
Normal file
16
src/types/sources/object.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
source_id = 'OBJECT'
|
||||||
|
name = 'Object ID'
|
||||||
|
description = 'Uses the object id as basis for the ID mask.'
|
||||||
|
|
||||||
|
connected_properties = []
|
||||||
|
|
||||||
|
def get_targets(objects):
|
||||||
|
targets = []
|
||||||
|
for obj in objects:
|
||||||
|
mesh = obj.data
|
||||||
|
targets.append((mesh, mesh.polygons))
|
||||||
|
|
||||||
|
return targets
|
||||||
|
|
||||||
|
def estimate_ids(objects):
|
||||||
|
return len(objects)
|
||||||
23
src/types/targets/__init__.py
Normal file
23
src/types/targets/__init__.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
from . import vertex_colors
|
||||||
|
|
||||||
|
_targets = [
|
||||||
|
vertex_colors
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_target(id):
|
||||||
|
for target in _targets:
|
||||||
|
if target.target_id == id:
|
||||||
|
return target
|
||||||
|
|
||||||
|
raise Exception("Target not found: " + id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_targets_enum():
|
||||||
|
enum_list = []
|
||||||
|
i = 0
|
||||||
|
for target in _targets:
|
||||||
|
enum_list.append((target.target_id, target.name, target.description, i))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return enum_list
|
||||||
|
|
@ -3,7 +3,8 @@ name = 'Color Attribute / Vertex Color'
|
||||||
description = 'Bakes the ID onto a color attribute (previously known as Vertex Color)'
|
description = 'Bakes the ID onto a color attribute (previously known as Vertex Color)'
|
||||||
|
|
||||||
connected_properties = [
|
connected_properties = [
|
||||||
'target_vertex_color_attribute_name'
|
'target_vertex_color_attribute_name',
|
||||||
|
'target_vertex_color_override_attribute'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,12 +22,22 @@ def paint_targets(props, targets, colors):
|
||||||
|
|
||||||
layer_name = props.target_vertex_color_attribute_name
|
layer_name = props.target_vertex_color_attribute_name
|
||||||
for mesh in sorted_targets:
|
for mesh in sorted_targets:
|
||||||
|
|
||||||
if layer_name in mesh.attributes:
|
if layer_name in mesh.attributes:
|
||||||
mesh.attributes.remove(mesh.attributes[layer_name])
|
mesh.attributes.remove(mesh.attributes[layer_name])
|
||||||
|
|
||||||
color_attribute = mesh.attributes.new(name=layer_name, type='FLOAT_COLOR', domain='CORNER')
|
color_attribute = get_color_attribute(props, mesh.attributes, layer_name)
|
||||||
|
|
||||||
for (indecies, color) in sorted_targets[mesh]:
|
for (indecies, color) in sorted_targets[mesh]:
|
||||||
for polygon in indecies:
|
for polygon in indecies:
|
||||||
for idx in polygon.loop_indices:
|
for idx in polygon.loop_indices:
|
||||||
color_attribute.data[idx].color = (color[0], color[1], color[2], 1.0)
|
color_attribute.data[idx].color = (color[0], color[1], color[2], 1.0)
|
||||||
|
|
||||||
|
def get_color_attribute(props, attributes, layer_name):
|
||||||
|
if layer_name in attributes:
|
||||||
|
if not props.target_vertex_color_override_attribute:
|
||||||
|
return attributes[layer_name]
|
||||||
|
|
||||||
|
attributes.remove(attributes[layer_name])
|
||||||
|
|
||||||
|
return attributes.new(name=layer_name, type='FLOAT_COLOR', domain='CORNER')
|
||||||
Loading…
Add table
Add a link
Reference in a new issue