forked from LeenkxTeam/LNXSDK
Update
This commit is contained in:
@ -610,19 +610,30 @@ class LeenkxExporter:
|
||||
return None
|
||||
|
||||
def write_bone_matrices(self, scene, action):
|
||||
# profile_time = time.time()
|
||||
begin_frame, end_frame = int(action.frame_range[0]), int(action.frame_range[1])
|
||||
if len(self.bone_tracks) > 0:
|
||||
for i in range(begin_frame, end_frame + 1):
|
||||
scene.frame_set(i)
|
||||
for track in self.bone_tracks:
|
||||
values, pose_bone = track[0], track[1]
|
||||
parent = pose_bone.parent
|
||||
if parent:
|
||||
values += LeenkxExporter.write_matrix((parent.matrix.inverted_safe() @ pose_bone.matrix))
|
||||
else:
|
||||
values += LeenkxExporter.write_matrix(pose_bone.matrix)
|
||||
# print('Bone matrices exported in ' + str(time.time() - profile_time))
|
||||
hidden_states = {}
|
||||
try:
|
||||
for obj in list(scene.collection.all_objects):
|
||||
if obj is not None and obj.type != 'ARMATURE' and not obj.hide_viewport:
|
||||
hidden_states[obj] = False
|
||||
obj.hide_viewport = True
|
||||
|
||||
for i in range(begin_frame, end_frame + 1):
|
||||
scene.frame_set(i)
|
||||
for track in self.bone_tracks:
|
||||
values, pose_bone = track[0], track[1]
|
||||
parent = pose_bone.parent
|
||||
if parent:
|
||||
values += LeenkxExporter.write_matrix((parent.matrix.inverted_safe() @ pose_bone.matrix))
|
||||
else:
|
||||
values += LeenkxExporter.write_matrix(pose_bone.matrix)
|
||||
finally:
|
||||
for obj, was_hidden in hidden_states.items():
|
||||
try:
|
||||
obj.hide_viewport = was_hidden
|
||||
except ReferenceError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def has_baked_material(bobject, materials):
|
||||
@ -849,23 +860,36 @@ class LeenkxExporter:
|
||||
out_object['tilesheet_ref'] = bobject.lnx_tilesheet
|
||||
out_object['tilesheet_action_ref'] = bobject.lnx_tilesheet_action
|
||||
|
||||
|
||||
if len(bobject.vertex_groups) > 0:
|
||||
if len(bobject.vertex_groups) > 0 and bobject.data is not None and len(bobject.data.vertices) > 0:
|
||||
out_object['vertex_groups'] = []
|
||||
_verts = bobject.data.vertices
|
||||
_num_verts = len(_verts)
|
||||
_all_co = np.empty(_num_verts * 3, dtype='<f8')
|
||||
_verts.foreach_get('co', _all_co)
|
||||
_all_str = _all_co.astype('U32').tolist()
|
||||
_num_groups = len(bobject.vertex_groups)
|
||||
_vg_idx = [[] for _ in range(_num_groups)]
|
||||
for v in _verts:
|
||||
vi = v.index
|
||||
for g in v.groups:
|
||||
gi = g.group
|
||||
if 0 <= gi < _num_groups:
|
||||
_vg_idx[gi].append(vi)
|
||||
for group in bobject.vertex_groups:
|
||||
verts = []
|
||||
for v in bobject.data.vertices:
|
||||
for g in v.groups:
|
||||
if g.group == group.index:
|
||||
verts.append(str(v.co.x))
|
||||
verts.append(str(v.co.y))
|
||||
verts.append(str(v.co.z))
|
||||
|
||||
out_vertex_groups = {
|
||||
indices = _vg_idx[group.index]
|
||||
if len(indices) > 0:
|
||||
_values = []
|
||||
for vi in indices:
|
||||
base = vi * 3
|
||||
_values.append(_all_str[base])
|
||||
_values.append(_all_str[base + 1])
|
||||
_values.append(_all_str[base + 2])
|
||||
else:
|
||||
_values = []
|
||||
out_object['vertex_groups'].append({
|
||||
'name': group.name,
|
||||
'value': verts
|
||||
}
|
||||
out_object['vertex_groups'].append(out_vertex_groups)
|
||||
'value': _values
|
||||
})
|
||||
|
||||
if len(bobject.lnx_camera_list) > 0:
|
||||
out_camera_list = []
|
||||
@ -1114,13 +1138,27 @@ class LeenkxExporter:
|
||||
_o.select_set(False)
|
||||
skelobj.select_set(True)
|
||||
|
||||
bake_result = bpy.ops.nla.bake(
|
||||
frame_start=int(action.frame_range[0]),
|
||||
frame_end=int(action.frame_range[1]),
|
||||
step=1,
|
||||
only_selected=False,
|
||||
visual_keying=True
|
||||
)
|
||||
_bake_hidden = {}
|
||||
try:
|
||||
for _obj in list(bpy.context.scene.collection.all_objects):
|
||||
if _obj is not None and _obj.type != 'ARMATURE' and not _obj.hide_viewport:
|
||||
_bake_hidden[_obj] = False
|
||||
_obj.hide_viewport = True
|
||||
|
||||
bake_result = bpy.ops.nla.bake(
|
||||
frame_start=int(action.frame_range[0]),
|
||||
frame_end=int(action.frame_range[1]),
|
||||
step=1,
|
||||
only_selected=False,
|
||||
visual_keying=True
|
||||
)
|
||||
finally:
|
||||
for _obj, _was_hidden in _bake_hidden.items():
|
||||
try:
|
||||
_obj.hide_viewport = _was_hidden
|
||||
except ReferenceError:
|
||||
pass
|
||||
|
||||
action = skelobj.animation_data.action
|
||||
|
||||
skelobj.select_set(False)
|
||||
@ -1225,44 +1263,67 @@ class LeenkxExporter:
|
||||
else:
|
||||
group_remap.append(-1)
|
||||
|
||||
bone_count_array = np.empty(len(export_mesh.loops), dtype='<i2')
|
||||
bone_index_array = np.empty(len(export_mesh.loops) * 4, dtype='<i2')
|
||||
bone_weight_array = np.empty(len(export_mesh.loops) * 4, dtype='<f4')
|
||||
num_loops = len(export_mesh.loops)
|
||||
_loop_vidx = np.empty(num_loops, dtype='<i4')
|
||||
export_mesh.loops.foreach_get('vertex_index', _loop_vidx)
|
||||
|
||||
num_verts = len(bobject.data.vertices)
|
||||
verts = bobject.data.vertices
|
||||
|
||||
vert_bone_count = np.zeros(num_verts, dtype='<i2')
|
||||
vert_bone_index = np.zeros((num_verts, 4), dtype='<i2')
|
||||
vert_bone_weight = np.zeros((num_verts, 4), dtype='<f4')
|
||||
|
||||
_group_remap = group_remap
|
||||
|
||||
for vi in range(num_verts):
|
||||
groups = verts[vi].groups
|
||||
n = len(groups)
|
||||
if n == 0:
|
||||
continue
|
||||
|
||||
vertices = bobject.data.vertices
|
||||
count = 0
|
||||
for index, l in enumerate(export_mesh.loops):
|
||||
bone_count = 0
|
||||
total_weight = 0.0
|
||||
bone_values = []
|
||||
for g in vertices[l.vertex_index].groups:
|
||||
bone_index = group_remap[g.group]
|
||||
bone_weight = g.weight
|
||||
if bone_index >= 0: #and bone_weight != 0.0:
|
||||
bone_values.append((bone_weight, bone_index))
|
||||
total_weight += bone_weight
|
||||
bone_count += 1
|
||||
total_weight = 0.0
|
||||
for gi in range(n):
|
||||
g = groups[gi]
|
||||
bone_index = _group_remap[g.group]
|
||||
if bone_index >= 0:
|
||||
bone_values.append((g.weight, bone_index))
|
||||
total_weight += g.weight
|
||||
|
||||
bone_count = len(bone_values)
|
||||
if bone_count == 0:
|
||||
continue
|
||||
|
||||
if bone_count > 4:
|
||||
bone_count = 4
|
||||
bone_values.sort(reverse=True)
|
||||
bone_values = bone_values[:4]
|
||||
|
||||
bone_count_array[index] = bone_count
|
||||
for bv in bone_values:
|
||||
bone_weight_array[count] = bv[0]
|
||||
bone_index_array[count] = bv[1]
|
||||
count += 1
|
||||
bone_count = 4
|
||||
total_weight = sum(bv[0] for bv in bone_values)
|
||||
|
||||
if total_weight not in (0.0, 1.0):
|
||||
normalizer = 1.0 / total_weight
|
||||
for i in range(bone_count):
|
||||
bone_weight_array[count - i - 1] *= normalizer
|
||||
bone_values = [(bv[0] * normalizer, bv[1]) for bv in bone_values]
|
||||
|
||||
vert_bone_count[vi] = bone_count
|
||||
for j in range(bone_count):
|
||||
vert_bone_weight[vi, j] = bone_values[j][0]
|
||||
vert_bone_index[vi, j] = bone_values[j][1]
|
||||
|
||||
bone_count_array = vert_bone_count[_loop_vidx]
|
||||
|
||||
loop_bone_weight = vert_bone_weight[_loop_vidx]
|
||||
loop_bone_index = vert_bone_index[_loop_vidx]
|
||||
loop_bone_count = bone_count_array
|
||||
|
||||
slot_idx = np.arange(4, dtype='<i2')
|
||||
active_mask = slot_idx[np.newaxis, :] < loop_bone_count[:, np.newaxis]
|
||||
|
||||
bone_weight_array = loop_bone_weight[active_mask].astype('<f4')
|
||||
bone_index_array = loop_bone_index[active_mask].astype('<i2')
|
||||
|
||||
bone_index_array = bone_index_array[:count]
|
||||
bone_weight_array = bone_weight_array[:count]
|
||||
bone_weight_array *= 32767
|
||||
bone_weight_array = np.array(bone_weight_array, dtype='<i2')
|
||||
bone_weight_array = bone_weight_array.astype('<i2')
|
||||
|
||||
oskin['bone_count_array'] = bone_count_array
|
||||
oskin['bone_index_array'] = bone_index_array
|
||||
@ -1291,6 +1352,8 @@ class LeenkxExporter:
|
||||
shape_key_base = bobject.data.shape_keys.key_blocks[0]
|
||||
|
||||
count = 0
|
||||
_base_co = None
|
||||
_base_nor = None
|
||||
# Loop through all shape keys
|
||||
for shape_key in bobject.data.shape_keys.key_blocks[1:]:
|
||||
|
||||
@ -1299,7 +1362,13 @@ class LeenkxExporter:
|
||||
# get vertex data from shape key
|
||||
if shape_key.mute:
|
||||
continue
|
||||
vert_data = self.get_vertex_data_from_shape_key(shape_key_base, shape_key)
|
||||
vert_data = self.get_vertex_data_from_shape_key(
|
||||
shape_key_base, shape_key,
|
||||
_base_co=_base_co, _base_nor=_base_nor)
|
||||
# Cache base arrays after first call
|
||||
if _base_co is None:
|
||||
_base_co = vert_data['_base_co']
|
||||
_base_nor = vert_data['_base_nor']
|
||||
vert_pos.append(vert_data['pos'])
|
||||
vert_nor.append(vert_data['nor'])
|
||||
names.append(shape_key.name)
|
||||
@ -1342,37 +1411,39 @@ class LeenkxExporter:
|
||||
morph_target['morph_target_ref'] = names
|
||||
morph_target['morph_target_defaults'] = default_values
|
||||
morph_target['num_morph_targets'] = count
|
||||
morph_target['morph_scale'] = max - min
|
||||
morph_target['morph_offset'] = min
|
||||
morph_target['morph_img_size'] = img_size
|
||||
morph_target['morph_block_size'] = block_size
|
||||
morph_target['morph_scale'] = float(max - min)
|
||||
morph_target['morph_offset'] = float(min)
|
||||
morph_target['morph_img_size'] = int(img_size)
|
||||
morph_target['morph_block_size'] = int(block_size)
|
||||
|
||||
out_mesh['morph_target'] = morph_target
|
||||
return
|
||||
|
||||
def get_vertex_data_from_shape_key(self, shape_key_base, shape_key_data):
|
||||
def get_vertex_data_from_shape_key(self, shape_key_base, shape_key_data, _base_co=None, _base_nor=None):
|
||||
|
||||
base_vert_pos = shape_key_base.data.values()
|
||||
base_vert_nor = shape_key_base.normals_split_get()
|
||||
vert_pos = shape_key_data.data.values()
|
||||
vert_nor = shape_key_data.normals_split_get()
|
||||
num_verts = len(shape_key_data.data)
|
||||
|
||||
num_verts = len(vert_pos)
|
||||
sk_co = np.empty(num_verts * 3, dtype='<f4')
|
||||
shape_key_data.data.foreach_get('co', sk_co)
|
||||
sk_co = sk_co.reshape(num_verts, 3)
|
||||
|
||||
pos = []
|
||||
nor = []
|
||||
if _base_co is None:
|
||||
_base_co = np.empty(num_verts * 3, dtype='<f4')
|
||||
shape_key_base.data.foreach_get('co', _base_co)
|
||||
_base_co = _base_co.reshape(num_verts, 3)
|
||||
|
||||
# Loop through all vertices
|
||||
for i in range(num_verts):
|
||||
# Vertex position relative to base vertex
|
||||
pos.append(list(vert_pos[i].co - base_vert_pos[i].co))
|
||||
temp = []
|
||||
for j in range(3):
|
||||
# Vertex normal relative to base vertex
|
||||
temp.append(vert_nor[j + i * 3] - base_vert_nor[j + i * 3])
|
||||
nor.append(temp)
|
||||
pos_delta = sk_co - _base_co
|
||||
|
||||
return {'pos': pos, 'nor': nor}
|
||||
sk_nor_raw = np.array(shape_key_data.normals_split_get(), dtype='<f4')
|
||||
sk_nor = sk_nor_raw[:num_verts * 3].reshape(num_verts, 3)
|
||||
|
||||
if _base_nor is None:
|
||||
base_nor_raw = np.array(shape_key_base.normals_split_get(), dtype='<f4')
|
||||
_base_nor = base_nor_raw[:num_verts * 3].reshape(num_verts, 3)
|
||||
|
||||
nor_delta = sk_nor - _base_nor
|
||||
|
||||
return {'pos': pos_delta, 'nor': nor_delta, '_base_co': _base_co, '_base_nor': _base_nor}
|
||||
|
||||
def bake_to_image(self, pos_array, nor_array, pos_max, pos_min, extra_x, img_size, name, output_dir):
|
||||
# Scale position data between [0, 1] to bake to image
|
||||
@ -1387,21 +1458,19 @@ class LeenkxExporter:
|
||||
def write_output_image(self, data, extra_x, img_size, name, output_dir):
|
||||
|
||||
# Pad data with zeros to make up for required number of pixels of 2^n format
|
||||
data = np.pad(data, ((0, 0), (0, extra_x), (0, 0)), 'minimum')
|
||||
pixel_list = []
|
||||
data = np.pad(data, ((0, 0), (0, int(extra_x)), (0, 0)), 'minimum')
|
||||
|
||||
for y in range(len(data)):
|
||||
for x in range(len(data[0])):
|
||||
# assign RGBA
|
||||
pixel_list.append(data[y, x, 0])
|
||||
pixel_list.append(data[y, x, 1])
|
||||
pixel_list.append(data[y, x, 2])
|
||||
pixel_list.append(1.0)
|
||||
total_pixels = data.shape[0] * data.shape[1]
|
||||
rgba = np.ones((total_pixels, 4), dtype=np.float32)
|
||||
rgba[:, :3] = data.reshape(-1, 3)
|
||||
|
||||
pixel_list = (pixel_list + [0] * (img_size * img_size * 4 - len(pixel_list)))
|
||||
img_total = img_size * img_size
|
||||
if total_pixels < img_total:
|
||||
padding = np.zeros((img_total - total_pixels, 4), dtype=np.float32)
|
||||
rgba = np.concatenate((rgba, padding), axis=0)
|
||||
|
||||
image = bpy.data.images.new(name, width = img_size, height = img_size, is_data = True)
|
||||
image.pixels = pixel_list
|
||||
image.pixels = rgba.ravel().tolist()
|
||||
output_path = os.path.join(output_dir, name + ".png")
|
||||
image.save_render(output_path, scene= bpy.context.scene)
|
||||
bpy.data.images.remove(image)
|
||||
@ -1579,30 +1648,28 @@ class LeenkxExporter:
|
||||
# Scale for packed coords
|
||||
lay0 = uv_layers[t0map]
|
||||
maxdim_uvlayer = lay0
|
||||
for v in lay0.data:
|
||||
if abs(v.uv[0]) > maxdim:
|
||||
maxdim = abs(v.uv[0])
|
||||
if abs(v.uv[1]) > maxdim:
|
||||
maxdim = abs(v.uv[1])
|
||||
_uv0_raw = np.empty(len(lay0.data) * 2, dtype='<f4')
|
||||
lay0.data.foreach_get('uv', _uv0_raw)
|
||||
_uv0_absmax = float(np.abs(_uv0_raw).max()) if len(_uv0_raw) > 0 else 0.0
|
||||
if _uv0_absmax > maxdim:
|
||||
maxdim = _uv0_absmax
|
||||
if has_tex1:
|
||||
lay1 = uv_layers[t1map]
|
||||
for v in lay1.data:
|
||||
if abs(v.uv[0]) > maxdim:
|
||||
maxdim = abs(v.uv[0])
|
||||
maxdim_uvlayer = lay1
|
||||
if abs(v.uv[1]) > maxdim:
|
||||
maxdim = abs(v.uv[1])
|
||||
maxdim_uvlayer = lay1
|
||||
_uv1_raw = np.empty(len(lay1.data) * 2, dtype='<f4')
|
||||
lay1.data.foreach_get('uv', _uv1_raw)
|
||||
_uv1_absmax = float(np.abs(_uv1_raw).max()) if len(_uv1_raw) > 0 else 0.0
|
||||
if _uv1_absmax > maxdim:
|
||||
maxdim = _uv1_absmax
|
||||
maxdim_uvlayer = lay1
|
||||
if has_morph_target:
|
||||
morph_data = np.empty(num_verts * 2, dtype='<f4')
|
||||
lay2 = uv_layers[morph_uv_index]
|
||||
for v in lay2.data:
|
||||
if abs(v.uv[0]) > maxdim:
|
||||
maxdim = abs(v.uv[0])
|
||||
maxdim_uvlayer = lay2
|
||||
if abs(v.uv[1]) > maxdim:
|
||||
maxdim = abs(v.uv[1])
|
||||
maxdim_uvlayer = lay2
|
||||
_uv2_raw = np.empty(len(lay2.data) * 2, dtype='<f4')
|
||||
lay2.data.foreach_get('uv', _uv2_raw)
|
||||
_uv2_absmax = float(np.abs(_uv2_raw).max()) if len(_uv2_raw) > 0 else 0.0
|
||||
if _uv2_absmax > maxdim:
|
||||
maxdim = _uv2_absmax
|
||||
maxdim_uvlayer = lay2
|
||||
if maxdim > 1:
|
||||
o['scale_tex'] = maxdim
|
||||
invscale_tex = (1 / o['scale_tex']) * 32767
|
||||
@ -1636,102 +1703,87 @@ class LeenkxExporter:
|
||||
invscale_pos = (1 / scale_pos) * 32767
|
||||
|
||||
verts = export_mesh.vertices
|
||||
|
||||
_all_co = np.empty(len(verts) * 3, dtype='<f4')
|
||||
verts.foreach_get('co', _all_co)
|
||||
_all_co = _all_co.reshape(-1, 3)
|
||||
|
||||
_loop_vidx = np.empty(num_verts, dtype='<i4')
|
||||
loops.foreach_get('vertex_index', _loop_vidx)
|
||||
|
||||
_loop_co = _all_co[_loop_vidx]
|
||||
|
||||
_all_normals = np.empty(num_verts * 3, dtype='<f4')
|
||||
loops.foreach_get('normal', _all_normals)
|
||||
_all_normals = _all_normals.reshape(-1, 3)
|
||||
|
||||
pdata[0::4] = _loop_co[:, 0]
|
||||
pdata[1::4] = _loop_co[:, 1]
|
||||
pdata[2::4] = _loop_co[:, 2]
|
||||
pdata[3::4] = _all_normals[:, 2] * scale_pos
|
||||
|
||||
ndata[0::2] = _all_normals[:, 0]
|
||||
ndata[1::2] = _all_normals[:, 1]
|
||||
|
||||
if has_tex:
|
||||
lay0 = export_mesh.uv_layers[t0map]
|
||||
lay0.data.foreach_get('uv', t0data)
|
||||
t0data[1::2] = 1.0 - t0data[1::2]
|
||||
if has_tex1:
|
||||
lay1 = export_mesh.uv_layers[t1map]
|
||||
lay1.data.foreach_get('uv', t1data)
|
||||
t1data[1::2] = 1.0 - t1data[1::2]
|
||||
if has_tang:
|
||||
loops.foreach_get('tangent', tangdata)
|
||||
if has_morph_target:
|
||||
lay2 = export_mesh.uv_layers[morph_uv_index]
|
||||
lay2.data.foreach_get('uv', morph_data)
|
||||
morph_data[1::2] = 1.0 - morph_data[1::2]
|
||||
if has_col:
|
||||
vcol0 = self.get_nth_vertex_colors(export_mesh, 0).data
|
||||
|
||||
loop: bpy.types.MeshLoop
|
||||
for i, loop in enumerate(loops):
|
||||
v = verts[loop.vertex_index]
|
||||
co = v.co
|
||||
normal = loop.normal
|
||||
tang = loop.tangent
|
||||
|
||||
i4 = i * 4
|
||||
i2 = i * 2
|
||||
pdata[i4 ] = co[0]
|
||||
pdata[i4 + 1] = co[1]
|
||||
pdata[i4 + 2] = co[2]
|
||||
pdata[i4 + 3] = normal[2] * scale_pos # Cancel scale
|
||||
ndata[i2 ] = normal[0]
|
||||
ndata[i2 + 1] = normal[1]
|
||||
if has_tex:
|
||||
uv = lay0.data[loop.index].uv
|
||||
t0data[i2 ] = uv[0]
|
||||
t0data[i2 + 1] = 1.0 - uv[1] # Reverse Y
|
||||
if has_tex1:
|
||||
uv = lay1.data[loop.index].uv
|
||||
t1data[i2 ] = uv[0]
|
||||
t1data[i2 + 1] = 1.0 - uv[1]
|
||||
if has_tang:
|
||||
i3 = i * 3
|
||||
tangdata[i3 ] = tang[0]
|
||||
tangdata[i3 + 1] = tang[1]
|
||||
tangdata[i3 + 2] = tang[2]
|
||||
if has_morph_target:
|
||||
uv = lay2.data[loop.index].uv
|
||||
morph_data[i2 ] = uv[0]
|
||||
morph_data[i2 + 1] = 1.0 - uv[1]
|
||||
if has_col:
|
||||
col = vcol0[loop.index].color
|
||||
i3 = i * 3
|
||||
cdata[i3 ] = col[0]
|
||||
cdata[i3 + 1] = col[1]
|
||||
cdata[i3 + 2] = col[2]
|
||||
_col_raw = np.empty(len(vcol0) * 4, dtype='<f4')
|
||||
vcol0.foreach_get('color', _col_raw)
|
||||
_col_raw = _col_raw.reshape(-1, 4)
|
||||
cdata[0::3] = _col_raw[:, 0]
|
||||
cdata[1::3] = _col_raw[:, 1]
|
||||
cdata[2::3] = _col_raw[:, 2]
|
||||
|
||||
mats = export_mesh.materials
|
||||
poly_map = []
|
||||
for i in range(max(len(mats), 1)):
|
||||
poly_map.append([])
|
||||
for poly in export_mesh.polygons:
|
||||
poly_map[poly.material_index].append(poly)
|
||||
|
||||
o['index_arrays'] = []
|
||||
|
||||
# map polygon indices to triangle loops
|
||||
tri_loops = {}
|
||||
for loop in export_mesh.loop_triangles:
|
||||
if loop.polygon_index not in tri_loops:
|
||||
tri_loops[loop.polygon_index] = []
|
||||
tri_loops[loop.polygon_index].append(loop)
|
||||
num_tris = len(export_mesh.loop_triangles)
|
||||
if num_tris > 0:
|
||||
_tri_loops = np.empty(num_tris * 3, dtype='<i4')
|
||||
export_mesh.loop_triangles.foreach_get('loops', _tri_loops)
|
||||
|
||||
for index, polys in enumerate(poly_map):
|
||||
tris = 0
|
||||
for poly in polys:
|
||||
tris += poly.loop_total - 2
|
||||
if tris == 0: # No face assigned
|
||||
continue
|
||||
prim = np.empty(tris * 3, dtype='<i4')
|
||||
v_map = np.empty(tris * 3, dtype='<i4')
|
||||
_tri_polys = np.empty(num_tris, dtype='<i4')
|
||||
export_mesh.loop_triangles.foreach_get('polygon_index', _tri_polys)
|
||||
|
||||
i = 0
|
||||
for poly in polys:
|
||||
for loop in tri_loops[poly.index]:
|
||||
prim[i ] = loops[loop.loops[0]].index
|
||||
prim[i + 1] = loops[loop.loops[1]].index
|
||||
prim[i + 2] = loops[loop.loops[2]].index
|
||||
i += 3
|
||||
num_polys = len(export_mesh.polygons)
|
||||
_poly_mat_idx = np.empty(num_polys, dtype='<i4')
|
||||
export_mesh.polygons.foreach_get('material_index', _poly_mat_idx)
|
||||
|
||||
j = 0
|
||||
for poly in polys:
|
||||
for loop in tri_loops[poly.index]:
|
||||
v_map[j ] = loops[loop.loops[0]].vertex_index
|
||||
v_map[j + 1] = loops[loop.loops[1]].vertex_index
|
||||
v_map[j + 2] = loops[loop.loops[2]].vertex_index
|
||||
j += 3
|
||||
_tri_mat_idx = _poly_mat_idx[_tri_polys]
|
||||
|
||||
ia = {'values': prim, 'material': 0, 'vertex_map': v_map}
|
||||
if len(mats) > 1:
|
||||
for i in range(len(mats)): # Multi-mat mesh
|
||||
if mats[i] == mats[index]: # Default material for empty slots
|
||||
ia['material'] = i
|
||||
break
|
||||
o['index_arrays'].append(ia)
|
||||
_tri_loops_2d = _tri_loops.reshape(-1, 3)
|
||||
|
||||
for mat_idx in range(max(len(mats), 1)):
|
||||
mask = (_tri_mat_idx == mat_idx)
|
||||
num_mat_tris = int(np.count_nonzero(mask))
|
||||
if num_mat_tris == 0:
|
||||
continue
|
||||
|
||||
mat_tri_loops = _tri_loops_2d[mask].flatten().astype('<i4')
|
||||
prim = mat_tri_loops.copy()
|
||||
v_map = _loop_vidx[mat_tri_loops].astype('<i4')
|
||||
|
||||
ia = {'values': prim, 'material': 0, 'vertex_map': v_map}
|
||||
if len(mats) > 1:
|
||||
for i in range(len(mats)): # Multi-mat mesh
|
||||
if mats[i] == mats[mat_idx]: # Default material for empty slots
|
||||
ia['material'] = i
|
||||
break
|
||||
o['index_arrays'].append(ia)
|
||||
|
||||
# Pack
|
||||
pdata *= invscale_pos
|
||||
@ -1821,7 +1873,6 @@ class LeenkxExporter:
|
||||
|
||||
def export_mesh(self, object_ref):
|
||||
"""Exports a single mesh object."""
|
||||
# profile_time = time.time()
|
||||
table = object_ref[1]["objectTable"]
|
||||
bobject = table[0]
|
||||
oid = lnx.utils.safestr(object_ref[1]["structName"])
|
||||
@ -1921,7 +1972,6 @@ class LeenkxExporter:
|
||||
out_mesh['dynamic_usage'] = bobject.data.lnx_dynamic_usage
|
||||
|
||||
self.write_mesh(bobject, fp, out_mesh)
|
||||
# print('Mesh exported in ' + str(time.time() - profile_time))
|
||||
|
||||
if hasattr(bobject, 'evaluated_get'):
|
||||
bobject_eval.to_mesh_clear()
|
||||
@ -2680,13 +2730,12 @@ class LeenkxExporter:
|
||||
}
|
||||
|
||||
# Set viewport camera projection
|
||||
if is_viewport_camera:
|
||||
proj, is_persp = self.get_viewport_projection_matrix()
|
||||
if proj is not None:
|
||||
if is_persp:
|
||||
self.extract_projection(out_camera, proj, with_planes=False)
|
||||
else:
|
||||
self.extract_ortho(out_camera, proj)
|
||||
proj, is_persp = self.get_viewport_projection_matrix()
|
||||
if proj is not None:
|
||||
if is_persp:
|
||||
self.extract_projection(out_camera, proj, with_planes=False)
|
||||
else:
|
||||
self.extract_ortho(out_camera, proj)
|
||||
self.output['camera_datas'].append(out_camera)
|
||||
|
||||
out_object = {
|
||||
|
||||
@ -43,6 +43,8 @@ _last_render_engine = None
|
||||
def on_depsgraph_update_post(self):
|
||||
if state.proc_build is not None:
|
||||
return
|
||||
if state.is_exporting:
|
||||
return
|
||||
|
||||
# Recache
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
|
||||
@ -1,19 +1,75 @@
|
||||
import atexit
|
||||
import gzip
|
||||
import http.server
|
||||
import io
|
||||
import os
|
||||
import socketserver
|
||||
import subprocess
|
||||
|
||||
haxe_server = None
|
||||
|
||||
_GZIP_MIN_SIZE = 1400
|
||||
|
||||
_COMPRESSIBLE = {
|
||||
'text/html', 'text/css', 'text/javascript', 'text/plain',
|
||||
'application/javascript', 'application/json', 'application/xml',
|
||||
'application/wasm', 'image/svg+xml',
|
||||
}
|
||||
|
||||
|
||||
def run_tcp(port: int, do_log: bool):
|
||||
class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
protocol_version = "HTTP/1.1"
|
||||
|
||||
def log_message(self, format, *args):
|
||||
if do_log:
|
||||
print(format % args)
|
||||
|
||||
def end_headers(self):
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Cache-Control', 'no-cache')
|
||||
super().end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
ae = self.headers.get('Accept-Encoding', '')
|
||||
if 'gzip' not in ae:
|
||||
return super().do_GET()
|
||||
|
||||
fpath = self.translate_path(self.path)
|
||||
if os.path.isdir(fpath):
|
||||
return super().do_GET()
|
||||
|
||||
try:
|
||||
ctype = self.guess_type(fpath)
|
||||
if ctype.split(';')[0].strip() not in _COMPRESSIBLE:
|
||||
return super().do_GET()
|
||||
fsize = os.path.getsize(fpath)
|
||||
if fsize < _GZIP_MIN_SIZE:
|
||||
return super().do_GET()
|
||||
|
||||
with open(fpath, 'rb') as f:
|
||||
raw = f.read()
|
||||
|
||||
buf = io.BytesIO()
|
||||
with gzip.GzipFile(fileobj=buf, mode='wb', compresslevel=1) as gz:
|
||||
gz.write(raw)
|
||||
compressed = buf.getvalue()
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', ctype)
|
||||
self.send_header('Content-Length', str(len(compressed)))
|
||||
self.send_header('Content-Encoding', 'gzip')
|
||||
self.end_headers()
|
||||
self.wfile.write(compressed)
|
||||
except (FileNotFoundError, PermissionError):
|
||||
self.send_error(404)
|
||||
|
||||
class ThreadedHTTPServer(socketserver.ThreadingTCPServer):
|
||||
allow_reuse_address = True
|
||||
daemon_threads = True
|
||||
|
||||
try:
|
||||
http_server = socketserver.TCPServer(("", port), HTTPRequestHandler)
|
||||
http_server = ThreadedHTTPServer(("", port), HTTPRequestHandler)
|
||||
http_server.serve_forever()
|
||||
except:
|
||||
print("Server already running")
|
||||
|
||||
@ -5,7 +5,7 @@ from fractions import Fraction
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
import bgl
|
||||
import bgl
|
||||
|
||||
def splitLogLuvAlphaAtlas(imageIn, outDir, quality):
|
||||
pass
|
||||
|
||||
@ -2,8 +2,7 @@ import bpy, blf, os, gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
import bgl
|
||||
|
||||
import bgl
|
||||
class ViewportDraw:
|
||||
|
||||
def __init__(self, context, text):
|
||||
|
||||
@ -9,6 +9,7 @@ import shutil
|
||||
import stat
|
||||
from string import Template
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
@ -181,6 +182,14 @@ def clear_external_scenes():
|
||||
appended_scenes = []
|
||||
|
||||
def export_data(fp, sdk_path):
|
||||
state.is_exporting = True
|
||||
try:
|
||||
export_data_impl(fp, sdk_path)
|
||||
finally:
|
||||
state.is_exporting = False
|
||||
|
||||
|
||||
def export_data_impl(fp, sdk_path):
|
||||
load_external_blends()
|
||||
|
||||
wrd = bpy.data.worlds['Lnx']
|
||||
@ -316,7 +325,11 @@ def export_data(fp, sdk_path):
|
||||
shaders_path = build_dir + '/compiled/Shaders'
|
||||
if not os.path.exists(shaders_path):
|
||||
os.makedirs(shaders_path)
|
||||
write_data.write_compiledglsl(defs + cdefs, make_variants=has_config)
|
||||
inc_changed = write_data.write_compiledglsl(defs + cdefs, make_variants=has_config)
|
||||
|
||||
if inc_changed:
|
||||
for g in glob.glob(shaders_path + '/*.glsl'):
|
||||
os.utime(g, None)
|
||||
|
||||
# Write referenced shader passes
|
||||
if not os.path.isfile(build_dir + '/compiled/Shaders/shader_datas.lnx') or state.last_world_defs != wrd.world_defs:
|
||||
@ -625,7 +638,7 @@ def _kill_all_viewport_processes():
|
||||
|
||||
for viewport_id in list(_viewport_processes.keys()):
|
||||
_kill_viewport_process(viewport_id)
|
||||
|
||||
|
||||
if lnx.utils.get_os() == 'win':
|
||||
try:
|
||||
result = subprocess.run(
|
||||
@ -643,20 +656,20 @@ def _kill_all_viewport_processes():
|
||||
def run_viewport_runtime(viewport_id, width=1920, height=1080):
|
||||
"""Launch a viewport which gets its own Krom process with unique shared memory."""
|
||||
global _viewport_processes
|
||||
|
||||
|
||||
if 'Lnx' not in bpy.data.worlds:
|
||||
log.warn('No Lnx world found - cannot start viewport server')
|
||||
return None, None
|
||||
|
||||
|
||||
_kill_viewport_process(viewport_id)
|
||||
|
||||
|
||||
shmem_name = _get_viewport_shmem_name(viewport_id)
|
||||
|
||||
wrd = bpy.data.worlds['Lnx']
|
||||
krom_location, krom_path = lnx.utils.krom_paths()
|
||||
path = lnx.utils.get_fp_build() + '/debug/krom'
|
||||
path_resources = path + '-resources'
|
||||
|
||||
|
||||
if not os.path.exists(path + '/krom.js'):
|
||||
log.warn(f'Krom build not found at {path}/krom.js - build project first')
|
||||
return None, None
|
||||
@ -669,7 +682,7 @@ def run_viewport_runtime(viewport_id, width=1920, height=1080):
|
||||
cmd.append(str(os.getpid()))
|
||||
if wrd.lnx_audio == 'Disabled':
|
||||
cmd.append('--nosound')
|
||||
|
||||
|
||||
cmd.append('--viewport-server')
|
||||
cmd.append('--shmem')
|
||||
cmd.append(shmem_name)
|
||||
@ -703,13 +716,13 @@ def build_viewport(viewport_id, width=1920, height=1080):
|
||||
if not wrd:
|
||||
log.warn('No Lnx world found - cannot build for viewport')
|
||||
return
|
||||
|
||||
|
||||
krom_js_path = lnx.utils.get_fp_build() + '/debug/krom/krom.js'
|
||||
if os.path.exists(krom_js_path) and not wrd.lnx_recompile:
|
||||
log.info(f'Using cached viewport build for {viewport_id}')
|
||||
run_viewport_runtime(viewport_id, width, height)
|
||||
return
|
||||
|
||||
|
||||
pending_entry = (viewport_id, width, height)
|
||||
if pending_entry not in _viewport_pending_launches:
|
||||
_viewport_pending_launches.append(pending_entry)
|
||||
@ -719,7 +732,7 @@ def build_viewport(viewport_id, width=1920, height=1080):
|
||||
# _viewport_proc_build checks viewport ,not state.proc_build (which is for play button)
|
||||
if _viewport_build_in_progress:
|
||||
if _viewport_proc_build is not None and _viewport_proc_build.poll() is None:
|
||||
# log.info(f'Build in progress: {viewport_id} - launching when ready')
|
||||
log.info(f'Build in progress, viewport {viewport_id} will launch when ready')
|
||||
return
|
||||
else:
|
||||
log.info(f'Resetting stale viewport build state')
|
||||
@ -738,20 +751,20 @@ def build_viewport(viewport_id, width=1920, height=1080):
|
||||
state.is_play = False # NOT play mode
|
||||
state.is_publish = False
|
||||
state.is_export = False
|
||||
|
||||
|
||||
log.clear(clear_warnings=True, clear_errors=True)
|
||||
|
||||
sdk_path = lnx.utils.get_sdk_path()
|
||||
fp = lnx.utils.get_fp()
|
||||
os.chdir(fp)
|
||||
|
||||
|
||||
sources_path = 'Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
|
||||
if not os.path.exists(sources_path):
|
||||
os.makedirs(sources_path)
|
||||
|
||||
|
||||
log.info('Exporting scene data...')
|
||||
export_data(fp, sdk_path)
|
||||
|
||||
|
||||
log.info('Starting Krom compilation for viewport...')
|
||||
compile_viewport(assets_only=(not wrd.lnx_recompile))
|
||||
|
||||
@ -811,7 +824,7 @@ def viewport_build_done():
|
||||
|
||||
if result == 0:
|
||||
bpy.data.worlds['Lnx'].lnx_recompile = False
|
||||
|
||||
|
||||
if _viewport_pending_launches:
|
||||
pending = _viewport_pending_launches.copy()
|
||||
_viewport_pending_launches.clear()
|
||||
@ -829,18 +842,20 @@ def play_viewport(viewport_id, width=1920, height=1080):
|
||||
global _viewport_build_in_progress, _viewport_pending_launches, _viewport_proc_build
|
||||
|
||||
if not viewport_id:
|
||||
log.error('No viewport_id: play_viewport requires an id')
|
||||
return
|
||||
|
||||
|
||||
if 'Lnx' not in bpy.data.worlds:
|
||||
log.error('No Lnx world found - cannot start viewport server')
|
||||
return
|
||||
|
||||
wrd = bpy.data.worlds['Lnx']
|
||||
|
||||
|
||||
krom_js_path = lnx.utils.get_fp_build() + '/debug/krom/krom.js'
|
||||
if os.path.exists(krom_js_path) and not wrd.lnx_recompile:
|
||||
run_viewport_runtime(viewport_id, width, height)
|
||||
return
|
||||
|
||||
|
||||
pending_entry = (viewport_id, width, height)
|
||||
if pending_entry not in _viewport_pending_launches:
|
||||
_viewport_pending_launches.append(pending_entry)
|
||||
@ -851,13 +866,13 @@ def play_viewport(viewport_id, width=1920, height=1080):
|
||||
return
|
||||
else:
|
||||
_viewport_build_in_progress = False # Reset stale state
|
||||
|
||||
|
||||
_viewport_build_in_progress = True
|
||||
|
||||
sdk_path = lnx.utils.get_sdk_path()
|
||||
fp = lnx.utils.get_fp()
|
||||
os.chdir(fp)
|
||||
|
||||
|
||||
sources_path = 'Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
|
||||
if not os.path.exists(sources_path):
|
||||
os.makedirs(sources_path)
|
||||
@ -867,7 +882,7 @@ def play_viewport(viewport_id, width=1920, height=1080):
|
||||
state.is_publish = False
|
||||
state.is_export = False
|
||||
export_data(fp, sdk_path)
|
||||
|
||||
|
||||
compile_viewport(assets_only=(not wrd.lnx_recompile))
|
||||
|
||||
|
||||
@ -982,16 +997,8 @@ def build_success():
|
||||
if wrd.lnx_runtime == 'Browser':
|
||||
os.chdir(lnx.utils.get_fp())
|
||||
prefs = lnx.utils.get_lnx_preferences()
|
||||
host = 'localhost'
|
||||
t = threading.Thread(name='localserver',
|
||||
target=lnx.lib.server.run_tcp,
|
||||
args=(prefs.html5_server_port,
|
||||
prefs.html5_server_log),
|
||||
daemon=True)
|
||||
t.start()
|
||||
build_dir = lnx.utils.build_dir()
|
||||
path = '{}/debug/html5/'.format(build_dir)
|
||||
url = 'http://{}:{}/{}'.format(host, prefs.html5_server_port, path)
|
||||
browser = webbrowser.get()
|
||||
browsername = None
|
||||
if hasattr(browser, "name"):
|
||||
@ -1004,6 +1011,14 @@ def build_success():
|
||||
if len(envcmd) == 0:
|
||||
log.warn(f"Your {envvar} environment variable is set to an empty string")
|
||||
else:
|
||||
host = 'localhost'
|
||||
t = threading.Thread(name='localserver',
|
||||
target=lnx.lib.server.run_tcp,
|
||||
args=(prefs.html5_server_port,
|
||||
prefs.html5_server_log),
|
||||
daemon=True)
|
||||
t.start()
|
||||
url = 'http://{}:{}/{}'.format(host, prefs.html5_server_port, path)
|
||||
tplstr = Template(envcmd).safe_substitute({
|
||||
'host': host,
|
||||
'port': prefs.html5_server_port,
|
||||
@ -1016,10 +1031,27 @@ def build_success():
|
||||
})
|
||||
cmd = re.split(' +', tplstr)
|
||||
if len(cmd) == 0:
|
||||
# try file:// protocol with a Chromium-based browser
|
||||
fast = browsername if browsername and any(b in browsername.lower() for b in ('chrome', 'chromium', 'edge', 'msedge')) else lnx.utils.find_browser()
|
||||
if fast is not None:
|
||||
file_url = 'file:///' + os.path.abspath(path + 'index.html').replace('\\', '/')
|
||||
subprocess.Popen([fast, '--allow-file-access-from-files', '--no-first-run',
|
||||
'--user-data-dir=' + os.path.join(tempfile.gettempdir(), 'leenkx_browser'),
|
||||
file_url])
|
||||
return
|
||||
host = 'localhost'
|
||||
t = threading.Thread(name='localserver',
|
||||
target=lnx.lib.server.run_tcp,
|
||||
args=(prefs.html5_server_port,
|
||||
prefs.html5_server_log),
|
||||
daemon=True)
|
||||
t.start()
|
||||
url = 'http://{}:{}/{}'.format(host, prefs.html5_server_port, path)
|
||||
if browsername in (None, '', 'default'):
|
||||
webbrowser.open(url)
|
||||
return
|
||||
cmd = [browsername, url]
|
||||
else:
|
||||
cmd = [browsername, url]
|
||||
elif wrd.lnx_runtime == 'Krom':
|
||||
if wrd.lnx_live_patch:
|
||||
live_patch.start()
|
||||
|
||||
@ -16,6 +16,7 @@ if not lnx.is_reload(__name__):
|
||||
proc_publish_build = None
|
||||
mod_scripts = []
|
||||
is_export = False
|
||||
is_exporting = False
|
||||
is_play = False
|
||||
is_publish = False
|
||||
is_viewport = False
|
||||
|
||||
@ -1007,7 +1007,7 @@ def make_texture(
|
||||
if max_size > 0 and image is not None:
|
||||
original_filepath = filepath
|
||||
filepath = resize_texture_if_needed(image, filepath, max_size)
|
||||
|
||||
|
||||
if filepath != original_filepath:
|
||||
resized_filename = lnx.utils.extract_filename(filepath)
|
||||
tex['file'] = lnx.utils.safestr(resized_filename)
|
||||
|
||||
@ -150,7 +150,7 @@ if bpy.app.version > (4, 1, 0):
|
||||
if state.parse_surface:
|
||||
c.write_normal(node.inputs[5])
|
||||
state.out_basecol = c.parse_vector_input(node.inputs[0])
|
||||
|
||||
|
||||
sss_input = node.inputs.get('Subsurface Weight') or node.inputs.get('Subsurface')
|
||||
if sss_input is not None:
|
||||
if sss_input.is_linked or sss_input.default_value > 0.0:
|
||||
|
||||
@ -310,7 +310,6 @@ def parse_tex_noise(node: bpy.types.ShaderNodeTexNoise, out_socket: bpy.types.No
|
||||
if bpy.app.version < (5, 0, 0):
|
||||
def parse_tex_pointdensity(node: bpy.types.ShaderNodeTexPointDensity, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
|
||||
# Pass through
|
||||
|
||||
# Color
|
||||
if out_socket == node.outputs[0]:
|
||||
return c.to_vec3([0.0, 0.0, 0.0])
|
||||
|
||||
@ -166,18 +166,14 @@ def material_needs_sss(material: Material) -> bool:
|
||||
if sss_node is not None and sss_node.outputs[0].is_linked:
|
||||
return True
|
||||
|
||||
for sss_node in lnx.node_utils.iter_nodes_by_type(material.node_tree, 'BSDF_PRINCIPLED'):
|
||||
if sss_node is not None and sss_node.outputs[0].is_linked and (sss_node.inputs[1].is_linked or sss_node.inputs[1].default_value != 0.0):
|
||||
return True
|
||||
|
||||
for sss_node in mat_utils.iter_nodes_leenkxpbr(material.node_tree):
|
||||
if sss_node is not None and sss_node.outputs[0].is_linked and (sss_node.inputs[8].is_linked or sss_node.inputs[8].default_value != 0.0):
|
||||
return True
|
||||
|
||||
for principled_node in lnx.node_utils.iter_nodes_by_type(material.node_tree, 'BSDF_PRINCIPLED'):
|
||||
if principled_node is not None and principled_node.outputs[0].is_linked:
|
||||
sss_input = principled_node.inputs.get('Subsurface Weight') or principled_node.inputs.get('Subsurface')
|
||||
if sss_input is not None and (sss_input.is_linked or sss_input.default_value > 0.0):
|
||||
return True
|
||||
|
||||
for sss_node in mat_utils.iter_nodes_leenkxpbr(material.node_tree):
|
||||
if sss_node is not None and sss_node.outputs[0].is_linked and (sss_node.inputs[8].is_linked or sss_node.inputs[8].default_value != 0.0):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@ -94,15 +94,16 @@ def make(context_id, rpasses, shadowmap=False, shadowmap_transparent=False):
|
||||
make_attrib.write_norpos(con_depth, vert)
|
||||
frag.write_attrib('vec3 n = normalize(wnormal);')
|
||||
cycles.parse(mat_state.nodes, con_depth, vert, frag, geom, tesc, tese, basecol_only=True, parse_opacity=True)
|
||||
elif parse_opacity:
|
||||
frag.write('float opacity;')
|
||||
frag.write('float ior;')
|
||||
else:
|
||||
if parse_opacity:
|
||||
frag.write('float opacity;')
|
||||
frag.write('float ior;')
|
||||
|
||||
if(con_depth).is_elem('morph'):
|
||||
make_morph_target.morph_pos(vert)
|
||||
if(con_depth).is_elem('morph'):
|
||||
make_morph_target.morph_pos(vert)
|
||||
|
||||
if con_depth.is_elem('bone'):
|
||||
make_skin.skin_pos(vert)
|
||||
if con_depth.is_elem('bone'):
|
||||
make_skin.skin_pos(vert)
|
||||
|
||||
if (not is_disp and parse_custom_particle):
|
||||
cycles.parse(mat_state.nodes, con_depth, vert, frag, geom, tesc, tese, parse_surface=False, parse_opacity=parse_opacity)
|
||||
|
||||
@ -133,6 +133,7 @@ def build(material: Material, mat_users: Dict[Material, List[Object]], mat_lnxus
|
||||
shader_data_path = lnx.utils.get_fp_build() + '/compiled/Shaders/' + shader_data_name + '.lnx'
|
||||
assets.add_shader_data(shader_data_path)
|
||||
|
||||
# Store SSS state in the return tuple so it's preserved per-material
|
||||
needs_sss_result = mat_state.needs_sss
|
||||
return rpasses, mat_state.data, shader_data_name, bind_constants, bind_textures, needs_sss_result
|
||||
|
||||
@ -172,10 +173,9 @@ def write_shader(rel_path: str, shader: Shader, ext: str, rpass: str, matname: s
|
||||
shader_path = lnx.utils.get_fp() + '/' + rel_path + '/' + shader_file
|
||||
assets.add_shader(shader_path)
|
||||
if not os.path.isfile(shader_path) or not keep_cache:
|
||||
with open(shader_path, 'w') as f:
|
||||
f.write(shader.get())
|
||||
written = lnx.utils.write_if_changed(shader_path, shader.get())
|
||||
|
||||
if shader.noprocessing:
|
||||
if written and shader.noprocessing:
|
||||
cwd = os.getcwd()
|
||||
os.chdir(lnx.utils.get_fp() + '/' + rel_path)
|
||||
hlslbin_path = lnx.utils.get_sdk_path() + '/lib/leenkx_tools/hlslbin/hlslbin.exe'
|
||||
|
||||
@ -154,14 +154,14 @@ def _deferred_set_rendered_mode():
|
||||
_rendered_mode_pending = False
|
||||
_rendered_mode_retries = 0
|
||||
return None
|
||||
|
||||
|
||||
space.shading.type = 'RENDERED'
|
||||
area.tag_redraw()
|
||||
|
||||
_rendered_mode_pending = False
|
||||
_rendered_mode_retries = 0
|
||||
return None
|
||||
|
||||
|
||||
_rendered_mode_retries += 1
|
||||
if _rendered_mode_retries < 10:
|
||||
print(f"Viewport VIEW_3D not found, retrying... ({_rendered_mode_retries})")
|
||||
@ -232,14 +232,14 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
return
|
||||
|
||||
current_mode = space.shading.type
|
||||
|
||||
|
||||
if self._last_shading_mode == 'SOLID' and current_mode == 'RENDERED':
|
||||
self._initialized = False
|
||||
self._rendered_mode_set = False
|
||||
self._krom_launch_attempted = False
|
||||
self._cleanup_shared_memory()
|
||||
self._pending_camera_sync = True
|
||||
|
||||
|
||||
if self._last_shading_mode == 'RENDERED' and current_mode != 'RENDERED':
|
||||
self._rendered_mode_set = False
|
||||
# keep krom running in solid mode for switching back ti rendered
|
||||
@ -260,9 +260,9 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
|
||||
view_matrix = rv3d.view_matrix
|
||||
proj_matrix = rv3d.window_matrix
|
||||
|
||||
|
||||
view_data = struct.pack('<16f', *[view_matrix[i][j] for j in range(4) for i in range(4)])
|
||||
|
||||
|
||||
proj_data = struct.pack('<16f', *[proj_matrix[i][j] for j in range(4) for i in range(4)])
|
||||
|
||||
if sys.platform == 'win32':
|
||||
@ -275,7 +275,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._shm.write(view_data)
|
||||
self._shm.seek(OFFSET_PROJ_MATRIX)
|
||||
self._shm.write(proj_data)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed camera sync: {e}")
|
||||
|
||||
@ -295,11 +295,11 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
if not self._viewport_id:
|
||||
import time
|
||||
self._viewport_id = hex(int(time.time() * 1000) % 0xFFFFFF)[-6:]
|
||||
|
||||
|
||||
region = context.region
|
||||
width = region.width // 2 if region else 960
|
||||
height = region.height if region else 540
|
||||
|
||||
|
||||
viewport_id = self._viewport_id
|
||||
engine_id = self._engine_id
|
||||
shmem_name = _get_viewport_shmem_name(viewport_id)
|
||||
@ -331,7 +331,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._ensure_initialized()
|
||||
if self._initialized:
|
||||
return True
|
||||
|
||||
|
||||
if self._viewport_id is None and context is not None:
|
||||
try:
|
||||
space = context.space_data
|
||||
@ -339,11 +339,11 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._viewport_id = hex(space.as_pointer())[-6:]
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if self._viewport_id is None:
|
||||
import time
|
||||
self._viewport_id = hex(int(time.time() * 1000) % 0xFFFFFF)[-6:]
|
||||
|
||||
|
||||
shmem_name = _get_viewport_shmem_name(self._viewport_id)
|
||||
self._shmem_name = shmem_name
|
||||
|
||||
@ -356,7 +356,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
|
||||
FILE_MAP_ALL_ACCESS = 0x000F001F
|
||||
|
||||
|
||||
kernel32.OpenFileMappingW.argtypes = [ctypes.wintypes.DWORD, ctypes.wintypes.BOOL, ctypes.wintypes.LPCWSTR]
|
||||
kernel32.OpenFileMappingW.restype = ctypes.wintypes.HANDLE
|
||||
|
||||
@ -371,7 +371,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
|
||||
kernel32.VirtualQuery.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t]
|
||||
kernel32.VirtualQuery.restype = ctypes.c_size_t
|
||||
|
||||
|
||||
handle = kernel32.OpenFileMappingW(
|
||||
FILE_MAP_ALL_ACCESS,
|
||||
False,
|
||||
@ -395,7 +395,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
kernel32.CloseHandle(handle)
|
||||
print(f"Failed to map shared memory: {error}")
|
||||
return False
|
||||
|
||||
|
||||
self._shm_handle = handle
|
||||
self._shm_ptr = ptr
|
||||
|
||||
@ -420,11 +420,10 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
actual_size = mbi.RegionSize
|
||||
|
||||
self._shm_size = actual_size
|
||||
|
||||
|
||||
self._shm_buffer = (ctypes.c_ubyte * actual_size).from_address(ptr)
|
||||
|
||||
self._initialized = True
|
||||
# print(f"Connected: {shmem_name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Failed to open shared memory: {e}")
|
||||
@ -451,7 +450,6 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._shm_file = open(shm_path, 'r+b')
|
||||
self._shm = mmap.mmap(self._shm_file.fileno(), max_size, mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE)
|
||||
self._initialized = True
|
||||
# print(f"Connected: {shm_path}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Failed to open /dev/shm: {e}")
|
||||
@ -548,7 +546,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
header = self._read_header()
|
||||
if not header:
|
||||
return None
|
||||
|
||||
|
||||
if header['frame_id'] == self._last_frame_id:
|
||||
return None
|
||||
|
||||
@ -570,7 +568,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
pixel_buffer = (ctypes.c_ubyte * pixel_size)()
|
||||
ctypes.memmove(pixel_buffer, self._shm_ptr + VIEWPORT_HEADER_SIZE, pixel_size)
|
||||
pixels = bytes(pixel_buffer)
|
||||
|
||||
|
||||
ready_bytes = struct.pack('<I', 0)
|
||||
ctypes.memmove(self._shm_ptr + OFFSET_READY_FLAG, ready_bytes, 4)
|
||||
else:
|
||||
@ -602,9 +600,9 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
|
||||
view_matrix = rv3d.view_matrix
|
||||
proj_matrix = rv3d.window_matrix
|
||||
|
||||
|
||||
view_data = struct.pack('<16f', *[view_matrix[i][j] for j in range(4) for i in range(4)])
|
||||
|
||||
|
||||
proj_data = struct.pack('<16f', *[proj_matrix[i][j] for j in range(4) for i in range(4)])
|
||||
|
||||
if sys.platform == 'win32':
|
||||
@ -640,7 +638,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
dirty = struct.unpack('<I', dirty_data)[0]
|
||||
if dirty == 0:
|
||||
return
|
||||
|
||||
|
||||
if sys.platform == 'win32':
|
||||
pos_data = ctypes.string_at(self._shm_ptr + OFFSET_KROM_CAMERA_POS, 12)
|
||||
rot_data = ctypes.string_at(self._shm_ptr + OFFSET_KROM_CAMERA_ROT, 16)
|
||||
@ -652,16 +650,16 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
|
||||
pos = struct.unpack('<3f', pos_data)
|
||||
rot = struct.unpack('<4f', rot_data) # quaternion (x, y, z, w)
|
||||
|
||||
|
||||
clear_data = struct.pack('<I', 0)
|
||||
if sys.platform == 'win32':
|
||||
ctypes.memmove(self._shm_ptr + OFFSET_KROM_CAMERA_DIRTY, clear_data, 4)
|
||||
else:
|
||||
self._shm.seek(OFFSET_KROM_CAMERA_DIRTY)
|
||||
self._shm.write(clear_data)
|
||||
|
||||
|
||||
from mathutils import Quaternion, Matrix, Vector
|
||||
|
||||
|
||||
rv3d = context.space_data.region_3d if context.space_data else None
|
||||
if not rv3d:
|
||||
return
|
||||
@ -670,12 +668,12 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
quat = Quaternion((rot[3], rot[0], rot[1], rot[2]))
|
||||
|
||||
forward = quat @ Vector((0, 0, -1))
|
||||
|
||||
|
||||
camera_pos = Vector(pos)
|
||||
view_dist = rv3d.view_distance if rv3d.view_distance > 0 else 10.0
|
||||
|
||||
|
||||
target = camera_pos + forward * view_dist
|
||||
|
||||
|
||||
rv3d.view_location = target
|
||||
rv3d.view_rotation = quat
|
||||
|
||||
@ -687,7 +685,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._ensure_initialized()
|
||||
if not self._initialized:
|
||||
return
|
||||
|
||||
|
||||
if hasattr(self, '_last_requested_width') and hasattr(self, '_last_requested_height'):
|
||||
if self._last_requested_width == width and self._last_requested_height == height:
|
||||
return
|
||||
@ -764,23 +762,23 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
|
||||
# calculate event offset in ring buffer
|
||||
event_offset = OFFSET_INPUT_EVENTS + (write_idx % MAX_INPUT_EVENTS) * INPUT_EVENT_SIZE
|
||||
|
||||
|
||||
event_data = struct.pack('<BBhhhI', event_type, button, x, y, delta, modifiers)
|
||||
ctypes.memmove(self._shm_ptr + event_offset, event_data, len(event_data))
|
||||
|
||||
|
||||
new_write_idx = struct.pack('<I', (write_idx + 1) % MAX_INPUT_EVENTS)
|
||||
ctypes.memmove(self._shm_ptr + OFFSET_INPUT_WRITE_IDX, new_write_idx, 4)
|
||||
return True
|
||||
else:
|
||||
self._shm.seek(OFFSET_INPUT_WRITE_IDX)
|
||||
write_idx = struct.unpack('<I', self._shm.read(4))[0]
|
||||
|
||||
|
||||
event_offset = OFFSET_INPUT_EVENTS + (write_idx % MAX_INPUT_EVENTS) * INPUT_EVENT_SIZE
|
||||
|
||||
|
||||
self._shm.seek(event_offset)
|
||||
event_data = struct.pack('<BBhhhI', event_type, button, x, y, delta, modifiers)
|
||||
self._shm.write(event_data)
|
||||
|
||||
|
||||
self._shm.seek(OFFSET_INPUT_WRITE_IDX)
|
||||
self._shm.write(struct.pack('<I', (write_idx + 1) % MAX_INPUT_EVENTS))
|
||||
return True
|
||||
@ -799,7 +797,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._ensure_initialized()
|
||||
try:
|
||||
num_pixels = width * height * 4
|
||||
|
||||
|
||||
if len(pixels) < num_pixels:
|
||||
return
|
||||
|
||||
@ -823,7 +821,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
else:
|
||||
float_pixels.append(_srgb_to_linear_lut_list[p])
|
||||
buffer = gpu.types.Buffer('FLOAT', len(float_pixels), float_pixels)
|
||||
|
||||
|
||||
self._texture = gpu.types.GPUTexture((width, height), format='RGBA32F', data=buffer)
|
||||
self._tex_width = width
|
||||
self._tex_height = height
|
||||
@ -856,11 +854,11 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
was_initialized = self._initialized
|
||||
if not self._init_shared_memory(context):
|
||||
if not self._krom_launch_attempted:
|
||||
# print(f"Shared memory not found, launching Krom for viewport {self._viewport_id}")
|
||||
print(f"Shared memory not found, launching Krom for viewport {self._viewport_id}")
|
||||
self._launch_krom_process(context)
|
||||
self._draw_placeholder(context)
|
||||
return
|
||||
|
||||
|
||||
if not was_initialized and self._initialized:
|
||||
region = context.region
|
||||
if region and region.width > 0 and region.height > 0:
|
||||
@ -879,13 +877,13 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
# TODO: input forwarding when viewport is active
|
||||
global _active_krom_engine, _active_krom_engines, _active_viewport_id, _input_operator_running
|
||||
_active_krom_engine = self # legacy single-engine reference
|
||||
|
||||
|
||||
if self._viewport_id:
|
||||
_active_krom_engines[self._viewport_id] = self
|
||||
_active_viewport_id = self._viewport_id
|
||||
|
||||
self._set_input_enabled(True)
|
||||
|
||||
|
||||
if not _input_operator_running:
|
||||
bpy.app.timers.register(_start_input_capture, first_interval=0.1)
|
||||
else:
|
||||
@ -895,13 +893,13 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
if self._input_check_counter > 60:
|
||||
self._input_check_counter = 0
|
||||
bpy.app.timers.register(_start_input_capture, first_interval=0.5)
|
||||
|
||||
|
||||
region = context.region
|
||||
if region and region.width > 0 and region.height > 0:
|
||||
self._request_resize(region.width, region.height)
|
||||
|
||||
|
||||
self._read_krom_camera(context)
|
||||
|
||||
|
||||
pixels = self._read_framebuffer()
|
||||
|
||||
if pixels:
|
||||
@ -910,16 +908,16 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
if not self._texture:
|
||||
self._draw_placeholder(context)
|
||||
return
|
||||
|
||||
|
||||
self._create_shader()
|
||||
|
||||
|
||||
if HAS_GPU_STATE:
|
||||
gpu.state.blend_set('NONE')
|
||||
else:
|
||||
bgl.glDisable(bgl.GL_BLEND)
|
||||
|
||||
|
||||
region = context.region
|
||||
|
||||
|
||||
vertices = (
|
||||
(0, 0),
|
||||
(region.width, 0),
|
||||
@ -1000,13 +998,13 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
pass
|
||||
_input_operator_running = False
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
if event.type in {'SCREEN_SET', 'SCREEN_CHANGED'}:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
|
||||
if not context.space_data or not context.area:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
|
||||
if context.space_data.type == 'VIEW_3D':
|
||||
if context.engine != 'KROM_VIEWPORT':
|
||||
try:
|
||||
@ -1016,18 +1014,18 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
pass
|
||||
_input_operator_running = False
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
current_engine = self._get_engine_for_area(context, event)
|
||||
if current_engine:
|
||||
self._engine = current_engine
|
||||
|
||||
|
||||
try:
|
||||
if not self._engine or not self._engine._initialized:
|
||||
return {'PASS_THROUGH'}
|
||||
except ReferenceError:
|
||||
_input_operator_running = False
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
region = context.region
|
||||
if not region:
|
||||
return {'PASS_THROUGH'}
|
||||
@ -1041,7 +1039,7 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
|
||||
if not mouse_in_region:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
|
||||
area = context.area
|
||||
if area:
|
||||
mouse_x_abs = event.mouse_x
|
||||
@ -1059,14 +1057,14 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
mouse_x = event.mouse_region_x
|
||||
# flip Y for Krom
|
||||
mouse_y = region.height - event.mouse_region_y
|
||||
|
||||
|
||||
try:
|
||||
if event.type == 'MOUSEMOVE':
|
||||
self._engine._send_input_event(INPUT_EVENT_MOUSE_MOVE, x=mouse_x, y=mouse_y)
|
||||
self._last_mouse_x = mouse_x
|
||||
self._last_mouse_y = mouse_y
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
if event.type == 'LEFTMOUSE':
|
||||
if event.value == 'PRESS':
|
||||
self._engine._send_input_event(INPUT_EVENT_MOUSE_DOWN, button=MOUSE_BUTTON_LEFT, x=mouse_x, y=mouse_y)
|
||||
@ -1087,14 +1085,14 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
elif event.value == 'RELEASE':
|
||||
self._engine._send_input_event(INPUT_EVENT_MOUSE_UP, button=MOUSE_BUTTON_MIDDLE, x=mouse_x, y=mouse_y)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
if event.type == 'WHEELUPMOUSE':
|
||||
self._engine._send_input_event(INPUT_EVENT_MOUSE_WHEEL, delta=1)
|
||||
return {'RUNNING_MODAL'}
|
||||
if event.type == 'WHEELDOWNMOUSE':
|
||||
self._engine._send_input_event(INPUT_EVENT_MOUSE_WHEEL, delta=-1)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
if event.value in {'PRESS', 'RELEASE'}:
|
||||
key_code = self._blender_to_krom_key(event.type)
|
||||
if key_code is not None:
|
||||
@ -1106,7 +1104,7 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
except ReferenceError:
|
||||
_input_operator_running = False
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def _blender_to_krom_key(self, blender_key):
|
||||
@ -1136,15 +1134,15 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
if event:
|
||||
mouse_x = event.mouse_x
|
||||
mouse_y = event.mouse_y
|
||||
|
||||
|
||||
for window in bpy.context.window_manager.windows:
|
||||
for area in window.screen.areas:
|
||||
if area.type != 'VIEW_3D':
|
||||
continue
|
||||
|
||||
|
||||
if (area.x <= mouse_x < area.x + area.width and
|
||||
area.y <= mouse_y < area.y + area.height):
|
||||
|
||||
|
||||
for space in area.spaces:
|
||||
if space.type == 'VIEW_3D':
|
||||
try:
|
||||
@ -1154,7 +1152,7 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
except:
|
||||
pass
|
||||
break
|
||||
|
||||
|
||||
if context.space_data:
|
||||
try:
|
||||
viewport_id = hex(context.space_data.as_pointer())[-6:]
|
||||
@ -1162,10 +1160,10 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
return _active_krom_engines[viewport_id]
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if _active_viewport_id and _active_viewport_id in _active_krom_engines:
|
||||
return _active_krom_engines[_active_viewport_id]
|
||||
|
||||
|
||||
return _active_krom_engine
|
||||
|
||||
def invoke(self, context, event):
|
||||
@ -1209,7 +1207,7 @@ def get_panels():
|
||||
'VIEWLAYER_PT_filter',
|
||||
'VIEWLAYER_PT_layer_passes',
|
||||
}
|
||||
|
||||
|
||||
compatible_engines = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}
|
||||
|
||||
panels = []
|
||||
@ -1256,7 +1254,7 @@ class KROM_OT_stop_viewport(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
import lnx.make as make
|
||||
|
||||
|
||||
viewport_id = self.viewport_id
|
||||
if not viewport_id and context.space_data:
|
||||
viewport_id = hex(context.space_data.as_pointer())[-6:]
|
||||
@ -1279,12 +1277,12 @@ def draw_viewport_stop_button(self, context):
|
||||
space = context.space_data
|
||||
if not space or not hasattr(space, 'shading'):
|
||||
return
|
||||
|
||||
|
||||
if space.shading.type != 'RENDERED':
|
||||
return
|
||||
|
||||
|
||||
viewport_id = hex(space.as_pointer())[-6:]
|
||||
|
||||
|
||||
global _active_krom_engines
|
||||
if viewport_id not in _active_krom_engines:
|
||||
layout = self.layout
|
||||
@ -1301,12 +1299,12 @@ def register():
|
||||
bpy.utils.register_class(KromViewportEngine)
|
||||
bpy.utils.register_class(KROM_OT_viewport_input)
|
||||
bpy.utils.register_class(KROM_OT_stop_viewport)
|
||||
|
||||
|
||||
bpy.types.VIEW3D_HT_header.append(draw_viewport_stop_button)
|
||||
|
||||
|
||||
for panel in get_panels():
|
||||
panel.COMPAT_ENGINES.add('KROM_VIEWPORT')
|
||||
|
||||
|
||||
if _on_depsgraph_update not in bpy.app.handlers.depsgraph_update_post:
|
||||
bpy.app.handlers.depsgraph_update_post.append(_on_depsgraph_update)
|
||||
atexit.register(_cleanup_krom_on_exit)
|
||||
@ -1319,21 +1317,21 @@ def unregister():
|
||||
_active_krom_engines.clear()
|
||||
_active_viewport_id = None
|
||||
_input_operator_running = False
|
||||
|
||||
|
||||
if _on_depsgraph_update in bpy.app.handlers.depsgraph_update_post:
|
||||
bpy.app.handlers.depsgraph_update_post.remove(_on_depsgraph_update)
|
||||
|
||||
|
||||
_cleanup_krom_on_exit()
|
||||
|
||||
|
||||
try:
|
||||
atexit.unregister(_cleanup_krom_on_exit)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
for panel in get_panels():
|
||||
if 'KROM_VIEWPORT' in panel.COMPAT_ENGINES:
|
||||
panel.COMPAT_ENGINES.remove('KROM_VIEWPORT')
|
||||
|
||||
|
||||
bpy.types.VIEW3D_HT_header.remove(draw_viewport_stop_button)
|
||||
|
||||
bpy.utils.unregister_class(KROM_OT_stop_viewport)
|
||||
|
||||
@ -52,6 +52,46 @@ class WorkingDir:
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
os.chdir(self.prev_cwd)
|
||||
|
||||
def write_if_changed(filepath, content):
|
||||
"""Write content to filepath only if it differs from existing file preserving mtime when unchanged"""
|
||||
if os.path.isfile(filepath):
|
||||
with open(filepath, 'r') as f:
|
||||
if f.read() == content:
|
||||
return False
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
return True
|
||||
|
||||
def find_browser():
|
||||
"""Find a browser that supports file:// with --allow-file-access-from-files - Chrome/Chromium/Edge"""
|
||||
osn = get_os()
|
||||
if osn == 'win':
|
||||
candidates = []
|
||||
for p in (os.environ.get('PROGRAMFILES', ''), os.environ.get('PROGRAMFILES(X86)', ''), os.environ.get('LOCALAPPDATA', '')):
|
||||
if p:
|
||||
candidates.append(os.path.join(p, 'Google', 'Chrome', 'Application', 'chrome.exe'))
|
||||
candidates.append(os.path.join(p, 'Google', 'Chrome', 'chrome.exe'))
|
||||
candidates.append(os.path.join(p, 'Google', 'Chromium', 'chrome.exe'))
|
||||
candidates.append(os.path.join(p, 'Chromium', 'Application', 'chrome.exe'))
|
||||
for p in (os.environ.get('PROGRAMFILES(X86)', ''), os.environ.get('PROGRAMFILES', '')):
|
||||
if p:
|
||||
candidates.append(os.path.join(p, 'Microsoft', 'Edge', 'Application', 'msedge.exe'))
|
||||
for c in candidates:
|
||||
if os.path.isfile(c):
|
||||
return c
|
||||
elif osn == 'mac':
|
||||
for app in ('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
||||
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'):
|
||||
if os.path.isfile(app):
|
||||
return app
|
||||
else:
|
||||
for name in ('google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', 'microsoft-edge'):
|
||||
found = shutil.which(name)
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
|
||||
def write_lnx(filepath, output):
|
||||
if filepath.endswith('.lz4'):
|
||||
with open(filepath, 'wb') as f:
|
||||
@ -279,7 +319,7 @@ def get_khamake_threads() -> int:
|
||||
addon_prefs = get_lnx_preferences()
|
||||
if hasattr(addon_prefs, 'khamake_threads_use_auto') and addon_prefs.khamake_threads_use_auto:
|
||||
return -1
|
||||
return 1 if not hasattr(addon_prefs, 'khamake_threads') else addon_prefs.khamake_threads
|
||||
return -1 if not hasattr(addon_prefs, 'khamake_threads') else addon_prefs.khamake_threads
|
||||
|
||||
def get_compilation_server():
|
||||
addon_prefs = get_lnx_preferences()
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import glob
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@ -623,7 +624,8 @@ def write_compiledglsl(defs, make_variants):
|
||||
rpdat = lnx.utils.get_rp()
|
||||
wrd = bpy.data.worlds['Lnx']
|
||||
shadowmap_size = lnx.utils.get_cascade_size(rpdat) if rpdat.rp_shadows else 0
|
||||
with open(lnx.utils.build_dir() + '/compiled/Shaders/compiled.inc', 'w') as f:
|
||||
inc_path = lnx.utils.build_dir() + '/compiled/Shaders/compiled.inc'
|
||||
with io.StringIO() as f:
|
||||
f.write(
|
||||
"""#ifndef _COMPILED_GLSL_
|
||||
#define _COMPILED_GLSL_
|
||||
@ -883,6 +885,7 @@ const float clusterNear = 3.0;
|
||||
|
||||
f.write("""#endif // _COMPILED_GLSL_
|
||||
""")
|
||||
return lnx.utils.write_if_changed(inc_path, f.getvalue())
|
||||
|
||||
def write_traithx(class_path):
|
||||
wrd = bpy.data.worlds['Lnx']
|
||||
|
||||
Reference in New Issue
Block a user