Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0bf5827
Support for 'tsnmapcalc' Flavor
Michaleczeq Jun 12, 2022
aeeecf8
Support for 'tsnmapcalc' Flavor
Michaleczeq Jun 13, 2022
5052f2e
Merge branch 'SCSSoftware:master' into master
Michaleczeq Oct 25, 2023
7b07802
amod support, aliasing nmap, aux fix
Michaleczeq Sep 22, 2024
4dec2d0
Support for custom company locators
Michaleczeq Sep 23, 2024
0e07623
Fix error when import .pip without "Flags"
Michaleczeq Sep 23, 2024
2c73770
Fixes with custom spawn point, bgl remove
Michaleczeq Oct 4, 2024
3075e45
reflection fix, shaders update
Michaleczeq Oct 10, 2024
dcef0c8
First "eut2.interior" shader implementation
Michaleczeq Oct 11, 2024
8ebe28e
amod update, interior tweaks
Michaleczeq Oct 30, 2024
0e17894
support for old tg0 & tg1 float2
Michaleczeq Nov 5, 2024
0295b75
fixes, new shader, new tool
Michaleczeq Nov 29, 2024
a2266f9
aux5 fix, "baked" shader
Michaleczeq Dec 3, 2024
e98dece
day/night replace, "building" fixes, visibility tools tweaks
Michaleczeq Dec 4, 2024
42d5865
new effects (.bin) + "baked" fixes
Michaleczeq Jan 4, 2025
f76e333
bgl replaces
Michaleczeq Jan 6, 2025
99d1153
Fast truckpaint fix
Michaleczeq Jan 6, 2025
4fcf4bb
update to blender 4.1.1
Michaleczeq Jan 8, 2025
272c2a4
Old node replace, code tweaks
Michaleczeq Jan 11, 2025
6a45a65
export auto_smooth fix
Michaleczeq Jan 13, 2025
cfc5d83
fix 2
Michaleczeq Jan 13, 2025
d86aec6
truckpaint fix
Michaleczeq Jan 25, 2025
a920028
Import and TOBJ fixes
Michaleczeq Jan 30, 2025
b14424c
Updated to 4.2
Michaleczeq Jan 31, 2025
4d4538f
fixing normals, flavors replace
Michaleczeq Feb 5, 2025
b5582ac
piko.alldir "support", sky shader tweaks
Michaleczeq Feb 18, 2025
223517b
dif.spec.weight fix + mat 'effect' aliasing
Michaleczeq Feb 21, 2025
8205102
aliasing update
Michaleczeq Feb 24, 2025
0c1fe80
vfcol fix, aliasing fixes
Michaleczeq Feb 27, 2025
4f251bb
Revert some changes in material.py and path.py files
Michaleczeq Mar 4, 2025
54de631
some fixes for 4.4
Michaleczeq Mar 25, 2025
a0d1900
shader_presets update
Michaleczeq Apr 5, 2025
a96a279
export fix
Michaleczeq Apr 5, 2025
1b7200a
Support for oinv and tsnmapuv2 + some tweaks
Michaleczeq May 5, 2025
64151e8
"sky" shader update
Michaleczeq Jun 5, 2025
52337eb
Billboard shader support + updated old
Michaleczeq Jun 12, 2025
23993d3
Support for dif.spec.add.env.over.dif.opac
Michaleczeq Jun 13, 2025
2aeb54b
New shader and flavor support
Michaleczeq Jun 15, 2025
07b6e62
Support for flavors, matterial ui change
Michaleczeq Jun 16, 2025
939be07
New shaders, node pos tweaks, tg1 fix
Michaleczeq Jun 16, 2025
ec592a8
New Shaders + flavor support
Michaleczeq Jun 18, 2025
a5df6bb
Support for new shader & tweaks
Michaleczeq Jun 20, 2025
c8ca8e5
New shaders + fixes
Michaleczeq Jul 1, 2025
50fba9e
Replaced deprecated nodes + fixes
Michaleczeq Jul 6, 2025
06058c5
fixes
Michaleczeq Jul 16, 2025
285b23a
Vulkan support + checkbox tweaks
Michaleczeq Jul 19, 2025
955eea7
Custom spawn point settings, UI tweaks & version detection fix
Michaleczeq Jul 22, 2025
49c5382
zone preview for owned trailer + service station
Michaleczeq Jul 22, 2025
c44456d
fixes + updated version
Michaleczeq Jul 24, 2025
229c423
Lamp System update + small tweaks
Michaleczeq Sep 27, 2025
95a6855
Update to Blender 5.0
Michaleczeq Nov 25, 2025
ae74365
New options for parts and some fixes
Michaleczeq Nov 30, 2025
760af2b
Fixes in nmaps and parts
Michaleczeq Dec 17, 2025
abecf6a
preview model fix + other fixes
Michaleczeq Dec 19, 2025
f362a53
Changes in SCS Part
Michaleczeq Dec 20, 2025
66cb3b1
Squashed commit of the following:
Michaleczeq Dec 22, 2025
cb43456
Animation import/export fix
Michaleczeq Dec 29, 2025
a890e1e
Merge branch 'development'
Michaleczeq Dec 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var/
*.egg-info/
.installed.cfg
*.egg
.vs/

