Node Wrangler: Improved accuracy on Align Nodes operator #104551
@ -586,11 +586,10 @@ def make_material_texture_chunk(chunk_id, texslots, pct):
|
||||
either 0x100 or 0x200, tintcolor will be processed if colorchunks are present"""
|
||||
|
||||
mapflags = 0
|
||||
|
||||
# no perfect mapping for mirror modes - 3DS only has uniform mirror w. repeat=2
|
||||
if texslot.extension == 'EXTEND':
|
||||
mapflags |= 0x1
|
||||
|
||||
if texslot.extension == 'MIRROR':
|
||||
mapflags |= 0x2
|
||||
if texslot.extension == 'CLIP':
|
||||
mapflags |= 0x10
|
||||
|
||||
|
@ -2,10 +2,10 @@
|
||||
# Copyright 2005 Bob Holcomb
|
||||
|
||||
import os
|
||||
import time
|
||||
import struct
|
||||
import bpy
|
||||
import time
|
||||
import math
|
||||
import struct
|
||||
import mathutils
|
||||
from bpy_extras.image_utils import load_image
|
||||
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
|
||||
@ -293,16 +293,12 @@ def add_texture_to_material(image, contextWrapper, pct, extend, alpha, scale, of
|
||||
img_wrap.rotation[2] = angle
|
||||
|
||||
if extend == 'mirror':
|
||||
# 3DS mirror flag can be emulated by these settings (at least so it seems)
|
||||
# TODO: bring back mirror
|
||||
pass
|
||||
# texture.repeat_x = texture.repeat_y = 2
|
||||
# texture.use_mirror_x = texture.use_mirror_y = True
|
||||
img_wrap.extension = 'MIRROR'
|
||||
elif extend == 'decal':
|
||||
# 3DS' decal mode maps best to Blenders EXTEND
|
||||
img_wrap.extension = 'EXTEND'
|
||||
elif extend == 'noWrap':
|
||||
img_wrap.extension = 'CLIP'
|
||||
|
||||
if alpha == 'alpha':
|
||||
for link in links:
|
||||
if link.from_node.type == 'TEX_IMAGE' and link.to_node.type == 'MIX_RGB':
|
||||
|
@ -4,7 +4,7 @@
|
||||
bl_info = {
|
||||
'name': 'glTF 2.0 format',
|
||||
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
||||
"version": (3, 6, 15),
|
||||
"version": (3, 6, 18),
|
||||
'blender': (3, 5, 0),
|
||||
'location': 'File > Import-Export',
|
||||
'description': 'Import-Export as glTF 2.0',
|
||||
|
@ -169,6 +169,12 @@ class PrimitiveCreator:
|
||||
attr['gltf_attribute_name'] = 'COLOR_0'
|
||||
attr['get'] = self.get_function()
|
||||
|
||||
# Seems we sometime can have name collision about attributes
|
||||
# Avoid crash and ignoring one of duplicated attribute name
|
||||
if 'COLOR_0' in [a['gltf_attribute_name'] for a in self.blender_attributes]:
|
||||
print_console('WARNING', 'Attribute (vertex color) collision name: ' + blender_attribute.name + ", ignoring one of them")
|
||||
continue
|
||||
|
||||
else:
|
||||
# Custom attributes
|
||||
# Keep only attributes that starts with _
|
||||
@ -186,6 +192,12 @@ class PrimitiveCreator:
|
||||
attr['gltf_attribute_name'] = keep_attribute.attr_name.upper()
|
||||
attr['get'] = self.get_function()
|
||||
|
||||
# Seems we sometime can have name collision about attributes
|
||||
# Avoid crash and ignoring one of duplicated attribute name
|
||||
if attr['gltf_attribute_name'] in [a['gltf_attribute_name'] for a in self.blender_attributes]:
|
||||
print_console('WARNING', 'Attribute collision name: ' + blender_attribute.name + ", ignoring one of them")
|
||||
continue
|
||||
|
||||
self.blender_attributes.append(attr)
|
||||
|
||||
# Manage POSITION
|
||||
|
@ -225,8 +225,8 @@ class VExportTree:
|
||||
|
||||
###### Manage children ######
|
||||
|
||||
# standard children
|
||||
if blender_bone is None and blender_object.is_instancer is False:
|
||||
# standard children (of object, or of instance collection)
|
||||
if blender_bone is None:
|
||||
for child_object in blender_children[blender_object]:
|
||||
if child_object.parent_bone:
|
||||
# Object parented to bones
|
||||
|
@ -204,7 +204,9 @@ class ExportImage:
|
||||
def __encode_from_image(self, image: bpy.types.Image, export_settings) -> bytes:
|
||||
# See if there is an existing file we can use.
|
||||
data = None
|
||||
if image.source == 'FILE' and not image.is_dirty:
|
||||
# Sequence image can't be exported, but it avoid to crash to check that default image exists
|
||||
# Else, it can crash when trying to access a non existing image
|
||||
if image.source in ['FILE', 'SEQUENCE'] and not image.is_dirty:
|
||||
if image.packed_file is not None:
|
||||
data = image.packed_file.data
|
||||
else:
|
||||
|
@ -2944,7 +2944,7 @@ class NWResetNodes(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
node_active = context.active_node
|
||||
node_selected = context.selected_nodes
|
||||
node_ignore = ["FRAME", "REROUTE", "GROUP"]
|
||||
node_ignore = ["FRAME", "REROUTE", "GROUP", "SIMULATION_INPUT", "SIMULATION_OUTPUT"]
|
||||
|
||||
# Check if one node is selected at least
|
||||
if not (len(node_selected) > 0):
|
||||
|
@ -7,7 +7,7 @@ bl_info = {
|
||||
"name": "Storypencil - Storyboard Tools",
|
||||
"description": "Storyboard tools",
|
||||
"author": "Antonio Vazquez, Matias Mendiola, Daniel Martinez Lara, Rodrigo Blaas, Samuel Bernou",
|
||||
"version": (1, 1, 3),
|
||||
"version": (1, 1, 4),
|
||||
"blender": (3, 3, 0),
|
||||
"location": "",
|
||||
"warning": "",
|
||||
|
@ -106,9 +106,16 @@ class STORYPENCIL_OT_RenderAction(Operator):
|
||||
|
||||
# Create list of selected strips because the selection is changed when adding new strips
|
||||
Strips = []
|
||||
Metas = []
|
||||
for sq in sequences:
|
||||
if sq.type == 'SCENE':
|
||||
if sq.type in ('SCENE', 'META'):
|
||||
if only_selected is False or sq.select is True:
|
||||
if sq.type == 'META' and is_video_output:
|
||||
Metas.append(sq)
|
||||
continue
|
||||
if sq.type == 'SCENE' and is_video_output and sq.parent_meta():
|
||||
continue
|
||||
if sq.type == 'SCENE':
|
||||
Strips.append(sq)
|
||||
|
||||
# Sort strips
|
||||
@ -133,8 +140,38 @@ class STORYPENCIL_OT_RenderAction(Operator):
|
||||
|
||||
try:
|
||||
Videos = []
|
||||
Sheets = []
|
||||
# Read all strips and render the output
|
||||
# Render Meta Strips (Only video)
|
||||
for meta in Metas:
|
||||
meta_name = meta.name
|
||||
scene.frame_start = int(meta.frame_start + meta.frame_offset_start)
|
||||
scene.frame_end = int(meta.frame_start + meta.frame_final_duration - 1)
|
||||
|
||||
print("Meta:" + meta_name)
|
||||
print("Video From:", scene.frame_start,
|
||||
"To", scene.frame_end)
|
||||
# Video
|
||||
filepath = os.path.join(rootpath, meta_name)
|
||||
|
||||
if image_settings.file_format == 'FFMPEG':
|
||||
ext = self.video_ext[scene.render.ffmpeg.format]
|
||||
else:
|
||||
ext = '.avi'
|
||||
|
||||
if not filepath.endswith(ext):
|
||||
filepath += ext
|
||||
|
||||
scene.render.use_file_extension = False
|
||||
scene.render.filepath = filepath
|
||||
|
||||
# Render Animation
|
||||
bpy.ops.render.render(animation=True)
|
||||
|
||||
# Add video to add meta strip later
|
||||
if scene.storypencil_add_render_strip:
|
||||
Videos.append(
|
||||
[filepath, meta.frame_start + meta.frame_offset_start])
|
||||
|
||||
# Read all scene strips and render the output (No META)
|
||||
for sq in Strips:
|
||||
strip_name = sq.name
|
||||
strip_scene = sq.scene
|
||||
@ -187,12 +224,6 @@ class STORYPENCIL_OT_RenderAction(Operator):
|
||||
self.format_to4(frame_nrr)
|
||||
|
||||
filepath = os.path.join(root_folder, framename)
|
||||
|
||||
sheet = os.path.realpath(filepath)
|
||||
sheet = bpy.path.ensure_ext(
|
||||
sheet, self.image_ext[image_settings.file_format])
|
||||
Sheets.append([sheet, keyframe])
|
||||
|
||||
scene.render.filepath = filepath
|
||||
|
||||
# Render Frame
|
||||
|
@ -19,6 +19,7 @@ class STORYPENCIL_OT_NewScene(Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
scene_name: bpy.props.StringProperty(default="Scene")
|
||||
num_strips: bpy.props.IntProperty(default=1, min=1, max=128, description="Number of scenes to add")
|
||||
|
||||
# ------------------------------
|
||||
# Poll
|
||||
@ -39,6 +40,7 @@ class STORYPENCIL_OT_NewScene(Operator):
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
col.prop(self, "scene_name", text="Scene Name")
|
||||
col.prop(self, "num_strips", text="Repeat")
|
||||
|
||||
def format_to3(self, value):
|
||||
return f"{value:03}"
|
||||
@ -50,7 +52,10 @@ class STORYPENCIL_OT_NewScene(Operator):
|
||||
scene_prv = context.scene
|
||||
cfra_prv = scene_prv.frame_current
|
||||
scene_base = scene_prv.storypencil_base_scene
|
||||
repeat = self.num_strips
|
||||
|
||||
offset = 0
|
||||
for i in range(repeat):
|
||||
# Set context to base scene and duplicate
|
||||
context.window.scene = scene_base
|
||||
bpy.ops.scene.new(type='FULL_COPY')
|
||||
@ -72,7 +77,10 @@ class STORYPENCIL_OT_NewScene(Operator):
|
||||
context.window.scene = scene_prv
|
||||
scene_prv.frame_current = cfra_prv
|
||||
bpy.ops.sequencer.scene_strip_add(
|
||||
frame_start=cfra_prv, scene=scene_new.name)
|
||||
frame_start=cfra_prv + offset, scene=scene_new.name)
|
||||
|
||||
# Add offset for repeat
|
||||
offset += scene_new.frame_end - scene_new.frame_start + 1
|
||||
|
||||
scene_new.update_tag()
|
||||
scene_prv.update_tag()
|
||||
|
349
vdm_brush_baker/__init__.py
Normal file
349
vdm_brush_baker/__init__.py
Normal file
@ -0,0 +1,349 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Copyright (C) 2023 Robin Hohnsbeen
|
||||
|
||||
from mathutils import Vector
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import os
|
||||
import bpy
|
||||
from . import bakematerial
|
||||
import importlib
|
||||
importlib.reload(bakematerial)
|
||||
|
||||
bl_info = {
|
||||
'name': 'VDM Brush Baker',
|
||||
'author': 'Robin Hohnsbeen',
|
||||
'description': 'Bake vector displacement brushes easily from a plane',
|
||||
'blender': (3, 5, 0),
|
||||
'version': (1, 0, 2),
|
||||
'location': 'Sculpt Mode: View3D > Sidebar > Tool Tab',
|
||||
'warning': '',
|
||||
'category': 'Baking'
|
||||
}
|
||||
|
||||
|
||||
class vdm_brush_baker_addon_data(bpy.types.PropertyGroup):
|
||||
draft_brush_name: bpy.props.StringProperty(
|
||||
name='Name',
|
||||
default='NewVDMBrush',
|
||||
description='The name that will be used for the brush and texture')
|
||||
render_resolution: bpy.props.EnumProperty(items={
|
||||
('128', '128 px', 'Render with 128 x 128 pixels', 1),
|
||||
('256', '256 px', 'Render with 256 x 256 pixels', 2),
|
||||
('512', '512 px', 'Render with 512 x 512 pixels', 3),
|
||||
('1024', '1024 px', 'Render with 1024 x 1024 pixels', 4),
|
||||
('2048', '2048 px', 'Render with 2048 x 2048 pixels', 5),
|
||||
},
|
||||
default='512', name='Map Resolution')
|
||||
compression: bpy.props.EnumProperty(items={
|
||||
('none', 'None', '', 1),
|
||||
('zip', 'ZIP (lossless)', '', 2),
|
||||
},
|
||||
default='zip', name='Compression')
|
||||
color_depth: bpy.props.EnumProperty(items={
|
||||
('16', '16', '', 1),
|
||||
('32', '32', '', 2),
|
||||
},
|
||||
default='16',
|
||||
name='Color Depth',
|
||||
description='A color depth of 32 can give better results but leads to far bigger file sizes. 16 should be good if the sculpt doesn\'t extend "too far" from the original plane')
|
||||
render_samples: bpy.props.IntProperty(name='Render Samples',
|
||||
default=64,
|
||||
min=2,
|
||||
max=4096)
|
||||
|
||||
|
||||
def get_addon_data() -> vdm_brush_baker_addon_data:
|
||||
return bpy.context.scene.VDMBrushBakerAddonData
|
||||
|
||||
|
||||
def get_output_path(filename):
|
||||
save_path = bpy.path.abspath('/tmp')
|
||||
if bpy.data.is_saved:
|
||||
save_path = os.path.dirname(bpy.data.filepath)
|
||||
save_path = os.path.join(save_path, 'output_vdm', filename)
|
||||
|
||||
if bpy.data.is_saved:
|
||||
return bpy.path.relpath(save_path)
|
||||
else:
|
||||
return save_path
|
||||
|
||||
|
||||
class PT_VDMBaker(bpy.types.Panel):
|
||||
"""
|
||||
The main panel of the add-on which contains a button to create a sculpting plane and a button to bake the vector displacement map.
|
||||
It also has settings for name (image, texture and brush at once), resolution, compression and color depth.
|
||||
"""
|
||||
bl_label = 'VDM Brush Baker'
|
||||
bl_idname = 'Editor_PT_LayoutPanel'
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'Tool'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
addon_data = get_addon_data()
|
||||
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
layout.operator(create_sculpt_plane.bl_idname, icon='ADD')
|
||||
|
||||
layout.separator()
|
||||
|
||||
is_occupied, brush_name = get_new_brush_name()
|
||||
button_text = 'Overwrite VDM Brush' if is_occupied else 'Render and Create VDM Brush'
|
||||
|
||||
createvdmlayout = layout.row()
|
||||
createvdmlayout.enabled = context.active_object is not None and context.active_object.type == 'MESH'
|
||||
createvdmlayout.operator(
|
||||
create_vdm_brush.bl_idname, text=button_text, icon='BRUSH_DATA')
|
||||
|
||||
if is_occupied:
|
||||
layout.label(
|
||||
text='Name Taken: Brush will be overwritten.', icon='INFO')
|
||||
|
||||
col = layout.column()
|
||||
col.alert = is_occupied
|
||||
col.prop(addon_data, 'draft_brush_name')
|
||||
|
||||
settings_layout = layout.column(align=True)
|
||||
settings_layout.label(text='Settings')
|
||||
layout_box = settings_layout.box()
|
||||
|
||||
col = layout_box.column()
|
||||
col.prop(addon_data, 'render_resolution')
|
||||
|
||||
col = layout_box.column()
|
||||
col.prop(addon_data, 'compression')
|
||||
|
||||
col = layout_box.column()
|
||||
col.prop(addon_data, 'color_depth')
|
||||
|
||||
col = layout_box.column()
|
||||
col.prop(addon_data, 'render_samples')
|
||||
|
||||
layout.separator()
|
||||
|
||||
|
||||
def get_new_brush_name():
|
||||
addon_data = get_addon_data()
|
||||
|
||||
is_name_occupied = False
|
||||
for custom_brush in bpy.data.brushes:
|
||||
if custom_brush.name == addon_data.draft_brush_name:
|
||||
is_name_occupied = True
|
||||
break
|
||||
|
||||
if addon_data.draft_brush_name != '':
|
||||
return is_name_occupied, addon_data.draft_brush_name
|
||||
else:
|
||||
date = datetime.now()
|
||||
dateformat = date.strftime('%b-%d-%Y-%H-%M-%S')
|
||||
return False, f'vdm-{dateformat}'
|
||||
|
||||
|
||||
class create_sculpt_plane(bpy.types.Operator):
|
||||
"""
|
||||
Creates a grid with 128 vertices per side plus two multires subdivisions.
|
||||
It uses 'Preserve corners' so further subdivisions can be made while the corners of the grid stay pointy.
|
||||
"""
|
||||
bl_idname = 'sculptplane.create'
|
||||
bl_label = 'Create Sculpting Plane'
|
||||
bl_description = 'Creates a plane with a multires modifier to sculpt on'
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.mesh.primitive_grid_add(x_subdivisions=128, y_subdivisions=128, size=2,
|
||||
enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
|
||||
new_grid = bpy.context.active_object
|
||||
multires = new_grid.modifiers.new('MultiresVDM', type='MULTIRES')
|
||||
multires.boundary_smooth = 'PRESERVE_CORNERS'
|
||||
bpy.ops.object.multires_subdivide(
|
||||
modifier='MultiresVDM', mode='CATMULL_CLARK')
|
||||
bpy.ops.object.multires_subdivide(
|
||||
modifier='MultiresVDM', mode='CATMULL_CLARK') # 512 vertices per one side
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class create_vdm_brush(bpy.types.Operator):
|
||||
"""
|
||||
This operator will bake a vector displacement map from the active object and create a texture and brush datablock.
|
||||
"""
|
||||
bl_idname = 'vdmbrush.create'
|
||||
bl_label = 'Create vdm brush from plane'
|
||||
bl_description = 'Creates a vector displacement map from your sculpture and creates a brush with it'
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object is not None and context.active_object.type == 'MESH'
|
||||
|
||||
def execute(self, context):
|
||||
if context.active_object is None or context.active_object.type != 'MESH':
|
||||
return {'CANCELLED'}
|
||||
|
||||
vdm_plane = context.active_object
|
||||
|
||||
addon_data = get_addon_data()
|
||||
new_brush_name = addon_data.draft_brush_name
|
||||
reference_brush_name = addon_data.draft_brush_name
|
||||
|
||||
is_occupied, brush_name = get_new_brush_name()
|
||||
if len(addon_data.draft_brush_name) == 0 or is_occupied:
|
||||
addon_data.draft_brush_name = brush_name
|
||||
|
||||
# Saving user settings
|
||||
scene = context.scene
|
||||
default_render_engine = scene.render.engine
|
||||
default_view_transform = scene.view_settings.view_transform
|
||||
default_display_device = scene.display_settings.display_device
|
||||
default_file_format = scene.render.image_settings.file_format
|
||||
default_color_mode = scene.render.image_settings.color_mode
|
||||
default_codec = scene.render.image_settings.exr_codec
|
||||
default_denoise = scene.cycles.use_denoising
|
||||
default_compute_device = scene.cycles.device
|
||||
default_scene_samples = scene.cycles.samples
|
||||
default_plane_location = vdm_plane.location.copy()
|
||||
default_plane_rotation = vdm_plane.rotation_euler.copy()
|
||||
default_mode = bpy.context.object.mode
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
vdm_bake_material = bakematerial.get_vdm_bake_material()
|
||||
try:
|
||||
# Prepare baking
|
||||
scene.render.engine = 'CYCLES'
|
||||
scene.cycles.samples = addon_data.render_samples
|
||||
scene.cycles.use_denoising = False
|
||||
scene.cycles.device = 'GPU'
|
||||
|
||||
old_image_name = f'{reference_brush_name}'
|
||||
if old_image_name in bpy.data.images:
|
||||
bpy.data.images[old_image_name].name = 'Old VDM texture'
|
||||
|
||||
# Removing the image right away can cause a crash when sculpt mode is exited.
|
||||
# bpy.data.images.remove(bpy.data.images[old_image_name])
|
||||
|
||||
vdm_plane.data.materials.clear()
|
||||
vdm_plane.data.materials.append(vdm_bake_material)
|
||||
vdm_plane.location = Vector([0, 0, 0])
|
||||
vdm_plane.rotation_euler = (0, 0, 0)
|
||||
|
||||
vdm_texture_node = vdm_bake_material.node_tree.nodes['VDMTexture']
|
||||
render_resolution = int(addon_data.render_resolution)
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
vdm_plane.select_set(True)
|
||||
output_path = get_output_path(f'{new_brush_name}.exr')
|
||||
vdm_texture_image = bpy.data.images.new(
|
||||
name=new_brush_name, width=render_resolution, height=render_resolution, alpha=False, float_buffer=True)
|
||||
vdm_bake_material.node_tree.nodes.active = vdm_texture_node
|
||||
|
||||
vdm_texture_image.filepath_raw = output_path
|
||||
|
||||
scene.render.image_settings.file_format = 'OPEN_EXR'
|
||||
scene.render.image_settings.color_mode = 'RGB'
|
||||
scene.render.image_settings.exr_codec = 'NONE'
|
||||
if addon_data.compression == 'zip':
|
||||
scene.render.image_settings.exr_codec = 'ZIP'
|
||||
|
||||
scene.render.image_settings.color_depth = addon_data.color_depth
|
||||
vdm_texture_image.use_half_precision = addon_data.color_depth == '16'
|
||||
|
||||
vdm_texture_image.colorspace_settings.is_data = True
|
||||
vdm_texture_image.colorspace_settings.name = 'Non-Color'
|
||||
|
||||
vdm_texture_node.image = vdm_texture_image
|
||||
vdm_texture_node.select = True
|
||||
|
||||
# Bake
|
||||
bpy.ops.object.bake(type='EMIT')
|
||||
# save as render so we have more control over compression settings
|
||||
vdm_texture_image.save_render(
|
||||
filepath=bpy.path.abspath(output_path), scene=scene, quality=0)
|
||||
# Removes the dirty flag, so the image doesn't have to be saved again by the user.
|
||||
vdm_texture_image.pack()
|
||||
vdm_texture_image.unpack(method='REMOVE')
|
||||
|
||||
except BaseException as Err:
|
||||
self.report({"ERROR"}, f"{Err}")
|
||||
|
||||
finally:
|
||||
scene.render.image_settings.file_format = default_file_format
|
||||
scene.render.image_settings.color_mode = default_color_mode
|
||||
scene.render.image_settings.exr_codec = default_codec
|
||||
scene.cycles.samples = default_scene_samples
|
||||
scene.display_settings.display_device = default_display_device
|
||||
scene.view_settings.view_transform = default_view_transform
|
||||
scene.cycles.use_denoising = default_denoise
|
||||
scene.cycles.device = default_compute_device
|
||||
scene.render.engine = default_render_engine
|
||||
vdm_plane.data.materials.clear()
|
||||
vdm_plane.location = default_plane_location
|
||||
vdm_plane.rotation_euler = default_plane_rotation
|
||||
|
||||
# Needs to be in sculpt mode to set 'AREA_PLANE' mapping on new brush.
|
||||
bpy.ops.object.mode_set(mode='SCULPT')
|
||||
|
||||
# Texture
|
||||
vdm_texture: bpy.types.Texture = None
|
||||
if bpy.data.textures.find(reference_brush_name) != -1:
|
||||
vdm_texture = bpy.data.textures[reference_brush_name]
|
||||
else:
|
||||
vdm_texture = bpy.data.textures.new(
|
||||
name=new_brush_name, type='IMAGE')
|
||||
vdm_texture.extension = 'EXTEND'
|
||||
vdm_texture.image = vdm_texture_image
|
||||
vdm_texture.name = new_brush_name
|
||||
|
||||
# Brush
|
||||
new_brush: bpy.types.Brush = None
|
||||
if bpy.data.brushes.find(reference_brush_name) != -1:
|
||||
new_brush = bpy.data.brushes[reference_brush_name]
|
||||
self.report({'INFO'}, f'Changed draw brush \'{new_brush.name}\'')
|
||||
else:
|
||||
new_brush = bpy.data.brushes.new(
|
||||
name=new_brush_name, mode='SCULPT')
|
||||
self.report(
|
||||
{'INFO'}, f'Created new draw brush \'{new_brush.name}\'')
|
||||
|
||||
new_brush.texture = vdm_texture
|
||||
new_brush.texture_slot.map_mode = 'AREA_PLANE'
|
||||
new_brush.stroke_method = 'ANCHORED'
|
||||
new_brush.name = new_brush_name
|
||||
new_brush.use_color_as_displacement = True
|
||||
new_brush.strength = 1.0
|
||||
new_brush.hardness = 0.9
|
||||
|
||||
bpy.ops.object.mode_set(mode = default_mode)
|
||||
|
||||
if bpy.context.object.mode == 'SCULPT':
|
||||
context.tool_settings.sculpt.brush = new_brush
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
registered_classes = [
|
||||
PT_VDMBaker,
|
||||
vdm_brush_baker_addon_data,
|
||||
create_vdm_brush,
|
||||
create_sculpt_plane
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for registered_class in registered_classes:
|
||||
bpy.utils.register_class(registered_class)
|
||||
|
||||
bpy.types.Scene.VDMBrushBakerAddonData = bpy.props.PointerProperty(
|
||||
type=vdm_brush_baker_addon_data)
|
||||
|
||||
|
||||
def unregister():
|
||||
for registered_class in registered_classes:
|
||||
bpy.utils.unregister_class(registered_class)
|
||||
|
||||
del bpy.types.Scene.VDMBrushBakerAddonData
|
67
vdm_brush_baker/bakematerial.py
Normal file
67
vdm_brush_baker/bakematerial.py
Normal file
@ -0,0 +1,67 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Copyright (C) 2023 Robin Hohnsbeen
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def get_vdm_bake_material():
|
||||
"""Creates a material that is used to bake the displacement from a plane against its UVs.
|
||||
|
||||
Returns:
|
||||
material: Baking material
|
||||
"""
|
||||
material_name = 'VDM_baking_material'
|
||||
if material_name not in bpy.data.materials:
|
||||
new_material = bpy.data.materials.new(name=material_name)
|
||||
|
||||
new_material.use_nodes = True
|
||||
nodes = new_material.node_tree.nodes
|
||||
nodes.remove(nodes['Principled BSDF'])
|
||||
material_output = nodes['Material Output']
|
||||
|
||||
# Create relevant nodes
|
||||
combine_node = nodes.new('ShaderNodeCombineXYZ')
|
||||
|
||||
separate_node1 = nodes.new('ShaderNodeSeparateXYZ')
|
||||
separate_node2 = nodes.new('ShaderNodeSeparateXYZ')
|
||||
|
||||
vector_subtract_node = nodes.new('ShaderNodeVectorMath')
|
||||
vector_subtract_node.operation = 'SUBTRACT'
|
||||
|
||||
vector_multiply_node = nodes.new('ShaderNodeVectorMath')
|
||||
vector_multiply_node.operation = 'MULTIPLY'
|
||||
vector_multiply_node.inputs[1].default_value = [2.0, 2.0, 2.0]
|
||||
|
||||
vector_add_node = nodes.new('ShaderNodeVectorMath')
|
||||
vector_add_node.operation = 'ADD'
|
||||
vector_add_node.inputs[1].default_value = [-0.5, -0.5, -0.5]
|
||||
|
||||
tex_coord_node = nodes.new('ShaderNodeTexCoord')
|
||||
|
||||
image_node = nodes.new('ShaderNodeTexImage')
|
||||
image_node.name = "VDMTexture"
|
||||
|
||||
# Connect nodes
|
||||
tree = new_material.node_tree
|
||||
tree.links.new(combine_node.outputs[0], material_output.inputs[0])
|
||||
|
||||
tree.links.new(separate_node1.outputs[0], combine_node.inputs[0])
|
||||
tree.links.new(separate_node1.outputs[1], combine_node.inputs[1])
|
||||
|
||||
tree.links.new(
|
||||
vector_subtract_node.outputs[0], separate_node1.inputs[0])
|
||||
|
||||
tree.links.new(
|
||||
vector_multiply_node.outputs[0], vector_subtract_node.inputs[1])
|
||||
|
||||
tree.links.new(
|
||||
vector_add_node.outputs[0], vector_multiply_node.inputs[0])
|
||||
|
||||
tree.links.new(tex_coord_node.outputs[2], vector_add_node.inputs[0])
|
||||
tree.links.new(
|
||||
tex_coord_node.outputs[3], vector_subtract_node.inputs[0])
|
||||
tree.links.new(tex_coord_node.outputs[3], separate_node2.inputs[0])
|
||||
tree.links.new(separate_node2.outputs[2], combine_node.inputs[2])
|
||||
|
||||
return bpy.data.materials[material_name]
|
Loading…
Reference in New Issue
Block a user