Merge pull request #93 from RodZill4/dev-export

Added export for Godot, Unity and Unreal and command line arguments support
This commit is contained in:
Rodz Labs 2020-03-10 22:55:45 +01:00 committed by GitHub
commit 59c9e3b7b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1280 additions and 289 deletions

View File

@ -228,12 +228,15 @@ func _serialize(data: Dictionary) -> Dictionary:
print("cannot save "+name)
return data
func _serialize_data(data: Dictionary) -> Dictionary:
return data
func serialize() -> Dictionary:
var rv = { name=name, type=get_type(), parameters={}, node_position={ x=position.x, y=position.y } }
for p in get_parameter_defs():
if parameters.has(p.name):
rv.parameters[p.name] = MMType.serialize_value(parameters[p.name])
else:
elif p.has("default"):
rv.parameters[p.name] = p.default
if seed_locked:
rv.seed_value = seed_value
@ -241,6 +244,7 @@ func serialize() -> Dictionary:
rv.type = model
else:
rv = _serialize(rv)
rv = _serialize_data(rv)
return rv
func _deserialize(_data : Dictionary) -> void:

View File

@ -34,19 +34,13 @@ func get_parameter_defs() -> Array:
func get_input_defs() -> Array:
return [ { name="in", type="rgba" } ]
func render_textures() -> void:
func export_material(prefix : String, _profile : String, size : int = 0) -> void:
if size == 0:
size = get_image_size()
var source = get_source(0)
if source != null:
var result = source.generator.render(source.output_index, get_image_size())
var result = source.generator.render(source.output_index, size)
while result is GDScriptFunctionState:
result = yield(result, "completed")
texture = ImageTexture.new()
result.copy_to_texture(texture)
result.save_to_file("%s_%s.png" % [ prefix, parameters.suffix])
result.release()
else:
texture = null
func export_textures(prefix, __ = null) -> void:
if texture != null:
var image = texture.get_data()
image.save_png("%s_%s.png" % [ prefix, parameters.suffix])

View File

