Port libvgcode/improved G-code viewer from PrusaSlicer 2.8.0 (#10735)

# Description

Fixes #6813 
Fixes #7782 
Fixes #6367

This is a port of libvgcode, OpenGL Core Profile (libvgcode requires OpenGL 3.2+), and probably other stuff I forgot from PrusaSlicer. libvgcode is also known as "Improved G-code Viewer" which was introduced in [PrusaSlicer 2.8.0](https://github.com/prusa3d/PrusaSlicer/releases/tag/version_2.8.0-alpha5). It significantly improves performance of the G-code preview and allows displaying the actual speed and volumetric flow rate.

## TODO

- [x] Make the sliders work properly again
- [x] Make view type selection work
- [x] Fix toggling of feature type visibility
- [x] Expose actual speed/actual flow view types
- [x] Fix display of G2/G3 arcs
- [x] Wire up actual speed graph widgets
- [x] Fix painter gizmos
- [x] Fix crash when selecting filament view type on Windows
- [ ] Figure out display color management (#10827)
- [x] Fix incorrect rendering of small area flow compensation (or any extrusions with varying flow)
- [ ] Localization
- [ ] Testing
- [ ] Code cleanup, performance optimization

## Removed functionality

Some functionality is not supported by libvgcode:

- Toggling visibility of tools
- Toggling visibility of colors in color print view
- Support for systems with OpenGL <3.2
- Bambu's implementation of G2/G3 arcs was replaced with PrusaSlicer's implementation

# Screenshots/Recordings/Graphs

https://github.com/user-attachments/assets/c1413a82-3058-4541-b96c-3d5f7cdef174

<details>
<summary>Outdated Stuff</summary>

https://github.com/user-attachments/assets/ca172ef4-0828-4d21-8768-b08a4132c9ab

<img width="1611" height="1145" alt="image" src="https://github.com/user-attachments/assets/5e0d04ff-8046-4636-a08a-cdedc60303bc" />
</details>

## Tests

- [x] Normally sliced G-code
- [ ] G-code viewer mode
- [x] Arc fitting enabled
- [x] Spiral Z-hops
- [x] Multi-extruder/multi-color
- [x] Vase mode
- [x] Skirt/brim
- [x] Supports
- [ ] Support transition (whatever this is)
- [x] Painter gizmos
- [x] Measurement gizmo
- [x] Move/rotate/scale gizmos
- [x] H2D/H2S support (oh no)
- [ ] Windows
- [x] macOS
- [ ] Linux
This commit is contained in:
SoftFever
2026-01-06 23:15:16 +08:00
committed by GitHub
117 changed files with 19332 additions and 6172 deletions

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.0"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 16 16"
enable-background="new 0 0 16 16"
xml:space="preserve"
sodipodi:docname="hide_horizontal.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs945">
</defs><sodipodi:namedview
id="namedview943"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="50.75"
inkscape:cx="7.9901478"
inkscape:cy="8.0098522"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="3191"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<polygon
fill="#009688"
points="1,8 1,11 8,16 15,11 15,8 "
style="stroke:none;stroke-width:1"
id="polygon937"
transform="translate(0,-4)" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.0"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 16 16"
enable-background="new 0 0 16 16"
xml:space="preserve"
sodipodi:docname="show_horizontal.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1127">
</defs><sodipodi:namedview
id="namedview1125"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="50.75"
inkscape:cx="7.9901478"
inkscape:cy="8.0098522"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="3191"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<polygon
fill="#009688"
points="15,7 15,5 8,0 1,5 1,7 1,8 15,8 "
style="stroke:none;stroke-width:1"
id="polygon1119"
transform="translate(0,4)" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -5,7 +5,9 @@ uniform vec4 bottom_color;
in vec2 tex_coord;
out vec4 out_color;
void main()
{
gl_FragColor = mix(bottom_color, top_color, tex_coord.y);
out_color = mix(bottom_color, top_color, tex_coord.y);
}

View File

@@ -0,0 +1,34 @@
#version 150
// see as reference: https://github.com/mhalber/Lines/blob/master/geometry_shader_lines.h
// https://stackoverflow.com/questions/52928678/dashed-line-in-opengl3
const float aa_radius = 0.5;
uniform float dash_size;
uniform float gap_size;
uniform vec4 uniform_color;
in float line_width;
// x = v tex coord, y = s coord
in vec2 seg_params;
out vec4 out_color;
void main()
{
float inv_stride = 1.0 / (dash_size + gap_size);
if (gap_size > 0.0 && fract(seg_params.y * inv_stride) > dash_size * inv_stride)
discard;
// We render a quad that is fattened by r, giving total width of the line to be w+r. We want smoothing to happen
// around w, so that the edge is properly smoothed out. As such, in the smoothstep function we have:
// Far edge : 1.0 = (w+r) / (w+r)
// Close edge : 1.0 - (2r / (w+r)) = (w+r)/(w+r) - 2r/(w+r)) = (w-r) / (w+r)
// This way the smoothing is centered around 'w'.
out_color = uniform_color;
float inv_line_width = 1.0 / line_width;
float aa = 1.0 - smoothstep(1.0 - (2.0 * aa_radius * inv_line_width), 1.0, abs(seg_params.x * inv_line_width));
out_color.a *= aa;
}

View File

@@ -0,0 +1,50 @@
#version 150
// see as reference: https://github.com/mhalber/Lines/blob/master/geometry_shader_lines.h
// https://stackoverflow.com/questions/52928678/dashed-line-in-opengl3
layout(lines) in;
layout(triangle_strip, max_vertices = 4) out;
const float aa_radius = 0.5;
uniform vec2 viewport_size;
uniform float width;
in float coord_s[];
out float line_width;
// x = v tex coord, y = s coord
out vec2 seg_params;
void main()
{
vec2 ndc_0 = gl_in[0].gl_Position.xy / gl_in[0].gl_Position.w;
vec2 ndc_1 = gl_in[1].gl_Position.xy / gl_in[1].gl_Position.w;
vec2 dir = normalize((ndc_1 - ndc_0) * viewport_size);
vec2 normal_dir = vec2(-dir.y, dir.x);
line_width = max(1.0, width) + 2.0 * aa_radius;
float half_line_width = 0.5 * line_width;
vec2 normal = vec2(line_width / viewport_size[0], line_width / viewport_size[1]) * normal_dir;
seg_params = vec2(-half_line_width, coord_s[0]);
gl_Position = vec4((ndc_0 + normal) * gl_in[0].gl_Position.w, gl_in[0].gl_Position.zw);
EmitVertex();
seg_params = vec2(-half_line_width, coord_s[0]);
gl_Position = vec4((ndc_0 - normal) * gl_in[0].gl_Position.w, gl_in[0].gl_Position.zw);
EmitVertex();
seg_params = vec2(half_line_width, coord_s[1]);
gl_Position = vec4((ndc_1 + normal) * gl_in[1].gl_Position.w, gl_in[1].gl_Position.zw);
EmitVertex();
seg_params = vec2(half_line_width, coord_s[1]);
gl_Position = vec4((ndc_1 - normal) * gl_in[1].gl_Position.w, gl_in[1].gl_Position.zw);
EmitVertex();
EndPrimitive();
}

View File

@@ -0,0 +1,18 @@
#version 150
// see as reference: https://github.com/mhalber/Lines/blob/master/geometry_shader_lines.h
// https://stackoverflow.com/questions/52928678/dashed-line-in-opengl3
uniform mat4 view_model_matrix;
uniform mat4 projection_matrix;
// v_position.w = coordinate along the line
in vec4 v_position;
out float coord_s;
void main()
{
coord_s = v_position.w;
gl_Position = projection_matrix * view_model_matrix * vec4(v_position.xyz, 1.0);
}

View File

@@ -2,7 +2,9 @@
uniform vec4 uniform_color;
out vec4 out_color;
void main()
{
gl_FragColor = uniform_color;
out_color = uniform_color;
}

View File

@@ -4,7 +4,9 @@ uniform sampler2D uniform_texture;
in vec2 tex_coord;
out vec4 out_color;
void main()
{
gl_FragColor = texture(uniform_texture, tex_coord);
out_color = texture(uniform_texture, tex_coord);
}

View File

@@ -6,7 +6,9 @@ uniform float emission_factor;
// x = tainted, y = specular;
in vec2 intensity;
out vec4 out_color;
void main()
{
gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
out_color = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
}

View File

@@ -6,7 +6,9 @@ uniform float emission_factor;
// x = tainted, y = specular;
in vec2 intensity;
out vec4 out_color;
void main()
{
gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
out_color = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
}

View File

@@ -0,0 +1,46 @@
#version 140
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
const vec3 WHITE = vec3(1.0, 1.0, 1.0);
struct PrintVolumeDetection
{
// 0 = rectangle, 1 = circle, 2 = custom, 3 = invalid
int type;
// type = 0 (rectangle):
// x = min.x, y = min.y, z = max.x, w = max.y
// type = 1 (circle):
// x = center.x, y = center.y, z = radius
vec4 xy_data;
// x = min z, y = max z
vec2 z_data;
};
uniform vec4 uniform_color;
uniform float emission_factor;
uniform PrintVolumeDetection print_volume;
// x = diffuse, y = specular;
in vec2 intensity;
in vec4 world_pos;
out vec4 out_color;
void main()
{
vec3 color = uniform_color.rgb;
float alpha = uniform_color.a;
// if the fragment is outside the print volume -> use darker color
vec3 pv_check_min = ZERO;
vec3 pv_check_max = ZERO;
if (print_volume.type == 0) {// rectangle
pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x);
pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y);
}
else if (print_volume.type == 1) {// circle
float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy);
pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x);
pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y);
}
color = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color, WHITE, 0.3333) : color;
//out_color = vec4(vec3(intensity.y) + color * intensity.x, alpha);
out_color = vec4(vec3(intensity.y) + color * (intensity.x + emission_factor), alpha);
}