# PyInstaller
# Usually these files are written by a python script from a template
Expand Down
11 changes: 8 additions & 3 deletions addon/io_scs_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
bl_info = {
"name": "SCS Tools",
"description": "Setup models, Import-Export SCS data format",
"author": "Simon Lusenc (50keda), Milos Zajic (4museman)",
"version": (2, 4, "aeadde03"),
"blender": (3, 2, 0),
"author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)",
"version": (2, 4, "aeadde03", 8, 1),
"blender": (5, 0, 0),
"location": "File > Import-Export",
"doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools",
"tracker_url": "http://forum.scssoft.com/viewforum.php?f=163",
Expand Down Expand Up @@ -222,6 +222,11 @@ class SCS_TOOLS_OT_Export(bpy.types.Operator, _SCSExportHelper, ExportHelper):
filename_ext = ".pim"
filter_glob: StringProperty(default=str("*" + filename_ext), options={'HIDDEN'})

def __init__(self, *args, **kwargs):
bpy.types.Operator.__init__(self, *args, **kwargs)
_SCSExportHelper.__init__(self, *args, **kwargs)
ExportHelper.__init__(self)

def execute(self, context):
# convert filepath to None if empty, so export will ignore given menu file path and try to export to other none menu set paths
if self.filepath == "":
Expand Down
56 changes: 55 additions & 1 deletion addon/io_scs_tools/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ class VehicleSides(Enum):
FrontRight = 1
RearLeft = 2
RearRight = 3
Middle = 4
MiddleLeft = 4
MiddleRight = 5

class VehicleLampTypes(Enum):
"""Defined lamp types for vehicles.
Expand Down Expand Up @@ -227,6 +228,15 @@ class TrafficLightTypes(Enum):
Yellow = 1
Green = 2

class InteriorWindowTools:
"""Constants related to interior window tools
"""

class GlassReflection(Enum):
"""Defined states of glass reflection.
"""
Enable = 0
Disable = 1

class VertexColorTools:
"""Constants related to vertex color tools
Expand Down Expand Up @@ -267,6 +277,7 @@ class Mesh:

default_uv = "UV"
default_vcol = "Col"
default_vfactor = "Factor"


class PrefabLocators:
Expand Down Expand Up @@ -444,6 +455,49 @@ class PIF:
TYPE_END = 0x00020000
TYPE_CROSS_SHARP = 0x00040000

class PSPCF:
"""Constants represetning spawn point custom flags.
"""
# Depot Types
DEPOT_TYPE_MASK = 0x00000004
DEPOT_TYPE_UNLOAD = 0x00000000
DEPOT_TYPE_LOAD = 0x00000004

# Difficulties
DIFFICULTY_MASK = 0x00000003
DIFFICULTY_NONE = 0x00000000
DIFFICULTY_EASY = 0x00000001
DIFFICULTY_MEDIUM = 0x00000002
DIFFICULTY_HARD = 0x00000003

# Lenght
LENGHT_MASK = 0x000000F0
LENGHT_14 = 0x00000000
LENGHT_15 = 0x00000010
LENGHT_16 = 0x00000020
LENGHT_17 = 0x00000030
LENGHT_18 = 0x00000040
LENGHT_19 = 0x00000050
LENGHT_20 = 0x00000060
LENGHT_21 = 0x00000070
LENGHT_22 = 0x00000080
LENGHT_23 = 0x00000090
LENGHT_24 = 0x000000A0
LENGHT_25 = 0x000000B0
LENGHT_26 = 0x000000C0
LENGHT_27 = 0x000000D0
LENGHT_28 = 0x000000E0
UNLIMITED = 0x000000F0

# Rule
TRAILER_MASK = 0x000F0000
TRAILER_ANY = 0x00000000
TRAILER_BOX = 0x00010000
TRAILER_TANK = 0x00020000
TRAILER_DUMP_BULK = 0x00030000
TRAILER_PLATFORM_LOG_CONT = 0x00040000
TRAILER_LIVESTOCK = 0x00050000
TRAILER_LOG = 0x00060000

class Bones:
init_scale_key = "scs_init_scale"
Expand Down
15 changes: 11 additions & 4 deletions addon/io_scs_tools/exp/pia.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import os

import bpy
from bpy_extras import anim_utils
from collections import OrderedDict
from mathutils import Vector, Matrix, Euler, Quaternion
from io_scs_tools.utils import convert as _convert_utils
Expand All @@ -31,7 +32,7 @@
from io_scs_tools.internals.containers import pix as _pix_container


def _get_custom_channels(scs_animation, action):
def _get_custom_channels(armature, scs_animation, action):
custom_channels = []
frame_start = scs_animation.anim_start
frame_end = scs_animation.anim_end
Expand All @@ -40,8 +41,11 @@ def _get_custom_channels(scs_animation, action):

loc_curves = {} # dictionary for storing "location" curves of action

action_slot = armature.animation_data.action_slot
channelbag = anim_utils.action_get_channelbag_for_slot(action, action_slot)

# get curves which are related to moving of armature object
for fcurve in action.fcurves:
for fcurve in channelbag.fcurves:
if fcurve.data_path == 'location':
loc_curves[fcurve.array_index] = fcurve

Expand Down Expand Up @@ -103,7 +107,10 @@ def _get_bone_channels(scs_root_obj, armature, scs_animation, action, export_sca
curves_per_bone = OrderedDict() # store all the curves we are interested in per bone names

for bone in armature.data.bones:
for fcurve in action.fcurves:
action_slot = armature.animation_data.action_slot
channelbag = anim_utils.action_get_channelbag_for_slot(action, action_slot)

for fcurve in channelbag.fcurves:

# check if curve belongs to bone
if '["' + bone.name + '"]' in fcurve.data_path:
Expand Down Expand Up @@ -331,7 +338,7 @@ def export(scs_root_obj, armature, scs_animation, dirpath, name_suffix, skeleton
total_time = scs_animation.length
action = bpy.data.actions[scs_animation.action]
bone_channels = _get_bone_channels(scs_root_obj, armature, scs_animation, action, scs_globals.export_scale)
custom_channels = _get_custom_channels(scs_animation, action)
custom_channels = _get_custom_channels(armature, scs_animation, action)

# DATA CREATION
header_section = _fill_header_section(scs_animation.name, scs_globals.export_write_signature)
Expand Down
39 changes: 33 additions & 6 deletions addon/io_scs_tools/exp/pim/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat
missing_uv_layers = {} # stores missing uvs specified by materials of this object
missing_vcolor = False # indicates if object is missing vertex color layer
missing_vcolor_a = False # indicates if object is missing vertex color alpha layer
missing_vfcolor = False # indicates if object is missing vertex color factor layer
missing_skinned_verts = set() # indicates if object is having only partial skin, which is not allowed in our models
has_unnormalized_skin = False # indicates if object has vertices which bones weight sum is smaller then one
last_tangents_uv_layer = None # stores uv layer for which tangents were calculated, so tangents won't be calculated all over again
Expand Down Expand Up @@ -425,23 +426,46 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat

vcol += (alpha * 2,)

# 5. tangent -> loop.tangent; loop.bitangent_sign -> calc_tangents() has to be called before
# 5. vfcol -> vfcol_lay = mesh.color_attributes[2].data; vfcol_lay[loop_i].color
vfcol = None
vfactor_shader = ("piko.alldir") in material.scs_props.mat_effect_name
if vfactor_shader:
if _MESH_consts.default_vfactor not in mesh.color_attributes: # get FACTOR component
vfcol = (1.0,) * 4
missing_vfcolor = True
else:
vfcolors = mesh.color_attributes[_MESH_consts.default_vfactor]

if vfcolors.domain == 'POINT':
color = Color(vfcolors.data[vert_i].color[:3])
alpha = vfcolors.data[vert_i].color[3]
elif vfcolors.domain == 'CORNER':
color = Color(vfcolors.data[loop_i].color[:3])
alpha = vfcolors.data[loop_i].color[3]
else:
raise TypeError("Invalid vertex color domain type!")

color = color.from_scene_linear_to_srgb()

vfcol = tuple(round(value * 255) / 1.0 for value in (*color, alpha))

# 6. tangent -> loop.tangent; loop.bitangent_sign -> calc_tangents() has to be called before
if pim_materials[pim_mat_name].get_nmap_uv_name(): # calculate tangents only if needed
tangent = (tangent_transf_mat @ loop.tangent).normalized()
tangent = (tangent[0], tangent[1], tangent[2], loop.bitangent_sign)
else:
tangent = None

# 6. There we go, vertex data collected! Now create internal vertex index, for triangle and skin stream construction
# 7. There we go, vertex data collected! Now create internal vertex index, for triangle and skin stream construction
# Construct unique vertex index - donated by mesh and vertex index, as we may export more mesh objects into same piece,
# thus only vertex index wouldn't be unique representation.
unique_vert_i = "%i|%i" % (mesh_i, vert_i)
piece_vert_index = mesh_piece.add_vertex(unique_vert_i, position, normal, uvs, uvs_aliases, vcol, tangent)
piece_vert_index = mesh_piece.add_vertex(unique_vert_i, position, normal, uvs, uvs_aliases, vcol, vfcol, tangent)

# 7. Add vertex to triangle creation list
# 8. Add vertex to triangle creation list
triangle_pvert_indices.append(piece_vert_index)

# 8. Get skinning data for vertex and save it to skin stream
# 9. Get skinning data for vertex and save it to skin stream
if is_skin_used:
bone_weights = {}
bone_weights_sum = 0
Expand All @@ -463,7 +487,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat
if bone_weights_sum < 1:
has_unnormalized_skin = True

# 9. Terrain Points: save vertex to terrain points storage, if present in correct vertex group
# 10. Terrain Points: save vertex to terrain points storage, if present in correct vertex group
if has_terrain_points:
for group in mesh.vertices[vert_i].groups:

Expand Down Expand Up @@ -526,6 +550,9 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat
if missing_vcolor_a:
lprint("W Object %r is missing vertex color alpha layer with name %r! Default alpha will be exported (0.5)",
(mesh_obj.name, _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix))
if missing_vfcolor:
lprint("W Object %r is missing vertex factor color layer with name %r! Default factor will be exported (0.0)",
(mesh_obj.name, _MESH_consts.default_vfactor))
if len(missing_skinned_verts) > 0:
lprint("E Object %r from SCS Root %r has %s vertices which are not skinned to any bone, expect errors during conversion!",
(mesh_obj.name, root_object.name, len(missing_skinned_verts)))
Expand Down
20 changes: 20 additions & 0 deletions addon/io_scs_tools/exp/pim/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,26 @@ def __init__(self, index, alias, effect, blend_mat):

self.__used_textures_without_uv_count += 1

if blend_mat and "scs_shader_attributes" in blend_mat and "mappings" in blend_mat["scs_shader_attributes"]:
for tex_entry in blend_mat["scs_shader_attributes"]["mappings"].values():
self.__used_textures_count += 1
if "Tag" in tex_entry:
tex_type = tex_entry["Tag"]
mappings = getattr(blend_mat.scs_props, "shader_mapping_" + tex_type, [])

for uv_map_i, uv_map in enumerate(mappings):
if uv_map.value != "": # filter out none specified mappings

tex_coord_map[uv_map.tex_coord] = uv_map.value

elif uv_map.tex_coord != -1: # if tex coord is -1 texture doesn't use uvs
lprint("W Mapping type '%s' on material '%s' is missing UV mapping value, expect problems in game!",
(tex_type, blend_mat.name))

else: # if texture doesn't have mappings it means uv is not required for it

self.__used_textures_without_uv_count += 1

# create uv layer map with used tex_coord on it (this tex_coords now represents aliases for given uv layers)
# It also uses ordered dictionary because order of keys now defines actually physical order for uvs in PIM file
self.__uvs_map_by_name = OrderedDict()
Expand Down
26 changes: 22 additions & 4 deletions addon/io_scs_tools/exp/pim/piece.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ def get_global_triangle_count():
return Piece.__global_triangle_count

@staticmethod
def __calc_vertex_hash(index, normal, uvs, rgba, tangent):
"""Calculates vertex hash from original vertex index, uvs components and vertex color.
def __calc_vertex_hash(index, normal, uvs, rgba, factor, tangent):
"""Calculates vertex hash from original vertex index, uvs components, vertex color and vertex factor color.
:param index: original index from Blender mesh
:type index: str
:param normal: normalized vector representation of the normal
Expand All @@ -69,6 +69,8 @@ def __calc_vertex_hash(index, normal, uvs, rgba, tangent):
:type uvs: list of (tuple | mathutils.Vector)
:param rgba: rgba representation of vertex color in SCS values
:type rgba: tuple | mathutils.Color
:param factor: rgba representation of vertex factor color in SCS values
:type factor: tuple | mathutils.Color
:param tangent: vertex tangent in SCS coordinates or none, if piece doesn't have tangents
:type tangent: tuple | None
:return: calculated vertex hash
Expand Down Expand Up @@ -99,6 +101,12 @@ def __calc_vertex_hash(index, normal, uvs, rgba, tangent):
int(rgba[2] * fprec),
int(rgba[3] * fprec))

if factor:
vertex_hash += (int(factor[0] * fprec),
int(factor[1] * fprec),
int(factor[2] * fprec),
int(factor[3] * fprec))

for uv in uvs:
vertex_hash += (int(uv[0] * fprec),
int(uv[1] * fprec))
Expand Down Expand Up @@ -155,7 +163,7 @@ def add_triangle(self, triangle):

return True

def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, tangent):
def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, factor, tangent):
"""Adds new vertex to position and normal streams
:param vert_index: original vertex index from Blender mesh
:type vert_index: str
Expand All @@ -169,13 +177,15 @@ def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, tange
:type uvs_aliases: list[list[str]]
:param rgba: rgba representation of vertex color in SCS values
:type rgba: tuple | mathutils.Color
:param factor: rgba representation of vertex factor color in SCS values
:type factor: tuple | mathutils.Color
:param tangent: tuple representation of vertex tangent in SCS values or None if piece doesn't have tangents
:type tangent: tuple | None
:return: vertex index inside piece streams ( use it for adding triangles )
:rtype: int
"""

