def __init__(self): HUDElement.__init__(self) self.dir = ani.model_dir / 'hud' / 'english' self.text_scale = 0.2 self.text_color = (1, 1, 1, 1) self.circle = OnscreenImage(image=panda_path(self.dir / 'circle.png'), parent=self.dummy_right, scale=0.15) self.circle.setTransparency(TransparencyAttrib.MAlpha) autils.alignTo(self.circle, self.dummy_right, autils.CL, autils.C) self.circle.setZ(-0.65) self.crosshairs = OnscreenImage(image=panda_path(self.dir / 'crosshairs.png'), pos=(0, 0, 0), parent=self.circle, scale=0.14) self.crosshairs.setTransparency(TransparencyAttrib.MAlpha) self.text = OnscreenText( text="(0.00, 0.00)", pos=(0, -1.15), scale=self.text_scale, fg=self.text_color, align=TextNode.ACenter, mayChange=True, parent=self.circle, )
def add_backbutton(self, item): """Add a back button""" func_name = item[0].text # This is the button you click. NOTE `command` is assigned ad hoc. See # Menus.populate_menus button = DirectButton( scale=BACKBUTTON_TEXT_SCALE, geom=( loadImageAsPlane(panda_path(MENU_ASSETS / 'backbutton.png')), loadImageAsPlane(panda_path(MENU_ASSETS / 'backbutton.png')), loadImageAsPlane( panda_path(MENU_ASSETS / 'backbutton_hover.png')), loadImageAsPlane(panda_path(MENU_ASSETS / 'backbutton.png')), ), relief=None, ) button_np = NodePath(button) # functional_button-<menu_name>-<button_text> button_id = f"functional_button-{self.name}-back" button_np.setName(button_id) button_np.reparentTo(self.area) button_np.setPos(-0.92, 0, 0.22) self.elements.append({ 'type': 'backbutton', 'content': button_np, 'object': button, 'func_name': func_name, }) return button_np
def init_cloth(self): if not self.has_model or not ani.settings['graphics']['table']: node = render.find('scene').attachNewNode('cloth') path = ani.model_dir / 'table' / 'custom' / 'custom.glb' model = loader.loadModel(panda_path(path)) model.reparentTo(node) model.setScale(self.w, self.l, 1) else: path_dir = ani.model_dir / 'table' / self.name pbr_path = path_dir / (self.name + '_pbr.glb') standard_path = path_dir / (self.name + '.glb') if ani.settings['graphics']['physical_based_rendering']: path = pbr_path if not path.exists(): path = standard_path else: path = standard_path if not path.exists(): raise ConfigError(f"Couldn't find table model at {standard_path} or {pbr_path}") node = loader.loadModel(panda_path(path)) node.reparentTo(render.find('scene')) node.setName('cloth') self.nodes['cloth'] = node self.collision_nodes = {}
def init_sphere(self): """Initialize the ball's nodes""" position = render.find('scene').find('cloth').attachNewNode( f"ball_{self.id}_position") ball = position.attachNewNode(f"ball_{self.id}") if self.rel_model_path is None: fallback_path = ani.model_dir / 'balls' / 'set_1' / '1.glb' expected_path = ani.model_dir / 'balls' / 'set_1' / f'{self.id}.glb' path = expected_path if expected_path.exists() else fallback_path sphere_node = base.loader.loadModel(panda_path(path)) sphere_node.reparentTo(position) if path == fallback_path: tex = sphere_node.find_texture(Path(fallback_path).stem) else: tex = sphere_node.find_texture(self.id) # Here, we define self.rel_model_path based on path. Since rel_model_path is defined relative to # the directory, pooltool/models/balls, some work has to be done to define rel_model_path # relative to this directory. NOTE assumes no child directory is named balls parents = [] parent = path.parent while True: if parent.stem == 'balls': self.rel_model_path = Path('/'.join( parents[::-1])) / path.name break parents.append(parent.stem) parent = parent.parent else: sphere_node = base.loader.loadModel( panda_path(ani.model_dir / 'balls' / self.rel_model_path)) sphere_node.reparentTo(position) tex = sphere_node.find_texture(Path(self.rel_model_path).stem) # https://discourse.panda3d.org/t/visual-artifact-at-poles-of-uv-sphere-gltf-format/27975/8 tex.set_minfilter(SamplerState.FT_linear) sphere_node.setScale(self.get_scale_factor(sphere_node)) position.setPos(*self.rvw[0, :]) self.nodes['sphere'] = sphere_node self.nodes['ball'] = ball self.nodes['pos'] = position self.nodes['shadow'] = self.init_shadow() if ani.settings['graphics']['angular_vectors']: self.nodes['vector'] = self.init_angular_vector() if self.initial_orientation: # This ball already has a defined initial orientation, so load it up self.set_orientation(self.initial_orientation) else: self.randomize_orientation() self.initial_orientation = self.get_orientation()
def load_room(self, path): self.room = loader.loadModel(panda_path(path)) self.room.reparentTo(render.find('scene')) self.room.setPos(self.offset) self.room.setName('room') self.room_loaded = True return self.room
def load_floor(self, path): self.floor = loader.loadModel(panda_path(path)) self.floor.reparentTo(render.find('scene')) self.floor.setPos(self.offset) self.floor.setName('floor') self.floor_loaded = True return self.floor
def add_transparent_ball(self): self.trans_ball = base.loader.loadModel( panda_path(ani.model_dir / 'balls' / self.closest_ball.rel_model_path)) self.trans_ball.reparentTo(render.find('scene').find('cloth')) self.trans_ball.setTransparency(TransparencyAttrib.MAlpha) self.trans_ball.setAlphaScale(0.4) self.trans_ball.setPos(self.closest_ball.get_node('pos').getPos()) self.trans_ball.setHpr(self.closest_ball.get_node('sphere').getHpr())
def __init__(self, xml): self.xml = xml self.name = self.xml.attrib['name'] self.title_font = loader.loadFont(panda_path(TITLE_FONT)) self.button_font = loader.loadFont(panda_path(BUTTON_FONT)) # No idea why this conditional must exist if self.title_font.get_num_pages() == 0: self.title_font.setPixelsPerUnit(90) self.last_element = None self.num_elements = 0 self.elements = [] self.area_backdrop = DirectFrame( frameColor=FRAME_COLOR, frameSize=(-1, 1, -1, 1), parent=render2d, ) self.area_backdrop.setImage( panda_path(MENU_ASSETS / 'menu_background.jpeg')) img = OnscreenImage(image=panda_path(ani.logo_paths['default']), pos=(0, 0, 0.65), parent=self.area_backdrop, scale=(1.4 * 0.25, 1, 1.4 * 0.22)) img.setTransparency(TransparencyAttrib.MAlpha) self.area = DirectScrolledFrame( frameColor=(1, 1, 1, 0.2), # alpha == 0 canvasSize=(-1, 1, -3, 1), frameSize=(-1, 1, -0.9, 0.3), scrollBarWidth=0.04, horizontalScroll_frameSize=(0, 0, 0, 0), parent=aspect2d, ) self.area.setPos(0, 0, 0) self.area.setTransparency(TransparencyAttrib.MAlpha) # 0.05 means you scroll from top to bottom in 20 discrete steps self.area.verticalScroll['pageSize'] = 0.05 self.hovered_entry = None
def __init__(self): HUDElement.__init__(self) self.dir = ani.model_dir / 'hud' / 'jack' self.text_scale = 0.4 self.text_color = (1, 1, 1, 1) self.arc = OnscreenImage(image=panda_path(self.dir / 'arc.png'), pos=(1.4, 0, -0.45), parent=aspect2d, scale=0.075) self.arc.setTransparency(TransparencyAttrib.MAlpha) self.cue_cartoon = OnscreenImage( image=panda_path(self.dir / 'cue.png'), parent=aspect2d, pos=(0, 0, 0), scale=(0.15, 1, 0.01), ) self.cue_cartoon.setTransparency(TransparencyAttrib.MAlpha) autils.alignTo(self.cue_cartoon, self.dummy_right, autils.CL, autils.C) self.cue_cartoon.setZ(-0.40) autils.alignTo(self.arc, self.cue_cartoon, autils.LR, autils.CR) self.rotational_point = OnscreenImage(image=panda_path( ani.model_dir / 'hud' / 'english' / 'circle.png'), parent=self.arc, scale=0.15) self.rotational_point.setTransparency(TransparencyAttrib.MAlpha) autils.alignTo(self.rotational_point, self.arc, autils.C, autils.LR) self.cue_cartoon.wrtReparentTo(self.rotational_point) self.text = OnscreenText( text="0 deg", pos=(-1, -1.4), scale=self.text_scale, fg=self.text_color, align=TextNode.ACenter, mayChange=True, parent=self.arc, )
def init_model(self, R=c.R): path = utils.panda_path(ani.model_dir / 'cue' / 'cue.glb') cue_stick_model = loader.loadModel(path) cue_stick_model.setName('cue_stick_model') cue_stick = render.find('scene').find('cloth').attachNewNode( 'cue_stick') cue_stick_model.reparentTo(cue_stick) self.nodes['cue_stick'] = cue_stick self.nodes['cue_stick_model'] = cue_stick_model
def add_image(self, path, pos, scale): """Add an image to the menu Notes ===== - images are parented to self.titleMenuBackdrop (as opposed self.titleMenu) in order to preserve their aspect ratios. """ img = OnscreenImage(image=panda_path(path), pos=pos, parent=self.titleMenuBackdrop, scale=scale) img.setTransparency(TransparencyAttrib.MAlpha) self.elements.append({ 'type': 'image', 'name': panda_path(path), 'content': img, 'convert_factor': None, })
def init_shadow(self): N = 20 start, stop = 0.5, 0.9 # fraction of ball radius z_offset = 0.0005 scales = np.linspace(start, stop, N) shadow_path = ani.model_dir / 'balls' / 'set_1' / f'shadow.glb' shadow_node = render.find('scene').find('cloth').attachNewNode( f'shadow_{self.id}') shadow_node.setPos(self.rvw[0, 0], self.rvw[0, 1], 0) # allow transparency of shadow to change shadow_node.setTransparency(TransparencyAttrib.MAlpha) for i, scale in enumerate(scales): shadow_layer = base.loader.loadModel(panda_path(shadow_path)) shadow_layer.reparentTo(shadow_node) shadow_layer.setScale(self.get_scale_factor(shadow_layer) * scale) shadow_layer.setZ(z_offset * (1 - i / N)) return shadow_node
def __init__(self, path): self.path = panda_path(path) self.tree = ET.parse(path) self.root = self.tree.getroot()
#! /usr/bin/env python import ast import pooltool as pt import configparser from pooltool.utils import panda_path from pooltool.error import ConfigError, TableConfigError from pathlib import Path from panda3d.core import * loadPrcFile( panda_path(Path(pt.__file__).parent / 'config' / 'config_panda3d.prc')) # This is hard-coded. Change it and everything looks bad aspect_ratio = 1.6 menu_text_scale = 0.07 menu_text_scale_small = 0.04 zoom_sensitivity = 0.3 min_player_cam = 2 max_english = 6 / 10 power_sensitivity = 2 elevate_sensitivity = 13 english_sensitivity = 0.1 rotate_sensitivity_x = 19 rotate_sensitivity_y = 5 rotate_fine_sensitivity_x = 2 rotate_fine_sensitivity_y = 0 move_sensitivity = 0.6
def add_dropdown(self, item): name = self.search_child_tag(item, 'name').text desc = self.search_child_tag(item, 'description').text if item.attrib.get('from_yaml'): # Populate the options from a YAML path = Path( pooltool.__file__).parent / item.attrib.get('from_yaml') config_obj = configparser.ConfigParser() config_obj.read(path) options = [option for option in config_obj.sections()] else: # Read the options directly from the XML options = [ subitem.text for subitem in item if subitem.tag == 'option' ] try: func_name = self.search_child_tag(item, 'func').text except ValueError: func_name = None title = DirectLabel( text=name + ":", scale=AUX_TEXT_SCALE, parent=self.area.getCanvas(), relief=None, text_fg=TEXT_COLOR, text_align=TextNode.ALeft, text_font=self.title_font, ) title.reparentTo(self.area.getCanvas()) title_np = NodePath(title) title_np.reparentTo(self.area.getCanvas()) dropdown = DirectOptionMenu( scale=BUTTON_TEXT_SCALE * 0.8, items=options, highlightColor=(0.65, 0.65, 0.65, 1), textMayChange=1, text_align=TextNode.ALeft, #text_font = self.button_font, relief=DGG.RIDGE, popupMarker_scale=0.6, popupMarker_image=loadImageAsPlane( panda_path(MENU_ASSETS / 'dropdown_marker.png')), popupMarker_relief=None, item_pad=(0.2, 0.2), ) dropdown['frameColor'] = (1, 1, 1, 0.3) dropdown.reparentTo(self.area.getCanvas()) dropdown_np = NodePath(dropdown) # functional_dropdown-<menu_name>-<dropdown_text> dropdown_id = f"functional_dropdown-{self.name}-{name.replace(' ','_')}" dropdown_np.setName(dropdown_id) dropdown_np.reparentTo(self.area.getCanvas()) if self.last_element: autils.alignTo(title_np, self.last_element, autils.CT, autils.CB) else: title_np.setPos(-0.63, 0, 0.8) title_np.setX(-0.63) title_np.setZ(title_np.getZ() - MOVE) # Align the dropdown next to the title that refers to it autils.alignTo(dropdown_np, title_np, autils.CL, autils.CR) # Then shift it over just a bit to give some space dropdown_np.setX(dropdown_np.getX() + 0.02) # Then shift it down a little to align the text dropdown_np.setZ(dropdown_np.getZ() - 0.005) # This is the info button you hover over info_button = DirectButton( text='', text_align=TextNode.ALeft, scale=INFO_SCALE, image=panda_path(MENU_ASSETS / 'info_button.png'), relief=None, ) # Bind mouse hover to displaying button info info_button.bind(DGG.ENTER, self.display_button_info, extraArgs=[desc]) info_button.bind(DGG.EXIT, self.destroy_button_info) info_button = NodePath(info_button) info_button.reparentTo(self.area.getCanvas()) # Align the info button next to the button it refers to autils.alignTo(info_button, title_np, autils.CR, autils.CL) # Then shift it over just a bit to give some space info_button.setX(info_button.getX() - 0.02) # Create a parent for all the nodes dropdown_id = 'dropdown_' + item.text.replace(' ', '_') dropdown_obj = self.area.getCanvas().attachNewNode(dropdown_id) title_np.reparentTo(dropdown_obj) dropdown_np.reparentTo(dropdown_obj) info_button.reparentTo(dropdown_obj) self.last_element = dropdown_np self.elements.append({ 'type': 'dropdown', 'name': name, 'content': dropdown_obj, 'object': dropdown, 'convert_factor': None, 'func_name': func_name, })
def add_checkbox(self, item): name = self.search_child_tag(item, 'name').text desc = self.search_child_tag(item, 'description').text title = DirectLabel( text=name + ":", scale=AUX_TEXT_SCALE, parent=self.area.getCanvas(), relief=None, text_fg=TEXT_COLOR, text_align=TextNode.ALeft, text_font=self.title_font, ) title.reparentTo(self.area.getCanvas()) title_np = NodePath(title) title_np.reparentTo(self.area.getCanvas()) checkbox = DirectCheckButton( scale=BUTTON_TEXT_SCALE * 0.5, boxImage=( panda_path(MENU_ASSETS / 'unchecked.png'), panda_path(MENU_ASSETS / 'checked.png'), None, ), text="", relief=None, boxRelief=None, ) checkbox_np = NodePath(checkbox) # functional_checkbox-<menu_name>-<checkbox_text> checkbox_id = f"functional_checkbox-{self.name}-{name.replace(' ','_')}" checkbox_np.setName(checkbox_id) checkbox_np.reparentTo(self.area.getCanvas()) if self.last_element: autils.alignTo(title_np, self.last_element, autils.CT, autils.CB) else: title_np.setPos(-0.63, 0, 0.8) title_np.setX(-0.63) title_np.setZ(title_np.getZ() - MOVE) # Align the checkbox next to the title that refers to it autils.alignTo(checkbox_np, title_np, autils.CL, autils.CR) # Then shift it over just a bit to give some space checkbox_np.setX(checkbox_np.getX() + 0.02) # Then shift it down a little to align the text checkbox_np.setZ(checkbox_np.getZ() - 0.005) # This is the info button you hover over info_button = DirectButton( text='', text_align=TextNode.ALeft, scale=INFO_SCALE, image=panda_path(MENU_ASSETS / 'info_button.png'), relief=None, ) # Bind mouse hover to displaying button info info_button.bind(DGG.ENTER, self.display_button_info, extraArgs=[desc]) info_button.bind(DGG.EXIT, self.destroy_button_info) info_button = NodePath(info_button) info_button.reparentTo(self.area.getCanvas()) # Align the info button next to the button it refers to autils.alignTo(info_button, title_np, autils.CR, autils.CL) # Then shift it over just a bit to give some space info_button.setX(info_button.getX() - 0.02) # Create a parent for all the nodes checkbox_id = 'checkbox_' + item.text.replace(' ', '_') checkbox_obj = self.area.getCanvas().attachNewNode(checkbox_id) title_np.reparentTo(checkbox_obj) checkbox_np.reparentTo(checkbox_obj) info_button.reparentTo(checkbox_obj) self.last_element = checkbox_np self.elements.append({ 'type': 'checkbox', 'name': name, 'content': checkbox_obj, 'object': checkbox, 'convert_factor': None, })
def add_entry(self, item): name = self.search_child_tag(item, 'name').text desc = self.search_child_tag(item, 'description').text validator = item.attrib.get('validator') if validator is None: validator = lambda value: True else: try: validator = getattr(self, validator) except AttributeError: raise AttributeError( f"Unknown validator string '{validator}' for element with name '{name}'" ) try: initial = item.attrib['initial'] except KeyError: initial = '' try: width = int(item.attrib['width']) except KeyError: width = 4 title = DirectLabel( text=name + ":", scale=AUX_TEXT_SCALE, parent=self.area.getCanvas(), relief=None, text_fg=TEXT_COLOR, text_align=TextNode.ALeft, text_font=self.title_font, ) title.reparentTo(self.area.getCanvas()) title_np = NodePath(title) title_np.reparentTo(self.area.getCanvas()) entry = DirectEntry( text="", scale=BUTTON_TEXT_SCALE * 0.7, initialText=initial, relief=DGG.RIDGE, numLines=1, width=width, focus=0, focusInCommand=self.entry_buildup, focusInExtraArgs=[True, name], focusOutCommand=self.entry_teardown, focusOutExtraArgs=[name, initial], suppressKeys=True, ) entry['frameColor'] = (1, 1, 1, 0.3) # If the mouse hovers over a direct entry, update self.hovered_entry entry.bind(DGG.ENTER, self.update_hovered_entry, extraArgs=[name]) entry.bind(DGG.EXIT, self.update_hovered_entry, extraArgs=[None]) entry_np = NodePath(entry) # functional_entry-<menu_name>-<entry_text> entry_id = f"functional_entry-{self.name}-{name.replace(' ','_')}" entry_np.setName(entry_id) entry_np.reparentTo(self.area.getCanvas()) if self.last_element: autils.alignTo(title_np, self.last_element, autils.CT, autils.CB) else: title_np.setPos(-0.63, 0, 0.8) title_np.setX(-0.63) title_np.setZ(title_np.getZ() - MOVE) # Align the entry next to the title that refers to it autils.alignTo(entry_np, title_np, autils.CL, autils.CR) # Then shift it over just a bit to give some space entry_np.setX(entry_np.getX() + 0.02) # Then shift it down a little to align the text entry_np.setZ(entry_np.getZ() - 0.005) # This is the info button you hover over info_button = DirectButton( text='', text_align=TextNode.ALeft, scale=INFO_SCALE, image=panda_path(MENU_ASSETS / 'info_button.png'), relief=None, ) # Bind mouse hover to displaying button info info_button.bind(DGG.ENTER, self.display_button_info, extraArgs=[desc]) info_button.bind(DGG.EXIT, self.destroy_button_info) info_button = NodePath(info_button) info_button.reparentTo(self.area.getCanvas()) # Align the info button next to the button it refers to autils.alignTo(info_button, title_np, autils.CR, autils.CL) # Then shift it over just a bit to give some space info_button.setX(info_button.getX() - 0.02) # This text is shown if an error is detected in the user input error = DirectLabel( text="", textMayChange=1, text_fg=ERROR_COLOR, text_bg=(0, 0, 0, 0.3), scale=ERROR_TEXT_SCALE, parent=self.area.getCanvas(), relief=None, text_align=TextNode.ALeft, ) error.reparentTo(self.area.getCanvas()) error_np = NodePath(error) error_np.reparentTo(self.area.getCanvas()) error_np.hide() # Align the error msg next to the entry it refers to autils.alignTo(error, entry_np, autils.CL, autils.CR) # Then shift it over just a bit to give some space error_np.setX(error_np.getX() + 0.02) # And shift it down a little too error_np.setZ(error_np.getZ() - 0.01) # Create a parent for all the nodes entry_id = 'entry_' + item.text.replace(' ', '_') entry_obj = self.area.getCanvas().attachNewNode(entry_id) title_np.reparentTo(entry_obj) entry_np.reparentTo(entry_obj) info_button.reparentTo(entry_obj) error_np.reparentTo(entry_obj) self.last_element = entry_np self.elements.append({ 'type': 'entry', 'initial': initial, 'name': name, 'content': entry_obj, 'object': entry, 'error_msg': error, 'validator': validator, 'convert_factor': None, })
def add_button(self, item): """Add a button""" name = self.search_child_tag(item, 'name').text func_name = self.search_child_tag(item, 'func').text desc = self.search_child_tag(item, 'description').text # This is the button you click. NOTE `command` is assigned ad hoc. See # Menus.populate_menus button = DirectButton( text=name, text_align=TextNode.ALeft, text_font=self.button_font, scale=BUTTON_TEXT_SCALE, geom=loadImageAsPlane(panda_path(MENU_ASSETS / 'button.png')), relief=None, ) # Bind mouse hover to highlighting option button.bind(DGG.ENTER, self.highlight_button, extraArgs=[button]) button.bind(DGG.EXIT, self.unhighlight_button) button_np = NodePath(button) # functional_button-<menu_name>-<button_text> button_id = f"functional_button-{self.name}-{name.replace(' ','_')}" button_np.setName(button_id) button_np.reparentTo(self.area.getCanvas()) if self.last_element: autils.alignTo(button_np, self.last_element, autils.CT, autils.CB) else: button_np.setPos(-0.63, 0, 0.8) button_np.setX(-0.63) button_np.setZ(button_np.getZ() - MOVE) # This is the info button you hover over info_button = DirectButton( text='', text_align=TextNode.ALeft, scale=INFO_SCALE, image=panda_path(MENU_ASSETS / 'info_button.png'), relief=None, ) # Bind mouse hover to displaying button info info_button.bind(DGG.ENTER, self.display_button_info, extraArgs=[desc]) info_button.bind(DGG.EXIT, self.destroy_button_info) info_button = NodePath(info_button) info_button.reparentTo(self.area.getCanvas()) # Align the info button next to the button it refers to autils.alignTo(info_button, button_np, autils.CR, autils.CL) # Then shift it over just a bit to give some space info_button.setX(info_button.getX() - 0.02) # Create a parent for all the nodes button_id = 'button_' + item.text.replace(' ', '_') button_obj = self.area.getCanvas().attachNewNode(button_id) button_np.reparentTo(button_obj) info_button.reparentTo(button_obj) self.last_element = button_np self.elements.append({ 'type': 'button', 'name': name, 'content': button_obj, 'object': button, 'convert_factor': None, 'func_name': func_name, }) return button_obj