Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
BreakStringLiterals: true
ColumnLimit: 240
ColumnLimit: 180
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
PackConstructorInitializers: CurrentLine
Expand Down
25 changes: 14 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,34 @@ set(savitar_SRCS
src/MeshData.cpp
src/Vertex.cpp
src/Face.cpp
)
src/UVCoordinate.cpp
src/UVCoordinatesIndices.cpp
src/TextureData.cpp
)

if(BUILD_SHARED_LIBS)
if (BUILD_SHARED_LIBS)
add_library(Savitar SHARED ${savitar_SRCS})
if(WIN32)
if (WIN32)
set_target_properties(Savitar PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
else()
endif ()
else ()
add_library(Savitar STATIC ${savitar_SRCS})
endif()
endif ()

set_project_warnings(Savitar)

target_link_libraries(Savitar PUBLIC pugixml::pugixml)

target_include_directories(Savitar
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
)
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
)

option(ENABLE_TESTING "Enable unit-testing" OFF)
if (ENABLE_TESTING)
enable_testing()
add_subdirectory(tests)
endif()
endif ()
17 changes: 15 additions & 2 deletions include/Savitar/Face.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,38 @@
#ifndef FACE_H
#define FACE_H

#include "Savitar/UVCoordinatesIndices.h"

#include <optional>

namespace Savitar
{
class Face
{
public:
/**
* A face uses the index of 3 vertices to describe a triangle
* A face uses the index of 3 vertices to describe a triangle, and possibly indices of UV coordinates
*/
Face(int v1, int v2, int v3);
Face(int v1, int v2, int v3, const std::optional<UVCoordinatesIndices>& uv_coordinates = std::nullopt);
~Face() = default;

[[nodiscard]] int getV1() const;
void setV1(const int v1);

[[nodiscard]] int getV2() const;
void setV2(const int v2);

[[nodiscard]] int getV3() const;
void setV3(const int v3);

[[nodiscard]] const std::optional<UVCoordinatesIndices>& getUVCoordinates() const;
void setUVCoordinates(const std::optional<UVCoordinatesIndices>& uv_coordinates);

private:
int vertex_1_index_;
int vertex_2_index_;
int vertex_3_index_;
std::optional<UVCoordinatesIndices> uv_coordinates_;
};
} // namespace Savitar

Expand Down
37 changes: 35 additions & 2 deletions include/Savitar/MeshData.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#ifndef MESHDATA_H
#define MESHDATA_H

#include <cstdint>
#include <string>
#include <vector>

#include "Savitar/Face.h"
Expand All @@ -16,6 +14,9 @@

namespace Savitar
{

class Scene;

class MeshData
{
public:
Expand Down Expand Up @@ -56,6 +57,28 @@ class MeshData
*/
[[nodiscard]] bytearray getFlatVerticesAsBytes();

/**
* Retrieves the list of actual UV coordinates for each vertex of each face, as a raw byte array
* @param scene The scene which actually holds the UV coordinates set (can be shared amongst meshes)
* @return The coordinates array, or an empty array if the mesh doesn't have texture data
*/
[[nodiscard]] bytearray getUVCoordinatesPerVertexAsBytes(const Scene* scene) const;

/**
* Sets the UV coordinates from a raw byte array, containing the actual coordinates for each vertex of each face
* @param data The raw coordinates data
* @param texture_path The path of the texture file te be stored besides the model description
* @param scene The scene which actually holds the UV coordinates set (can be shared amongst meshes)
*/
void setUVCoordinatesPerVertexAsBytes(const bytearray& data, const std::string& texture_path, Scene* scene);

/**
* Get the path of the texture used by this mesh
* @param scene The scene which actually contains the list of stored textures (can be shared amongst models)
* @return The texture path, or an empty string if the mesh doesn't have texture data
*/
[[nodiscard]] std::string getTexturePath(const Scene* scene) const;

/**
* Set the vertices of the meshdata by bytearray (as set from python)
*
Expand All @@ -77,9 +100,19 @@ class MeshData
*/
void clear();

private:
/**
* @tparam T The type of data to be exported
* @param data The byte array containing the exported raw data
* @param value The value to be exported as raw data in the byte array
*/
template<typename T>
static void exportToByteArray(bytearray& data, const T value);

private:
std::vector<Vertex> vertices_;
std::vector<Face> faces_;
int uv_group_id_{ -1 };
};
} // namespace Savitar