@ -1,19 +1,30 @@
tool
extends MMGenBase
extends MMGenShader
class_name MMGenMaterial
var export_paths = {}
var material : SpatialMaterial
var generated_textures = {}
const TEXTURE_LIST = [
{ port=0, texture="albedo" },
{ port=3, texture="emission" },
{ port=4, texture="normal" },
{ ports=[5, 2, 1], default_values=["1.0", "1.0", "1.0"], texture="orm" },
{ port=6, texture="depth" },
{ port=7, texture="subsurf_scatter" }
{ port=0, texture="albedo", sources=[0] },
{ port=1, texture="orm", sources=[1, 2, 5] },
{ port=2, texture="emission", sources=[3] },
{ port=3, texture="normal", sources=[4] },
{ port=4, texture="depth", sources=[6] },
{ port=5, texture="sss", sources=[7] }
]
const INPUT_ALBEDO : int = 0
const INPUT_METALLIC : int = 1
const INPUT_ROUGHNESS : int = 2
const INPUT_EMISSION : int = 3
const INPUT_NORMAL : int = 4
const INPUT_OCCLUSION : int = 5
const INPUT_DEPTH : int = 6
const INPUT_SSS : int = 7
# The minimum allowed texture size as a power-of-two exponent
const TEXTURE_SIZE_MIN = 4 # 16x16
@ -23,6 +34,7 @@ const TEXTURE_SIZE_MAX = 12 # 4096x4096
# The default texture size as a power-of-two exponent
const TEXTURE_SIZE_DEFAULT = 10 # 1024x1024
func _ready() -> void:
for t in TEXTURE_LIST:
generated_textures[t.texture] = null
@ -37,30 +49,8 @@ func get_type() -> String:
func get_type_name() -> String:
return "Material"
func get_parameter_defs() -> Array:
return [
{ name="albedo_color", label="Albedo", type="color", default={ r=1.0, g=1.0, b=1.0, a=1.0} },
{ name="metallic", label="Metallic", type="float", min=0.0, max=1.0, step=0.05, default=1.0 },
{ name="roughness", label="Roughness", type="float", min=0.0, max=1.0, step=0.05, default=1.0 },
{ name="emission_energy", label="Emission", type="float", min=0.0, max=8.0, step=0.05, default=1.0 },
{ name="normal_scale", label="Normal", type="float", min=0.0, max=8.0, step=0.05, default=1.0 },
{ name="ao_light_affect", label="Ambient occlusion", type="float", min=0.0, max=1.0, step=0.05, default=1.0 },
{ name="depth_scale", label="Depth", type="float", min=0.0, max=1.0, step=0.05, default=1.0 },
{ name="subsurf_scatter_strength", label="Subsurf. Scatter.", type="float", min=0.0, max=1.0, step=0.05, default=0.0 },
{ name="size", label="Size", type="size", first=TEXTURE_SIZE_MIN, last=TEXTURE_SIZE_MAX, default=TEXTURE_SIZE_DEFAULT }
]
func get_input_defs() -> Array:
return [
{ name="albedo_texture", label="", type="rgb" },
{ name="metallic_texture", label="", type="f" },
{ name="roughness_texture", label="", type="f" },
{ name="emission_texture", label="", type="rgb" },
{ name="normal_texture", label="", type="rgb" },
{ name="ao_texture", label="", type="f" },
{ name="depth_texture", label="", type="f" },
{ name="subsurf_scatter_texture", label="", type="f" }
]
func get_output_defs() -> Array:
return []
func get_image_size() -> int:
var rv : int
@ -83,9 +73,7 @@ func set_parameter(p, v) -> void:
func source_changed(input_index : int) -> void:
for t in TEXTURE_LIST:
if t.has("port") and t.port == input_index:
generated_textures[t.texture] = null
elif t.has("ports") and t.ports.has(input_index):
if t.has("sources") and t.sources.find(input_index) != -1:
generated_textures[t.texture] = null
update_preview()
@ -96,37 +84,10 @@ func render_textures() -> void:
if t.has("port"):
if generated_textures[t.texture] != null:
continue
var source = get_source(t.port)
if source == null:
generated_textures[t.texture] = null
continue
result = source.generator.render(source.output_index, get_image_size())
elif t.has("ports"):
var context : MMGenContext = MMGenContext.new()
var code = []
var shader_textures = {}
var sources = 0
for i in range(t.ports.size()):
var source = get_source(t.ports[i])
if source != null:
var status = source.generator.get_shader_code("UV", source.output_index, context)
while status is GDScriptFunctionState:
status = yield(status, "completed")
code.push_back(status)
for t in status.textures.keys():
shader_textures[t] = status.textures[t]
sources += 1
else:
code.push_back({ defs="", code="", f=t.default_values[i] })
if sources == 0:
generated_textures[t.texture] = null
continue
var shader : String = mm_renderer.generate_combined_shader(code[0], code[1], code[2])
result = mm_renderer.render_shader(shader, shader_textures, get_image_size())
result = render(t.port, get_image_size())
else:
generated_textures[t.texture] = null
continue
while result is GDScriptFunctionState:
result = yield(result, "completed")
texture = ImageTexture.new()
@ -143,8 +104,9 @@ func render_textures() -> void:
generated_textures[t.texture] = texture
func update_materials(material_list) -> void:
render_textures()
for m in material_list:
update_spatial_material(m)
update_material(m)
func get_generated_texture(slot, file_prefix = null) -> ImageTexture:
if file_prefix != null:
@ -157,62 +119,63 @@ func get_generated_texture(slot, file_prefix = null) -> ImageTexture:
else:
return generated_textures[slot]
func update_spatial_material(m, file_prefix = null) -> void:
func update_material(m, file_prefix = null) -> void:
var texture
if m is SpatialMaterial:
# Make the material double-sided for better visiblity in the preview
m.params_cull_mode = SpatialMaterial.CULL_DISABLED
# Albedo
m.albedo_color = parameters.albedo_color
m.albedo_texture = get_generated_texture("albedo", file_prefix)
m.metallic = parameters.metallic
m.roughness = parameters.roughness
# Metallic
texture = get_generated_texture("orm", file_prefix)
m.metallic_texture = texture
m.metallic_texture_channel = SpatialMaterial.TEXTURE_CHANNEL_BLUE
# Roughness
m.roughness_texture = texture
m.roughness_texture_channel = SpatialMaterial.TEXTURE_CHANNEL_GREEN
# Emission
texture = get_generated_texture("emission", file_prefix)
if texture != null:
m.emission_enabled = true
m.emission_energy = parameters.emission_energy
m.emission_texture = texture
else:
m.emission_enabled = false
# Normal map
texture = get_generated_texture("normal", file_prefix)
if texture != null:
m.normal_enabled = true
m.normal_texture = texture
else:
m.normal_enabled = false
# Ambient occlusion
if get_source(5) != null:
if get_source(INPUT_OCCLUSION) != null:
m.ao_enabled = true
m.ao_light_affect = parameters.ao_light_affect
m.ao_texture = m.metallic_texture
m.ao_light_affect = parameters.ao
m.ao_texture = get_generated_texture("orm", file_prefix)
m.ao_texture_channel = SpatialMaterial.TEXTURE_CHANNEL_RED
else:
m.ao_enabled = false
# Roughness
m.roughness = parameters.roughness
if get_source(INPUT_ROUGHNESS) != null:
m.roughness_texture = get_generated_texture("orm", file_prefix)
m.roughness_texture_channel = SpatialMaterial.TEXTURE_CHANNEL_GREEN
else:
m.roughness_texture = null
# Metallic
m.metallic = parameters.metallic
if get_source(INPUT_METALLIC) != null:
m.metallic_texture = get_generated_texture("orm", file_prefix)
m.metallic_texture_channel = SpatialMaterial.TEXTURE_CHANNEL_BLUE
else:
m.metallic_texture = null
# Emission
if get_source(INPUT_EMISSION) != null:
m.emission_enabled = true
m.emission_energy = parameters.emission_energy
m.emission_texture = get_generated_texture("emission", file_prefix)
else:
m.emission_enabled = false
# Normal map
if get_source(INPUT_NORMAL) != null:
m.normal_enabled = true
m.normal_texture = get_generated_texture("normal", file_prefix)
m.normal_scale = parameters.normal
else:
m.normal_enabled = false
# Depth
texture = get_generated_texture("depth", file_prefix)
if texture != null and parameters.depth_scale > 0:
if get_source(INPUT_DEPTH) != null and parameters.depth_scale > 0:
m.depth_enabled = true
m.depth_deep_parallax = true
m.depth_scale = parameters.depth_scale * 0.2
m.depth_texture = texture
m.depth_texture = get_generated_texture("depth", file_prefix)
else:
m.depth_enabled = false
# Subsurface scattering
texture = get_generated_texture("subsurf_scatter", file_prefix)
if texture != null:
if get_source(INPUT_SSS) != null:
m.subsurf_scatter_enabled = true
m.subsurf_scatter_strength = parameters.subsurf_scatter_strength
m.subsurf_scatter_texture = texture
m.subsurf_scatter_strength = parameters.sss
m.subsurf_scatter_texture = get_generated_texture("sss", file_prefix)
else:
m.subsurf_scatter_enabled = false
else:
@ -231,25 +194,149 @@ func update_spatial_material(m, file_prefix = null) -> void:
m.set_shader_param("depth_scale", parameters.depth_scale * 0.2)
m.set_shader_param("texture_depth", get_generated_texture("depth", file_prefix))
func export_textures(prefix, editor_interface = null) -> SpatialMaterial:
for t in TEXTURE_LIST:
var texture = generated_textures[t.texture]
if texture != null:
var image = texture.get_data()
image.save_png("%s_%s.png" % [ prefix, t.texture ])
if Engine.editor_hint and editor_interface != null:
var resource_filesystem = editor_interface.get_resource_filesystem()
resource_filesystem.scan()
yield(resource_filesystem, "resources_reimported")
print("resources_reimported")
var new_material = SpatialMaterial.new()
update_spatial_material(new_material, prefix)
var file_name : String = "%s.tres" % [ prefix ]
ResourceSaver.save(file_name, new_material)
resource_filesystem.update_file(file_name)
return new_material
# Export
return null
func get_export_profiles() -> Array:
return shader_model.exports.keys()
func _serialize(data: Dictionary) -> Dictionary:
func get_export_extension(profile : String) -> String:
return shader_model.exports[profile].export_extension
func get_export_path(profile : String) -> String:
if export_paths.has(profile):
return export_paths[profile]
return ""
func subst_string(s : String, export_context : Dictionary) -> String:
var modified : bool = true
while modified:
modified = false
for k in export_context.keys():
var new_s = s.replace(k, export_context[k])
if new_s != s:
s = new_s
modified = true
while (true):
var search_string = "$(expr:"
var position = s.find(search_string)
if position == -1:
break
var parenthesis_level = 0
var expr_begin = position+search_string.length()
for i in range(expr_begin, s.length()):
if s[i] == '(':
parenthesis_level += 1
elif s[i] == ')':
if parenthesis_level == 0:
var expression = s.substr(expr_begin, i-expr_begin)
var expr = Expression.new()
var error = expr.parse(expression, [])
if error == OK:
s = s.replace(s.substr(position, i+1-position), str(expr.execute()))
else:
s = s.replace(s.substr(position, i+1-position), "EXPRESSION ERROR ("+expression+")")
break
parenthesis_level -= 1
return s
func create_file_from_template(template : String, file_name : String, export_context : Dictionary) -> bool:
var in_file = File.new()
var out_file = File.new()
if in_file.open(mm_loader.STD_GENDEF_PATH+"/"+template, File.READ) != OK:
if in_file.open(OS.get_executable_path().get_base_dir()+"/generators/"+template, File.READ) != OK:
print("Cannot find template file "+template)
return false
Directory.new().remove(file_name)
if out_file.open(file_name, File.WRITE) != OK:
print("Cannot write file '"+file_name+"' ("+str(out_file.get_error())+")")
return false
var skip_state : Array = [ false ]
while ! in_file.eof_reached():
var l = in_file.get_line()
if l.left(4) == "$if ":
var condition = subst_string(l.right(4), export_context)
var expr = Expression.new()
var error = expr.parse(condition, [])
if error != OK:
print("Error in expression "+condition+": "+expr.get_error_text())
continue
skip_state.push_back(!expr.execute())
elif l.left(3) == "$fi":
skip_state.pop_back()
elif l.left(5) == "$else":
skip_state.push_back(!skip_state.pop_back())
elif ! skip_state.back():
out_file.store_line(subst_string(l, export_context))
return true
func export_material(prefix : String, profile : String, size : int = 0) -> void:
if size == 0:
size = get_image_size()
export_paths[profile] = prefix
var export_context : Dictionary = {
"$(path_prefix)":prefix,
"$(file_prefix)":prefix.get_file()
}
for i in range(shader_model.inputs.size()):
var input = shader_model.inputs[i]
export_context["$(connected:"+input.name+")"] = "true" if get_source(i) != null else "false"
for p in shader_model.parameters:
var value = p.default
if parameters.has(p.name):
value = parameters[p.name]
match p.type:
"float", "size":
export_context["$(param:"+p.name+")"] = str(value)
"color":
export_context["$(param:"+p.name+".r)"] = str(value.r)
export_context["$(param:"+p.name+".g)"] = str(value.g)
export_context["$(param:"+p.name+".b)"] = str(value.b)
export_context["$(param:"+p.name+".a)"] = str(value.a)
_:
print(p.type+" not supported in material")
if shader_model.exports[profile].has("uids"):
for i in range(shader_model.exports[profile].uids):
var uid : String
var r = []
for k in range(16):
r.append(randi() & 255)
r[6] = (r[6] & 0x0f) | 0x40
r[8] = (r[8] & 0x3f) | 0x80
for k in range(16):
uid += '%02x' % r[k]
export_context["$(uid:"+str(i)+")"] = uid
for f in shader_model.exports[profile].files:
if f.has("conditions"):
var condition = subst_string(f.conditions, export_context)
var expr = Expression.new()
var error = expr.parse(condition, [])
if error != OK:
print("Error in expression: "+expr.get_error_text())
continue
if !expr.execute():
continue
match f.type:
"texture":
var file_name = subst_string(f.file_name, export_context)
var result = render(f.output, size)
while result is GDScriptFunctionState:
result = yield(result, "completed")
result.save_to_file(file_name)
result.release()
"template":
var file_export_context = export_context.duplicate()
if f.has("file_params"):
for p in f.file_params.keys():
file_export_context["$(file_param:"+p+")"] = f.file_params[p]
var file_name = subst_string(f.file_name, export_context)
create_file_from_template(f.template, file_name, file_export_context)
func _serialize_data(data: Dictionary) -> Dictionary:
data = ._serialize_data(data)
data.export_paths = export_paths
return data
func _deserialize(data : Dictionary) -> void:
._deserialize(data)
if data.has("export_paths"):
export_paths = data.export_paths.duplicate()

