Jump to content

Mesh to Mesh Report and alignment structure


Recommended Posts

Hi all,

I'm having some trouble establishing a reliable and repeatable workflow for our before-and-after scan comparisons in GOM Software 2022.

Currently, we compare two independently acquired 3D meshes to generate a color-coded heat map showing surface deviations, typically to evaluate material added or removed.

Our current workflow is:

  1. Import both meshes (previous and current scan).

  2. Convert the previous scan into nominal CAD to serve as the reference.

  3. Recalculate our alignment using Local Best Fit.

  4. Recalculate the surface comparisons and deviation labels.

  5. Export the inspection report.

The problem I'm encountering is that when I import the meshes and convert the reference scan to CAD, the converted CAD sometimes appears offset from its original position in space.

When Local Best Fit recalculates, it aligns the current scan to this offset reference. As a result, some deviation labels no longer correspond to their intended measurement locations, while others fail to compute entirely.

Because each part has its own unique reference scan, maintaining a single fixed CAD model isn't an option.

My questions are:

  • Is there a way to preserve the original mesh coordinates when converting an actual mesh to CAD?

  • Can the two meshes be aligned to one another before converting the reference mesh into CAD?

  • Would a reference-stage comparison or another mesh-to-mesh workflow be more suitable for this application?

  • What would be the recommended approach for automating this process so that surface comparisons, deviation labels, and report pages update reliably for each new pair of scans?

Ideally, we'd like to establish a project template or automated workflow where we import two meshes, calculate their differences, and generate a report without manually correcting their positions each time.

Any guidance specific to GOM Software 2022 would be greatly appreciated.

Thank you!

Please sign in to view this username.

 I am not sure I understand everything correctly. You bring two STL files into a project presumably that has no CAD file, designate one of the mesh as a reference by turning it into the "CAD", and then a localized best fit recalculates (from what?) the other STL file location. What is the initial alignment?

When you transform the STL into the "CAD", the alignment coordinates do not change. 

Please sign in to view this quote.

This is going to need more explanation. 

If you want the parts to align correctly to an actual CAD (normal to the global axes) without having the CAD present in the project file with the surface comparison, here is my suggestion - 

1. Create new project.

2. Import actual 3d CAD file (STP etc) into project.

3. Align the CAD file to whatever orientation your surface to surface comparison project needs by using the menu Operations>Alignment>SingleElementTransformation>SetMatrix. 

4. Save this as your CAD alignment template.

5. Import your reference STL file. Menu Operations>Alignment>InitialAlignment>Pre-alignment. This snaps your reference STL to the CAD.

6. Import any other STL files you want, and do recalculate the project. All files are now aligned to the CAD and to each other.

7. Select the entire aligned reference STL file.

8. Menu File>Export>Mesh>STL.

9. Select the radio button "Export in multiple files". Under Export stages, select "All stages." Click OK.

10. Your exported files will retain their new aligned coordinates.

 

Now when you import them into your surface to surface comparison project, they are already best fit aligned to each other. You can set the Operations>Alignment>InitialAlignment>DefineOriginalAlignmentAsInitialAlignment.

Also for automation, use the Python editor and record whatever sequence you are doing and then tweak it as needed. Once finished, you can just fire the macro and the magic happens.

 

 

 

Please sign in to view this username.

 Our workflow has us comparing scans of previous coating, to the current coating and we derive a deviation as our coating thickness. Since my "cad" or reference is the previous scan, I cannot use a general Cad model of the part for alignment and deviation. I take the previous coating scan and turn that into my reference mesh or cad body.

image.thumb.png.295f42ceb0b6ba9c02b8696b7a6def0a.png

image.thumb.png.8937b8ee15c98698b55a86845017fd43.png

First photo is the previous scan and the next is the current scan. I am assuming it an alignment problem on my master scanning program. Both Scans came from the same program, same fixture etc. 

My frustrations was from when I would convert my reference scan into CAD, it would get offset from my deviation markers. Although now I believe that the alignment or original coordinate system changes. 

 

If you wouldn't mind, where could I see examples of those python scripts? 

Thanks,

 

Please sign in to view this quote.

Please sign in to view this username.

 You can absolutely use a CAD for part alignment (not deviation) if you follow the directions in the previous post.

It looks and sounds like you are trying to use the fixture to keep the location the same from part to part, which is not going to work. 

You need to have a single source of truth for every part location and that would be to have a separate program as mentioned above. When you export the parts after alignment, they stay in that exact alignment. So when you bring the 1st, 50, 1000 parts into your other template, they would already be aligned each other and your flags would always calculate. You can still do your reference STL to CAD like you do now. The difference being you aren't chasing an alignment and hoping the fixture keeps everything in the same spot. 

From your screenshot, you would use the editor under the "Scripting" menu. I use a different version than you do but the concept is the same. You can hit the record button for a new Python script and then do whatever is normal to your workflow and then end the recording. You will see a bunch of code that was generated for each thing you did. This is basically allowing you to "look under the hood" at the actual language that is doing the things you are doing graphically (this drop down menu, selecting this thing, pushing this button, etc). From there you can tweak the code (use AI to help you understand by copy/pasting the script into an LLM and ask for explanation and commenting). Once your script is functional and polished, you can just go to that script and hit execute and everything happens line by line. 

Here is a custom script I made for maintaining part traceability when scanning lots of parts. It prompts the user with a couple of questions, and then you click on the part and it separates that part from the big lot, renames it with the sample number, increments by one, and then exports everything at the end (calculated from the prompts).