vertex_hash = self.__calc_vertex_hash(vert_index, normal, uvs, rgba, tangent)
vertex_hash = self.__calc_vertex_hash(vert_index, normal, uvs, rgba, factor, tangent)

# save vertex if the vertex with the same properties doesn't exists yet in streams
if vertex_hash not in self.__vertices_hash:
Expand Down Expand Up @@ -213,6 +223,14 @@ def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, tange
stream = self.__streams[Stream.Types.RGBA]
stream.add_entry(rgba)

if factor:
# create factor stream on demand
if Stream.Types.FACTOR not in self.__streams:
self.__streams[Stream.Types.FACTOR] = Stream(Stream.Types.FACTOR, -1)

stream = self.__streams[Stream.Types.FACTOR]
stream.add_entry(factor)

vert_index_internal = stream.get_size() - 1 # streams has to be alligned so I can take last one for the index
self.__vertices_hash[vertex_hash] = vert_index_internal

Expand Down
5 changes: 5 additions & 0 deletions addon/io_scs_tools/exp/pim/piece_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Types:
TANGENT = "_TANGENT"
RGB = "_RGB"
RGBA = "_RGBA"
FACTOR = "_FACTOR" # NOTE: used only in piko.alldir flavor
UV = "_UV" # NOTE: there can be up to 9 uv streams
TUV = "_TUV" # NOTE: there can be up to 9 tuv streams

Expand Down Expand Up @@ -66,6 +67,8 @@ def __init__(self, stream_type, index):
self.__format = "FLOAT3"
elif stream_type == Stream.Types.RGBA:
self.__format = "FLOAT4"
elif stream_type == Stream.Types.FACTOR:
self.__format = "FLOAT4"
elif stream_type == Stream.Types.UV:
self.__tag_index = index
self.__format = "FLOAT2"
Expand Down Expand Up @@ -93,6 +96,8 @@ def add_entry(self, value):
# return False
# if self.__tag == Stream.Types.RGBA and len(value) != 4:
# return False
# if self.__tag == Stream.Types.FACTOR and len(value) != 4:
# return False
# if self.__tag == Stream.Types.UV and len(value) != 2:
# return False

Expand Down
Loading