View File

@ -157,6 +157,10 @@ func replace_variable(string : String, variable : String, value : String) -> Str
break
new_string += string.left(pos)
string = string.right(pos)
if string.length() > keyword_size and is_word_letter(string[keyword_size]):
new_string += string.left(keyword_size)
string = string.right(keyword_size)
continue
if string.empty() or !is_word_letter(string[0]):
new_string += value
else:

View File

@ -6,9 +6,8 @@ var types : Dictionary = {}
func _ready():
var file = File.new()
if file.open("res://addons/material_maker/nodes/io_types.mmt", File.READ) != OK:
print("Cannot read types")
return false
for p in mm_loader.get_nodes_paths():
if file.open(p+"/io_types.mmt", File.READ) == OK:
var type_list = parse_json(file.get_as_text())
file.close()
for t in type_list:
@ -16,7 +15,8 @@ func _ready():
type_names.push_back(t.name)
var c = t.color
t.color = Color(c.r, c.g, c.b, c.a)
if file.open("res://addons/material_maker/nodes/preview_"+t.name+".shader", File.READ) == OK:
if file.open(p+"/preview_"+t.name+".shader", File.READ) == OK:
t.preview = file.get_as_text()
file.close()
types[t.name] = t
break

View File

@ -8,9 +8,12 @@ var predefined_generators = {}
func _ready()-> void:
update_predefined_generators()
func get_nodes_paths() -> Array:
return [ STD_GENDEF_PATH, OS.get_executable_path().get_base_dir()+"/nodes" ]
func update_predefined_generators()-> void:
predefined_generators = {}
for path in [ STD_GENDEF_PATH, OS.get_executable_path().get_base_dir()+"/generators" ]:
for path in get_nodes_paths():
var dir = Directory.new()
if dir.open(path) == OK:
dir.list_dir_begin()
@ -24,7 +27,7 @@ func update_predefined_generators()-> void:
file_name = dir.get_next()
func generator_name_from_path(path : String) -> String:
for p in [ STD_GENDEF_PATH, OS.get_executable_path().get_base_dir()+"/generators" ]:
for p in get_nodes_paths():
print(p)
print(path.get_base_dir())
return path.get_basename().get_file()
@ -56,6 +59,8 @@ func add_to_gen_graph(gen_graph, generators, connections) -> Dictionary:
func create_gen(data) -> MMGenBase:
var guess = [
{ keyword="export", type=MMGenMaterial },
{ keyword="connections", type=MMGenGraph },
{ keyword="connections", type=MMGenGraph },
{ keyword="nodes", type=MMGenGraph },
{ keyword="shader_model", type=MMGenShader },
@ -65,7 +70,7 @@ func create_gen(data) -> MMGenBase:
{ keyword="widgets", type=MMGenRemote }
]
var types = {
material = MMGenMaterial,
material_export = MMGenMaterial,
buffer = MMGenBuffer,
image = MMGenImage,
ios = MMGenIOs,
@ -99,7 +104,7 @@ func create_gen(data) -> MMGenBase:
func get_generator_list() -> Array:
var rv = []
var dir : Directory = Directory.new()
for p in [ STD_GENDEF_PATH, OS.get_executable_path().get_base_dir()+"/generators" ]:
for p in get_nodes_paths():
dir.open(p)
dir.list_dir_begin(true)
while true:

View File

@ -46,20 +46,26 @@ func get_visible_name() -> String:
return "Material Maker Importer"
func import(source_file: String, save_path: String, options: Dictionary, platform_variants: Array, gen_files: Array) -> int:
var filename = save_path + "." + get_save_extension()
var material = null
if options.render:
material = plugin.generate_material(source_file)
while material is GDScriptFunctionState:
material = yield(material, "completed")
var gen = mm_loader.load_gen(source_file)
if gen != null:
plugin.add_child(gen)
for c in gen.get_children():
if c.has_method("get_export_profiles"):
var result = c.export_material(source_file.get_basename(), "Godot")
while result is GDScriptFunctionState:
result = yield(result, "completed")
break
gen.queue_free()
else:
var filename = save_path + "." + get_save_extension()
material = SpatialMaterial.new()
material.set_script(preload("res://addons/material_maker/import_plugin/ptex_spatial_material.gd"))
var file : File = File.new()
if file.open(source_file, File.READ) == OK:
material.set_ptex_no_render(to_json(parse_json(file.get_as_text())))
file.close()
if material != null:
material.uv1_scale = options.scale * Vector3(1.0, 1.0, 1.0)
ResourceSaver.save(filename, material)
return OK

View File

@ -23,6 +23,6 @@ func update_texture() -> void:
var status = mm_material.render_textures()
while status is GDScriptFunctionState:
status = yield(status, "completed")
mm_material.update_spatial_material(self)
mm_material.update_material(self)
mm_graph.queue_free()

View File

@ -0,0 +1,52 @@
[gd_resource type="SpatialMaterial" load_steps=5 format=2]
$if $(connected:albedo_tex)
[ext_resource path="$(file_prefix)_albedo.png" type="Texture" id=1]
$fi
$if $(connected:ao_tex) or $(connected:roughness_tex) or $(connected:metallic_tex)
[ext_resource path="$(file_prefix)_orm.png" type="Texture" id=2]
$fi
$if $(connected:normal_tex)
[ext_resource path="$(file_prefix)_normal.png" type="Texture" id=3]
$fi
$if $(connected:depth_tex)
[ext_resource path="$(file_prefix)_depth.png" type="Texture" id=4]
$fi
[resource]
albedo_color = Color($(param:albedo_color.r), $(param:albedo_color.g), $(param:albedo_color.b), $(param:albedo_color.a))
$if $(connected:albedo_tex)
albedo_texture = ExtResource( 1 )
$fi
metallic = $(param:metallic)
$if $(connected:metallic_tex)
metallic_texture = ExtResource( 2 )
metallic_texture_channel = 2
$fi
roughness = $(param:roughness)
$if $(connected:ao_tex) or $(connected:roughness_tex) or $(connected:metallic_tex)
roughness_texture = ExtResource( 2 )
roughness_texture_channel = 1
$fi
$if $(connected:normal_tex)
normal_enabled = true
normal_scale = $(param:normal)
normal_texture = ExtResource( 3 )
$fi
$if $(connected:ao_tex)
ao_enabled = true
ao_light_affect = $(param:ao)
ao_texture = ExtResource( 2 )
ao_on_uv2 = false
ao_texture_channel = 0
$fi
$if $(connected:depth_tex)
depth_enabled = true
depth_scale = $(expr:0.2*$(param:depth_scale))
depth_deep_parallax = true
depth_min_layers = 8
depth_max_layers = 32
depth_flip_tangent = false
depth_flip_binormal = false
depth_texture = ExtResource( 4 )
$fi

View File

@ -0,0 +1,427 @@
{
"name": "material",
"node_position": {
"x": 0,
"y": 0
},
"parameters": {
"albedo_color": {
"a": 1,
"b": 1,
"g": 1,
"r": 1,
"type": "Color"
},
"ao": 1,
"depth_scale": 0.5,
"emission": 1,
"metallic": 1,
"normal": 1,
"roughness": 1,
"size": 11,
"sss": 0
},
"export": {
},
"shader_model": {
"code": "",
"global": "",
"inputs": [
{
"default": "vec3(1.0)",
"label": "",
"name": "albedo_tex",
"type": "rgb"
},
{
"default": "1.0",
"label": "",
"name": "metallic_tex",
"type": "f"
},
{
"default": "1.0",
"label": "",
"name": "roughness_tex",
"type": "f"
},
{
"default": "vec3(0.0)",
"label": "",
"name": "emission_tex",
"type": "rgb"
},
{
"default": "vec3(0.5)",
"label": "",
"name": "normal_tex",
"type": "rgb"
},
{
"default": "1.0",
"label": "",
"name": "ao_tex",
"type": "f"
},
{
"default": "0.0",
"label": "",
"name": "depth_tex",
"type": "f"
},
{
"default": "0.0",
"label": "",
"name": "sss_tex",
"type": "f"
}
],
"instance": "",
"name": "Material",
"outputs": [
{
"desc":"0: albedo",
"rgb": "$albedo_tex($uv)",
"type": "rgb"
},
{
"desc":"1: ambient occlusion, roughness, metallic",
"rgb": "vec3($ao_tex($uv), $roughness_tex($uv), $metallic_tex($uv))",
"type": "rgb"
},
{
"desc":"2: emission",
"rgb": "$emission_tex($uv)",
"type": "rgb"
},
{
"desc":"3: normal map for Godot",
"rgb": "$normal_tex($uv)*vec3(-1.0, 1.0, 1.0)+vec3(1.0, 0.0, 0.0)",
"type": "rgb"
},
{
"desc":"4: normal map for Godot",
"f": "$depth_tex($uv)",
"type": "f"
},
{
"desc":"5: sub surface scattering",
"f": "$sss_tex($uv)",
"type": "f"
},
{
"desc":"6: unity metallic/smoothness",
"rgba": "vec4(vec3($metallic_tex($uv)), 1.0-$roughness_tex($uv))",
"type": "rgba"
},
{
"desc":"7: unity normal",
"rgb": "$normal_tex($uv)*vec3(-1.0, 1.0, -1.0)+vec3(1.0, 0.0, 1.0)",
"type": "rgb"
},
{
"desc":"8: unity height",
"f": "1.0-$depth_tex($uv)",
"type": "f"
},
{
"desc":"9: unity occlusion",
"f": "$ao_tex($uv)",
"type": "f"
},
{
"desc":"10: unreal normal",
"rgb": "$normal_tex($uv)*vec3(-1.0)+vec3(1.0)",
"type": "rgb"
}
],
"exports": {
"Godot": {
"export_extension":"tres",
"files": [
{
"type":"texture",
"file_name":"$(path_prefix)_albedo.png",
"output":0,
"conditions":"$(connected:albedo_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_orm.png",
"output":1,
"conditions":"$(connected:ao_tex) or $(connected:roughness_tex) or $(connected:metallic_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_emission.png",
"output":2,
"conditions":"$(connected:emission_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_normal.png",
"output":3,
"conditions":"$(connected:normal_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_depth.png",
"output":4,
"conditions":"$(connected:depth_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_sss.png",
"output":5,
"conditions":"$(connected:sss_tex)"
},
{
"type":"template",
"file_name":"$(path_prefix).tres",
"template":"godot.tres.tmpl"
}
]
},
"Unity": {
"export_extension":"mat",
"uids":6,
"files": [
{
"type":"texture",
"file_name":"$(path_prefix)_albedo.png",
"output":0,
"conditions":"$(connected:albedo_tex)"
},
{
"type":"template",
"file_name":"$(path_prefix)_albedo.png.meta",
"template":"unity.png.meta.tmpl",
"file_params": {
"uid":"$(uid:0)",
"srgb":"1",
"normal":"0"
},
"conditions":"$(connected:albedo_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_metal_smoothness.png",
"output":6,
"conditions":"$(connected:roughness_tex) or $(connected:metallic_tex)"
},
{
"type":"template",
"file_name":"$(path_prefix)_metal_smoothness.png.meta",
"template":"unity.png.meta.tmpl",
"file_params": {
"uid":"$(uid:1)",
"srgb":"1",
"normal":"0"
},
"conditions":"$(connected:roughness_tex) or $(connected:metallic_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_normal.png",
"output":7,
"conditions":"$(connected:normal_tex)"
},
{
"type":"template",
"file_name":"$(path_prefix)_normal.png.meta",
"template":"unity.png.meta.tmpl",
"file_params": {
"uid":"$(uid:2)",
"srgb":"0",
"normal":"1"
},
"conditions":"$(connected:normal_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_height.png",
"output":8,
"conditions":"$(connected:depth_tex)"
},
{
"type":"template",
"file_name":"$(path_prefix)_height.png.meta",
"template":"unity.png.meta.tmpl",
"file_params": {
"uid":"$(uid:3)",
"srgb":"1",
"normal":"0"
},
"conditions":"$(connected:depth_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_occlusion.png",
"output":9,
"conditions":"$(connected:ao_tex)"
},
{
"type":"template",
"file_name":"$(path_prefix)_occlusion.png.meta",
"template":"unity.png.meta.tmpl",
"file_params": {
"uid":"$(uid:4)",
"srgb":"1",
"normal":"0"
},
"conditions":"$(connected:ao_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_emission.png",
"output":2,
"conditions":"$(connected:emission_tex)"
},
{
"type":"template",
"file_name":"$(path_prefix)_emission.png.meta",
"template":"unity.png.meta.tmpl",
"file_params": {
"uid":"$(uid:5)",
"srgb":"1",
"normal":"0"
},
"conditions":"$(connected:emission_tex)"
},
{
"type":"template",
"file_name":"$(path_prefix).mat",
"template":"unity.mat.tmpl"
}
]
},
"Unreal": {
"export_extension":"tres",
"files": [
{
"type":"texture",
"file_name":"$(path_prefix)_albedo.png",
"output":0,
"conditions":"$(connected:albedo_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_orm.png",
"output":1,
"conditions":"$(connected:ao_tex) or $(connected:roughness_tex) or $(connected:metallic_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_emission.png",
"output":2,
"conditions":"$(connected:emission_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_normal.png",
"output":10,
"conditions":"$(connected:normal_tex)"
},
{
"type":"texture",
"file_name":"$(path_prefix)_height.png",
"output":8,
"conditions":"$(connected:depth_tex)"
}
]
},
},
"parameters": [
{
"default": {
"a": 1,
"b": 1,
"g": 1,
"r": 1
},
"label": "Albedo",
"name": "albedo_color",
"type": "color"
},
{
"control": "None",
"default": 1,
"label": "Metallic",
"max": 1,
"min": 0,
"name": "metallic",
"step": 0.01,
"type": "float"
},
{
"control": "None",
"default": 1,
"label": "Roughness",
"max": 1,
"min": 0,
"name": "roughness",
"step": 0.01,
"type": "float"
},
{
"control": "None",
"default": 1,
"label": "Emission",
"max": 1,
"min": 0,
"name": "emission_energy",
"step": 0.01,
"type": "float"
},
{
"control": "None",
"default": 1,
"label": "Normal",
"max": 10,
"min": 0,
"name": "normal",
"step": 0.01,
"type": "float"
},
{
"control": "None",
"default": 1,
"label": "Ambient occlusion",
"max": 1,
"min": 0,
"name": "ao",
"step": 0.01,
"type": "float"
},
{
"control": "None",
"default": 0.5,
"label": "Depth",
"max": 1,
"min": 0,
"name": "depth_scale",
"step": 0.01,
"type": "float"
},
{
"control": "None",
"default": 0,
"label": "Subsurf. scatter.",
"max": 1,
"min": 0,
"name": "sss",
"step": 0.01,
"type": "float"
},
{
"default": 11,
"first": 6,
"label": "Size",
"last": 12,
"name": "size",
"type": "size"
}
]
},
"type": "material_export"
}

View File

@ -0,0 +1,97 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: test
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
m_ShaderKeywords:$(expr:" _METALLICGLOSSMAP" if $(connected:roughness_tex) or $(connected:metallic_tex) else "")$(expr:" _NORMALMAP" if $(connected:normal_tex) else "")$(expr:" _PARALLAXMAP" if $(connected:depth_tex) else "")
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
$if $(connected:normal_tex)
m_Texture: {fileID: 2800000, guid: $(uid:2), type: 3}
$else
m_Texture: {fileID: 0}
$fi
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
$if $(connected:albedo_tex)
m_Texture: {fileID: 2800000, guid: $(uid:0), type: 3}
$else
m_Texture: {fileID: 0}
$fi
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
$if $(connected:roughness_tex) or $(connected:metallic_tex)
m_Texture: {fileID: 2800000, guid: $(uid:1), type: 3}
$else
m_Texture: {fileID: 0}
$fi
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
$if $(connected:ao_tex)
m_Texture: {fileID: 2800000, guid: $(uid:4), type: 3}
$else
m_Texture: {fileID: 0}
$fi
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
$if $(connected:depth_tex)
m_Texture: {fileID: 2800000, guid: $(uid:3), type: 3}
$else
m_Texture: {fileID: 0}
$fi
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}