Expand Down
29 changes: 28 additions & 1 deletion include/Savitar/Scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#define SCENE_H

#include "Savitar/SceneNode.h"
#include "TextureData.h"

#include <map> // For std::map
#include <string> // For std::string
Expand All @@ -29,7 +30,7 @@ class Scene
*/
[[nodiscard]] std::vector<SceneNode*> getSceneNodes();

[[nodiscard]] std::vector<SceneNode*> getAllSceneNodes();
[[nodiscard]] std::vector<SceneNode*> getAllSceneNodes() const;

/**
* Add a scene node to the scene.
Expand All @@ -42,6 +43,11 @@ class Scene
*/
void fillByXMLNode(pugi::xml_node xml_node);

/**
* Serialise the scene to model_node
*/
void toXmlNode(pugi::xml_node& model_node);

/**
* Store a metadata entry as metadata.
* @param key The key of the metadata.
Expand All @@ -68,6 +74,12 @@ class Scene
*/
[[nodiscard]] const std::map<std::string, MetadataEntry>& getMetadata() const;

/**
* Find the next available resource ID amongst actually stored texture, UV coordinates and scene nodes. This should be called before
* adding any of these resources, so that IDs are unique in the end.
*/
[[nodiscard]] int getNextAvailableResourceId() const;

/**
* Get the unit (milimeter, inch, etc) of the scene.
* This is in milimeter by default.
Expand All @@ -76,10 +88,25 @@ class Scene

void setUnit(std::string unit);

[[nodiscard]] std::string getTexturePathFromGroupId(const int uv_group_id) const;

[[nodiscard]] const TextureData::UVCoordinatesGroup* getUVCoordinatesGroup(const int uv_group_id) const;

void addTexturePath(const std::string& texture_path, const int texture_id);

/**
* Stores a UV coordinates group from raw data
* @param data The raw data to be stored
* @param texture_id The ID of the associated texture
* @param group_id The ID of the newly created coordinates group
*/
void setUVCoordinatesGroupFromBytes(const bytearray& data, const int texture_id, const int group_id);

private:
std::vector<SceneNode*> scene_nodes_;
std::map<std::string, MetadataEntry> metadata_;
std::string unit_{ "millimeter" };
TextureData texture_data_;

/**
* Used to recursively create SceneNode objects based on xml nodes.
Expand Down
6 changes: 3 additions & 3 deletions include/Savitar/SceneNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ class SceneNode
/**
* Get the (unique) identifier of the node.
*/
[[nodiscard]] std::string getId();
[[nodiscard]] int getId() const;

void setId(std::string id);
void setId(int id);
Comment on lines +47 to +49
Copy link
Contributor

@casperlamboo casperlamboo Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason this change is needed? We just added savitar to neoprep and since javascript doens't have true integer numeric types. This change would be a bit non-idiomatic to implement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes ! Since we now have to handle:

  • An ID for the object
  • An ID for the coordinates group
  • An ID for the texture path
    And all of them should be unique, libSavitar calculates proper IDs for everyone, so I need to store them as integers so that I can find a proper value for the next time I need an ID. If this really is an issue, I can find an other way, but I would prefer not to.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strings could also solve the that specific issue right? If we use integers, can we then somehow make sure the ID never excedes $2^{53} – 1$ as this is the max safe integer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDs are using incremented integers starting from 0, so we should be safe 😄


