def create_settings(filename: str): """Generate initial settings file based on current working directory. :param filename: :return: """ # default stl directory default_stl_dir = Util.path_conversion("assets/models/") # default part name default_part_name = "untitled.dat" # default part name directory default_part_dir = Util.path_conversion("assets/parts/") # default author default_author = "First Last" # default license default_license = "Redistributable under CCAL version 2.0 : see CAreadme.txt" # default Log directory default_log_dir = Util.path_conversion(str(Path.home()) + "/Documents") default_settings = { "stl_dir": default_stl_dir, "part_name": default_part_name, "part_dir": default_part_dir, "author": default_author, "license": default_license, "log_dir": default_log_dir } file_path = Util.path_conversion(f"assets/settings/{filename}") try: with open(file_path, "w") as file: json.dump(default_settings, file, indent=4) except FileNotFoundError as ferr: print(ferr)
def testExportPlane(self): # Create an empty temp folder to use for temporary model files. try: os.mkdir(Util.path_conversion("tests/temp")) except OSError: pass pass # The file path we will use. file_path = Util.path_conversion("tests/temp/plane.dat") # Import the model. mesh = ModelShipper.load_stl_model(Util.path_conversion("tests/test_models/plane.stl")) self.assertNotEqual(mesh, False) model = LDrawModel(mesh) """# Export the model. ModelShipper.save_ldraw_file_model(file_path, model) # Read the file. with open(file_path, 'r') as file: file_data = file.read() # The final file data should look like this. self.assertEqual( file_data, "0 LScan auto generated part plane\n0 Name: plane.dat\n0 Author: Rando\n0 !LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt\n3 4 -0.5 0.0 0.5 -0.5 0.0 -0.5 0.5 0.0 -0.5\n3 4 0.5 0.0 -0.5 0.5 0.0 0.5 -0.5 0.0 0.5\n") """ # Cleanup. if os.path.exists(file_path): os.remove(file_path) os.rmdir(Util.path_conversion("tests/temp/"))
def test_rmdir(self): temp_test_dir = Util.path_conversion("tests/temp/temp_test") Util.mkdir(temp_test_dir) self.assertTrue(Util.is_dir(temp_test_dir)) Util.rmdir(temp_test_dir) self.assertFalse(Util.is_dir(temp_test_dir)) Util.rmdir(Util.path_conversion("tests/temp"))
def build_mesh_triangulation_data(file_path): mesh = Mesh.from_file(Util.path_conversion(file_path)) triangles = MeshTriangulation.get_mesh_triangles(mesh) normal_groups = MeshTriangulation.make_normal_groups(triangles) normal_groups_copy = copy.deepcopy(normal_groups) faces = MeshTriangulation.make_face_groups_loop(normal_groups_copy) face_boundaries, normals = MeshTriangulation.make_face_boundaries( faces) simple_boundaries = MeshTriangulation.make_simple_boundaries( face_boundaries) separate_boundaries = MeshTriangulation.split_boundaries( simple_boundaries) ordered_separate_boundaries = MeshTriangulation.find_outside_boundary( separate_boundaries) triangulated_faces = MeshTriangulation.buckets_to_dicts( ordered_separate_boundaries) mesh_dict = { "input_mesh": mesh, "triangles": triangles, "normal_groups": normal_groups, "faces": faces, "separate_boundaries": separate_boundaries, "ordered_separate_boundaries": ordered_separate_boundaries, "triangulated_faces": triangulated_faces } return mesh_dict
def test_(self): test_message = "test input model message" input_model = ModelShipper.load_stl_model( Util.path_conversion("tests/test_models/plane.stl")) model_message = InputModelMessage(LogType.INFORMATION, test_message, input_model) self.assertEqual(model_message.get_message(), test_message) self.assertEqual(model_message.get_message_type(), LogType.INFORMATION) self.assertIsNotNone(model_message.get_timestamp()) self.assertIsNotNone(model_message.get_model())
def testImportPlane(self): # Load the model from the assets folder. mesh = ModelShipper.load_stl_model(Util.path_conversion("tests/test_models/plane.stl")) self.assertNotEqual(mesh, False) # First triangle facet. self.assertEqual(mesh.v0[0], Vector3([0.5, 0., -0.5])) self.assertEqual(mesh.v1[0], Vector3([-0.5, 0., -0.5])) self.assertEqual(mesh.v2[0], Vector3([-0.5, 0., 0.5])) # Second triangle facet. self.assertEqual(mesh.v0[1], Vector3([-0.5, 0., 0.5])) self.assertEqual(mesh.v1[1], Vector3([0.5, 0., 0.5])) self.assertEqual(mesh.v2[1], Vector3([0.5, 0., -0.5]))
def test_(self): # Load the model from the assets folder. input_model = ModelShipper.load_stl_model( Util.path_conversion("tests/test_models/plane.stl")) output_model = LDrawModel(input_model # Mesh ) test_message = "test output model message" log_type = LogType.ERROR model_message = InputModelMessage(log_type, test_message, output_model.get_mesh()) self.assertEqual(model_message.get_message(), test_message) self.assertEqual(model_message.get_message_type(), log_type) self.assertIsNotNone(model_message.get_timestamp())
def setUp(self): mesh = Mesh.from_file( Util.path_conversion("tests/test_models/2_holes.stl")) mesh_triangles = [] # array of Triangles self.triangles_count = 0 for data in mesh.data: normal = get_unit_normal(data[0]) vertex_1 = data[1][0] vertex_2 = data[1][1] vertex_3 = data[1][2] edge_1 = Edge(vertex_1[0], vertex_1[1], vertex_1[2], vertex_2[0], vertex_2[1], vertex_2[2]) edge_2 = Edge(vertex_2[0], vertex_2[1], vertex_2[2], vertex_3[0], vertex_3[1], vertex_3[2]) edge_3 = Edge(vertex_3[0], vertex_3[1], vertex_3[2], vertex_1[0], vertex_1[1], vertex_1[2]) self.triangles_count += 1 mesh_triangles.append( Triangle(edge_1, edge_2, edge_3, normal=normal)) self.face = Face(mesh_triangles) self.normal = [0, 0, 1]
def get_assets_file_text(file_name: str): """Return the contents of the file in the folder CWD/assets/info/ :param file_name: The name of the file contained in the assets/info folder. :return: The text that was read from the file or None """ enc = "utf-8" file_path = Util.path_conversion("assets/info/" + file_name) print(file_path) text = None # Try to open the complete file path and record the text. try: with open(str(file_path), "r", encoding=enc) as file: text = file.read() except PermissionError as perr: pass except FileNotFoundError as ferr: pass return text
def test_mesh_triangulation(self): file_path = self.model_folder + "3001_dense.stl" mesh = Mesh.from_file(Util.path_conversion(file_path)) # Step 1: Create list of triangle objects from mesh triangles = MeshTriangulation.get_mesh_triangles(mesh) # Step 2: Group triangles by their normals normal_groups = MeshTriangulation.make_normal_groups(triangles) # Group normal groups into faces (by connected parts) faces = MeshTriangulation.make_face_groups_loop(normal_groups) # Step 3: Get only outline edges for each face face_boundaries, face_normals = MeshTriangulation.make_face_boundaries( faces) # Simplify outline edges for each face (remove redundant vertices) simple_boundaries = MeshTriangulation.make_simple_boundaries( face_boundaries) # Split each outline by connected parts separate_boundaries = MeshTriangulation.split_boundaries( simple_boundaries) # Rearranges edges in each face so that outer ordered_separate_boundaries = MeshTriangulation.find_outside_boundary( separate_boundaries) triangulated_faces = MeshTriangulation.buckets_to_dicts( ordered_separate_boundaries) triangulations = [] for face in triangulated_faces: triangulations.append(MeshTriangulation.triangulate(face)) MeshTriangulation.triangulation_to_mesh(triangulations, face_normals)
def save(self, event): """Save the finalized conversion of the input file. Hide main window options and replace them with metadata options. Once the user finalizes their metadata options (back or save), they return to the original options. :param event: The wx event that was recorded. :return: None """ self.save_button.Disable() with open(SettingsManager.file_path, "r") as file: file_settings = json.load(file) part_dir = file_settings["part_dir"] part_name = file_settings["part_name"] file_path = Util.path_conversion(part_dir + "/" + part_name) with open(file_path, "w") as text_file: text_file.write(ModelShipper.get_metadata() + ModelShipper.output_data_text) self.save_button.Enable() UIDriver.fire_event( UserEvent( UserEventType.LOG_INFO, LogMessage(LogType.INFORMATION, "File was saved to '" + file_path + "'.")))
class SettingsManager: settings_path = Util.path_conversion("assets/settings") filename = "user_settings.json" file_path = settings_path + "/" + filename @staticmethod def create_settings(filename: str): """Generate initial settings file based on current working directory. :param filename: :return: """ # default stl directory default_stl_dir = Util.path_conversion("assets/models/") # default part name default_part_name = "untitled.dat" # default part name directory default_part_dir = Util.path_conversion("assets/parts/") # default author default_author = "First Last" # default license default_license = "Redistributable under CCAL version 2.0 : see CAreadme.txt" # default Log directory default_log_dir = Util.path_conversion(str(Path.home()) + "/Documents") default_settings = { "stl_dir": default_stl_dir, "part_name": default_part_name, "part_dir": default_part_dir, "author": default_author, "license": default_license, "log_dir": default_log_dir } file_path = Util.path_conversion(f"assets/settings/{filename}") try: with open(file_path, "w") as file: json.dump(default_settings, file, indent=4) except FileNotFoundError as ferr: print(ferr) @staticmethod def save_settings(setting: str, val: str): """Save changes to user settings file. :param setting: :param val: :return: """ # Write out settings changes # default_part_name is always "untitled.dat" try: with open(SettingsManager.file_path, "r") as file: file_settings = json.load(file) file_settings[setting] = val with open(SettingsManager.file_path, "w") as file: json.dump(file_settings, file, indent=4) except FileNotFoundError as ferr: print(ferr) @staticmethod def display_settings(): """Display all settings and stl file path to standard out.""" print("\n\nDisplay settings\n") try: with open(SettingsManager.file_path, "r") as file: all_settings = json.load(file) print(all_settings) except FileNotFoundError as ferr: print(ferr) @staticmethod def get_settings(settings: [str]): """ Return a dictionary of settings and their values :param settings: :return requested settings: """ if settings: try: with open(SettingsManager.file_path, "r") as file: all_settings = json.load(file) requested = [] for setting in settings: if all_settings[setting]: requested[setting] = all_settings[setting] return requested except FileNotFoundError as ferr: print(ferr)
# "Brandon Goldbeck" <*****@*****.**> # “Anthony Namba” <*****@*****.**> # “Brandon Le” <*****@*****.**> # “Ann Peake” <*****@*****.**> # “Sohan Tamang” <*****@*****.**> # “An Huynh” <*****@*****.**> # “Theron Anderson” <*****@*****.**> # This software is licensed under the MIT License. See LICENSE file for the full text. import numpy import unittest from stl import Mesh from src.util import Util from src.model_conversion.ldraw_model import LDrawModel path = Util.path_conversion("tests/test_models/cube.stl") class LDrawModelTest(unittest.TestCase): """Class to verify the proper implementation of the ldraw_model :return: None """ def setUp(self): self.mesh = Mesh.from_file(path) self.children = [] self.ldraw_model = LDrawModel(self.mesh) def test_initialized(self): self.assertEqual(self.mesh.__dict__, self.ldraw_model.mesh.__dict__) self.assertEqual([], self.ldraw_model.children)
def test_path_conversion(self): self.assertEqual(Util.path_conversion(self.relative_path), self.valid_file)
def test_simple_plane_triangles(self): file_path = self.model_folder + "simple_plane_on_xy_180_tris.stl" mesh = Mesh.from_file(Util.path_conversion(file_path)) triangles = MeshTriangulation.get_mesh_triangles(mesh) self.assertTrue(len(triangles) == 180)
def __init__(self, triangle_data): """Constructor for the BasicMaterial. :param triangle_data: The triangle data to use in OpenGL Rendering context. """ Material.__init__(self) self.vertex_shader = """ # version 420 in layout(location = 0) vec3 vertex_position; in layout(location = 1) vec2 vertex_uv; in layout(location = 2) vec3 vertex_normal; out vec2 uv; out vec3 position_worldspace; out vec3 normal_cameraspace; out vec3 eye_direction_cameraspace; out vec3 light_direction_cameraspace; uniform mat4 model; uniform mat4 view; uniform mat4 projection; uniform vec3 light_position_worldspace; void main() { // Output position of the vertex, in clip space gl_Position = projection * view * model * vec4(vertex_position, 1.0); // Position of the vertex, in worldspace : M * position position_worldspace = (model * vec4(vertex_position,1)).xyz; // Vector that goes from the vertex to the camera, in camera space. // In camera space, the camera is at the origin (0,0,0). vec3 vertex_position_cameraspace = (view * model * vec4(vertex_position,1)).xyz; eye_direction_cameraspace = vec3(0,0,0) - vertex_position_cameraspace; // Vector that goes from the vertex to the light, in camera space. M is ommited because it's identity. vec3 light_position_cameraspace = (view * vec4(light_position_worldspace,1)).xyz; light_direction_cameraspace = light_position_cameraspace + eye_direction_cameraspace; // Normal of the the vertex, in camera space // Only correct if ModelMatrix does not scale the model ! Use its inverse transpose if not. normal_cameraspace = (view * model * vec4(vertex_normal,0)).xyz; // UV of the vertex. No special space for this one. uv = vertex_uv; } """ self.fragment_shader = """ # version 420 // Interpolated values from the vertex shaders in vec2 uv; in vec3 position_worldspace; in vec3 normal_cameraspace; in vec3 eye_direction_cameraspace; in vec3 light_direction_cameraspace; // Ouput data out vec3 color; uniform sampler2D texture_sampler; uniform vec3 light_position_worldspace = vec3(0,0,0); // Light emission properties uniform vec3 light_color = vec3(0,1,0); uniform float light_power = 50.0f; uniform vec3 ambient_color = vec3(0.3, 0.3, 0.3); uniform vec3 specular_color = vec3(0.2, 0.2, 0.2); void main() { // Material properties vec3 material_diffuse_color = texture(texture_sampler, uv ).rgb; vec3 material_ambient_color = ambient_color * material_diffuse_color; // Distance to the light float distance = length(light_position_worldspace - position_worldspace ); // Normal of the computed fragment, in camera space vec3 n = normalize(normal_cameraspace ); // Direction of the light (from the fragment to the light) vec3 l = normalize(light_direction_cameraspace ); // Cosine of the angle between the normal and the light direction, // clamped above 0 // - light is at the vertical of the triangle -> 1 // - light is perpendicular to the triangle -> 0 // - light is behind the triangle -> 0 float cos_theta = clamp( dot( n,l ), 0,1 ); // Eye vector (towards the camera) vec3 eye_vector = normalize(eye_direction_cameraspace); // Direction in which the triangle reflects the light vec3 reflection = reflect(-l,n); // Cosine of the angle between the Eye vector and the Reflect vector, // clamped to 0 // - Looking into the reflection -> 1 // - Looking elsewhere -> < 1 float cos_alpha = clamp(dot(eye_vector, reflection), 0,1 ); color = // Ambiant : simulates indirect lighting material_ambient_color + // Diffuse : "color" of the object material_diffuse_color * light_color * light_power * cos_theta / (distance*distance) + // Specular : reflective highlight, like a mirror specular_color * light_color * light_power * pow(cos_alpha,5) / (distance*distance); } """ try: self.shader = OpenGL.GL.shaders.compileProgram( OpenGL.GL.shaders.compileShader(self.vertex_shader, GL_VERTEX_SHADER), OpenGL.GL.shaders.compileShader(self.fragment_shader, GL_FRAGMENT_SHADER)) except Error: print("Failed to compile glsl shader.") return glUseProgram(self.shader) vbo = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, vbo) glBufferData(GL_ARRAY_BUFFER, len(triangle_data) * 4, triangle_data, GL_STATIC_DRAW) # Positions input to shader. glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(0)) # UV input to shader glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(12)) # Normal input to shader glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(20)) glEnableVertexAttribArray(0) glEnableVertexAttribArray(1) glEnableVertexAttribArray(2) texture = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, texture) # Set the texture wrapping parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) # Set texture filtering parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) # load image image = Image.open( Util.path_conversion("assets/images/default_brick_diffuse.jpg")) img_data = numpy.array(list(image.getdata()), numpy.uint8) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, img_data) glEnable(GL_TEXTURE_2D) self.set_uniform_matrix4fv("view", RenderingEngine.camera.get_view_matrix()) self.set_uniform_matrix4fv("projection", RenderingEngine.projection) self.set_uniform3f("light_position_worldspace", Vector3([0.0, 10.0, 10.0])) self.set_uniform3f("light_color", Vector3([0.0, 0.0, 1.0])) self.set_uniform3f("ambient_color", Vector3([0.5, 0.5, 0.5])) self.set_uniform3f("specular_color", Vector3([0.3, 0.3, 0.3])) self.set_uniform3f("light_color", Vector3([0.2, 0.2, 0.2])) self.set_uniform1f("light_power", 100.0)