View File

@ -0,0 +1,91 @@
fileFormatVersion: 2
guid: $(file_param:uid)
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: $(file_param:srgb)
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: $(file_param:normal)
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +1,7 @@
[plugin]
name="Material Maker"
description="Procedural Material creation tool"
description="Import plugin for Material Maker"
author="RodZilla"
version="0.6"
version="0.9"
script="plugin.gd"

View File

@ -11,16 +11,3 @@ func _exit_tree() -> void:
if importer != null:
remove_import_plugin(importer)
importer = null
func generate_material(ptex_filename: String) -> Material:
var generator = mm_loader.load_gen(ptex_filename)
add_child(generator)
if generator.has_node("Material"):
var gen_material = generator.get_node("Material")
if gen_material != null:
var return_value = gen_material.render_textures()
while return_value is GDScriptFunctionState:
return_value = yield(return_value, "completed")
var prefix = ptex_filename.left(ptex_filename.rfind("."))
return gen_material.export_textures(prefix, get_editor_interface())
return null

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
Command line arguments
======================
When launched with no command line argument, Material Maker will start with an empty project.
When launched with the path of a .ptex file as command line argument, Material Maker will
start with this project file. Material Maker can thus be associated to files with .ptex
extension so double-clicking on them will directly open them.
Material Maker can also be used to export several .ptex file with the following command line:
material_maker --export -t <engine> -o <output_path> <input_files>
Where:
* **engine** is the target engine (Godot, Unity or Unreal)
* **output_path** is the path where files will be generated
* **input_files** is the list of input files (wildcards are accepted)

