3DScene.pm 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252
  1. package Slic3r::GUI::3DScene::Base;
  2. use strict;
  3. use warnings;
  4. use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
  5. # must load OpenGL *before* Wx::GLCanvas
  6. use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
  7. use base qw(Wx::GLCanvas Class::Accessor);
  8. use Math::Trig qw(asin);
  9. use List::Util qw(reduce min max first);
  10. use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan scale unscale scaled_epsilon);
  11. use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl);
  12. use Wx::GLCanvas qw(:all);
  13. __PACKAGE__->mk_accessors( qw(_quat _dirty init
  14. enable_cutting
  15. enable_picking
  16. enable_moving
  17. on_hover
  18. on_select
  19. on_double_click
  20. on_right_click
  21. on_move
  22. volumes
  23. _sphi _stheta
  24. cutting_plane_z
  25. cut_lines_vertices
  26. bed_shape
  27. bed_triangles
  28. bed_grid_lines
  29. background
  30. origin
  31. _mouse_pos
  32. _hover_volume_idx
  33. _drag_volume_idx
  34. _drag_start_pos
  35. _drag_start_xy
  36. _dragged
  37. _camera_target
  38. _zoom
  39. ) );
  40. use constant TRACKBALLSIZE => 0.8;
  41. use constant TURNTABLE_MODE => 1;
  42. use constant GROUND_Z => -0.02;
  43. use constant DEFAULT_COLOR => [1,1,0];
  44. use constant SELECTED_COLOR => [0,1,0,1];
  45. use constant HOVER_COLOR => [0.4,0.9,0,1];
  46. # make OpenGL::Array thread-safe
  47. {
  48. no warnings 'redefine';
  49. *OpenGL::Array::CLONE_SKIP = sub { 1 };
  50. }
  51. sub new {
  52. my ($class, $parent) = @_;
  53. # we request a depth buffer explicitely because it looks like it's not created by
  54. # default on Linux, causing transparency issues
  55. my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "",
  56. [WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, 0]);
  57. $self->background(1);
  58. $self->_quat((0, 0, 0, 1));
  59. $self->_stheta(45);
  60. $self->_sphi(45);
  61. $self->_zoom(1);
  62. # 3D point in model space
  63. $self->_camera_target(Slic3r::Pointf3->new(0,0,0));
  64. $self->reset_objects;
  65. EVT_PAINT($self, sub {
  66. my $dc = Wx::PaintDC->new($self);
  67. $self->Render($dc);
  68. });
  69. EVT_SIZE($self, sub { $self->_dirty(1) });
  70. EVT_IDLE($self, sub {
  71. return unless $self->_dirty;
  72. return if !$self->IsShownOnScreen;
  73. $self->Resize( $self->GetSizeWH );
  74. $self->Refresh;
  75. });
  76. EVT_MOUSEWHEEL($self, sub {
  77. my ($self, $e) = @_;
  78. # Calculate the zoom delta and apply it to the current zoom factor
  79. my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
  80. $zoom = max(min($zoom, 4), -4);
  81. $zoom /= 10;
  82. $self->_zoom($self->_zoom / (1-$zoom));
  83. # In order to zoom around the mouse point we need to translate
  84. # the camera target
  85. my $size = Slic3r::Pointf->new($self->GetSizeWH);
  86. my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #-
  87. $self->_camera_target->translate(
  88. # ($pos - $size/2) represents the vector from the viewport center
  89. # to the mouse point. By multiplying it by $zoom we get the new,
  90. # transformed, length of such vector.
  91. # Since we want that point to stay fixed, we move our camera target
  92. # in the opposite direction by the delta of the length of such vector
  93. # ($zoom - 1). We then scale everything by 1/$self->_zoom since
  94. # $self->_camera_target is expressed in terms of model units.
  95. -($pos->x - $size->x/2) * ($zoom) / $self->_zoom,
  96. -($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
  97. 0,
  98. ) if 0;
  99. $self->_dirty(1);
  100. $self->Refresh;
  101. });
  102. EVT_MOUSE_EVENTS($self, \&mouse_event);
  103. return $self;
  104. }
  105. sub mouse_event {
  106. my ($self, $e) = @_;
  107. my $pos = Slic3r::Pointf->new($e->GetPositionXY);
  108. if ($e->Entering && &Wx::wxMSW) {
  109. # wxMSW needs focus in order to catch mouse wheel events
  110. $self->SetFocus;
  111. } elsif ($e->LeftDClick) {
  112. $self->on_double_click->()
  113. if $self->on_double_click;
  114. } elsif ($e->LeftDown || $e->RightDown) {
  115. # If user pressed left or right button we first check whether this happened
  116. # on a volume or not.
  117. my $volume_idx = $self->_hover_volume_idx // -1;
  118. # select volume in this 3D canvas
  119. if ($self->enable_picking) {
  120. $self->deselect_volumes;
  121. $self->select_volume($volume_idx);
  122. if ($volume_idx != -1) {
  123. my $group_id = $self->volumes->[$volume_idx]->select_group_id;
  124. my @volumes;
  125. if ($group_id != -1) {
  126. $self->select_volume($_)
  127. for grep $self->volumes->[$_]->select_group_id == $group_id,
  128. 0..$#{$self->volumes};
  129. }
  130. }
  131. $self->Refresh;
  132. }
  133. # propagate event through callback
  134. $self->on_select->($volume_idx)
  135. if $self->on_select;
  136. if ($volume_idx != -1) {
  137. if ($e->LeftDown && $self->enable_moving) {
  138. $self->_drag_volume_idx($volume_idx);
  139. $self->_drag_start_pos($self->mouse_to_3d(@$pos));
  140. } elsif ($e->RightDown) {
  141. # if right clicking on volume, propagate event through callback
  142. $self->on_right_click->($e->GetPosition)
  143. if $self->on_right_click;
  144. }
  145. }
  146. } elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) {
  147. # get new position at the same Z of the initial click point
  148. my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY);
  149. my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z);
  150. # calculate the translation vector
  151. my $vector = $self->_drag_start_pos->vector_to($cur_pos);
  152. # get volume being dragged
  153. my $volume = $self->volumes->[$self->_drag_volume_idx];
  154. # get all volumes belonging to the same group, if any
  155. my @volumes;
  156. if ($volume->drag_group_id == -1) {
  157. @volumes = ($volume);
  158. } else {
  159. @volumes = grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes};
  160. }
  161. # apply new temporary volume origin and ignore Z
  162. $_->origin->translate($vector->x, $vector->y, 0) for @volumes; #,,
  163. $self->_drag_start_pos($cur_pos);
  164. $self->_dragged(1);
  165. $self->Refresh;
  166. } elsif ($e->Dragging) {
  167. if ($e->LeftIsDown) {
  168. # if dragging over blank area with left button, rotate
  169. if (defined $self->_drag_start_pos) {
  170. my $orig = $self->_drag_start_pos;
  171. if (TURNTABLE_MODE) {
  172. $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE);
  173. $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #-
  174. $self->_stheta(150) if $self->_stheta > 150;
  175. $self->_stheta(0) if $self->_stheta < 0;
  176. } else {
  177. my $size = $self->GetClientSize;
  178. my @quat = trackball(
  179. $orig->x / ($size->width / 2) - 1,
  180. 1 - $orig->y / ($size->height / 2), #/
  181. $pos->x / ($size->width / 2) - 1,
  182. 1 - $pos->y / ($size->height / 2), #/
  183. );
  184. $self->_quat(mulquats($self->_quat, \@quat));
  185. }
  186. $self->Refresh;
  187. }
  188. $self->_drag_start_pos($pos);
  189. } elsif ($e->MiddleIsDown || $e->RightIsDown) {
  190. # if dragging over blank area with right button, translate
  191. if (defined $self->_drag_start_xy) {
  192. # get point in model space at Z = 0
  193. my $cur_pos = $self->mouse_ray($e->GetX, $e->GetY)->intersect_plane(0);
  194. my $orig = $self->mouse_ray(@{$self->_drag_start_xy})->intersect_plane(0);
  195. $self->_camera_target->translate(
  196. @{$orig->vector_to($cur_pos)->negative},
  197. );
  198. $self->Refresh;
  199. }
  200. $self->_drag_start_xy($pos);
  201. }
  202. } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
  203. if ($self->on_move && defined($self->_drag_volume_idx) && $self->_dragged) {
  204. # get all volumes belonging to the same group, if any
  205. my @volume_idxs;
  206. my $group_id = $self->volumes->[$self->_drag_volume_idx]->drag_group_id;
  207. if ($group_id == -1) {
  208. @volume_idxs = ($self->_drag_volume_idx);
  209. } else {
  210. @volume_idxs = grep $self->volumes->[$_]->drag_group_id == $group_id,
  211. 0..$#{$self->volumes};
  212. }
  213. $self->on_move->(@volume_idxs);
  214. }
  215. $self->_drag_volume_idx(undef);
  216. $self->_drag_start_pos(undef);
  217. $self->_drag_start_xy(undef);
  218. $self->_dragged(undef);
  219. } elsif ($e->Moving) {
  220. $self->_mouse_pos($pos);
  221. $self->Refresh;
  222. } else {
  223. $e->Skip();
  224. }
  225. }
  226. sub reset_objects {
  227. my ($self) = @_;
  228. $self->volumes([]);
  229. $self->_dirty(1);
  230. }
  231. sub zoom_to_bounding_box {
  232. my ($self, $bb) = @_;
  233. # calculate the zoom factor needed to adjust viewport to
  234. # bounding box
  235. my $max_size = max(@{$bb->size}) * 2;
  236. my $min_viewport_size = min($self->GetSizeWH);
  237. $self->_zoom($min_viewport_size / $max_size);
  238. # center view around bounding box center
  239. $self->_camera_target($bb->center);
  240. }
  241. sub zoom_to_bed {
  242. my ($self) = @_;
  243. if ($self->bed_shape) {
  244. $self->zoom_to_bounding_box($self->bed_bounding_box);
  245. }
  246. }
  247. sub zoom_to_volume {
  248. my ($self, $volume_idx) = @_;
  249. my $volume = $self->volumes->[$volume_idx];
  250. my $bb = $volume->transformed_bounding_box;
  251. $self->zoom_to_bounding_box($bb);
  252. }
  253. sub zoom_to_volumes {
  254. my ($self) = @_;
  255. $self->zoom_to_bounding_box($self->volumes_bounding_box);
  256. }
  257. sub volumes_bounding_box {
  258. my ($self) = @_;
  259. my $bb = Slic3r::Geometry::BoundingBoxf3->new;
  260. $bb->merge($_->transformed_bounding_box) for @{$self->volumes};
  261. return $bb;
  262. }
  263. sub bed_bounding_box {
  264. my ($self) = @_;
  265. my $bb = Slic3r::Geometry::BoundingBoxf3->new;
  266. $bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape};
  267. return $bb;
  268. }
  269. sub max_bounding_box {
  270. my ($self) = @_;
  271. my $bb = $self->bed_bounding_box;
  272. $bb->merge($self->volumes_bounding_box);
  273. return $bb;
  274. }
  275. sub set_auto_bed_shape {
  276. my ($self, $bed_shape) = @_;
  277. # draw a default square bed around object center
  278. my $max_size = max(@{ $self->volumes_bounding_box->size });
  279. my $center = $self->volumes_bounding_box->center;
  280. $self->set_bed_shape([
  281. [ $center->x - $max_size, $center->y - $max_size ], #--
  282. [ $center->x + $max_size, $center->y - $max_size ], #--
  283. [ $center->x + $max_size, $center->y + $max_size ], #++
  284. [ $center->x - $max_size, $center->y + $max_size ], #++
  285. ]);
  286. $self->origin(Slic3r::Pointf->new(@$center[X,Y]));
  287. }
  288. sub set_bed_shape {
  289. my ($self, $bed_shape) = @_;
  290. $self->bed_shape($bed_shape);
  291. # triangulate bed
  292. my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]);
  293. my $bed_bb = $expolygon->bounding_box;
  294. {
  295. my @points = ();
  296. foreach my $triangle (@{ $expolygon->triangulate }) {
  297. push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle; #))
  298. }
  299. $self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points));
  300. }
  301. {
  302. my @lines = ();
  303. for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) {
  304. push @lines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
  305. }
  306. for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) {
  307. push @lines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
  308. }
  309. # clip with a slightly grown expolygon because our lines lay on the contours and
  310. # may get erroneously clipped
  311. @lines = @{intersection_pl(\@lines, [ @{$expolygon->offset(+scaled_epsilon)} ])};
  312. # append bed contours
  313. foreach my $line (map @{$_->lines}, @$expolygon) {
  314. push @lines, $line->as_polyline;
  315. }
  316. my @points = ();
  317. foreach my $polyline (@lines) {
  318. push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$polyline; #))
  319. }
  320. $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points));
  321. }
  322. $self->origin(Slic3r::Pointf->new(0,0));
  323. }
  324. sub deselect_volumes {
  325. my ($self) = @_;
  326. $_->selected(0) for @{$self->volumes};
  327. }
  328. sub select_volume {
  329. my ($self, $volume_idx) = @_;
  330. $self->volumes->[$volume_idx]->selected(1)
  331. if $volume_idx != -1;
  332. }
  333. sub SetCuttingPlane {
  334. my ($self, $z) = @_;
  335. $self->cutting_plane_z($z);
  336. # perform cut and cache section lines
  337. my @verts = ();
  338. foreach my $volume (@{$self->volumes}) {
  339. foreach my $volume (@{$self->volumes}) {
  340. next if !$volume->mesh;
  341. my $expolygons = $volume->mesh->slice([ $z - $volume->origin->z ])->[0];
  342. $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
  343. foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
  344. push @verts, (
  345. unscale($line->a->x), unscale($line->a->y), $z, #))
  346. unscale($line->b->x), unscale($line->b->y), $z, #))
  347. );
  348. }
  349. }
  350. }
  351. $self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts));
  352. }
  353. # Given an axis and angle, compute quaternion.
  354. sub axis_to_quat {
  355. my ($ax, $phi) = @_;
  356. my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax));
  357. my @q = map { $_ * (1 / $lena) } @$ax;
  358. @q = map { $_ * sin($phi / 2.0) } @q;
  359. $q[$#q + 1] = cos($phi / 2.0);
  360. return @q;
  361. }
  362. # Project a point on the virtual trackball.
  363. # If it is inside the sphere, map it to the sphere, if it outside map it
  364. # to a hyperbola.
  365. sub project_to_sphere {
  366. my ($r, $x, $y) = @_;
  367. my $d = sqrt($x * $x + $y * $y);
  368. if ($d < $r * 0.70710678118654752440) { # Inside sphere
  369. return sqrt($r * $r - $d * $d);
  370. } else { # On hyperbola
  371. my $t = $r / 1.41421356237309504880;
  372. return $t * $t / $d;
  373. }
  374. }
  375. sub cross {
  376. my ($v1, $v2) = @_;
  377. return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1],
  378. @$v1[2] * @$v2[0] - @$v1[0] * @$v2[2],
  379. @$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]);
  380. }
  381. # Simulate a track-ball. Project the points onto the virtual trackball,
  382. # then figure out the axis of rotation, which is the cross product of
  383. # P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a
  384. # deformed trackball-- is a trackball in the center, but is deformed
  385. # into a hyperbolic sheet of rotation away from the center.
  386. # It is assumed that the arguments to this routine are in the range
  387. # (-1.0 ... 1.0).
  388. sub trackball {
  389. my ($p1x, $p1y, $p2x, $p2y) = @_;
  390. if ($p1x == $p2x && $p1y == $p2y) {
  391. # zero rotation
  392. return (0.0, 0.0, 0.0, 1.0);
  393. }
  394. # First, figure out z-coordinates for projection of P1 and P2 to
  395. # deformed sphere
  396. my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y));
  397. my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y));
  398. # axis of rotation (cross product of P1 and P2)
  399. my @a = cross(\@p2, \@p1);
  400. # Figure out how much to rotate around that axis.
  401. my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1);
  402. my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE);
  403. # Avoid problems with out-of-control values...
  404. $t = 1.0 if ($t > 1.0);
  405. $t = -1.0 if ($t < -1.0);
  406. my $phi = 2.0 * asin($t);
  407. return axis_to_quat(\@a, $phi);
  408. }
  409. # Build a rotation matrix, given a quaternion rotation.
  410. sub quat_to_rotmatrix {
  411. my ($q) = @_;
  412. my @m = ();
  413. $m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]);
  414. $m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]);
  415. $m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]);
  416. $m[3] = 0.0;
  417. $m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]);
  418. $m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]);
  419. $m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]);
  420. $m[7] = 0.0;
  421. $m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]);
  422. $m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]);
  423. $m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]);
  424. $m[11] = 0.0;
  425. $m[12] = 0.0;
  426. $m[13] = 0.0;
  427. $m[14] = 0.0;
  428. $m[15] = 1.0;
  429. return @m;
  430. }
  431. sub mulquats {
  432. my ($q1, $rq) = @_;
  433. return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1],
  434. @$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2],
  435. @$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0],
  436. @$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2])
  437. }
  438. sub mouse_to_3d {
  439. my ($self, $x, $y, $z) = @_;
  440. my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items
  441. my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items
  442. my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items
  443. $y = $viewport[3] - $y;
  444. $z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT);
  445. my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport);
  446. return Slic3r::Pointf3->new(@projected);
  447. }
  448. sub mouse_ray {
  449. my ($self, $x, $y) = @_;
  450. return Slic3r::Linef3->new(
  451. $self->mouse_to_3d($x, $y, 0),
  452. $self->mouse_to_3d($x, $y, 1),
  453. );
  454. }
  455. sub GetContext {
  456. my ($self) = @_;
  457. if (Wx::wxVERSION >= 2.009) {
  458. return $self->{context} ||= Wx::GLContext->new($self);
  459. } else {
  460. return $self->SUPER::GetContext;
  461. }
  462. }
  463. sub SetCurrent {
  464. my ($self, $context) = @_;
  465. if (Wx::wxVERSION >= 2.009) {
  466. return $self->SUPER::SetCurrent($context);
  467. } else {
  468. return $self->SUPER::SetCurrent;
  469. }
  470. }
  471. sub Resize {
  472. my ($self, $x, $y) = @_;
  473. return unless $self->GetContext;
  474. $self->_dirty(0);
  475. $self->SetCurrent($self->GetContext);
  476. glViewport(0, 0, $x, $y);
  477. $x /= $self->_zoom;
  478. $y /= $self->_zoom;
  479. glMatrixMode(GL_PROJECTION);
  480. glLoadIdentity();
  481. glOrtho(
  482. -$x/2, $x/2, -$y/2, $y/2,
  483. -200, 10 * max(@{ $self->max_bounding_box->size }),
  484. );
  485. glMatrixMode(GL_MODELVIEW);
  486. }
  487. sub InitGL {
  488. my $self = shift;
  489. return if $self->init;
  490. return unless $self->GetContext;
  491. $self->init(1);
  492. glClearColor(0, 0, 0, 1);
  493. glColor3f(1, 0, 0);
  494. glEnable(GL_DEPTH_TEST);
  495. glClearDepth(1.0);
  496. glDepthFunc(GL_LEQUAL);
  497. glEnable(GL_CULL_FACE);
  498. glEnable(GL_BLEND);
  499. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  500. # Set antialiasing/multisampling
  501. glDisable(GL_LINE_SMOOTH);
  502. glDisable(GL_POLYGON_SMOOTH);
  503. glEnable(GL_MULTISAMPLE);
  504. # ambient lighting
  505. glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1);
  506. glEnable(GL_LIGHTING);
  507. glEnable(GL_LIGHT0);
  508. glEnable(GL_LIGHT1);
  509. # light from camera
  510. glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
  511. glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1);
  512. glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1);
  513. # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
  514. glShadeModel(GL_SMOOTH);
  515. glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 0.3, 0.3, 1);
  516. glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1);
  517. glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50);
  518. glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0, 0, 0.9);
  519. # A handy trick -- have surface material mirror the color.
  520. glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
  521. glEnable(GL_COLOR_MATERIAL);
  522. glEnable(GL_MULTISAMPLE);
  523. }
  524. sub Render {
  525. my ($self, $dc) = @_;
  526. # prevent calling SetCurrent() when window is not shown yet
  527. return unless $self->IsShownOnScreen;
  528. return unless my $context = $self->GetContext;
  529. $self->SetCurrent($context);
  530. $self->InitGL;
  531. glClearColor(1, 1, 1, 1);
  532. glClearDepth(1);
  533. glDepthFunc(GL_LESS);
  534. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  535. glMatrixMode(GL_MODELVIEW);
  536. glLoadIdentity();
  537. if (TURNTABLE_MODE) {
  538. glRotatef(-$self->_stheta, 1, 0, 0); # pitch
  539. glRotatef($self->_sphi, 0, 0, 1); # yaw
  540. } else {
  541. my @rotmat = quat_to_rotmatrix($self->quat);
  542. glMultMatrixd_p(@rotmat[0..15]);
  543. }
  544. glTranslatef(@{ $self->_camera_target->negative });
  545. # light from above
  546. glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0);
  547. glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1);
  548. glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1);
  549. if ($self->enable_picking) {
  550. glDisable(GL_LIGHTING);
  551. $self->draw_volumes(1);
  552. glFlush();
  553. glFinish();
  554. if (my $pos = $self->_mouse_pos) {
  555. my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
  556. my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
  557. $self->_hover_volume_idx(undef);
  558. $_->hover(0) for @{$self->volumes};
  559. if ($volume_idx <= $#{$self->volumes}) {
  560. $self->_hover_volume_idx($volume_idx);
  561. $self->volumes->[$volume_idx]->hover(1);
  562. my $group_id = $self->volumes->[$volume_idx]->select_group_id;
  563. if ($group_id != -1) {
  564. $_->hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes};
  565. }
  566. $self->on_hover->($volume_idx) if $self->on_hover;
  567. }
  568. }
  569. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  570. glFlush();
  571. glFinish();
  572. glEnable(GL_LIGHTING);
  573. }
  574. # draw fixed background
  575. if ($self->background) {
  576. glDisable(GL_LIGHTING);
  577. glPushMatrix();
  578. glLoadIdentity();
  579. glMatrixMode(GL_PROJECTION);
  580. glPushMatrix();
  581. glLoadIdentity();
  582. glBegin(GL_QUADS);
  583. glColor3f(0.0,0.0,0.0);
  584. glVertex2f(-1.0,-1.0);
  585. glVertex2f(1,-1.0);
  586. glColor3f(10/255,98/255,144/255);
  587. glVertex2f(1, 1);
  588. glVertex2f(-1.0, 1);
  589. glEnd();
  590. glPopMatrix();
  591. glMatrixMode(GL_MODELVIEW);
  592. glPopMatrix();
  593. glEnable(GL_LIGHTING);
  594. }
  595. # draw ground and axes
  596. glDisable(GL_LIGHTING);
  597. # draw ground
  598. my $ground_z = GROUND_Z;
  599. if ($self->bed_triangles) {
  600. glDisable(GL_DEPTH_TEST);
  601. glEnable(GL_BLEND);
  602. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  603. glEnableClientState(GL_VERTEX_ARRAY);
  604. glColor4f(0.8, 0.6, 0.5, 0.4);
  605. glNormal3d(0,0,1);
  606. glVertexPointer_p(3, $self->bed_triangles);
  607. glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
  608. glDisableClientState(GL_VERTEX_ARRAY);
  609. # we need depth test for grid, otherwise it would disappear when looking
  610. # the object from below
  611. glEnable(GL_DEPTH_TEST);
  612. # draw grid
  613. glLineWidth(3);
  614. glColor4f(0.2, 0.2, 0.2, 0.4);
  615. glEnableClientState(GL_VERTEX_ARRAY);
  616. glVertexPointer_p(3, $self->bed_grid_lines);
  617. glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
  618. glDisableClientState(GL_VERTEX_ARRAY);
  619. glDisable(GL_BLEND);
  620. }
  621. my $volumes_bb = $self->volumes_bounding_box;
  622. {
  623. # draw axes
  624. # disable depth testing so that axes are not covered by ground
  625. glDisable(GL_DEPTH_TEST);
  626. my $origin = $self->origin;
  627. my $axis_len = max(
  628. 0.3 * max(@{ $self->bed_bounding_box->size }),
  629. 2 * max(@{ $volumes_bb->size }),
  630. );
  631. glLineWidth(2);
  632. glBegin(GL_LINES);
  633. # draw line for x axis
  634. glColor3f(1, 0, 0);
  635. glVertex3f(@$origin, $ground_z);
  636. glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
  637. # draw line for y axis
  638. glColor3f(0, 1, 0);
  639. glVertex3f(@$origin, $ground_z);
  640. glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
  641. glEnd();
  642. # draw line for Z axis
  643. # (re-enable depth test so that axis is correctly shown when objects are behind it)
  644. glEnable(GL_DEPTH_TEST);
  645. glBegin(GL_LINES);
  646. glColor3f(0, 0, 1);
  647. glVertex3f(@$origin, $ground_z);
  648. glVertex3f(@$origin, $ground_z+$axis_len);
  649. glEnd();
  650. }
  651. glEnable(GL_LIGHTING);
  652. # draw objects
  653. $self->draw_volumes;
  654. # draw cutting plane
  655. if (defined $self->cutting_plane_z) {
  656. my $plane_z = $self->cutting_plane_z;
  657. my $bb = $volumes_bb;
  658. glDisable(GL_CULL_FACE);
  659. glDisable(GL_LIGHTING);
  660. glEnable(GL_BLEND);
  661. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  662. glBegin(GL_QUADS);
  663. glColor4f(0.8, 0.8, 0.8, 0.5);
  664. glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
  665. glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
  666. glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
  667. glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
  668. glEnd();
  669. glEnable(GL_CULL_FACE);
  670. glDisable(GL_BLEND);
  671. }
  672. glFlush();
  673. $self->SwapBuffers();
  674. }
  675. sub draw_volumes {
  676. my ($self, $fakecolor) = @_;
  677. glEnable(GL_BLEND);
  678. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  679. glEnableClientState(GL_VERTEX_ARRAY);
  680. glEnableClientState(GL_NORMAL_ARRAY);
  681. foreach my $volume_idx (0..$#{$self->volumes}) {
  682. my $volume = $self->volumes->[$volume_idx];
  683. glPushMatrix();
  684. glTranslatef(@{$volume->origin});
  685. if ($fakecolor) {
  686. my $r = ($volume_idx & 0x000000FF) >> 0;
  687. my $g = ($volume_idx & 0x0000FF00) >> 8;
  688. my $b = ($volume_idx & 0x00FF0000) >> 16;
  689. glColor4f($r/255.0, $g/255.0, $b/255.0, 1);
  690. } elsif ($volume->selected) {
  691. glColor4f(@{ &SELECTED_COLOR });
  692. } elsif ($volume->hover) {
  693. glColor4f(@{ &HOVER_COLOR });
  694. } else {
  695. glColor4f(@{ $volume->color });
  696. }
  697. my @sorted_z = ();
  698. my ($min_z, $max_z);
  699. if ($volume->range) {
  700. @sorted_z = sort { $a <=> $b } keys %{$volume->offsets};
  701. ($min_z, $max_z) = @{$volume->range};
  702. $min_z = first { $_ >= $min_z } @sorted_z;
  703. $max_z = first { $_ > $max_z } @sorted_z;
  704. }
  705. glCullFace(GL_BACK);
  706. if ($volume->quad_verts) {
  707. my ($min_offset, $max_offset);
  708. if (defined $min_z) {
  709. $min_offset = $volume->offsets->{$min_z}->[0];
  710. }
  711. if (defined $max_z) {
  712. $max_offset = $volume->offsets->{$max_z}->[0];
  713. }
  714. $min_offset //= 0;
  715. $max_offset //= $volume->quad_verts->elements;
  716. glVertexPointer_p(3, $volume->quad_verts);
  717. glNormalPointer_p($volume->quad_norms);
  718. glDrawArrays(GL_QUADS, $min_offset / 3, ($max_offset-$min_offset) / 3);
  719. }
  720. if ($volume->verts) {
  721. my ($min_offset, $max_offset);
  722. if (defined $min_z) {
  723. $min_offset = $volume->offsets->{$min_z}->[1];
  724. }
  725. if (defined $max_z) {
  726. $max_offset = $volume->offsets->{$max_z}->[1];
  727. }
  728. $min_offset //= 0;
  729. $max_offset //= $volume->verts->elements;
  730. glVertexPointer_p(3, $volume->verts);
  731. glNormalPointer_p($volume->norms);
  732. glDrawArrays(GL_TRIANGLES, $min_offset / 3, ($max_offset-$min_offset) / 3);
  733. }
  734. glPopMatrix();
  735. }
  736. glDisableClientState(GL_NORMAL_ARRAY);
  737. glDisable(GL_BLEND);
  738. if (defined $self->cutting_plane_z) {
  739. glLineWidth(2);
  740. glColor3f(0, 0, 0);
  741. glVertexPointer_p(3, $self->cut_lines_vertices);
  742. glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3);
  743. }
  744. glDisableClientState(GL_VERTEX_ARRAY);
  745. }
  746. package Slic3r::GUI::3DScene::Volume;
  747. use Moo;
  748. has 'bounding_box' => (is => 'ro', required => 1);
  749. has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) });
  750. has 'color' => (is => 'ro', required => 1);
  751. has 'select_group_id' => (is => 'rw', default => sub { -1 });
  752. has 'drag_group_id' => (is => 'rw', default => sub { -1 });
  753. has 'selected' => (is => 'rw', default => sub { 0 });
  754. has 'hover' => (is => 'rw', default => sub { 0 });
  755. has 'range' => (is => 'rw');
  756. # geometric data
  757. has 'quad_verts' => (is => 'rw'); # OpenGL::Array object
  758. has 'quad_norms' => (is => 'rw'); # OpenGL::Array object
  759. has 'verts' => (is => 'rw'); # OpenGL::Array object
  760. has 'norms' => (is => 'rw'); # OpenGL::Array object
  761. has 'mesh' => (is => 'rw'); # only required for cut contours
  762. has 'offsets' => (is => 'rw'); # [ z => [ quad_verts_idx, verts_idx ] ]
  763. sub transformed_bounding_box {
  764. my ($self) = @_;
  765. my $bb = $self->bounding_box;
  766. $bb->translate(@{$self->origin});
  767. return $bb;
  768. }
  769. package Slic3r::GUI::3DScene;
  770. use base qw(Slic3r::GUI::3DScene::Base);
  771. use OpenGL qw(:glconstants :gluconstants :glufunctions);
  772. use List::Util qw(first);
  773. use Slic3r::Geometry qw(scale unscale epsilon);
  774. use Slic3r::Print::State ':steps';
  775. use constant COLORS => [ [1,1,0,1], [1,0.5,0.5,1], [0.5,1,0.5,1], [0.5,0.5,1,1] ];
  776. __PACKAGE__->mk_accessors(qw(
  777. color_by
  778. select_by
  779. drag_by
  780. volumes_by_object
  781. _objects_by_volumes
  782. ));
  783. sub new {
  784. my $class = shift;
  785. my $self = $class->SUPER::new(@_);
  786. $self->color_by('volume'); # object | volume
  787. $self->select_by('object'); # object | volume | instance
  788. $self->drag_by('instance'); # object | instance
  789. $self->volumes_by_object({}); # obj_idx => [ volume_idx, volume_idx ... ]
  790. $self->_objects_by_volumes({}); # volume_idx => [ obj_idx, instance_idx ]
  791. return $self;
  792. }
  793. sub load_object {
  794. my ($self, $model, $obj_idx, $instance_idxs) = @_;
  795. my $model_object;
  796. if ($model->isa('Slic3r::Model::Object')) {
  797. $model_object = $model;
  798. $model = $model_object->model;
  799. $obj_idx = 0;
  800. } else {
  801. $model_object = $model->get_object($obj_idx);
  802. }
  803. $instance_idxs ||= [0..$#{$model_object->instances}];
  804. my @volumes_idx = ();
  805. foreach my $volume_idx (0..$#{$model_object->volumes}) {
  806. my $volume = $model_object->volumes->[$volume_idx];
  807. foreach my $instance_idx (@$instance_idxs) {
  808. my $instance = $model_object->instances->[$instance_idx];
  809. my $mesh = $volume->mesh->clone;
  810. $instance->transform_mesh($mesh);
  811. my $color_idx;
  812. if ($self->color_by eq 'volume') {
  813. $color_idx = $volume_idx;
  814. } elsif ($self->color_by eq 'object') {
  815. $color_idx = $obj_idx;
  816. }
  817. my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ];
  818. $color->[3] = $volume->modifier ? 0.5 : 1;
  819. push @{$self->volumes}, my $v = Slic3r::GUI::3DScene::Volume->new(
  820. bounding_box => $mesh->bounding_box,
  821. color => $color,
  822. );
  823. $v->mesh($mesh) if $self->enable_cutting;
  824. if ($self->select_by eq 'object') {
  825. $v->select_group_id($obj_idx*1000000);
  826. } elsif ($self->select_by eq 'volume') {
  827. $v->select_group_id($obj_idx*1000000 + $volume_idx*1000);
  828. } elsif ($self->select_by eq 'instance') {
  829. $v->select_group_id($obj_idx*1000000 + $volume_idx*1000 + $instance_idx);
  830. }
  831. if ($self->drag_by eq 'object') {
  832. $v->drag_group_id($obj_idx*1000);
  833. } elsif ($self->drag_by eq 'instance') {
  834. $v->drag_group_id($obj_idx*1000 + $instance_idx);
  835. }
  836. push @volumes_idx, my $scene_volume_idx = $#{$self->volumes};
  837. $self->_objects_by_volumes->{$scene_volume_idx} = [ $obj_idx, $volume_idx, $instance_idx ];
  838. {
  839. my $vertices = $mesh->vertices;
  840. my @verts = map @{ $vertices->[$_] }, map @$_, @{$mesh->facets};
  841. $v->verts(OpenGL::Array->new_list(GL_FLOAT, @verts));
  842. }
  843. {
  844. my @norms = map { @$_, @$_, @$_ } @{$mesh->normals};
  845. $v->norms(OpenGL::Array->new_list(GL_FLOAT, @norms));
  846. }
  847. }
  848. }
  849. $self->volumes_by_object->{$obj_idx} = [@volumes_idx];
  850. return @volumes_idx;
  851. }
  852. sub load_print_object_slices {
  853. my ($self, $object) = @_;
  854. my @verts = ();
  855. my @norms = ();
  856. my @quad_verts = ();
  857. my @quad_norms = ();
  858. foreach my $layer (@{$object->layers}) {
  859. my $gap = 0;
  860. my $top_z = $layer->print_z;
  861. my $bottom_z = $layer->print_z - $layer->height + $gap;
  862. foreach my $copy (@{ $object->_shifted_copies }) {
  863. {
  864. my @expolygons = map $_->clone, @{$layer->slices};
  865. $_->translate(@$copy) for @expolygons;
  866. $self->_expolygons_to_verts(\@expolygons, $layer->print_z, \@verts, \@norms);
  867. }
  868. foreach my $slice (@{$layer->slices}) {
  869. foreach my $polygon (@$slice) {
  870. foreach my $line (@{$polygon->lines}) {
  871. $line->translate(@$copy);
  872. push @quad_norms, (0,0,-1), (0,0,-1);
  873. push @quad_verts, (map unscale($_), @{$line->a}), $bottom_z;
  874. push @quad_verts, (map unscale($_), @{$line->b}), $bottom_z;
  875. push @quad_norms, (0,0,1), (0,0,1);
  876. push @quad_verts, (map unscale($_), @{$line->b}), $top_z;
  877. push @quad_verts, (map unscale($_), @{$line->a}), $top_z;
  878. # We'll use this for the middle normal when using 4 quads:
  879. #my $xy_normal = $line->normal;
  880. #$_xynormal->scale(1/$line->length);
  881. }
  882. }
  883. }
  884. }
  885. }
  886. my $obb = $object->bounding_box;
  887. my $bb = Slic3r::Geometry::BoundingBoxf3->new;
  888. $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$obb->min_point}, 0));
  889. $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$obb->max_point}, $object->size->z));
  890. push @{$self->volumes}, my $v = Slic3r::GUI::3DScene::Volume->new(
  891. bounding_box => $bb,
  892. color => COLORS->[0],
  893. verts => OpenGL::Array->new_list(GL_FLOAT, @verts),
  894. norms => OpenGL::Array->new_list(GL_FLOAT, @norms),
  895. quad_verts => OpenGL::Array->new_list(GL_FLOAT, @quad_verts),
  896. quad_norms => OpenGL::Array->new_list(GL_FLOAT, @quad_norms),
  897. );
  898. }
  899. sub load_print_object_toolpaths {
  900. my ($self, $object) = @_;
  901. my (@perim_qverts, @perim_qnorms, @perim_tverts, @perim_tnorms) = ();
  902. my (@infill_qverts, @infill_qnorms, @infill_tverts, @infill_tnorms) = ();
  903. my (@support_qverts, @support_qnorms, @support_tverts, @support_tnorms) = ();
  904. my %perim_offsets = (); # print_z => [ qverts, tverts ]
  905. my %infill_offsets = ();
  906. my %support_offsets = ();
  907. # order layers by print_z
  908. my @layers = sort { $a->print_z <=> $b->print_z }
  909. @{$object->layers}, @{$object->support_layers};
  910. foreach my $layer (@layers) {
  911. my $top_z = $layer->print_z;
  912. if (!exists $perim_offsets{$top_z}) {
  913. $perim_offsets{$top_z} = [
  914. scalar(@perim_qverts), scalar(@perim_tverts),
  915. ];
  916. $infill_offsets{$top_z} = [
  917. scalar(@infill_qverts), scalar(@infill_tverts),
  918. ];
  919. $support_offsets{$top_z} = [
  920. scalar(@support_qverts), scalar(@support_tverts),
  921. ];
  922. }
  923. foreach my $copy (@{ $object->_shifted_copies }) {
  924. foreach my $layerm (@{$layer->regions}) {
  925. if ($object->step_done(STEP_PERIMETERS)) {
  926. $self->_extrusionentity_to_verts($layerm->perimeters, $top_z, $copy,
  927. \@perim_qverts, \@perim_qnorms, \@perim_tverts, \@perim_tnorms);
  928. }
  929. if ($object->step_done(STEP_INFILL)) {
  930. $self->_extrusionentity_to_verts($layerm->fills, $top_z, $copy,
  931. \@infill_qverts, \@infill_qnorms, \@infill_tverts, \@infill_tnorms);
  932. }
  933. }
  934. if ($layer->isa('Slic3r::Layer::Support') && $object->step_done(STEP_SUPPORTMATERIAL)) {
  935. $self->_extrusionentity_to_verts($layer->support_fills, $top_z, $copy,
  936. \@support_qverts, \@support_qnorms, \@support_tverts, \@support_tnorms);
  937. $self->_extrusionentity_to_verts($layer->support_interface_fills, $top_z, $copy,
  938. \@support_qverts, \@support_qnorms, \@support_tverts, \@support_tnorms);
  939. }
  940. }
  941. }
  942. my $obb = $object->bounding_box;
  943. my $bb = Slic3r::Geometry::BoundingBoxf3->new;
  944. foreach my $copy (@{ $object->_shifted_copies }) {
  945. my $cbb = $obb->clone;
  946. $cbb->translate(@$copy);
  947. $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->min_point}, 0));
  948. $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->max_point}, $object->size->z));
  949. }
  950. push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new(
  951. bounding_box => $bb,
  952. color => COLORS->[0],
  953. quad_verts => OpenGL::Array->new_list(GL_FLOAT, @perim_qverts),
  954. quad_norms => OpenGL::Array->new_list(GL_FLOAT, @perim_qnorms),
  955. verts => OpenGL::Array->new_list(GL_FLOAT, @perim_tverts),
  956. norms => OpenGL::Array->new_list(GL_FLOAT, @perim_tnorms),
  957. offsets => { %perim_offsets },
  958. );
  959. push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new(
  960. bounding_box => $bb,
  961. color => COLORS->[1],
  962. quad_verts => OpenGL::Array->new_list(GL_FLOAT, @infill_qverts),
  963. quad_norms => OpenGL::Array->new_list(GL_FLOAT, @infill_qnorms),
  964. verts => OpenGL::Array->new_list(GL_FLOAT, @infill_tverts),
  965. norms => OpenGL::Array->new_list(GL_FLOAT, @infill_tnorms),
  966. offsets => { %infill_offsets },
  967. );
  968. push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new(
  969. bounding_box => $bb,
  970. color => COLORS->[2],
  971. quad_verts => OpenGL::Array->new_list(GL_FLOAT, @support_qverts),
  972. quad_norms => OpenGL::Array->new_list(GL_FLOAT, @support_qnorms),
  973. verts => OpenGL::Array->new_list(GL_FLOAT, @support_tverts),
  974. norms => OpenGL::Array->new_list(GL_FLOAT, @support_tnorms),
  975. offsets => { %support_offsets },
  976. );
  977. }
  978. sub set_toolpaths_range {
  979. my ($self, $min_z, $max_z) = @_;
  980. foreach my $volume (@{$self->volumes}) {
  981. $volume->range([ $min_z, $max_z ]);
  982. }
  983. }
  984. sub _expolygons_to_verts {
  985. my ($self, $expolygons, $z, $verts, $norms) = @_;
  986. my $tess = gluNewTess();
  987. gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT');
  988. gluTessCallback($tess, GLU_TESS_END, 'DEFAULT');
  989. gluTessCallback($tess, GLU_TESS_VERTEX, sub {
  990. my ($x, $y, $z) = @_;
  991. push @$verts, $x, $y, $z;
  992. push @$norms, (0,0,1), (0,0,1), (0,0,1);
  993. });
  994. gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT');
  995. gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT');
  996. gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
  997. foreach my $expolygon (@$expolygons) {
  998. gluTessBeginPolygon($tess);
  999. foreach my $polygon (@$expolygon) {
  1000. gluTessBeginContour($tess);
  1001. gluTessVertex_p($tess, (map unscale($_), @$_), $z) for @$polygon;
  1002. gluTessEndContour($tess);
  1003. }
  1004. gluTessEndPolygon($tess);
  1005. }
  1006. gluDeleteTess($tess);
  1007. }
  1008. sub _extrusionentity_to_verts {
  1009. my ($self, $entity, $top_z, $copy, $qverts, $qnorms, $tverts, $tnorms) = @_;
  1010. my ($lines, $widths, $heights, $closed);
  1011. if ($entity->isa('Slic3r::ExtrusionPath::Collection')) {
  1012. $self->_extrusionentity_to_verts($_, $top_z, $copy, $qverts, $qnorms, $tverts, $tnorms)
  1013. for @$entity;
  1014. return;
  1015. } elsif ($entity->isa('Slic3r::ExtrusionPath')) {
  1016. my $polyline = $entity->polyline->clone;
  1017. $polyline->translate(@$copy);
  1018. $lines = $polyline->lines;
  1019. $widths = [ map $entity->width, 0..$#$lines ];
  1020. $heights = [ map $entity->height, 0..$#$lines ];
  1021. $closed = 0;
  1022. } else {
  1023. $lines = [];
  1024. $widths = [];
  1025. $heights = [];
  1026. $closed = 1;
  1027. foreach my $path (@$entity) {
  1028. my $polyline = $path->polyline->clone;
  1029. $polyline->translate(@$copy);
  1030. my $path_lines = $polyline->lines;
  1031. push @$lines, @$path_lines;
  1032. push @$widths, map $path->width, 0..$#$path_lines;
  1033. push @$heights, map $path->height, 0..$#$path_lines;
  1034. }
  1035. }
  1036. Slic3r::GUI::_3DScene::_extrusionentity_to_verts_do($lines, $widths, $heights,
  1037. $closed, $top_z, $copy, $qverts, $qnorms, $tverts, $tnorms);
  1038. }
  1039. sub object_idx {
  1040. my ($self, $volume_idx) = @_;
  1041. return $self->_objects_by_volumes->{$volume_idx}[0];
  1042. }
  1043. sub volume_idx {
  1044. my ($self, $volume_idx) = @_;
  1045. return $self->_objects_by_volumes->{$volume_idx}[1];
  1046. }
  1047. sub instance_idx {
  1048. my ($self, $volume_idx) = @_;
  1049. return $self->_objects_by_volumes->{$volume_idx}[2];
  1050. }
  1051. 1;