/**
* Get the (non-unique) display name of the node.
Expand Down Expand Up @@ -81,7 +81,7 @@ class SceneNode
std::vector<SceneNode*> children_;
MeshData mesh_data_;
std::map<std::string, MetadataEntry> settings_;
std::string id_;
int id_{ -1 };
std::string name_;
std::string type_{ "model" };
std::string component_path_;
Expand Down
64 changes: 64 additions & 0 deletions include/Savitar/TextureData.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) 2025 Ultimaker B.V.
// libSavitar is released under the terms of the LGPLv3 or higher.

#ifndef TEXTUREDATA_H
#define TEXTUREDATA_H

#include "Types.h"
#include "UVCoordinate.h"

#include <map>
#include <pugixml.hpp>
#include <string>
#include <vector>

namespace Savitar
{
/**
* The TextureData stores UV coordinates groups and textures paths, that will end-up as resources in the global model description
*/
class TextureData
{
public:
struct UVCoordinatesGroup
{
int texture_id; // The ID of the associated texture
std::vector<UVCoordinate> coordinates; // The actual UV coordinates of the group
};

TextureData();
virtual ~TextureData() = default;

void fillByXMLNode(pugi::xml_node xml_node);

void toXmlNode(pugi::xml_node& resources_node);

[[nodiscard]] std::string getTexturePath(const int texture_id) const;

[[nodiscard]] const UVCoordinatesGroup* getUVCoordinatesGroup(const int id) const;

/**
* Loads a UV coordinates group from raw data
* @param data The actual UV coordinates as an array of floats
* @param texture_id The ID of the associated texture
* @param group_id The ID of the newly created group
*/
void setUVCoordinatesGroupFromBytes(const bytearray& data, const int texture_id, const int group_id);

void addTexturePath(const std::string& texture_path, const int id);

[[nodiscard]] std::string getTexturePathFromGroupId(const int uv_group_id) const;

/**
* Find the next available resource ID amongst actually stored texture and UV coordinates. This should be called before
* adding any of these resources, so that IDs are unique in the end.
*/
[[nodiscard]] int getNextAvailableResourceId() const;

private:
std::map<int, std::string> textures_paths_;
std::map<int, UVCoordinatesGroup> uv_coordinates_;
};
} // namespace Savitar

#endif
27 changes: 27 additions & 0 deletions include/Savitar/UVCoordinate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2025 Ultimaker B.V.
// libSavitar is released under the terms of the LGPLv3 or higher.

#ifndef UVCOORDINATE_H
#define UVCOORDINATE_H

namespace Savitar
{
class UVCoordinate
{
public:
/**
* A UV coordinate represents the position of the point on a texture image.
*/
UVCoordinate(float u, float v);
virtual ~UVCoordinate() = default;

[[nodiscard]] float getU() const;
[[nodiscard]] float getV() const;

private:
float u_;
float v_;
};
} // namespace Savitar

#endif
32 changes: 32 additions & 0 deletions include/Savitar/UVCoordinatesIndices.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) 2025 Ultimaker B.V.
// libSavitar is released under the terms of the LGPLv3 or higher.

#ifndef UVCOORDINATESINDICES_H
#define UVCOORDINATESINDICES_H

namespace Savitar
{
/**
* UV coordinates indices contains the UV group ID and associated indices for each point of a face. A single mesh may contain indices
* from multiple texture.
*/
class UVCoordinatesIndices
{
public:
UVCoordinatesIndices(int group_index, int vertex_1_index, int vertex_2_index, int vertex_3_index);
virtual ~UVCoordinatesIndices() = default;

[[nodiscard]] int getGroupIndex() const;
[[nodiscard]] int getV1() const;
[[nodiscard]] int getV2() const;
[[nodiscard]] int getV3() const;

private:
int group_index_{};
int vertex_1_index_{};
int vertex_2_index_{};
int vertex_3_index_{};
};
} // namespace Savitar

#endif
Loading
Loading