View File

@ -24,7 +24,7 @@ copyright = '2018-2020, Rodz Labs'
author = 'Rodz Labs'
# The short X.Y version
version = '0.8'
version = '0.9'
# The full version, including alpha/beta/rc tags
release = ''

View File

@ -0,0 +1,59 @@
.. _export-section:
Exporting Materials
===================
When exporting a material, using either the Export submenu or the command line arguments,
Material Maker generates PNG image files for all elements of the material as well as
specific files for the target game engine.
In all cases, the generated PNG files (and especially the normal map) is generated in the
correct format.
Godot game engine
-----------------
When exporting for the Godot game engine, Material Maker will generate a .tres file that
describes a fully configured SpatialMaterial.
Note that exporting for Godot is not necessary if you use the Material maker addon, that
provides an import plugin. This import plugin can either generate a precomputed material,
or a material that will be rendered at runtime.
Unity game engine
-----------------
When exporting for the Unity game engine, Material Maker will generate a .mat file that
describes a fully configured material. It is thus possible to export materials directly
into one of your project assets directory, and Unity will automatically detect the newly
exported materials.
Unreal game engine
------------------
When exporting for the Unreal game engine, Material Maker will only generate PNG image
files, and it is necessary to create the material in Unreal.
To create a minimal material:
* import all PNG files into unreal
* create a new material using the **Add new -> Material** menu
* open the new material to edit it
* drag and drop all textures into the material graph to create a Texture Sample node
for each texture
* connect the RGB output of the albedo *Texture Sample* node to the *Base Color* input
of the material node
* connect the R output of the ORM *Texture Sample* node to the *Ambient Occlusion* input
of the material node
* connect the G output of the ORM *Texture Sample* node to the *Roughness* input
of the material node
* connect the B output of the ORM *Texture Sample* node to the *Metallic* input
of the material node
* connect the RGB output of the normal *Texture Sample* node to the *Normal* input
of the material node
More complex materials with support for emission textures, depth maps, texture
coordinate scaling...
.. image:: images/unreal_export.png
:align: center

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

