def __init__(self, pdf_panner2d, pdf_filepath, *args, **kwargs): TQGraphicsNodePath.__init__(self, *args, **kwargs) # logical variables self.pdf_filepath = pdf_filepath self.pptos = [] self.y_pages_distance = PDFViewer.y_pages_distance_0 # derived self.pdf_panner2d = pdf_panner2d # something like Panner2d self.ppr = PopplerPDFRenderer(self.pdf_filepath) # direct objects base.accept('1', self.return_to_middle_view) base.accept('space', self.scroll_spacebar_down) base.accept('shift-space', self.scroll_spacebar_up) base.accept('control-0', self.return_to_middle_view) # quad visualizing large margins for visual orientation self.margins_quad = Quad(thickness=10.) self.margins_quad.reparentTo(self) # plot self.render_pages() self.return_to_middle_view()
class DraggableFrame(TQGraphicsNodePath): """ """ def __init__(self, camera_gear, height=None, width=None, **kwargs): """ """ height_0 = 0.5 width_0 = 16 / 9 * height_0 TQGraphicsNodePath.__init__(self, **kwargs) self.camera_gear = camera_gear self.drag_point = Point3d() self.drag_point.reparentTo(engine.tq_graphics_basics.tq_render) self.drag_point.setColor(Vec4(1.0, 0., 0., 1.), 1) self.quad_border_thickness = None if "quad_border_thickness" in kwargs: self.quad_border_thickness = kwargs.get("quad_border_thickness") elif self.quad_border_thickness is None: self.quad_border_thickness = 1.5 self.bg_quad = Quad(thickness=self.quad_border_thickness) if height is None: height = height_0 if width is None: width = width_0 self.bg_quad.set_height(height) self.bg_quad.set_width(width) self.bg_quad.reparentTo(self) self.bg_quad.setPos(Vec3(0., 0., 0.)) self.bg_quad.setColor(Vec4(1., 1., 1., 0.5), 1) # ------------------------------------- self.pom = PickableObjectManager() self.pom.tag(self.drag_point.get_p3d_nodepath()) self.dadom = DragAndDropObjectsManager() self.pt_dragger = PickableObjectDragger(self.drag_point, self.camera_gear) self.pt_dragger.add_on_state_change_function( self.move_frame_when_dragged) self.dadom.add_dragger(self.pt_dragger) self.collisionPicker = CollisionPicker( camera_gear, engine.tq_graphics_basics.tq_render, base.mouseWatcherNode, self.dadom) # -- add a mouse task to check for picking self.p3d_draw_direct_object = DirectObject.DirectObject() self.p3d_draw_direct_object.accept('mouse1', self.collisionPicker.onMouseTask) def update_logical_position_from_drag_point(self): new_handle_pos = self.drag_point.getPos() self.v0 = new_handle_pos
def update(self): if self.is_done_function() == False: if self.x_pos_left_border is None: self.x_pos_left_border = -1.0 x_pos_waiting_symbol = self.x_pos_left_border + 0.1 x_pos_text = x_pos_waiting_symbol + 0.2 if self.y_pos_top_box is None: self.y_pos_top_box = 0. y_pos_waiting_symbol = self.y_pos_top_box - 0.05 y_pos_text = self.y_pos_top_box - 0.125 x_pos_elapsed_time = x_pos_text + 0.4 y_pos_elapsed_time = y_pos_text quad_thickness = 2.0 if self.waiting_symbol is None: self.waiting_symbol = WaitingSymbol(self.is_done_function, Vec3(x_pos_waiting_symbol, 0., y_pos_waiting_symbol), size=0.1) self.waiting_symbol.reparentTo(self) else: self.waiting_symbol.set_position(Vec3(x_pos_waiting_symbol, 0., y_pos_waiting_symbol)) if self.text is None: self.text = Fixed2dLabel(text=self.description, font="fonts/arial.egg", x=x_pos_text, y=y_pos_text) self.text.reparentTo(self) else: self.text.setPos(x_pos_text, y_pos_text) if self.elapsed_time_text is None: self.elapsed_time_text = Fixed2dLabel(text="elapsed time", font="fonts/arial.egg", x=x_pos_elapsed_time, y=y_pos_elapsed_time) self.elapsed_time_text.reparentTo(self) else: self.elapsed_time_text.setPos(x_pos_elapsed_time, y_pos_elapsed_time) elapsed_time = (time.time_ns() - self.time_initial) / 1.0e9 self.elapsed_time_text.setText("{0:.0f} s".format(elapsed_time)) if self.quad is None: self.quad = Quad(thickness=quad_thickness, TQGraphicsNodePath_creation_parent_node=engine.tq_graphics_basics.tq_aspect2d) self.quad.reparentTo(self) self.quad.set_pos_vec3(Vec3(self.x_pos_left_border, 0., self.y_pos_top_box)) self.quad.set_height(self.height) self.quad.set_width(self.width) else: self.quad.set_pos_vec3(Vec3(self.x_pos_left_border, 0., self.y_pos_top_box)) return True # call task.cont return False # call task.done
def __init__(self, done_function, position, size=0.2, frequency=1): """ Args: done_function: function which controls when the waiting symbol animation should be stopped (when True) position: Vec3 of the upper left hand corner in p3d coordinates """ # -- logical self.done_function = done_function self.position = position self.size = size self.frequency = frequency self.duration = 1. self.a = None TQGraphicsNodePath.__init__(self) # -- supporting graphics self.quad = Quad(thickness=3.0, TQGraphicsNodePath_creation_parent_node=engine. tq_graphics_basics.tq_aspect2d) self.quad.reparentTo(self) self.quad.set_pos_vec3(self.position) self.quad.set_height(self.size) self.quad.set_width(self.size) self.line = Line1dSolid(thickness=3.0, TQGraphicsNodePath_creation_parent_node=engine. tq_graphics_basics.tq_aspect2d) self.line.reparentTo(self) self.anim_seq = Sequence() self.anim_seq.set_sequence_params( duration=self.duration, extraArgs=[], update_function=self.update, on_finish_function=self.restart_animation) self.anim_seq.start()
class WaitingSymbol(TQGraphicsNodePath): """ An animation that runs as long as done_function returns False. """ def __init__(self, done_function, position, size=0.2, frequency=1): """ Args: done_function: function which controls when the waiting symbol animation should be stopped (when True) position: Vec3 of the upper left hand corner in p3d coordinates """ # -- logical self.done_function = done_function self.position = position self.size = size self.frequency = frequency self.duration = 1. self.a = None TQGraphicsNodePath.__init__(self) # -- supporting graphics self.quad = Quad(thickness=3.0, TQGraphicsNodePath_creation_parent_node=engine. tq_graphics_basics.tq_aspect2d) self.quad.reparentTo(self) self.quad.set_pos_vec3(self.position) self.quad.set_height(self.size) self.quad.set_width(self.size) self.line = Line1dSolid(thickness=3.0, TQGraphicsNodePath_creation_parent_node=engine. tq_graphics_basics.tq_aspect2d) self.line.reparentTo(self) self.anim_seq = Sequence() self.anim_seq.set_sequence_params( duration=self.duration, extraArgs=[], update_function=self.update, on_finish_function=self.restart_animation) self.anim_seq.start() def restart_animation(self): self.anim_seq.set_t(0.) self.anim_seq.resume() def _get_center(self): return Vec3(self.position[0] + 0.5 * self.size, 0., self.position[2] - 0.5 * self.size) def set_position(self, vec3_pos): self.position = vec3_pos self.update(self.a) def update(self, a): """ a is in between 0 and 1 """ self.a = a if self.done_function() == True: self.remove() return elif self.done_function() != False: raise ValueError("self.done_function() should return Boolean") t = a * self.duration omega = 2 * np.pi * self.frequency theta = omega * t r = self.size * 0.4 p1 = self._get_center() p2 = p1 + Vec3(r * np.cos(theta), 0., r * np.sin(theta)) self.line.setTailPoint(p1) self.line.setTipPoint(p2) self.quad.set_pos_vec3(self.position) def remove(self): """ release all resources """ self.anim_seq.remove() self.line.remove() self.quad.remove()
def __init__( self, camera_gear=None, update_labels_orientation=False, with_ticks=True, attach_to_space="aspect2d", # or "render", attach_directly=True): """ """ TQGraphicsNodePath.__init__(self) self.attached_p = False if attach_to_space == "aspect2d": if update_labels_orientation == True: print( "WARNING: do not set attach_to_space to aspect2d and update_labels_orientation=True simultaneously!" ) update_labels_orientation = False if attach_to_space == "render": if camera_gear is None: print( "ERR: if attach_to_space == render, supply a camera_gear as well that is not None!" ) exit(1) self.attach_to_space = attach_to_space # "render" or "aspect2d" self.quad = None # if camera_gear == None: # self.update_labels_orientation == False # if update_labels_orientation == True and camera_gear == None: # print("WARNING: if update_labels_orientation == True, camera_gear cannot be None") # exit(1) self.camera_gear = camera_gear self.update_labels_orientation = update_labels_orientation size = 0.5 self.height = size self.width = size * 1.618 # self.lines = [] # lines # self.linesin2dframe = None self.linesin2dframe = LinesIn2dFrame() self.linesin2dframe.reparentTo(self) self.with_ticks = with_ticks self.d = 2 # dimension of frame self.xmin_hard = None self.xmax_hard = None self.ymin_hard = None self.ymax_hard = None self._xmin_soft = 0. self._xmax_soft = 1. self._ymin_soft = 0. self._ymax_soft = 1. self.axes_ticks_numbers = None self.regenerate_axes_ticks_numbers() self.clipping_planes_p3d_nodepaths = [] self.quad = Quad(thickness=1.5) self.quad.reparentTo(self) # --- Ticks ----- print("with_ticks: ", self.with_ticks) if self.with_ticks == True: self.axes_ticks = [] for i, n_vec in zip(range(self.d), Frame2d.axis_direction_vectors): ticks = Ticks( camera_gear, update_labels_orientation=self.update_labels_orientation, attach_to_space="aspect2d") self.axes_ticks.append(ticks) ticks.reparentTo(self) ticks.setMat_normal( math_utils.getMat4by4_to_rotate_xhat_to_vector(n_vec)) # h, p, r = ticks.getHpr() # ticks.setHpr(h, p , r - i * 90.) self.regenerate_ticks() self.update_alignment() if attach_directly == True and self.attached_p == False: # print("Attaching directly") if attach_to_space == "aspect2d": self.attach_to_aspect2d() elif attach_to_space == "render": self.attach_to_render() else: exit(1)
class Frame2d(TQGraphicsNodePath): """ a 2d frame within which 2d data can be displayed, i.e. numpy x and y arrays """ axis_direction_indices = [0, 1] # 0 for x axis, 1 for y axis axis_direction_indices_chars = ["x", "y"] axis_direction_vectors = [Vec3(1., 0., 0.), Vec3(0., 0., 1.)] def __init__( self, camera_gear=None, update_labels_orientation=False, with_ticks=True, attach_to_space="aspect2d", # or "render", attach_directly=True): """ """ TQGraphicsNodePath.__init__(self) self.attached_p = False if attach_to_space == "aspect2d": if update_labels_orientation == True: print( "WARNING: do not set attach_to_space to aspect2d and update_labels_orientation=True simultaneously!" ) update_labels_orientation = False if attach_to_space == "render": if camera_gear is None: print( "ERR: if attach_to_space == render, supply a camera_gear as well that is not None!" ) exit(1) self.attach_to_space = attach_to_space # "render" or "aspect2d" self.quad = None # if camera_gear == None: # self.update_labels_orientation == False # if update_labels_orientation == True and camera_gear == None: # print("WARNING: if update_labels_orientation == True, camera_gear cannot be None") # exit(1) self.camera_gear = camera_gear self.update_labels_orientation = update_labels_orientation size = 0.5 self.height = size self.width = size * 1.618 # self.lines = [] # lines # self.linesin2dframe = None self.linesin2dframe = LinesIn2dFrame() self.linesin2dframe.reparentTo(self) self.with_ticks = with_ticks self.d = 2 # dimension of frame self.xmin_hard = None self.xmax_hard = None self.ymin_hard = None self.ymax_hard = None self._xmin_soft = 0. self._xmax_soft = 1. self._ymin_soft = 0. self._ymax_soft = 1. self.axes_ticks_numbers = None self.regenerate_axes_ticks_numbers() self.clipping_planes_p3d_nodepaths = [] self.quad = Quad(thickness=1.5) self.quad.reparentTo(self) # --- Ticks ----- print("with_ticks: ", self.with_ticks) if self.with_ticks == True: self.axes_ticks = [] for i, n_vec in zip(range(self.d), Frame2d.axis_direction_vectors): ticks = Ticks( camera_gear, update_labels_orientation=self.update_labels_orientation, attach_to_space="aspect2d") self.axes_ticks.append(ticks) ticks.reparentTo(self) ticks.setMat_normal( math_utils.getMat4by4_to_rotate_xhat_to_vector(n_vec)) # h, p, r = ticks.getHpr() # ticks.setHpr(h, p , r - i * 90.) self.regenerate_ticks() self.update_alignment() if attach_directly == True and self.attached_p == False: # print("Attaching directly") if attach_to_space == "aspect2d": self.attach_to_aspect2d() elif attach_to_space == "render": self.attach_to_render() else: exit(1) def toggle_clipping_planes(self): """ """ if len(self.clipping_planes_p3d_nodepaths ) == 0: # no clipping planes enabled self.turn_clipping_planes_on() else: self.turn_clipping_planes_off() def turn_clipping_planes_off(self): """ """ for nodepath in self.clipping_planes_p3d_nodepaths: nodepath.removeNode() self.linesin2dframe.get_p3d_nodepath().setClipPlaneOff() self.clipping_planes_p3d_nodepaths = [] def turn_clipping_planes_on(self): """ the lines may extend outside of the 'frame' setting clipping planes are one way to prevent them from being rendered outside if dimensions of the frame are updated, set_clipping_panel_geometry must be called before this with the appriopriate panel geometry """ self.turn_clipping_planes_off() for i, normal_vector in zip(Frame2d.axis_direction_indices, Frame2d.axis_direction_vectors): d = self.get_p3d_axis_length_from_axis_direction_index(i) a, b, c = math_utils.p3d_to_np(normal_vector) plane1 = LPlanef(a, b, c, 0) plane1_node = PlaneNode('', plane1) plane1_node.setClipEffect(1) plane1_nodepath = NodePath(plane1_node) plane2 = LPlanef(-a, -b, -c, d) plane2_node = PlaneNode('', plane2) plane2_node.setClipEffect(1) plane2_nodepath = NodePath(plane2_node) clipped_thing_nodepath = self.linesin2dframe.get_p3d_nodepath() plane1_nodepath.reparentTo(clipped_thing_nodepath) clipped_thing_nodepath.setClipPlane(plane1_nodepath) self.clipping_planes_p3d_nodepaths.append(plane1_nodepath) plane2_nodepath.reparentTo(clipped_thing_nodepath) clipped_thing_nodepath.setClipPlane(plane2_nodepath) self.clipping_planes_p3d_nodepaths.append(plane2_nodepath) @staticmethod def get_axis_direction_index_from_char_index(char_index): if x_or_y_str == "x": return 0 elif x_or_y_str == "y": return 1 assert False def regenerate_ticks(self): """ make sure the density of ticks is between ticks_max_density and ticks_min_density, then adjust the ticks """ self.regenerate_axes_ticks_numbers( possibly_update_lims_from_internal_data=True) for i, axis_p3d_length, axis_direction_index in zip( range(self.d), [self.width, self.height], Frame2d.axis_direction_indices): self.axes_ticks[i].regenerate_ticks_graphics( self.axes_ticks_numbers[i], axis_p3d_length, axis_direction_index=axis_direction_index) def set_figsize(self, width, height, update_graphics=True): """ set the size of the figure, in p3d units """ self.height = height self.width = width self.quad.set_height(self.height) self.quad.set_width(self.width) if update_graphics == True: self.quad.setPos(Vec3(0., 0., self.height)) self.turn_clipping_planes_on() self.update_graphics_alignment() def update_graphics_alignment(self): """ """ if self.with_ticks == True: self.regenerate_ticks() self.update_alignment() def lims_hard_p(self): """ """ return [ self.xmin_hard is not None or self.xmax_hard is not None, self.ymin_hard is not None or self.ymax_hard is not None ] def lims_fully_hard(self): """ """ return (self.xmin_hard is not None and self.xmax_hard is not None and self.ymin_hard is not None and self.ymax_hard is not None) def get_lims_from_internal_data(self): """ TODO: update this if to take into account not just lines, but also e.g. scatter data or color plot data """ xmin = np.inf xmax = -np.inf ymin = np.inf ymax = -np.inf changed = False for line in self.linesin2dframe.lines: p3d_xyz_coords = line.getCoords_np() frame_x_coords = p3d_xyz_coords[:, 0] frame_y_coords = p3d_xyz_coords[:, 2] # in p3d, z is up, but in my frame2d, y is up cur_xmin = min(frame_x_coords) if cur_xmin < xmin: xmin = cur_xmin changed = True cur_xmax = max(frame_x_coords) if cur_xmax > xmax: xmax = cur_xmax changed = True cur_ymin = min(frame_y_coords) if cur_ymin < ymin: ymin = cur_ymin changed = True cur_ymax = max(frame_y_coords) if cur_ymax > ymax: ymax = cur_ymax changed = True _vars = [xmin, xmax, ymin, ymax] if np.inf in _vars or -np.inf in _vars: # print("ERR: infinity is not a valid axes limit!") return [] else: # set an offset to the data # offset the margin of the plot by 0.1 times the span of the displayed data margin_factor = 0.05 xspan = np.abs(xmax - xmin) offset = xspan * margin_factor xmin -= offset xmax += offset yspan = np.abs(ymax - ymin) offset = yspan * margin_factor ymin -= offset ymax += offset return [(xmin, xmax), (ymin, ymax)] def regenerate_axes_ticks_numbers( self, possibly_update_lims_from_internal_data=False): """ """ if possibly_update_lims_from_internal_data == True: # set soft xlims if xlims are not hard auto_lims = self.get_lims_from_internal_data() if len( auto_lims ) > 0: # auto_lims can not be determined if there is no data if not self.lims_hard_p()[0]: # x axis self.set_xlim(*auto_lims[0], update_graphics=False, hard=False, regenerate=False) if not self.lims_hard_p()[1]: # y axis self.set_ylim(*auto_lims[1], update_graphics=False, hard=False, regenerate=False) self.axes_ticks_numbers = [ np.linspace(*self.get_preceding_xlims(), num=5, endpoint=True), np.linspace(*self.get_preceding_ylims(), num=5, endpoint=True) ] def set_xlim(self, xmin, xmax, update_graphics=True, hard=True, regenerate=True): """ if hard == False, figure out the preceding limits automatically from the line data """ if math_utils.equal_up_to_epsilon(xmin, xmax): span_abs = np.abs(xmax - xmin) center = xmin + span_abs / 2. padding0 = 0.1 xmin = center - padding0 xmax = center + padding0 if hard == True: self.xmin_hard = xmin self.xmax_hard = xmax elif hard == False: self.unset_hard_xylims(reset_x_lim=True, reset_y_lim=False) self._xmin_soft = xmin self._xmax_soft = xmax else: print("hard should be boolean") exit(1) if regenerate == True: self.regenerate_axes_ticks_numbers() if update_graphics == True: self.update_graphics_alignment() def unset_hard_xylims(self, reset_x_lim=True, reset_y_lim=True): """ """ if reset_x_lim == True: self.xmax_hard = None self.xmin_hard = None assert self._xmax_soft is not None assert self._xmin_soft is not None if reset_y_lim == True: self.ymax_hard = None self.ymin_hard = None assert self._ymax_soft is not None assert self._ymin_soft is not None def get_preceding_xlims(self): """ """ return ((self.xmin_hard if self.xmin_hard is not None else self._xmin_soft), (self.xmax_hard if self.xmax_hard is not None else self._xmax_soft)) def get_preceding_ylims(self): """ """ return ((self.ymin_hard if self.ymin_hard is not None else self._ymin_soft), (self.ymax_hard if self.ymax_hard is not None else self._ymax_soft)) def set_ylim(self, ymin, ymax, update_graphics=True, hard=True, regenerate=True): """ if hard == False, figure out the preceding limits automatically from the line data """ if math_utils.equal_up_to_epsilon(ymin, ymax): span_abs = np.abs(ymax - ymin) center = ymin + span_abs / 2. padding0 = 0.1 ymin = center - padding0 ymax = center + padding0 if hard == True: self.ymin_hard = ymin self.ymax_hard = ymax elif hard == False: self.unset_hard_xylims(reset_x_lim=False, reset_y_lim=True) self._ymin_soft = ymin self._ymax_soft = ymax else: print("hard should be boolean") exit(1) self.regenerate_axes_ticks_numbers() if update_graphics == True: self.update_graphics_alignment() def get_p3d_to_frame_unit_length_scaling_factor(self, axis_direction_index): """ """ axes_ticks_numbers_y_max = max( self.axes_ticks_numbers[axis_direction_index]) axes_ticks_numbers_y_min = min( self.axes_ticks_numbers[axis_direction_index]) max_y_p3d_pos = Ticks.get_tick_pos_along_axis( self.get_p3d_axis_length_from_axis_direction_index( axis_direction_index), self.axes_ticks_numbers[axis_direction_index], max(self.axes_ticks_numbers[axis_direction_index])) min_y_p3d_pos = Ticks.get_tick_pos_along_axis( self.get_p3d_axis_length_from_axis_direction_index( axis_direction_index), self.axes_ticks_numbers[axis_direction_index], min(self.axes_ticks_numbers[axis_direction_index])) return np.abs(max_y_p3d_pos - min_y_p3d_pos) / np.abs(axes_ticks_numbers_y_max - axes_ticks_numbers_y_min) def update_alignment(self): """ """ pos_for_x = math_utils.p3d_to_np( Frame2d.axis_direction_vectors[0]) * Ticks.get_tick_pos_along_axis( self.width, self.axes_ticks_numbers[0], 0.0) pos_for_y = math_utils.p3d_to_np( Frame2d.axis_direction_vectors[1]) * Ticks.get_tick_pos_along_axis( self.height, self.axes_ticks_numbers[1], 0.0) pos_tmp = math_utils.p3d_to_np(pos_for_y) + math_utils.p3d_to_np( pos_for_x) for line in self.linesin2dframe.lines: x_scale = self.get_p3d_to_frame_unit_length_scaling_factor(0) y_scale = self.get_p3d_to_frame_unit_length_scaling_factor(1) # print("x_scale, y_scale: ", x_scale, y_scale) cond = np.abs(x_scale) > 1e-8 and np.abs(y_scale) > 1e-8 if cond: line.setScale(x_scale, 1., y_scale) line.setPos(math_utils.np_to_p3d_Vec3(pos_tmp)) else: print( "scaling by too low is not supported by p3d, do it differently!" ) self.clear_plot() def get_p3d_axis_length_from_axis_direction_index(self, axis_direction_index): """ """ if axis_direction_index == 0: return self.width elif axis_direction_index == 1: return self.height else: print("axis_direction_index: ", axis_direction_index, " does not correspond to any axis") exit(1) def plot(self, x, y, color="white", thickness=2): """ x, y: discrete points (each one a 1d numpy array, same size) """ # TODO: check if it is a line along only x or only y and then set soft xlims with a flag if x is not None and y is not None: assert np.shape(x) == np.shape(y) sl = primitives.SegmentedLinePrimitive(color=get_color(color), thickness=thickness) sl.extendCoords([Vec3(x[i], 0., y[i]) for i in range(len(x))]) sl.reparentTo(self.linesin2dframe) self.linesin2dframe.lines.append(sl) if self.with_ticks == True and not self.lims_fully_hard(): self.regenerate_ticks() self.update_alignment() def clear_plot(self, update_alignment=True): """ """ for line in self.linesin2dframe.lines: line.removeNode() self.linesin2dframe.lines = [] if update_alignment == True: if not self.lims_fully_hard(): self.update_graphics_alignment() def get_plot_x_range(self): return self.xmax_hard - self.xmin_hard def get_plot_y_range(self): return self.ymax_hard - self.ymin_hard def get_frame_p3d_origin(self): pos = self.quad.getPos() return np.array([pos[0], pos[2]]) def get_frame_p3d_width(self): return self.width def get_frame_p3d_height(self): return self.height def shift_curve_to_match_up_zeros(): pos0 = self.get_frame_p3d_origin() def attach_to_aspect2d(self): if self.attach_to_space == "aspect2d": super().attach_to_aspect2d() self.attached_p = True self.set_figsize(self.width, self.height, update_graphics=True) else: print("ERR: Do not call attach_to_aspect2d() to a frame for", "which self.attach_to_space is not set to aspect2d") exit(1) def attach_to_render(self): if self.attach_to_space == "render": super().attach_to_render() self.attached_p = True self.set_figsize(self.width, self.height, update_graphics=True) else: print("ERR: Do not call attach_to_render() to a frame for", "which self.attach_to_space is not set to render") exit(1)
class ProcessingBox(TQGraphicsNodePath): """ an individual box with text, position, loading symbol, elapsed time since start e""" def __init__(self, is_done_function, description): self.description = description self.x_pos_left_border = None self.y_pos_top_box = None self.is_done_function = is_done_function self.time_initial = time.time_ns() self.task_obj_update = None self.waiting_symbol = None self.text = None self.elapsed_time_text = None self.quad = None self.task_obj_update = None self.task_obj_update = taskMgr.add(self.update_task, 'update_task') self.height = None self.width = None if self.height is None: self.height = 0.2 if self.width is None: self.width = 0.9 TQGraphicsNodePath.__init__(self) pass def set_xy_pos(self, x_pos_left_border, y_pos_top_box): """ """ self.x_pos_left_border = x_pos_left_border self.y_pos_top_box = y_pos_top_box def update(self): if self.is_done_function() == False: if self.x_pos_left_border is None: self.x_pos_left_border = -1.0 x_pos_waiting_symbol = self.x_pos_left_border + 0.1 x_pos_text = x_pos_waiting_symbol + 0.2 if self.y_pos_top_box is None: self.y_pos_top_box = 0. y_pos_waiting_symbol = self.y_pos_top_box - 0.05 y_pos_text = self.y_pos_top_box - 0.125 x_pos_elapsed_time = x_pos_text + 0.4 y_pos_elapsed_time = y_pos_text quad_thickness = 2.0 if self.waiting_symbol is None: self.waiting_symbol = WaitingSymbol(self.is_done_function, Vec3(x_pos_waiting_symbol, 0., y_pos_waiting_symbol), size=0.1) self.waiting_symbol.reparentTo(self) else: self.waiting_symbol.set_position(Vec3(x_pos_waiting_symbol, 0., y_pos_waiting_symbol)) if self.text is None: self.text = Fixed2dLabel(text=self.description, font="fonts/arial.egg", x=x_pos_text, y=y_pos_text) self.text.reparentTo(self) else: self.text.setPos(x_pos_text, y_pos_text) if self.elapsed_time_text is None: self.elapsed_time_text = Fixed2dLabel(text="elapsed time", font="fonts/arial.egg", x=x_pos_elapsed_time, y=y_pos_elapsed_time) self.elapsed_time_text.reparentTo(self) else: self.elapsed_time_text.setPos(x_pos_elapsed_time, y_pos_elapsed_time) elapsed_time = (time.time_ns() - self.time_initial) / 1.0e9 self.elapsed_time_text.setText("{0:.0f} s".format(elapsed_time)) if self.quad is None: self.quad = Quad(thickness=quad_thickness, TQGraphicsNodePath_creation_parent_node=engine.tq_graphics_basics.tq_aspect2d) self.quad.reparentTo(self) self.quad.set_pos_vec3(Vec3(self.x_pos_left_border, 0., self.y_pos_top_box)) self.quad.set_height(self.height) self.quad.set_width(self.width) else: self.quad.set_pos_vec3(Vec3(self.x_pos_left_border, 0., self.y_pos_top_box)) return True # call task.cont return False # call task.done def update_task(self, task): """ This updates it's position, checks if the task is done, and if it is, removes the Processing Box """ ret = self.update() if ret == True: return task.cont self.remove() return task.done def _is_done(self): """ check the is_done_function """ return self.is_done_function() def get_height(self): return self.height def get_width(self): return self.width def remove(self): taskMgr.remove(self.task_obj_update) if self.waiting_symbol: self.waiting_symbol.remove() if self.text: self.text.remove() if self.elapsed_time_text: self.elapsed_time_text.remove() if self.quad: self.quad.remove()
class PDFViewer(TQGraphicsNodePath): """ includes the p3d_camera and rendering functionality for viewing a pdf """ y_pages_distance_0 = 0.1 x_left_offset = 0.1 y_top_offset = 0.1 spacebar_scroll_overlap = 0.1 upper_scroll_margin = 1. margins_quad_pos_0 = Vec3(0., 1., 0.) filmsize_x_0 = 1. filmsize_y_0 = 1. drawing_mode_1_width = 15. # in this drawing mode, you have huge margins in wich to draw and put derivations # TODO: make a drawing mode 2, where you exit those margins, this makes navigation a bit more messy, but you have # infinite margins drawing_mode_1_top_margin = 2. drawing_mode_1_bottom_margin = 2. def __init__(self, pdf_panner2d, pdf_filepath, *args, **kwargs): TQGraphicsNodePath.__init__(self, *args, **kwargs) # logical variables self.pdf_filepath = pdf_filepath self.pptos = [] self.y_pages_distance = PDFViewer.y_pages_distance_0 # derived self.pdf_panner2d = pdf_panner2d # something like Panner2d self.ppr = PopplerPDFRenderer(self.pdf_filepath) # direct objects base.accept('1', self.return_to_middle_view) base.accept('space', self.scroll_spacebar_down) base.accept('shift-space', self.scroll_spacebar_up) base.accept('control-0', self.return_to_middle_view) # quad visualizing large margins for visual orientation self.margins_quad = Quad(thickness=10.) self.margins_quad.reparentTo(self) # plot self.render_pages() self.return_to_middle_view() def scroll_spacebar_down(self): self.scroll_spacebar(-1.) def scroll_spacebar_up(self): self.scroll_spacebar(+1.) def get_total_view_height(self): _sum = len(self.pptos) * self.y_pages_distance for ppto in self.pptos: if ppto is self.pptos[-1]: x_sizef, y_sizef = self.pptos[-1].get_size() _sum += y_sizef continue x_size, y_size = ppto.get_size() _sum += y_size return _sum def scroll_spacebar(self, direction_sign): """ args: direction_sign: -1: scroll down, +1: scroll up """ filmsize_x, filmsize_y = self.pdf_panner2d.get_filmsize_with_window_active( ) self.pdf_panner2d.x[1] old_pos = self.pdf_panner2d.x[1] pos_step_try = direction_sign * (filmsize_y - PDFViewer.spacebar_scroll_overlap) new_pos = np.clip( old_pos + pos_step_try, -self.get_total_view_height(), -PDFViewer.upper_scroll_margin) # clip(val, min, max) actual_pos_step = new_pos - old_pos # clip to not run off self.pdf_panner2d.x[1] += actual_pos_step self.pdf_panner2d.p_previous_offset[1] -= actual_pos_step self.pdf_panner2d.update_camera_state() self.pdf_panner2d.set_lookat_after_updated_camera_pos() self.pdf_panner2d.update_film_size_from_view_distance() def return_to_middle_view(self): """ scroll so that the pdf is centered again """ assert len(self.pptos) > 0 first_page_width = self.pptos[0].get_size()[0] filmsize_x, filmsize_y = self.pdf_panner2d.get_filmsize_with_window_active( ) x_val = first_page_width / 2. self.pdf_panner2d.x[0] = x_val self.pdf_panner2d.p_previous_offset[0] = -x_val self.pdf_panner2d.update_camera_state() self.pdf_panner2d.set_lookat_after_updated_camera_pos() self.pdf_panner2d.update_film_size_from_view_distance() def get_page_width(self, page_num): x_size, y_size = self.pptos[page_num].get_size() return x_size def render_pages(self): for i in range(self.ppr.get_number_of_pages()): ppto = PDFPageTextureObject(i, self.ppr) ppto.reparentTo(self) pos_3d = ppto.getPos() x0 = pos_3d[0] y0 = pos_3d[ 2] # since z is up in p3d and y is up in the 2d pdf convention here x_size, y_size = ppto.get_size() y = y0 x = x0 if i > 0: y -= self.y_pages_distance y -= (i + 1.) * y_size ppto.setPos(Vec3(x, 0., y)) self.pptos.append(ppto) self.render_background_graphics() def render_background_graphics(self): width = PDFViewer.drawing_mode_1_width height = self.get_total_view_height( ) + PDFViewer.drawing_mode_1_top_margin + PDFViewer.drawing_mode_1_bottom_margin self.margins_quad.set_height(height) self.margins_quad.set_width(width) # self.margins_quad.setPos(PDFViewer.margins_quad_pos_0) self.margins_quad.setColor(Vec4(1., 0., 0., 0.3), 1) self.margins_quad.b2d.setColor(Vec4(1., 1., 1., 0.9), 1) first_page_width = self.pptos[0].get_size()[0] self.margins_quad.setPos(-width / 2. + first_page_width / 2., PDFViewer.margins_quad_pos_0[1], PDFViewer.drawing_mode_1_top_margin) def calculate_mean_width(self): x_sizes = [] for i, ppto in enumerate(self.pptos): x_size, y_size = ppto.get_size() x_sizes.append(x_size) return np.sum(x_sizes) / len(x_sizes)