| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  | from typing import Union | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import bpy | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import lnx | 
					
						
							|  |  |  | import lnx.log as log | 
					
						
							|  |  |  | import lnx.material.cycles as c | 
					
						
							|  |  |  | import lnx.material.cycles_functions as c_functions | 
					
						
							|  |  |  | from lnx.material.parser_state import ParserPass, ParserState | 
					
						
							|  |  |  | from lnx.material.shader import floatstr, vec3str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if lnx.is_reload(__name__): | 
					
						
							|  |  |  |     log = lnx.reload_module(log) | 
					
						
							|  |  |  |     c = lnx.reload_module(c) | 
					
						
							|  |  |  |     c_functions = lnx.reload_module(c_functions) | 
					
						
							|  |  |  |     lnx.material.parser_state = lnx.reload_module(lnx.material.parser_state) | 
					
						
							|  |  |  |     from lnx.material.parser_state import ParserState | 
					
						
							|  |  |  |     lnx.material.shader = lnx.reload_module(lnx.material.shader) | 
					
						
							|  |  |  |     from lnx.material.shader import floatstr, vec3str | 
					
						
							|  |  |  | else: | 
					
						
							|  |  |  |     lnx.enable_reload(__name__) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_maprange(node: bpy.types.ShaderNodeMapRange, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     interp = node.interpolation_type | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     value: str = c.parse_value_input(node.inputs[0]) if node.inputs[0].is_linked else c.to_vec1(node.inputs[0].default_value) | 
					
						
							|  |  |  |     fromMin = c.parse_value_input(node.inputs[1]) | 
					
						
							|  |  |  |     fromMax = c.parse_value_input(node.inputs[2]) | 
					
						
							|  |  |  |     toMin = c.parse_value_input(node.inputs[3]) | 
					
						
							|  |  |  |     toMax = c.parse_value_input(node.inputs[4]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if interp == "LINEAR": | 
					
						
							|  |  |  |         state.curshader.add_function(c_functions.str_map_range_linear) | 
					
						
							|  |  |  |         out = f'map_range_linear({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     elif interp == "STEPPED": | 
					
						
							|  |  |  |         steps = float(c.parse_value_input(node.inputs[5])) | 
					
						
							|  |  |  |         state.curshader.add_function(c_functions.str_map_range_stepped) | 
					
						
							|  |  |  |         out = f'map_range_stepped({value}, {fromMin}, {fromMax}, {toMin}, {toMax}, {steps})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     elif interp == "SMOOTHSTEP": | 
					
						
							|  |  |  |         state.curshader.add_function(c_functions.str_map_range_smoothstep) | 
					
						
							|  |  |  |         out = f'map_range_smoothstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     elif interp == "SMOOTHERSTEP": | 
					
						
							|  |  |  |         state.curshader.add_function(c_functions.str_map_range_smootherstep) | 
					
						
							|  |  |  |         out = f'map_range_smootherstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         log.warn(f'Interpolation mode {interp} not supported for Map Range node') | 
					
						
							|  |  |  |         return '0.0' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if node.clamp: | 
					
						
							|  |  |  |         out = f'clamp({out}, {toMin}, {toMax})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return out | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_blackbody(node: bpy.types.ShaderNodeBlackbody, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     t = c.parse_value_input(node.inputs[0]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     state.curshader.add_function(c_functions.str_blackbody) | 
					
						
							|  |  |  |     return f'blackbody({t})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_clamp(node: bpy.types.ShaderNodeClamp, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: | 
					
						
							|  |  |  |     value = c.parse_value_input(node.inputs['Value']) | 
					
						
							|  |  |  |     minVal = c.parse_value_input(node.inputs['Min']) | 
					
						
							|  |  |  |     maxVal = c.parse_value_input(node.inputs['Max']) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if node.clamp_type == 'MINMAX': | 
					
						
							|  |  |  |         # Condition is minVal < maxVal, otherwise use 'RANGE' type | 
					
						
							|  |  |  |         return f'clamp({value}, {minVal}, {maxVal})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     elif node.clamp_type == 'RANGE': | 
					
						
							|  |  |  |         return f'{minVal} < {maxVal} ? clamp({value}, {minVal}, {maxVal}) : clamp({value}, {maxVal}, {minVal})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         log.warn(f'Clamp node: unsupported clamp type {node.clamp_type}.') | 
					
						
							|  |  |  |         return value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     input_fac: bpy.types.NodeSocket = node.inputs[0] | 
					
						
							| 
									
										
										
										
											2025-05-31 06:07:19 +00:00
										 |  |  |     alpha_out = out_socket == node.outputs[1] | 
					
						
							| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  |     fac: str = c.parse_value_input(input_fac) if input_fac.is_linked else c.to_vec1(input_fac.default_value) | 
					
						
							|  |  |  |     interp = node.color_ramp.interpolation | 
					
						
							|  |  |  |     elems = node.color_ramp.elements | 
					
						
							| 
									
										
										
										
											2025-05-31 06:07:19 +00:00
										 |  |  |      | 
					
						
							| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  |     if len(elems) == 1: | 
					
						
							| 
									
										
										
										
											2025-05-31 06:07:19 +00:00
										 |  |  |         if alpha_out: | 
					
						
							|  |  |  |             return c.to_vec1(elems[0].color[3])  # Return alpha from the color | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return c.to_vec3(elems[0].color)  # Return RGB | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     name_prefix = c.node_name(node.name).upper() | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if alpha_out: | 
					
						
							|  |  |  |         cols_var = name_prefix + '_ALPHAS' | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         cols_var = name_prefix + '_COLS' | 
					
						
							| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if state.current_pass == ParserPass.REGULAR: | 
					
						
							| 
									
										
										
										
											2025-05-31 06:07:19 +00:00
										 |  |  |         if alpha_out: | 
					
						
							|  |  |  |             cols_entries = ', '.join(f'{elem.color[3]}' for elem in elems) | 
					
						
							|  |  |  |             # Add last value twice to avoid out of bounds access | 
					
						
							|  |  |  |             cols_entries += f', {elems[len(elems) - 1].color[3]}' | 
					
						
							|  |  |  |             state.curshader.add_const("float", cols_var, cols_entries, array_size=len(elems) + 1) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # Create array of RGB values for color output | 
					
						
							|  |  |  |             cols_entries = ', '.join(f'vec3({elem.color[0]}, {elem.color[1]}, {elem.color[2]})' for elem in elems) | 
					
						
							|  |  |  |             cols_entries += f', vec3({elems[len(elems) - 1].color[0]}, {elems[len(elems) - 1].color[1]}, {elems[len(elems) - 1].color[2]})' | 
					
						
							|  |  |  |             state.curshader.add_const("vec3", cols_var, cols_entries, array_size=len(elems) + 1) | 
					
						
							| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix() | 
					
						
							|  |  |  |     state.curshader.write(f'float {fac_var} = {fac};') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Get index of the nearest left element relative to the factor | 
					
						
							|  |  |  |     index = '0 + ' | 
					
						
							|  |  |  |     index += ' + '.join([f'(({fac_var} > {elems[i].position}) ? 1 : 0)' for i in range(1, len(elems))]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Write index | 
					
						
							|  |  |  |     index_var = c.node_name(node.name) + '_i' + state.get_parser_pass_suffix() | 
					
						
							|  |  |  |     state.curshader.write(f'int {index_var} = {index};') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if interp == 'CONSTANT': | 
					
						
							|  |  |  |         return f'{cols_var}[{index_var}]' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Linear interpolation | 
					
						
							|  |  |  |     else: | 
					
						
							| 
									
										
										
										
											2025-05-31 06:07:19 +00:00
										 |  |  |         # Write factor array - same for both color and alpha | 
					
						
							|  |  |  |         facs_var = name_prefix + '_FACS' | 
					
						
							| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  |         if state.current_pass == ParserPass.REGULAR: | 
					
						
							|  |  |  |             facs_entries = ', '.join(str(elem.position) for elem in elems) | 
					
						
							| 
									
										
										
										
											2025-05-31 06:07:19 +00:00
										 |  |  |             # Add one more entry at the rightmost position to avoid out of bounds access | 
					
						
							| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  |             facs_entries += ', 1.0' | 
					
						
							|  |  |  |             state.curshader.add_const("float", facs_var, facs_entries, array_size=len(elems) + 1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-31 06:07:19 +00:00
										 |  |  |         # Calculation for interpolation position | 
					
						
							| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  |         prev_stop_fac = f'{facs_var}[{index_var}]' | 
					
						
							|  |  |  |         next_stop_fac = f'{facs_var}[{index_var} + 1]' | 
					
						
							|  |  |  |         prev_stop_col = f'{cols_var}[{index_var}]' | 
					
						
							|  |  |  |         next_stop_col = f'{cols_var}[{index_var} + 1]' | 
					
						
							|  |  |  |         rel_pos = f'({fac_var} - {prev_stop_fac}) * (1.0 / ({next_stop_fac} - {prev_stop_fac}))' | 
					
						
							| 
									
										
										
										
											2025-05-31 06:07:19 +00:00
										 |  |  |          | 
					
						
							|  |  |  |         # Use mix function for both alpha and color outputs (mix works on floats too) | 
					
						
							| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  |         return f'mix({prev_stop_col}, {next_stop_col}, max({rel_pos}, 0.0))' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if bpy.app.version > (3, 2, 0): | 
					
						
							|  |  |  |     def parse_combine_color(node: bpy.types.ShaderNodeCombineColor, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: | 
					
						
							|  |  |  |         if node.mode == 'RGB': | 
					
						
							|  |  |  |             return parse_combrgb(node, out_socket, state) | 
					
						
							|  |  |  |         elif node.mode == 'HSV': | 
					
						
							|  |  |  |             return parse_combhsv(node, out_socket, state) | 
					
						
							|  |  |  |         elif node.mode == 'HSL': | 
					
						
							|  |  |  |             log.warn('Combine Color node: HSL mode is not supported, using default value') | 
					
						
							|  |  |  |             return c.to_vec3((0.0, 0.0, 0.0)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_combhsv(node: bpy.types.ShaderNodeCombineHSV, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | 
					
						
							|  |  |  |     state.curshader.add_function(c_functions.str_hue_sat) | 
					
						
							|  |  |  |     h = c.parse_value_input(node.inputs[0]) | 
					
						
							|  |  |  |     s = c.parse_value_input(node.inputs[1]) | 
					
						
							|  |  |  |     v = c.parse_value_input(node.inputs[2]) | 
					
						
							|  |  |  |     return f'hsv_to_rgb(vec3({h}, {s}, {v}))' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_combrgb(node: bpy.types.ShaderNodeCombineRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | 
					
						
							|  |  |  |     r = c.parse_value_input(node.inputs[0]) | 
					
						
							|  |  |  |     g = c.parse_value_input(node.inputs[1]) | 
					
						
							|  |  |  |     b = c.parse_value_input(node.inputs[2]) | 
					
						
							|  |  |  |     return f'vec3({r}, {g}, {b})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_combxyz(node: bpy.types.ShaderNodeCombineXYZ, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | 
					
						
							|  |  |  |     x = c.parse_value_input(node.inputs[0]) | 
					
						
							|  |  |  |     y = c.parse_value_input(node.inputs[1]) | 
					
						
							|  |  |  |     z = c.parse_value_input(node.inputs[2]) | 
					
						
							|  |  |  |     return f'vec3({x}, {y}, {z})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_wavelength(node: bpy.types.ShaderNodeWavelength, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: | 
					
						
							|  |  |  |     state.curshader.add_function(c_functions.str_wavelength_to_rgb) | 
					
						
							|  |  |  |     wl = c.parse_value_input(node.inputs[0]) | 
					
						
							|  |  |  |     # Roughly map to cycles - 450 to 600 nanometers | 
					
						
							|  |  |  |     return f'wavelength_to_rgb(({wl} - 450.0) / 150.0)' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_vectormath(node: bpy.types.ShaderNodeVectorMath, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: | 
					
						
							|  |  |  |     op = node.operation | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     vec1 = c.parse_vector_input(node.inputs[0]) | 
					
						
							|  |  |  |     vec2 = c.parse_vector_input(node.inputs[1]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if out_socket.type == 'VECTOR': | 
					
						
							|  |  |  |         if op == 'ADD': | 
					
						
							|  |  |  |             return f'({vec1} + {vec2})' | 
					
						
							|  |  |  |         elif op == 'SUBTRACT': | 
					
						
							|  |  |  |             return f'({vec1} - {vec2})' | 
					
						
							|  |  |  |         elif op == 'MULTIPLY': | 
					
						
							|  |  |  |             return f'({vec1} * {vec2})' | 
					
						
							|  |  |  |         elif op == 'DIVIDE': | 
					
						
							|  |  |  |             state.curshader.add_function(c_functions.str_safe_divide) | 
					
						
							|  |  |  |             return f'safe_divide({vec1}, {vec2})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         elif op == 'NORMALIZE': | 
					
						
							|  |  |  |             return f'normalize({vec1})' | 
					
						
							|  |  |  |         elif op == 'SCALE': | 
					
						
							|  |  |  |             # Scale is input 3 despite being visually on another position (see the python tooltip in Blender) | 
					
						
							|  |  |  |             scale = c.parse_value_input(node.inputs[3]) | 
					
						
							|  |  |  |             return f'{vec1} * {scale}' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         elif op == 'REFLECT': | 
					
						
							|  |  |  |             return f'reflect({vec1}, normalize({vec2}))' | 
					
						
							|  |  |  |         elif op == 'PROJECT': | 
					
						
							|  |  |  |             state.curshader.add_function(c_functions.str_project) | 
					
						
							|  |  |  |             return f'project({vec1}, {vec2})' | 
					
						
							|  |  |  |         elif op == 'CROSS_PRODUCT': | 
					
						
							|  |  |  |             return f'cross({vec1}, {vec2})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         elif op == 'SINE': | 
					
						
							|  |  |  |             return f'sin({vec1})' | 
					
						
							|  |  |  |         elif op == 'COSINE': | 
					
						
							|  |  |  |             return f'cos({vec1})' | 
					
						
							|  |  |  |         elif op == 'TANGENT': | 
					
						
							|  |  |  |             return f'tan({vec1})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         elif op == 'MODULO': | 
					
						
							|  |  |  |             return f'mod({vec1}, {vec2})' | 
					
						
							|  |  |  |         elif op == 'FRACTION': | 
					
						
							|  |  |  |             return f'fract({vec1})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         elif op == 'SNAP': | 
					
						
							|  |  |  |             state.curshader.add_function(c_functions.str_safe_divide) | 
					
						
							|  |  |  |             return f'floor(safe_divide({vec1}, {vec2})) * {vec2}' | 
					
						
							|  |  |  |         elif op == 'WRAP': | 
					
						
							|  |  |  |             vec3 = c.parse_vector_input(node.inputs[2]) | 
					
						
							|  |  |  |             state.curshader.add_function(c_functions.str_wrap) | 
					
						
							|  |  |  |             return f'wrap({vec1}, {vec2}, {vec3})' | 
					
						
							|  |  |  |         elif op == 'CEIL': | 
					
						
							|  |  |  |             return f'ceil({vec1})' | 
					
						
							|  |  |  |         elif op == 'FLOOR': | 
					
						
							|  |  |  |             return f'floor({vec1})' | 
					
						
							|  |  |  |         elif op == 'MAXIMUM': | 
					
						
							|  |  |  |             return f'max({vec1}, {vec2})' | 
					
						
							|  |  |  |         elif op == 'MINIMUM': | 
					
						
							|  |  |  |             return f'min({vec1}, {vec2})' | 
					
						
							|  |  |  |         elif op == 'ABSOLUTE': | 
					
						
							|  |  |  |             return f'abs({vec1})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         log.warn(f'Vectormath node: unsupported operation {node.operation}.') | 
					
						
							|  |  |  |         return vec1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Float output | 
					
						
							|  |  |  |     if op == 'DOT_PRODUCT': | 
					
						
							|  |  |  |         return f'dot({vec1}, {vec2})' | 
					
						
							|  |  |  |     elif op == 'DISTANCE': | 
					
						
							|  |  |  |         return f'distance({vec1}, {vec2})' | 
					
						
							|  |  |  |     elif op == 'LENGTH': | 
					
						
							|  |  |  |         return f'length({vec1})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     log.warn(f'Vectormath node: unsupported operation {node.operation}.') | 
					
						
							|  |  |  |     return '0.0' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: | 
					
						
							|  |  |  |     val1 = c.parse_value_input(node.inputs[0]) | 
					
						
							|  |  |  |     val2 = c.parse_value_input(node.inputs[1]) | 
					
						
							|  |  |  |     op = node.operation | 
					
						
							|  |  |  |     if op == 'ADD': | 
					
						
							|  |  |  |         out_val = '({0} + {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'SUBTRACT': | 
					
						
							|  |  |  |         out_val = '({0} - {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'MULTIPLY': | 
					
						
							|  |  |  |         out_val = '({0} * {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'DIVIDE': | 
					
						
							|  |  |  |         out_val = '({0} / {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'MULTIPLY_ADD': | 
					
						
							|  |  |  |         val3 = c.parse_value_input(node.inputs[2]) | 
					
						
							|  |  |  |         out_val = '({0} * {1} + {2})'.format(val1, val2, val3) | 
					
						
							|  |  |  |     elif op == 'POWER': | 
					
						
							|  |  |  |         out_val = 'pow({0}, {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'LOGARITHM': | 
					
						
							|  |  |  |         out_val = 'log({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'SQRT': | 
					
						
							|  |  |  |         out_val = 'sqrt({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'INVERSE_SQRT': | 
					
						
							|  |  |  |         out_val = 'inversesqrt({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'ABSOLUTE': | 
					
						
							|  |  |  |         out_val = 'abs({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'EXPONENT': | 
					
						
							|  |  |  |         out_val = 'exp({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'MINIMUM': | 
					
						
							|  |  |  |         out_val = 'min({0}, {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'MAXIMUM': | 
					
						
							|  |  |  |         out_val = 'max({0}, {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'LESS_THAN': | 
					
						
							|  |  |  |         out_val = 'float({0} < {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'GREATER_THAN': | 
					
						
							|  |  |  |         out_val = 'float({0} > {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'SIGN': | 
					
						
							|  |  |  |         out_val = 'sign({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'COMPARE': | 
					
						
							|  |  |  |         val3 = c.parse_value_input(node.inputs[2]) | 
					
						
							|  |  |  |         out_val = 'float((abs({0} - {1}) <= max({2}, 1e-5)) ? 1.0 : 0.0)'.format(val1, val2, val3) | 
					
						
							|  |  |  |     elif op == 'SMOOTH_MIN': | 
					
						
							|  |  |  |         val3 = c.parse_value_input(node.inputs[2]) | 
					
						
							|  |  |  |         out_val = 'float(float({2} != 0.0 ? min({0},{1}) - (max({2} - abs({0} - {1}), 0.0) / {2}) * (max({2} - abs({0} - {1}), 0.0) / {2}) * (max({2} - abs({0} - {1}), 0.0) / {2}) * {2} * (1.0 / 6.0) : min({0}, {1})))'.format(val1, val2, val3) | 
					
						
							|  |  |  |     elif op == 'SMOOTH_MAX': | 
					
						
							|  |  |  |         val3 = c.parse_value_input(node.inputs[2]) | 
					
						
							|  |  |  |         out_val = 'float(0-(float({2} != 0.0 ? min(-{0},-{1}) - (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * {2} * (1.0 / 6.0) : min(-{0}, (-{1})))))'.format(val1, val2, val3) | 
					
						
							|  |  |  |     elif op == 'ROUND': | 
					
						
							|  |  |  |         # out_val = 'round({0})'.format(val1) | 
					
						
							|  |  |  |         out_val = 'floor({0} + 0.5)'.format(val1) | 
					
						
							|  |  |  |     elif op == 'FLOOR': | 
					
						
							|  |  |  |         out_val = 'floor({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'CEIL': | 
					
						
							|  |  |  |         out_val = 'ceil({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'TRUNC': | 
					
						
							|  |  |  |         out_val = 'trunc({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'FRACT': | 
					
						
							|  |  |  |         out_val = 'fract({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'MODULO': | 
					
						
							|  |  |  |         # out_val = 'float({0} % {1})'.format(val1, val2) | 
					
						
							|  |  |  |         out_val = 'mod({0}, {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'WRAP': | 
					
						
							|  |  |  |         val3 = c.parse_value_input(node.inputs[2]) | 
					
						
							|  |  |  |         out_val = 'float((({1}-{2}) != 0.0) ? {0} - (({1}-{2}) * floor(({0} - {2}) / ({1}-{2}))) : {2})'.format(val1, val2, val3) | 
					
						
							|  |  |  |     elif op == 'SNAP': | 
					
						
							|  |  |  |         out_val = 'floor(({1} != 0.0) ? {0} / {1} : 0.0) * {1}'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'PINGPONG': | 
					
						
							|  |  |  |         out_val = 'float(({1} != 0.0) ? abs(fract(({0} - {1}) / ({1} * 2.0)) * {1} * 2.0 - {1}) : 0.0)'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'SINE': | 
					
						
							|  |  |  |         out_val = 'sin({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'COSINE': | 
					
						
							|  |  |  |         out_val = 'cos({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'TANGENT': | 
					
						
							|  |  |  |         out_val = 'tan({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'ARCSINE': | 
					
						
							|  |  |  |         out_val = 'asin({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'ARCCOSINE': | 
					
						
							|  |  |  |         out_val = 'acos({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'ARCTANGENT': | 
					
						
							|  |  |  |         out_val = 'atan({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'ARCTAN2': | 
					
						
							|  |  |  |         out_val = 'atan({0}, {1})'.format(val1, val2) | 
					
						
							|  |  |  |     elif op == 'SINH': | 
					
						
							|  |  |  |         out_val = 'sinh({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'COSH': | 
					
						
							|  |  |  |         out_val = 'cosh({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'TANH': | 
					
						
							|  |  |  |         out_val = 'tanh({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'RADIANS': | 
					
						
							|  |  |  |         out_val = 'radians({0})'.format(val1) | 
					
						
							|  |  |  |     elif op == 'DEGREES': | 
					
						
							|  |  |  |         out_val = 'degrees({0})'.format(val1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if node.use_clamp: | 
					
						
							|  |  |  |         return 'clamp({0}, 0.0, 1.0)'.format(out_val) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         return out_val | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_rgbtobw(node: bpy.types.ShaderNodeRGBToBW, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: | 
					
						
							|  |  |  |     return c.rgb_to_bw(c.parse_vector_input(node.inputs[0])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if bpy.app.version > (3, 2, 0): | 
					
						
							|  |  |  |     def parse_separate_color(node: bpy.types.ShaderNodeSeparateColor, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: | 
					
						
							|  |  |  |         if node.mode == 'RGB': | 
					
						
							|  |  |  |             return parse_seprgb(node, out_socket, state) | 
					
						
							|  |  |  |         elif node.mode == 'HSV': | 
					
						
							|  |  |  |             return parse_sephsv(node, out_socket, state) | 
					
						
							|  |  |  |         elif node.mode == 'HSL': | 
					
						
							|  |  |  |             log.warn('Separate Color node: HSL mode is not supported, using default value') | 
					
						
							|  |  |  |             return '0.0' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_sephsv(node: bpy.types.ShaderNodeSeparateHSV, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: | 
					
						
							|  |  |  |     state.curshader.add_function(c_functions.str_hue_sat) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     hsv_var = c.node_name(node.name) + '_hsv' + state.get_parser_pass_suffix() | 
					
						
							|  |  |  |     if not state.curshader.contains(hsv_var):  # Already written if a second output is parsed | 
					
						
							|  |  |  |         state.curshader.write(f'const vec3 {hsv_var} = rgb_to_hsv({c.parse_vector_input(node.inputs["Color"])}.rgb);') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if out_socket == node.outputs[0]: | 
					
						
							|  |  |  |         return f'{hsv_var}.x' | 
					
						
							|  |  |  |     elif out_socket == node.outputs[1]: | 
					
						
							|  |  |  |         return f'{hsv_var}.y' | 
					
						
							|  |  |  |     elif out_socket == node.outputs[2]: | 
					
						
							|  |  |  |         return f'{hsv_var}.z' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_seprgb(node: bpy.types.ShaderNodeSeparateRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: | 
					
						
							|  |  |  |     col = c.parse_vector_input(node.inputs[0]) | 
					
						
							|  |  |  |     if out_socket == node.outputs[0]: | 
					
						
							|  |  |  |         return '{0}.r'.format(col) | 
					
						
							|  |  |  |     elif out_socket == node.outputs[1]: | 
					
						
							|  |  |  |         return '{0}.g'.format(col) | 
					
						
							|  |  |  |     elif out_socket == node.outputs[2]: | 
					
						
							|  |  |  |         return '{0}.b'.format(col) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_sepxyz(node: bpy.types.ShaderNodeSeparateXYZ, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: | 
					
						
							|  |  |  |     vec = c.parse_vector_input(node.inputs[0]) | 
					
						
							|  |  |  |     if out_socket == node.outputs[0]: | 
					
						
							|  |  |  |         return '{0}.x'.format(vec) | 
					
						
							|  |  |  |     elif out_socket == node.outputs[1]: | 
					
						
							|  |  |  |         return '{0}.y'.format(vec) | 
					
						
							|  |  |  |     elif out_socket == node.outputs[2]: | 
					
						
							|  |  |  |         return '{0}.z'.format(vec) |