View File

@ -8,3 +8,5 @@ Material Maker User Manual
user_interface
nodes
base_library
export
command_line

View File

@ -21,17 +21,29 @@ Parameters
The **Normal map** node has the following parameters:
* the *format* of the normal map (default, OpenGL or DirectX)
* the *format* of the normal map (default, OpenGL or DirectX). The format should
always be set to "default" when connected to the Normal input of a Material node.
The correct normal map format for the target game engine will be generated when
the material is exported.
* the *size* of the normal map
* the *strength* of the normal map effect
* the *buffer* parameter decides if the input must be stored in a buffer before
generating the normal map. Using a buffer is faster but can create artifacts
in the normal map, and disabling this option will generate more accurate normal
maps. It is recommended to enable the buffer while editing a material and
disable it before exporting.
Notes
+++++
This node outputs an image that has a fixed size.
When using 3D Signed distance functions, it is recommended to use the normal map
generated by the
Example images
++++++++++++++

View File

@ -224,10 +224,10 @@ The main menu bar is organized in 5 menus:
File menu
^^^^^^^^^
* *New material* creates a new material and opens a tab in the right pane to edit it
* *New material* creates a new material and opens a tab in the center pane to edit it
* *Load material* opens a file dialog to select a procedural material (.ptex) file. If
a material file is selected, it will be open in a new tab in the right pane. If the current
a material file is selected, it will be open in a new tab in the center pane. If the current
tab contains an empty material (about material that only consists of a Material node), the
material will be loaded into this tab.
@ -239,12 +239,10 @@ File menu
* *Save all materials* saves all currently open materials. Materials that were already
saved are ignored.
* *Export material* generates PNG image files for all elements of the material. File names are
defined using the path of the **.ptex** material file and their role (albedo, emission...)
in the material.
The Ambient occlusion, roughness and metallic textures are saved into a single file whose
suffix is **orm**.
If the material contains **export** nodes, their textures will be exported as well.
* the *Export* submenu can be used to export the current Material for Godot, Unity
or Unreal. It will prompt for a file name and generate PNG files for all components
of the material. Exporting to one of those engines is described in the
:ref:`export-section` section.
* *Close material* closes the current material.

View File