View File

@@ -0,0 +1,47 @@
#version 140
#define INTENSITY_CORRECTION 0.6
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
#define LIGHT_TOP_SHININESS 20.0
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
#define INTENSITY_AMBIENT 0.3
uniform mat4 view_model_matrix;
uniform mat4 projection_matrix;
uniform mat3 view_normal_matrix;
uniform mat4 volume_world_matrix;
in vec3 v_position;
in vec3 v_normal;
// x = tainted, y = specular;
out vec2 intensity;
out vec4 world_pos;
void main()
{
// First transform the normal into camera space and normalize the result.
vec3 normal = normalize(view_normal_matrix * v_normal);
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
world_pos = volume_world_matrix * vec4(v_position, 1.0);
vec4 position = view_model_matrix * vec4(v_position, 1.0);
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
gl_Position = projection_matrix * position;
}

View File

@@ -5,7 +5,9 @@ uniform sampler2D Texture;
in vec2 Frag_UV;
in vec4 Frag_Color;
out vec4 out_color;
void main()
{
gl_FragColor = Frag_Color * texture(Texture, Frag_UV.st);
out_color = Frag_Color * texture(Texture, Frag_UV.st);
}

View File

@@ -2,7 +2,9 @@
uniform vec4 uniform_color;
out vec4 out_color;
void main()
{
gl_FragColor = uniform_color;
out_color = uniform_color;
}

View File

@@ -9,13 +9,15 @@ in vec2 intensity;
//varying float drop;
in vec4 world_pos;
out vec4 out_color;
void main()
{
if (world_pos.z < 0.0)
discard;
if(ban_light){
gl_FragColor = uniform_color;
out_color = uniform_color;
} else{
gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
out_color = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
}
}

View File

@@ -15,6 +15,8 @@ in vec2 intensity;
in float object_z;
out vec4 out_color;
void main()
{
float object_z_row = z_to_texture_row * object_z;
@@ -37,5 +39,5 @@ void main()
color = mix(texture(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.),
texture(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod);
// Mix the final color.
gl_FragColor = vec4(vec3(intensity.y), 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
out_color = vec4(vec3(intensity.y), 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
}