import gom
import tkinter as tk
from tkinter import simpledialog, messagebox

# Constants
EXPORT_DIRECTORY = 'PUT YOUR EXPORT DIRECTORY HERE'

def create_option_dialog(title, prompt, options):
    """Display a dialog to select an option using radio buttons."""
    root = tk.Tk()
    root.withdraw()  # Hide the root window

    selected_option = tk.StringVar(value=options[0])

    dialog = tk.Toplevel(root)
    dialog.title(title)

    tk.Label(dialog, text=prompt).pack(pady=10)

    for option in options:
        rb = tk.Radiobutton(dialog, text=option, variable=selected_option, value=option)
        rb.pack(anchor=tk.W)

    tk.Button(dialog, text="Confirm", command=dialog.destroy).pack(pady=10)
    root.wait_window(dialog)  # Wait for the dialog to close
    return selected_option.get()

def get_user_input(title, prompt):
    """Display a dialog to get user input."""
    root = tk.Tk()
    root.withdraw()  # Hide the root window
    user_input = simpledialog.askstring(title=title, prompt=prompt)
    return user_input

def get_validated_int_input(title, prompt):
    """Get and validate integer input from the user."""
    while True:
        user_input = get_user_input(title, prompt)
        if user_input is None:
            return None  # User cancelled
        try:
            return int(user_input)
        except ValueError:
            messagebox.showerror("Invalid Input", "Please enter a valid integer.")

def copy_and_edit_part(file_name):
    """Copy and edit the selected part."""
    try:
        gom.script.cad.copy_selected_points(select_points_in_copy=True)
        gom.script.sys.edit_properties(data=[gom.app.project.clipboard.actual_elements['Combined_Mesh_copy']], elem_name=file_name)
        gom.script.selection3d.deselect_all()
        messagebox.showinfo("Part Number", f"The assigned part number is: {file_name}")  # Notify user of the assigned number
    except Exception as e:
        print(f"Error processing part {file_name}: {str(e)}")
        return False
    return True

def main():
    """Main function to execute the mesh processing."""
    # Hide the root window before showing the message box
    root = tk.Tk()
    root.withdraw()  # Hide the root window
    messagebox.showinfo("Patch Selection", "Please select the first patch to begin numbering.")
    
    total_parts = get_validated_int_input("How many parts total are in this scan?", "How many parts total are in this scan?")
    if total_parts is None:
        print("User cancelled operation.")
        return

    total_parts1 = get_validated_int_input("What is the number of the first sample in this scan?", "What is the number of the first sample in this scan:")
    if total_parts1 is None:
        print("User cancelled operation.")
        return

    total_parts2 = total_parts + total_parts1

    # Get STL output unit
    stl_output = create_option_dialog("STL Output Unit", "Select STL output unit:", ["inch", "mm"])

    # Edit properties for parts
    try:
        gom.script.sys.edit_properties(data=[gom.app.project.parts['Part 1'].actual], elem_name='Combined_Mesh')
        gom.script.sys.edit_properties(data=[gom.app.project.parts['Part 1']], elem_name='Part')
    except Exception as e:
        print(f"Error editing properties: {str(e)}")
        return

    # Processing parts
    for n in range(total_parts):
        gom.script.cad.show_element(elements=[gom.app.project.parts['Part'].actual])
        
        user_input1 = total_parts1 + n  # Automatically increment user_input1
        file_name = str(user_input1)

        if not copy_and_edit_part(file_name):
            return

    # Exporting and processing remaining parts
    for n in range(total_parts1, total_parts2):
        try:
            gom.script.part.create_new_part(name=f'Part {n}')
            gom.script.part.add_elements_to_part(elements=[gom.app.project.clipboard.actual_elements[str(n)]],
                                                    import_mode='new_elements',
                                                    part=gom.app.project.parts[f'Part {n}'])
            gom.script.sys.export_stl(bgr_coding=False, binary=True, color=False,
                                            directory=EXPORT_DIRECTORY,
                                            elements=gom.ElementSelection(
                                                {'category': ['key', 'elements', 'part', gom.app.project.parts[f'Part {n}'],
                                                        'explorer_category', 'actual_part']}), 
                                            export_in_one_file=False, export_stages_mode='current',
                                            length_unit=stl_output, set_stl_color_bit=False)
        except Exception as e:
            print(f"Error exporting STL for Part {n}: {str(e)}")
            return
            
    # Deselect all before the final message
    gom.script.selection3d.deselect_all()

    # Successful completion message
    messagebox.showinfo("Success", "All parts have been processed and exported successfully!")
    print("All processing completed successfully.")
    
    # Cleanly exit after the user clicks OK
    root.quit()  # Exit the Tkinter main loop
    root.destroy()  # Destroy the Tkinter root window and clean up
    sys.exit(0)  # Cleanly exit the script with a success status

if __name__ == "__main__":
    main()

 

 

 

Edited

Please sign in to view this quote.

Hello John,

your project structure is not suited for this task. You added another mesh to the same part. Reference Mesh should be declared as pseudo CAD, then a second mesh can be added to the part.
The current structure is when you want to work with several mesh candidates, for example to find the best fitting door to a frame.

Then the proposed workflow by

Please sign in to view this username.

 and

Please sign in to view this username.

 should work.

Following parts could also be added as a stage. A stage project also supports comparison between stages.


Nanno

×
×
  • Create New...