@ -72,24 +72,12 @@
"to": "blend_0",
"to_port": 1
},
{
"from": "colorize_0",
"from_port": 0,
"to": "combine_0",
"to_port": 1
},
{
"from": "blend_0",
"from_port": 0,
"to": "colorize_2",
"to_port": 0
},
{
"from": "blend_0",
"from_port": 0,
"to": "combine_0",
"to_port": 0
},
{
"from": "colorize_2",
"from_port": 0,
@ -121,8 +109,8 @@
{
"name": "perlin_1",
"node_position": {
"x": -424,
"y": 343.5
"x": -635,
"y": 341.5
},
"parameters": {
"iterations": 3,
@ -135,8 +123,8 @@
{
"name": "perlin_0",
"node_position": {
"x": -424,
"y": 212.5
"x": -635,
"y": 210.5
},
"parameters": {
"iterations": 3,
@ -149,21 +137,20 @@
{
"name": "warp_0",
"node_position": {
"x": -180,
"y": 317.5
"x": -311,
"y": 280.5
},
"parameters": {
"amount": 0.1,
"eps": 0.05,
"epsilon": 0
"eps": 0.05
},
"type": "warp"
},
{
"name": "colorize_1",
"node_position": {
"x": -194,
"y": 466.5
"x": -340,
"y": 402.5
},
"parameters": {
"gradient": {
@ -192,21 +179,20 @@
{
"name": "warp_1",
"node_position": {
"x": -31,
"y": 349.5
"x": -89,
"y": 325.5
},
"parameters": {
"amount": 0.1,
"eps": 0.045,
"epsilon": 0
"eps": 0.045
},
"type": "warp"
},
{
"name": "voronoi_0",
"node_position": {
"x": -437,
"y": 484.5
"x": -648,
"y": 482.5
},
"parameters": {
"intensity": 1,
@ -221,8 +207,8 @@
{
"name": "blend_0",
"node_position": {
"x": 83,
"y": 245.5
"x": 101,
"y": 250.5
},
"parameters": {
"amount": 1,
@ -230,29 +216,11 @@
},
"type": "blend"
},
{
"name": "combine_0",
"node_position": {
"x": 515.35144,
"y": -15.818176
},
"parameters": {
"color": {
"a": 1,
"b": 1,
"g": 1,
"r": 1,
"type": "Color"
},
"name": 0
},
"type": "combine"
},
{
"name": "Material",
"node_position": {
"x": 544,
"y": 79
"x": 687,
"y": 172
},
"parameters": {
"albedo_color": {
@ -267,7 +235,6 @@
"emission_energy": 1,
"metallic": 1,
"normal_scale": 1,
"resolution": 1,
"roughness": 1,
"size": 11,
"subsurf_scatter_strength": 0
@ -277,25 +244,46 @@
{
"name": "colorize_2",
"node_position": {
"x": 305.35144,
"y": 76.181824
"x": 407.35144,
"y": 127.181824
},
"parameters": {
"gradient": {
"interpolation": 1,
"interpolation": 2,
"points": [
{
"a": 1,
"b": 0.071126,
"g": 0.34877,
"pos": 0,
"pos": 0.120364,
"r": 0.59375
},
{
"a": 1,
"b": 0.013021,
"g": 0.144043,
"pos": 1,
"pos": 0.263636,
"r": 0.3125
},
{
"a": 1,
"b": 0.071126,
"g": 0.34877,
"pos": 0.402182,
"r": 0.59375
},
{
"a": 1,
"b": 0.071126,
"g": 0.34877,
"pos": 0.663636,
"r": 0.59375
},
{
"a": 1,
"b": 0.013021,
"g": 0.144043,
"pos": 0.893091,
"r": 0.3125
}
],
@ -307,25 +295,22 @@
{
"name": "normal_map_0",
"node_position": {
"x": 319,
"y": 265.5
"x": 431,
"y": 423.5
},
"parameters": {
"amount": 0.1,
"param0": 11,
"param1": 0.99,
"param2": 0,
"param3": 0,
"param4": 1,
"size": 5
"param4": 1
},
"type": "normal_map"
},
{
"name": "colorize_0",
"node_position": {
"x": 313,
"y": 176.5
"x": 423,
"y": 306.5
},
"parameters": {
"gradient": {

View File

@ -213,16 +213,25 @@ func create_nodes(data, position : Vector2 = Vector2(0, 0)) -> Array:
func create_gen_from_type(gen_name) -> void:
create_nodes({ type=gen_name, parameters={} }, scroll_offset+0.5*rect_size)
func load_file(filename) -> void:
func load_file(filename) -> bool:
var new_generator = mm_loader.load_gen(filename)
if new_generator != null:
clear_material()
top_generator = mm_loader.load_gen(filename)
if top_generator != null:
top_generator = new_generator
add_child(top_generator)
move_child(top_generator, 0)
update_view(top_generator)
center_view()
set_save_path(filename)
set_need_save(false)
return true
else:
var dialog : AcceptDialog = AcceptDialog.new()
add_child(dialog)
dialog.window_title = "Load failed!"
dialog.dialog_text = "Failed to load "+filename
dialog.popup_centered()
return false
func save_file(filename) -> void:
var data = top_generator.serialize()
@ -233,14 +242,16 @@ func save_file(filename) -> void:
set_save_path(filename)
set_need_save(false)
func export_textures() -> void:
if save_path != null:
var prefix = save_path.left(save_path.rfind("."))
func get_material_node() -> MMGenMaterial:
for g in top_generator.get_children():
if g.has_method("render_textures"):
g.render_textures()
if g.has_method("export_textures"):
g.export_textures(prefix, editor_interface)
if g.has_method("get_export_profiles"):
return g
return null
func export_material(export_prefix, profile) -> void:
for g in top_generator.get_children():
if g.has_method("export_material"):
g.export_material(export_prefix, profile)
# Cut / copy / paste / duplicate

View File

@ -37,7 +37,8 @@ const MENU = [
{ menu="File", command="save_material_as", shortcut="Control+Shift+S", description="Save material as..." },
{ menu="File", command="save_all_materials", description="Save all materials..." },
{ menu="File" },
{ menu="File", command="export_material", shortcut="Control+E", description="Export material" },
{ menu="File", submenu="export_material", description="Export material" },
#{ menu="File", command="export_material", shortcut="Control+E", description="Export material" },
{ menu="File" },
{ menu="File", command="close_material", description="Close material" },
{ menu="File", command="quit", shortcut="Control+Q", description="Quit" },
@ -61,7 +62,7 @@ const MENU = [
{ menu="Tools", command="add_to_user_library", description="Add selected node to user library" },
{ menu="Tools", command="export_library", description="Export the nodes library" },
{ menu="Tools", command="generate_screenshots", description="Generate screenshots for the library nodes" },
#{ menu="Tools", command="generate_screenshots", description="Generate screenshots for the library nodes" },
@ -151,6 +152,8 @@ func _ready() -> void:
m.connect("about_to_show", self, "menu_about_to_show", [ m.name, menu ])
new_material()
do_load_materials(OS.get_cmdline_args())
func _input(event: InputEvent) -> void:
if event.is_action_pressed("toggle_fullscreen"):
OS.window_fullscreen = !OS.window_fullscreen
@ -199,7 +202,6 @@ func create_menu(menu, menu_name) -> PopupMenu:
func create_menu_load_recent(menu) -> void:
menu.clear()
if recent_files.empty():
menu.add_item("No items found", 0)
menu.set_item_disabled(0, true)
@ -210,7 +212,8 @@ func create_menu_load_recent(menu) -> void:
menu.connect("id_pressed", self, "_on_LoadRecent_id_pressed")
func _on_LoadRecent_id_pressed(id) -> void:
do_load_material(recent_files[id])
if !do_load_material(recent_files[id]):
recent_files.remove(id)
func load_recents() -> void:
var f = File.new()
@ -233,6 +236,42 @@ func add_recent(path) -> void:
f.store_string(to_json(recent_files))
f.close()
func create_menu_export_material(menu) -> void:
menu.clear()
var graph_edit : MMGraphEdit = get_current_graph_edit()
if graph_edit != null:
var material_node = graph_edit.get_material_node()
for p in material_node.get_export_profiles():
menu.add_item(p)
if !menu.is_connected("id_pressed", self, "_on_ExportMaterial_id_pressed"):
menu.connect("id_pressed", self, "_on_ExportMaterial_id_pressed")
func export_material(file_path : String, profile : String) -> void:
var graph_edit : MMGraphEdit = get_current_graph_edit()
if graph_edit == null:
return
var export_prefix = file_path.trim_suffix("."+file_path.get_extension())
graph_edit.export_material(export_prefix, profile)
func _on_ExportMaterial_id_pressed(id) -> void:
var graph_edit : MMGraphEdit = get_current_graph_edit()
if graph_edit == null:
return
var material_node = graph_edit.get_material_node()
if material_node == null:
return
var profile = material_node.get_export_profiles()[id]
var dialog : FileDialog = FileDialog.new()
dialog.rect_min_size = Vector2(500, 500)
dialog.access = FileDialog.ACCESS_FILESYSTEM
dialog.mode = FileDialog.MODE_SAVE_FILE
dialog.add_filter("*."+material_node.get_export_extension(profile)+";"+profile+" Material")
add_child(dialog)
dialog.connect("file_selected", self, "export_material", [ profile ])
dialog.popup_centered()
func create_menu_set_theme(menu) -> void:
menu.clear()
for t in THEMES:
@ -359,15 +398,6 @@ func save_material_as() -> void:
func close_material() -> void:
projects.close_tab()
func export_material() -> void:
var graph_edit : MMGraphEdit = get_current_graph_edit()
if graph_edit != null :
graph_edit.export_textures()
func export_material_is_disabled() -> bool:
var graph_edit : MMGraphEdit = get_current_graph_edit()
return graph_edit == null or graph_edit.save_path == null
func quit() -> void:
dim_window()
get_tree().quit()

View File

@ -64,7 +64,7 @@ _global_script_classes=[ {
"language": "GDScript",
"path": "res://addons/material_maker/engine/gen_image.gd"
}, {
"base": "MMGenBase",
"base": "MMGenShader",
"class": "MMGenMaterial",
"language": "GDScript",
"path": "res://addons/material_maker/engine/gen_material.gd"
@ -171,7 +171,7 @@ _global_script_class_icons={
[application]
config/name="Material Maker"
config/description="An open source, extensible procedural material generation tool"
config/description="An open source, extensible procedural material authoring tool"
run/main_scene="res://start.tscn"
config/use_custom_user_dir=true
config/custom_user_dir_name="material_maker"
@ -179,7 +179,8 @@ boot_splash/image="res://rodz_labs_logo.png"
boot_splash/fullsize=false
boot_splash/bg_color=Color( 0.0901961, 0.0941176, 0.141176, 1 )
config/icon="res://icon.png"
config/release="0.8"
config/windows_native_icon="res://icon.ico"
config/release="0.9"
[autoload]

View File

@ -21,7 +21,7 @@ compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=true
flags/repeat=0
flags/filter=true
flags/mipmaps=true
flags/anisotropic=false

View File

@ -1,12 +1,73 @@
extends Control
var loader
var loader = null
onready var progress_bar = $VBoxContainer/ProgressBar
func _ready():
set_process(false)
var path : String
if Directory.new().file_exists("res://material_maker/main_window.tscn"):
if OS.get_cmdline_args().size() > 0 && OS.get_cmdline_args()[0] == "--export":
var output = []
var dir : Directory = Directory.new()
match OS.get_name():
"Windows":
var bat_file_path : String = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS)+"\\mm_cd.bat"
var bat_file : File = File.new()
bat_file.open(bat_file_path, File.WRITE)
bat_file.store_line("cd")
bat_file.close()
OS.execute(bat_file_path, [], true, output)
dir.remove(bat_file_path)
dir.change_dir(output[0].split("\n")[2])
var target : String = "Godot"
var output_dir : String = dir.get_current_dir()
var size : int = 0
var files : Array = []
var i = 1
while i < OS.get_cmdline_args().size():
match OS.get_cmdline_args()[i]:
"-t", "--target":
i += 1
target = OS.get_cmdline_args()[i]
"-o", "--output-dir":
i += 1
output_dir = OS.get_cmdline_args()[i]
"--size":
i += 1
size = int(OS.get_cmdline_args()[i])
if size < 0:
show_error("ERROR: incorrect size "+OS.get_cmdline_args()[i])
return
_:
files.push_back(OS.get_cmdline_args()[i])
i += 1
if !dir.dir_exists(output_dir):
show_error("ERROR: Output directory '%s' does not exist" % output_dir)
return
var expanded_files = []
for f in files:
var basedir : String = f.get_base_dir()
if basedir == "":
basedir = "."
var basename : String = f.get_file()
if basename.find("*") != -1:
basename = basename.replace("*", ".*")
if dir.open(basedir) == OK:
var regex : RegEx = RegEx.new()
regex.compile("^"+basename+"$")
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
if regex.search(file_name) && file_name.get_extension() == "ptex":
expanded_files.push_back(basedir+"/"+file_name)
file_name = dir.get_next()
else:
expanded_files.push_back(f)
export_files(expanded_files, output_dir, target, size)
return
else:
path = "res://material_maker/main_window.tscn"
else:
path = "res://demo/demo.tscn"
@ -14,12 +75,14 @@ func _ready():
if loader == null: # check for errors
print("error")
queue_free()
set_process(true)
func _process(_delta):
func _process(_delta) -> void:
var err = loader.poll()
if err == ERR_FILE_EOF:
var resource = loader.get_resource()
get_node("/root").add_child(resource.instance())
var scene = resource.instance()
get_node("/root").add_child(scene)
queue_free()
elif err == OK:
var progress = float(loader.get_stage()) / loader.get_stage_count()
@ -27,3 +90,30 @@ func _process(_delta):
else: # error during loading
print("error")
queue_free()
func export_files(files, output_dir, target, size) -> void:
$VBoxContainer/ProgressBar.min_value = 0
$VBoxContainer/ProgressBar.max_value = files.size()
$VBoxContainer/ProgressBar.value = 0
for f in files:
var gen = mm_loader.load_gen(f)
if gen != null:
add_child(gen)
for c in gen.get_children():
if c.has_method("get_export_profiles"):
if c.get_export_profiles().find(target) == -1:
show_error("ERROR: Unsupported target %s"+target)
return
$VBoxContainer/Label.text = "Exporting "+f.get_file()
var prefix : String = output_dir+"/"+f.get_file().get_basename()
var result = c.export_material(prefix, target, size)
while result is GDScriptFunctionState:
result = yield(result, "completed")
break
gen.queue_free()
$VBoxContainer/ProgressBar.value += 1
get_tree().quit()
func show_error(message : String):
$ErrorPanel.show()
$ErrorPanel/Label.text = message

View File

@ -1,8 +1,16 @@
[gd_scene load_steps=3 format=2]
[gd_scene load_steps=4 format=2]
[ext_resource path="res://start.gd" type="Script" id=1]
[ext_resource path="res://rodz_labs_logo.png" type="Texture" id=2]
[sub_resource type="StyleBoxFlat" id=1]
bg_color = Color( 0.0235294, 0.0313726, 0.12549, 0.929412 )
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color( 1, 0, 0, 1 )
[node name="Start" type="Panel"]
anchor_right = 1.0
anchor_bottom = 1.0
@ -40,3 +48,26 @@ align = 1
margin_top = 492.0
margin_right = 256.0
margin_bottom = 506.0
[node name="ErrorPanel" type="Panel" parent="."]
visible = false
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -268.5
margin_top = -76.0
margin_right = 268.5
margin_bottom = 76.0
rect_pivot_offset = Vector2( 267.976, 75.7381 )
custom_styles/panel = SubResource( 1 )
[node name="Label" type="Label" parent="ErrorPanel"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 10.0
margin_top = 10.0
margin_right = -10.0
margin_bottom = -10.0
align = 1
valign = 1