class ListedArea(BaseWidget): name = 'Astronomical Objects' def __init__(self, parent, x, y, w, h): super().__init__(parent) self.image = Surface((w, h)) self.image.fill(COLOR_AREA) self.rect = self.image.get_rect(topleft=(x, y)) self.listed_objects = WidgetGroup() self.f = self.crear_fuente(14, underline=True) self.write(self.name, self.f, midtop=(self.rect.w / 2, 0), bg=COLOR_AREA) def populate(self, objects): return NotImplemented def hide(self): for listed in self.listed_objects.widgets(): listed.hide() super().hide() def on_mousebuttondown(self, event): if event.button == 1: self.deselect_all() def delete_objects(self, astronomical_object): for listed in self.listed_objects.widgets(): if listed.object_data is astronomical_object: listed.kill() break self.sort() def sort(self): for i, listed in enumerate(self.listed_objects.widgets()): listed.rect.y = i * 16 + self.rect.y + 21 def select_one(self, it): for listed in self.listed_objects.widgets(): listed.deselect() it.select() def deselect_all(self): for listed in self.listed_objects.widgets(): listed.deselect() def objects(self): return [o.object_data for o in self.listed_objects.widgets()] def show_current(self, idx): for listed in self.listed_objects.widgets(): listed.hide() for listed in self.listed_objects.get_widgets_from_layer(idx): listed.show() def update(self): self.image.fill(COLOR_AREA, (0, 17, self.rect.w, self.rect.h - 17)) self.show_current(Systems.get_current_idx()) def __len__(self): return len(self.listed_objects)
class AsteroidType(BaseWidget): current = None has_values = False locked = False def __init__(self, parent): super().__init__(parent) self.properties = WidgetGroup() self.create() EventHandler.register(self.clear, 'ClearData') self.relative_args = ['density', 'mass', 'volume'] def create(self): for i, prop in enumerate(["Density", "Mass", "Volume"]): vt = ValueText(self, prop, 50, 64 + i * 48, COLOR_TEXTO, COLOR_BOX) self.properties.add(vt, layer=2) for i, prop in enumerate(["A Axis", "B Axis", "C Axis", "Shape"], start=4): vt = ValueText(self, prop, 50, 52 + i * 40, COLOR_TEXTO, COLOR_BOX) self.properties.add(vt, layer=3) vt.modifiable = True for i, name in enumerate(sorted(material_densities)): a = ValueText(self, name.capitalize(), 3, 420 + 21 + i * 21, bg=COLOR_AREA) self.properties.add(a, layer=4) a.modifiable = True def calculate(self): data = {'composition': None} if self.current is None: data['composition'] = {} else: data['composition'] = self.current.composition for item in self.properties.get_widgets_from_layer(2): if item.text_area.value: data[item.text.lower()] = float(item.text_area.value) for item in self.properties.get_widgets_from_layer(3): if item.text_area.value: data[item.text.lower()] = float(item.text_area.value) for material in self.properties.get_widgets_from_layer(4): if material.text_area.value: # not empty data['composition'][material.text.lower()] = float( material.text_area.value) if self.current is not None: data['a axis'] = self.current.a_axis.m data['b axis'] = self.current.b_axis.m data['c axis'] = self.current.c_axis.m data['id'] = self.current.id data['system'] = self.current.system_id moon = minor_moon_by_composition(data) if self.current is None: if Systems.get_current().add_astro_obj(moon): self.current = moon self.parent.button_add.enable() else: Systems.get_current().remove_astro_obj(self.current) if Systems.get_current().add_astro_obj(moon): self.current = moon if self.current.system_id is None: self.current.system_id = Systems.get_current().id self.fill() def clear(self, event): if event.data['panel'] is self.parent: self.erase() def erase(self): self.current = None self.has_values = False for vt in self.properties: vt.value = '' def destroy_button(self): destroyed = Systems.get_current().remove_astro_obj(self.current) if destroyed: self.parent.del_button(self.current) self.erase() def show_current(self, asteroid): self.erase() self.current = asteroid self.calculate() def fill(self): tos = {'Mass': 'Me', 'Density': 'De', 'Volume': 'Ve'} for elemento in self.properties.get_widgets_from_layer(2): idx = self.properties.widgets().index(elemento) attr = self.relative_args[idx] if not self.parent.relative_mode: got_attr = getattr(self.current, attr) else: got_attr = getattr(self.current, attr).to(tos[elemento.text.capitalize()]) attr = q(str(got_attr.m), got_attr.u) if type(got_attr) is not str else got_attr elemento.value = attr if elemento.text_area.unit == 'earth_mass': elemento.do_round = False elemento.text_area.show() for elemento in self.properties.get_widgets_from_layer(3): name = elemento.text if ' ' in elemento.text: name = name.replace(' ', '_') got_attr = getattr(self.current, name.lower()) attr = q(str(round(got_attr.m, 3)), got_attr.u) if type(got_attr) is not str else got_attr elemento.value = attr elemento.text_area.show() for elemento in self.properties.get_widgets_from_layer(4): got_attr = self.current.composition.get(elemento.text.lower(), 0) attr = str(round(got_attr, 3)) + ' %' elemento.value = attr elemento.text_area.show() self.has_values = True def show(self): for p in self.properties.widgets(): p.show() def hide(self): for p in self.properties.widgets(): p.hide()
class StarPanel(BasePanel): curr_x = 3 curr_y = 440 add_on_exit = False def __init__(self, parent): super().__init__('Star', parent) self.properties = WidgetGroup() self.current = StarType(self) self.area_buttons = self.image.fill(COLOR_AREA, [0, 420, self.rect.w, 200]) f = self.crear_fuente(14, underline=True) render = f.render('Stars', True, COLOR_TEXTO, COLOR_AREA) self.image.blit(render, self.area_buttons.topleft) self.button_add = AddStarButton(self, ANCHO - 13, 398) self.button_del = DelStarButton(self, ANCHO - 13, 416) self.properties.add(self.button_add, self.button_del, layer=1) self.stars = [] EventHandler.register(self.save_stars, 'Save') EventHandler.register(self.load_stars, 'LoadData') EventHandler.register(self.name_current, 'NameObject') @property def star_buttons(self): # adds readability return self.properties.get_widgets_from_layer(2) def save_stars(self, event): data = [] for star_button in self.star_buttons: star = star_button.object_data star_data = { 'name': star.name, 'mass': star.mass.m, 'id': star.id, 'spin': star.spin } data.append(star_data) EventHandler.trigger(event.tipo + 'Data', 'Star', {"Stars": data}) def load_stars(self, event): for idx, star_data in enumerate(event.data.get('Stars', [])): star_data.update({'idx': idx}) star = Star(star_data) if star not in self.stars: self.stars.append(star) self.add_button(star) if len(self.star_buttons): self.current.current = self.star_buttons[0].object_data def show(self): super().show() for obj in self.properties.widgets(): obj.show() if self.current.has_values: self.current.current.sprite.show() def hide(self): super().hide() for obj in self.properties.widgets(): obj.hide() if self.add_on_exit: self.parent.set_skippable('Star System', True) Systems.set_system(self.current.current) else: self.parent.set_skippable('Star System', False) def add_button(self, star): button = StarButton(self.current, star, self.curr_x, self.curr_y) self.properties.add(button, layer=2) Systems.add_star(star) if star not in self.stars: self.stars.append(star) self.sort_buttons() self.current.erase() self.button_add.disable() def del_button(self, planet): button = [i for i in self.star_buttons if i.object_data == planet][0] self.properties.remove(button) self.sort_buttons() self.button_del.disable() self.stars.remove(button.object_data) def sort_buttons(self): x, y = self.curr_x, self.curr_y for bt in self.star_buttons: bt.move(x, y) if not self.area_buttons.contains(bt.rect): bt.hide() else: bt.show() if x + bt.rect.w + 10 < self.rect.w - bt.rect.w + 10: x += bt.rect.w + 10 else: x = 3 y += 32 def select_one(self, btn): for button in self.star_buttons: button.deselect() btn.select() def on_mousebuttondown(self, event): if event.button == 1: super().on_mousebuttondown(event) elif event.button in (4, 5): buttons = self.star_buttons if self.area_buttons.collidepoint(event.pos) and len(buttons): last_is_hidden = not buttons[-1].is_visible first_is_hidden = not buttons[0].is_visible if event.button == 4 and first_is_hidden: self.curr_y += 32 elif event.button == 5 and last_is_hidden: self.curr_y -= 32 self.sort_buttons() def update(self): self.add_on_exit = len(self.stars) == 1 def name_current(self, event): if event.data['object'] in self.stars: star = event.data['object'] star.name = event.data['name'] star.has_name = True
class PlanetaryOrbitPanel(BaseWidget): skippable = True skip = False current = None markers = None satellites = None curr_digit = 0 selected_marker = None curr_x = 0 curr_y = 0 added = None visible_markers = True def __init__(self, parent): super().__init__(parent) self.name = 'Planetary Orbit' self.image = Surface((ANCHO, ALTO - 32)) self.image.fill(COLOR_BOX) self.rect = self.image.get_rect() self.properties = WidgetGroup() self.buttons = WidgetGroup() self.orbit_descriptions = WidgetGroup() self._markers = {} self.markers = [] self.added = [] self.objects = [] self.satellites = {} self._loaded_orbits = [] self.area_buttons = self.image.fill(COLOR_AREA, [0, 420, self.rect.w, 200]) self.area_markers = Rect(3, 58, 380, 20 * 16) self.curr_x = self.area_buttons.x + 3 self.curr_y = self.area_buttons.y + 21 self.planet_area = AvailablePlanets(self, ANCHO - 200, 32, 200, 340) self.add_orbits_button = SetOrbitButton(self, ANCHO - 94, 394) self.area_modify = ModifyArea(self, ANCHO - 201, 374) self.show_markers_button = ToggleableButton(self, 'Satellites', self.toggle_stellar_orbits, 3, 421) self.show_markers_button.disable() self.resonances_button = AddResonanceButton(self, ANCHO - 140, 416) self.order_f = self.crear_fuente(14) self.write(self.name + ' Panel', self.crear_fuente(16, underline=True), centerx=(ANCHO // 4) * 1.5, y=0) self.digit_x = RatioDigit(self, 'x', self.resonances_button.rect.left - 60, self.resonances_button.rect.y) self.write(':', self.crear_fuente(16), topleft=[ self.digit_x.rect.right + 1, self.resonances_button.rect.y - 1 ]) self.digit_y = RatioDigit(self, 'y', self.digit_x.rect.right + 9, self.resonances_button.rect.y) self.ratios = [self.digit_x, self.digit_y] self.cycler = cycle(self.ratios) next(self.cycler) self.properties.add(self.area_modify, self.show_markers_button, self.digit_x, self.digit_y, self.planet_area, self.add_orbits_button, self.resonances_button, layer=2) EventHandler.register(self.save_orbits, 'Save') EventHandler.register(self.load_orbits, 'LoadData') def load_orbits(self, event): for position in event.data.get('Planetary Orbits', []): if position not in self._loaded_orbits: self._loaded_orbits.append(position) def set_loaded_orbits(self): for orbit_data in self._loaded_orbits: a = q(orbit_data['a'], 'earth_radius') e = q(orbit_data['e']) i = q(orbit_data['i'], 'degree') system = Systems.get_system_by_id(orbit_data['star_id']) planet = system.get_astrobody_by(orbit_data['planet_id'], tag_type='id') if planet.id not in self.satellites: self.satellites[planet.id] = [] if planet.id not in self._markers: self._markers[planet.id] = [] satellite = system.get_astrobody_by(orbit_data['astrobody'], tag_type='id') self.satellites[planet.id].append(satellite) satellite.set_orbit(planet, [a, e, i]) self.add_existing(satellite, planet.id) # borrar las órbitas cargadas para evitar que se dupliquen. self._loaded_orbits.clear() def save_orbits(self, event): orbits = self._loaded_orbits for planet_obj in self.planet_area.listed_objects.widgets(): planet = planet_obj.object_data for marker in self._markers.get(planet.id, []): if marker.orbit is not None: d = self.create_save_data(marker.orbit) orbits.append(d) EventHandler.trigger(event.tipo + 'Data', 'Orbit', {'Planetary Orbits': orbits}) @staticmethod def create_save_data(orb): d = {} if hasattr(orb, 'semi_major_axis'): d['a'] = round(orb.semi_major_axis.m, 2) if hasattr(orb, 'inclination'): d['i'] = orb.inclination.m if hasattr(orb, 'eccentricity'): d['e'] = orb.eccentricity.m if hasattr(orb, 'astrobody'): d['astrobody'] = orb.astrobody.id d['planet_id'] = orb.astrobody.parent.id d['star_id'] = orb.astrobody.parent.parent.id return d def toggle_stellar_orbits(self): if self.visible_markers: self.area_modify.color_alert() self.add_orbits_button.disable() for marker in self.markers: marker.hide() else: for button in self.buttons.widgets(): button.deselect() self.hide_orbit_types() if self.current is not None: for marker in self.markers: marker.show() self.show_markers_button.disable() self.area_modify.color_standby() self.visible_markers = not self.visible_markers self.area_modify.visible_markers = self.visible_markers def hide_orbit_types(self): for orbit_type in self.orbit_descriptions.widgets(): orbit_type.hide() for orbit_button in self.buttons.widgets(): orbit_button.enable() def populate(self): planet = self.current if planet.id not in self._markers: self._markers[planet.id] = [] self.markers = self._markers[planet.id] for marker in self.markers: if marker not in self.properties: self.properties.add(marker, layer=3) marker.show() self.create_hill_marker(planet) self.sort_markers() def add_objects(self): system = Systems.get_current() if system is not None: for obj in system.satellites + system.asteroids: if obj not in self.objects: self.objects.append(obj) btn = ObjectButton(self, obj, self.curr_x, self.curr_y) if obj.orbit is not None: btn.update_text(obj.orbit.a) markers = self._markers[obj.orbit.star.id] marker_idx = [ i for i in range(len(markers)) if markers[i].obj == obj ][0] marker = markers[marker_idx] btn.link_marker(marker) self.buttons.add(btn, layer=Systems.get_current_idx()) self.properties.add(btn) self.sort_buttons() def show(self): super().show() for prop in self.properties.get_widgets_from_layer(2): prop.show() self.set_loaded_orbits() self.add_objects() def hide(self): super().hide() for prop in self.properties.widgets(): prop.hide() def select_planet(self, planet): if planet is not self.current: self.hide_everything() self.current = planet self.populate() if planet.id not in self.satellites: self.satellites[planet.id] = [] for button in self.buttons.widgets(): button.enable() button.deselect() self.visible_markers = True sats = self.satellites[planet.id] densest = sorted(sats, key=lambda i: i.density.to('earth_density').m, reverse=True) if len(densest): self.create_roches_marker(densest[0]) self.sort_markers() def select_one(self, button): for bttn in self.buttons.widgets(): bttn.deselect() button.select() def anchor_maker(self, marker): self.area_modify.link(marker) self.area_modify.visible_markers = True self.add_orbits_button.link(marker) self.add_orbits_button.enable() self.selected_marker = marker def deselect_markers(self, m): for marker in self.markers: marker.deselect() marker.enable() m.select() def sort_markers(self): self.markers.sort(key=lambda m: m.value.m) for i, marker in enumerate(self.markers, start=1): marker.rect.y = i * 2 * 10 + 38 if not self.area_markers.contains(marker.rect): marker.hide() else: marker.show() def sort_buttons(self): x, y = self.curr_x, self.curr_y for bt in self.buttons.get_widgets_from_layer( Systems.get_current_idx()): bt.move(x, y) if not self.area_buttons.contains(bt.rect): bt.hide() else: bt.show() if x + bt.rect.w + 10 < self.rect.w - bt.rect.w + 10: x += bt.rect.w + 10 else: x = 3 y += 32 def create_roches_marker(self, obj): obj_density = obj.density.to('earth_density').m roches = self.current.set_roche(obj_density) roches_marker = Marker(self, "Roche's Limit", roches, lock=True) first = self.markers[0] if first.name == "Roche's Limit": self.properties.remove(first) self.markers[0] = roches_marker else: self.markers.append(roches_marker) self.properties.add(roches_marker, layer=3) return roches def create_hill_marker(self, planet): x = Marker(self, 'Hill Sphere', planet.hill_sphere) x.locked = True last = None if not len(self.markers) else self.markers[-1] if last is not None and last.name == 'Hill Sphere': self.properties.remove(last) self.markers[-1] = x else: self.markers.append(x) self.properties.add(x, layer=3) def add_new(self, obj): if obj not in self.added: self.added.append(obj) obj_name = obj.cls pln_habitable = Systems.get_current().is_habitable(self.current) pln_hill = self.current.hill_sphere.m obj_type = obj.celestial_type roches = self.create_roches_marker(obj) text = "A satellite's mass must be less than or equal to the\nmass of the planet." text += '\n\nConsider using a less massive satellite for this planet.' assert self.current.mass.to('earth_mass').m >= obj.mass.to( 'earth_mass').m, text pos = q( round( roll(self.current.roches_limit.m, self.current.hill_sphere.m / 2), 3), 'earth_radius') orbit = RawOrbit(Systems.get_current_star(), pos) obj_marker = Marker(self, obj_name, pos, color=COLOR_SELECTED, lock=False) max_value = pln_hill if pln_habitable and obj_type != 'asteroid': max_value /= 2 obj_marker.set_max_value(max_value) obj_marker.set_min_value(roches.m) obj_marker.links(orbit, obj) self.markers.append(obj_marker) self.properties.add(obj_marker, layer=3) self.sort_markers() return orbit, obj_marker def add_existing(self, obj, pln_id): if obj not in self.added: self.added.append(obj) obj_name = obj.cls orbit = obj.orbit pos = orbit.a obj_marker = Marker(self, obj_name, pos, color=COLOR_SELECTED, lock=False) obj_marker.links(orbit, obj) self._markers[pln_id].append(obj_marker) def hide_markers(self): for marker in self.markers: marker.hide() self.show_markers_button.enable() def hide_everything(self): for marker in self.markers: if marker.linked_button is not None: marker.linked_button.hide_info() marker.hide() self.visible_markers = False self.show_markers_button.disable() def is_added(self, obj): return obj in self.added def get_raw_orbit_markers(self): raws = [ m for m in self.markers if ((not m.locked) and (type(m.orbit) == RawOrbit)) ] return raws def link_satellite_to_planet(self, marker): marker._orbit = PseudoOrbit(marker.orbit) button = marker.linked_button self.hide_everything() button.update_text(marker.orbit.a) button.info.link_marker(marker) button.info.locked = False button.info.show() def notify(self): if not self.visible_markers: self.show_markers_button.enable() for button in self.buttons.widgets(): button.disable() def get_sorted_satellites(self): self.sort_markers() markers = [ marker.obj for marker in self.markers if marker.obj is not None ] return markers def set_current_digit(self, idx): self.curr_digit = self.ratios.index(idx) def cycle(self): has_values = False for ratio in self.ratios: ratio.deselect() has_values = ratio.value != '' valid = has_values and self.selected_marker is not None if valid: self.resonances_button.enable() else: ratio = next(self.cycler) ratio.select() WidgetHandler.set_origin(ratio) def ratios_to_string(self): x = int(self.digit_x.value) y = int(self.digit_y.value) diff = y - x if y > x else x - y self.write('{}° Order'.format(diff), self.order_f, right=self.digit_x.rect.left - 2, y=self.digit_x.rect.y) return '{}:{}'.format(x, y) def get_difference(self): x = int(self.digit_x.value) y = int(self.digit_y.value) return x - y def clear_ratios(self): self.digit_x.clear() self.digit_y.clear()
class AsteroidPanel(BasePanel): curr_x = 0 curr_y = 0 mass_number = None loaded_data = None last_idx = None def __init__(self, parent): super().__init__('Asteroid', parent) self.properties = WidgetGroup() self.current = AsteroidType(self) f1 = self.crear_fuente(16, underline=True) f2 = self.crear_fuente(13, underline=True) r = self.image.fill(COLOR_AREA, [0, 420, (self.rect.w // 4) + 32, 200]) self.write('Composition', f1, COLOR_AREA, topleft=(0, 420)) self.area_asteroids = self.image.fill(COLOR_AREA, (r.right + 10, r.y, 400, 200)) self.write('Asteroids', f2, COLOR_AREA, x=self.area_asteroids.x + 3, y=self.area_asteroids.y) self.curr_x = self.area_asteroids.x + 3 self.curr_y = self.area_asteroids.y + 21 self.properties.add(self.current) self.button_add = AddAsteroidButton(self, ANCHO - 13, 398) self.button_del = DelAsteroidButton(self, ANCHO - 13, 416) self.properties.add(self.button_add, self.button_del) self.asteroids = WidgetGroup() self.moons = [] EventHandler.register(self.load_satellites, 'LoadData') EventHandler.register(self.save_satellites, 'Save') EventHandler.register(self.name_current, 'NameObject') def name_current(self, event): if event.data['object'] in self.moons: moon = event.data['object'] moon.name = event.data['name'] moon.has_name = True def load_satellites(self, event): if 'Asteroids' in event.data and len(event.data['Asteroids']): self.loaded_data = event.data['Asteroids'] def show_loaded(self): if self.loaded_data is not None: for satellite_data in self.loaded_data: moon = minor_moon_by_composition(satellite_data) system = Systems.get_system_by_id(satellite_data['system']) if system.add_astro_obj(moon): self.current.current = moon self.add_button() self.loaded_data.clear() def save_satellites(self, event): data = [] for moon_button in self.asteroids.widgets(): moon = moon_button.object_data moon_data = { 'name': moon.name, 'a axis': moon.a_axis.m, 'b axis': moon.b_axis.m, 'c axis': moon.c_axis.m, 'composition': moon.composition, 'id': moon.id, 'system': moon.system_id } data.append(moon_data) EventHandler.trigger(event.tipo + 'Data', 'Planet', {"Asteroids": data}) def add_button(self): button = AsteroidButton(self.current, self.current.current, self.curr_x, self.curr_y) if self.current.current.system_id is not None: layer_number = Systems.get_system_idx_by_id( self.current.current.system_id) else: layer_number = Systems.get_current_idx() self.current.current.system_id = Systems.get_current().id self.moons.append(self.current.current) self.asteroids.add(button, layer=layer_number) self.properties.add(button) self.sort_buttons() self.current.erase() self.button_add.disable() def del_button(self, satellite): button = [ i for i in self.asteroids.widgets() if i.object_data == satellite ][0] self.moons.remove(satellite) self.asteroids.remove(button) self.sort_buttons() self.properties.remove(button) self.button_del.disable() def show_current(self, idx): for button in self.asteroids.widgets(): button.hide() for button in self.asteroids.get_widgets_from_layer(idx): button.show() self.sort_buttons() def select_one(self, btn): for button in self.asteroids.widgets(): button.deselect() btn.select() def sort_buttons(self): x, y = self.curr_x, self.curr_y for bt in self.asteroids.get_widgets_from_layer( Systems.get_current_idx()): bt.move(x, y) if not self.area_asteroids.contains(bt.rect): bt.hide() else: bt.show() if x + bt.rect.w + 10 < self.rect.w - bt.rect.w + 10: x += bt.rect.w + 10 else: x = 3 y += 32 def show(self): super().show() self.show_loaded() self.is_visible = True if self.mass_number is None: self.properties.add(ShownMass(self)) for pr in self.properties.widgets(): pr.show() def hide(self): super().hide() self.is_visible = False for pr in self.properties.widgets(): pr.hide() flag = Systems.get_current() is not None flag = not len(Systems.get_current().asteroids + Systems.get_current().satellites) if flag else False self.parent.set_skippable('Planetary Orbit', flag) def update(self): idx = Systems.get_current_idx() if idx != self.last_idx: self.show_current(idx) self.last_idx = idx
class PlanetPanel(BasePanel): curr_x = 0 curr_y = 440 unit = None is_visible = False last_idx = None mass_number = None def __init__(self, parent): super().__init__('Planet', parent) self.area_buttons = self.image.fill(COLOR_AREA, [0, 420, self.rect.w, 200]) self.current = PlanetType(self) self.properties = WidgetGroup() self.unit = Unit(self, 0, 416) self.properties.add(self.unit) self.button_add = AddPlanetButton(self, ANCHO - 13, 398) self.button_del = DelPlanetButton(self, ANCHO - 13, 416) self.properties.add(self.button_add, self.button_del) self.planet_buttons = WidgetGroup() self.planets = [] EventHandler.register(self.save_planets, 'Save') EventHandler.register(self.name_current, 'NameObject') def save_planets(self, event): data = self.current.loaded_data if self.current.loaded_data is not None else [] for system in Systems.get_systems(): for planet in self.planets: if planet in system.planets: planet_data = { 'name': planet.name, 'mass': planet.mass.m, 'radius': planet.radius.m, 'unit': planet.unit, 'atmosphere': planet.atmosphere, 'composition': planet.composition, 'clase': planet.clase, 'system': system.id, 'id': planet.id, 'albedo': planet.albedo.m, 'tilt': planet.tilt.m } data.append(planet_data) EventHandler.trigger(event.tipo + 'Data', 'Planet', {"Planets": data}) def add_button(self, planet): button = CreatedPlanet(self.current, planet, self.curr_x, self.curr_y) if planet.system_id is not None: layer_number = Systems.get_system_idx_by_id(planet.system_id) else: layer_number = Systems.get_current_idx() planet.system_id = Systems.get_current().id self.planet_buttons.add(button, layer=layer_number) self.planets.append(planet) self.sort_buttons() self.properties.add(button, layer=3) def del_button(self, planet): button = [ i for i in self.planet_buttons.widgets() if i.object_data == planet ][0] self.planet_buttons.remove(button) self.planets.remove(planet) self.sort_buttons() self.properties.remove(button) self.button_del.disable() def show_current(self, idx): for button in self.planet_buttons.widgets(): button.hide() for button in self.planet_buttons.get_widgets_from_layer(idx): button.show() self.sort_buttons() def sort_buttons(self): x, y = self.curr_x, self.curr_y for bt in self.planet_buttons.get_widgets_from_layer( Systems.get_current_idx()): bt.move(x, y) if not self.area_buttons.contains(bt.rect): bt.hide() else: bt.show() if x + bt.rect.w + 10 < self.rect.w - bt.rect.w + 10: x += bt.rect.w + 10 else: x = 3 y += 32 def select_one(self, btn=None): for button in self.planet_buttons.widgets(): button.deselect() if btn is not None: btn.select() def on_mousebuttondown(self, event): if event.button == 1: super().on_mousebuttondown(event) elif event.button in (4, 5): buttons = self.planet_buttons.widgets() if self.area_buttons.collidepoint(event.pos) and len(buttons): last_is_hidden = not buttons[-1].is_visible first_is_hidden = not buttons[0].is_visible if event.button == 4 and first_is_hidden: self.curr_y += 32 elif event.button == 5 and last_is_hidden: self.curr_y -= 32 self.sort_buttons() def show(self): super().show() if self.mass_number is None: self.properties.add(ShownMass(self)) props = self.properties.get_widgets_from_layer(1) props += self.properties.get_widgets_from_layer(2) for item in props: item.show() if self.last_idx is not None: self.show_current(self.last_idx) def hide(self): super().hide() for item in self.properties.widgets(): item.hide() def update(self): idx = Systems.get_current_idx() if idx != self.last_idx: self.show_current(idx) self.last_idx = idx def name_current(self, event): if event.data['object'] in self.planets: planet = event.data['object'] planet.name = event.data['name'] planet.has_name = True
class SystemType(BaseWidget): locked = False has_values = False current = None def __init__(self, parent): super().__init__(parent) self.properties = WidgetGroup() self.primary = None self.secondary = None self.separation = None self.ecc_p = None self.ecc_s = None self.create() EventHandler.register(self.clear, 'ClearData') def create(self): props = [ 'Primary Star', 'Secondary Star', 'Average Separation', 'Eccentriciy (primary)', 'Eccentricty (secondary)', 'Barycenter', 'Maximun Separation', 'Minimun Separation', 'Forbbiden Zone Inner edge', 'Forbbiden Zone Outer edge', 'System Type', 'System Name' ] for i, prop in enumerate([j for j in props]): vt = ValueText(self, prop, 3, 64 + i * 25, COLOR_TEXTO, COLOR_BOX) self.properties.add(vt) if i in [2, 3, 4]: vt.modifiable = True attrs = ['primary', 'secondary', 'separation', 'ecc_p', 'ecc_s'] for idx, attr in enumerate(attrs): setattr(self, attr, self.properties.get_sprite(idx)) def set_star(self, star): if str(self.primary.value) == '': self.primary.value = star elif star.spin == self.primary.value.spin: spin = 'clockwise' if star.spin == 'counter-clockwise' else 'counter-clockwise' raise AssertionError( 'The stars must spin\nin oposite directions\nas they would collide\notherwise.\n' f'\nSelect a star\nthat spins {spin}.') else: self.secondary.value = star self.parent.restore_button.enable() def unset_stars(self): self.parent.stars_area.populate(Systems.loose_stars) self.erase() def get_determinants(self): names = ['primary', 'secondary', 'separation', 'ecc_p', 'ecc_s'] dets = [self.primary.value, self.secondary.value] return dets + [ float(getattr(self, name).value) for name in names if name not in ('primary', 'secondary') ] def fill(self): if all([str(vt.value) != '' for vt in self.properties.widgets()[0:5]]): if self.current is None: self.current = system_type( self.separation.value)(*self.get_determinants()) props = [ 'average_separation', 'ecc_p', 'ecc_s', 'barycenter', 'max_sep', 'min_sep', 'inner_forbbiden_zone', 'outer_forbbiden_zone', 'system_name', 'name' ] for i, attr in enumerate(props, start=2): value = getattr(self.current, attr) pr = self.properties.get_widget(i) pr.value = value self.parent.setup_button.enable() def reset(self, system_data): self.set_star(system_data.primary) self.set_star(system_data.secondary) self.separation.value = system_data.average_separation self.ecc_p.value = system_data.ecc_p self.ecc_s.value = system_data.ecc_s self.fill() def clear(self, event): if event.data['panel'] is self.parent: self.erase() def erase(self): for button in self.properties.widgets(): button.text_area.clear() self.has_values = False self.current = None def destroy(self): self.parent.del_button(self.current) self.erase() def show(self): for prop in self.properties.widgets(): prop.show() def hide(self): for prop in self.properties.widgets(): prop.hide()
class StarSystemPanel(BaseWidget): selected = None curr_x = 0 curr_y = 440 skip = False skippable = True def __init__(self, parent): super().__init__(parent) self.name = 'Star System' self.image = Surface((ANCHO, ALTO - 32)) self.image.fill(COLOR_BOX) self.rect = self.image.get_rect() self.area_buttons = self.image.fill(COLOR_AREA, [0, 420, self.rect.w, 200]) self.f2 = self.crear_fuente(14, underline=True) self.write('Star Systems', self.f2, COLOR_AREA, x=3, y=420) self.properties = WidgetGroup() self.f1 = self.crear_fuente(16, underline=True) self.write(self.name + ' Panel', self.f1, centerx=(ANCHO // 4) * 1.5, y=0) self.stars_area = AvailableStars(self, ANCHO - 200, 32, 200, 340) self.current = SystemType(self) self.systems = [] self.setup_button = SetupButton(self, 484, 416) self.undo_button = DissolveButton(self, 334, 416) self.restore_button = UndoButton(self, 234, 416) self.properties.add(self.setup_button, self.undo_button, self.restore_button, self.stars_area, self.current) self.system_buttons = WidgetGroup() EventHandler.register(self.save_systems, 'Save') EventHandler.register(self.load_systems, 'LoadData') EventHandler.register(self.name_current, 'NameObject') def name_current(self, event): if event.data['object'] in self.systems: system = event.data['object'] system.name = event.data['name'] system.has_name = True def set_current(self, system_data): self.current.reset(system_data) def show_current(self, star): self.current.erase() self.current.current = star self.current.reset(star) def create_button(self, system_data): if system_data not in self.systems: idx = len(self.systems) button = SystemButton(self, system_data, idx, self.curr_x, self.curr_y) self.systems.append(system_data) self.system_buttons.add(button) self.properties.add(button) self.sort_buttons() return button def sort_buttons(self): x, y = self.curr_x, self.curr_y for bt in self.system_buttons.widgets(): bt.move(x, y) if not self.area_buttons.contains(bt.rect): bt.hide() else: bt.show() if x + bt.rect.w + 10 < self.rect.w - bt.rect.w + 10: x += bt.rect.w + 10 else: x = 3 y += 32 def save_systems(self, event): data = [] for button in self.system_buttons.widgets(): current = button.object_data d = { 'primary': current.primary.id, 'secondary': current.secondary.id, 'avg_s': current.average_separation.m, 'ecc_p': current.ecc_p.m, "ecc_s": current.ecc_s.m, "id": current.id, "name": current.name } data.append(d) EventHandler.trigger(event.tipo + 'Data', 'Systems', {'Systems': data}) def load_systems(self, event): for system_data in event.data.get('Systems', []): avg_s = system_data['avg_s'] ecc_p = system_data['ecc_p'] ecc_s = system_data['ecc_s'] prim = Systems.get_star_by_id(system_data['primary']) scnd = Systems.get_star_by_id(system_data['secondary']) idx = system_data['id'] system = system_type(avg_s)(prim, scnd, avg_s, ecc_p, ecc_s, id=idx) button = self.create_button(system) button.hide() Systems.set_system(system) def select_one(self, btn): for button in self.system_buttons.widgets(): button.deselect() btn.select() def del_button(self, system): button = [ i for i in self.system_buttons.widgets() if i.object_data == system ][0] self.systems.remove(system) self.system_buttons.remove(button) self.sort_buttons() self.properties.remove(button) self.undo_button.disable() def show(self): for system in Systems.get_systems(): self.create_button(system.star_system) super().show() for prop in self.properties.widgets(): prop.show() def hide(self): super().hide() for prop in self.properties.widgets(): prop.hide() if len(self.systems) or len(self.stars_area): for s in self.systems + self.stars_area.objects(): Systems.set_system(s)
class OrbitType(BaseWidget, Intertwined): linked_astrobody = None locked = True modifiable = True has_values = False def __init__(self, parent): super().__init__(parent) self.properties = WidgetGroup() def link_astrobody(self, astro): self.linked_astrobody = astro self.locked = False def get_orbit(self): orbit = self.linked_marker.orbit orbit = orbit if not hasattr(orbit, 'astrobody') else orbit.astrobody.orbit return orbit def create(self): orbit = self.get_orbit() self.clear() props = [ 'Semi-major axis', 'Semi-minor axis', 'Eccentricity', 'Inclination', 'Periapsis', 'Apoapsis', 'Orbital motion', 'Temperature', 'Orbital velocity', 'Orbital period', 'Argument of periapsis', 'Longitude of the ascending node', 'True anomaly', 'Body' ] attr = [ 'semi_major_axis', 'semi_minor_axis', 'eccentricity', 'inclination', 'periapsis', 'apoapsis', 'motion', 'temperature', 'velocity', 'period', 'argument_of_periapsis', 'longitude_of_the_ascending_node', 'true_anomaly', 'astrobody' ] modifiables = [ 'Semi-major axis', 'Eccentricity', 'Inclination', 'Argument of periapsis', 'Longitude of the ascending node' ] for i, prop in enumerate([j for j in attr if hasattr(orbit, j)]): value = getattr(orbit, prop) vt = ValueText(self, props[attr.index(prop)], 3, 64 + i * 21, COLOR_TEXTO, COLOR_BOX) vt.value = value vt.modifiable = props[attr.index(prop)] in modifiables self.properties.add(vt) def fill(self): parametros = [] for elemento in self.properties.widgets(): if elemento.text == 'Inclination': value = q( 0 if elemento.text_area.value == '' else elemento.text_area.value, 'degree') elif elemento.text not in ['Orbital motion', 'Temperature']: value = q(elemento.text_area.value) else: value = 'au' parametros.append(value) main = self.parent.current orbit = self.linked_astrobody.set_orbit(main, parametros) self.linked_marker.orbit = orbit self.show() self.parent.planet_area.delete_objects(self.linked_astrobody) if hasattr(self.parent, 'recomendation'): self.parent.recomendation.hide() self.locked = True self.has_values = True def clear(self): for prop in self.properties.widgets(): prop.kill() prop.text_area.kill() def show(self): self.create() for p in self.properties.widgets(): p.show() def hide(self): self.clear() @staticmethod def elevate_changes(key, new_value): if key == 'Eccentricity': if new_value.m < 0.001: return q(0.001) elif new_value.m > 0.9999: return q(0.999) elif key == 'Inclination': if new_value.m < 0: return q(0, new_value.u) elif new_value.m > 180: return q(180, new_value.u)
class OrbitPanel(BaseWidget): current = None # ahora será la estrella o sistema seleccionado. curr_idx = None # ahora será el layer de self.Buttons. selected_marker = None last_idx = 0 _loaded_orbits = None offset = 0 curr_x, curr_y = 3, 442 curr_digit = 0 visible_markers = True orbits = None markers = None buttons = None skippable = False no_star_error = False def __init__(self, parent): super().__init__(parent) self.name = 'Orbit' self.image = Surface((ANCHO, ALTO - 32)) self.image.fill(COLOR_BOX) self.rect = self.image.get_rect() self.properties = WidgetGroup() self.area_buttons = self.image.fill(COLOR_AREA, [0, 420, self.rect.w, 200]) self.area_markers = Rect(3, 58, 380, 20 * 16) self.area_scroll = Rect(3, 32, 387, 388) self.area_modify = ModifyArea(self, ANCHO - 201, 374) self.f = self.crear_fuente(16, underline=True) self.order_f = self.crear_fuente(14) self.write(self.name + ' Panel', self.f, centerx=(ANCHO // 4) * 1.5, y=0) self.planet_area = AvailablePlanets(self, ANCHO - 200, 32, 200, 340) self.recomendation = Recomendation(self, 80, ALTO // 2 - 130) self._loaded_orbits = [] self.indexes = [] self._orbits = {} self._buttons = {} self._markers = {} self.orbit_descriptions = WidgetGroup() self.show_markers_button = ToggleableButton(self, 'Stellar Orbits', self.toggle_stellar_orbits, 3, 421) self.show_markers_button.disable() self.add_orbits_button = AddOrbitButton(self, ANCHO - 94, 394) self.resonances_button = AddResonanceButton(self, ANCHO - 140, 416) self.digit_x = RatioDigit(self, 'x', self.resonances_button.rect.left - 60, self.resonances_button.rect.y) self.write(':', self.crear_fuente(16), topleft=[ self.digit_x.rect.right + 1, self.resonances_button.rect.y - 1 ]) self.digit_y = RatioDigit(self, 'y', self.digit_x.rect.right + 9, self.resonances_button.rect.y) self.ratios = [self.digit_x, self.digit_y] self.cycler = cycle(self.ratios) next(self.cycler) self.properties.add([ self.area_modify, self.planet_area, self.show_markers_button, self.add_orbits_button, self.resonances_button, self.digit_x, self.digit_y ], layer=2) EventHandler.register(self.clear, 'ClearData') EventHandler.register(self.save_orbits, 'Save') EventHandler.register(self.load_orbits, 'LoadData') def set_current(self): self.toggle_current_markers_and_buttons(False) star = Systems.get_current_star() self.current = star self.curr_idx = self.indexes.index(star) self.orbits = self._orbits[star] self.markers = self._markers[star] self.buttons = self._buttons[star] if not len(self.markers) or not self.markers[0].locked: self.populate() self.toggle_current_markers_and_buttons(True) self.sort_buttons() self.add_orbits_button.enable() self.visible_markers = False self.toggle_stellar_orbits() def populate(self): star = self.current markers = { 'Inner Boundary': star.inner_boundry, 'Habitable Inner': star.habitable_inner, 'Habitable Outer': star.habitable_outer, 'Frost Line': star.frost_line, 'Outer Boundary': star.outer_boundry } for marker in markers: x = OrbitMarker(self, marker, star, markers[marker]) x.locked = True self._markers[star].append(x) self.properties.add(x, layer=4) if hasattr(star, 'habitable_orbit'): markers = { 'Inner Forbbiden Zone': star.inner_forbbiden_zone, 'Outer Forbbiden Zone': star.outer_forbbiden_zone } for marker in markers: x = OrbitMarker(self, marker, star, markers[marker]) x.locked = True self._markers[star].append(x) self.properties.add(x, layer=4) self.add_orbit_marker(star.habitable_orbit) self.sort_markers() def toggle_current_markers_and_buttons(self, toggle: bool): if self.markers is not None: for marker in self.markers: marker.toggle(toggle) if self.buttons is not None: for button in self.buttons: button.toggle(toggle) def add_orbit_marker(self, position, resonance=False): star = self.current if not hasattr(position, 'star') else position.star inner = star.inner_boundry outer = star.outer_boundry bc = False if resonance is False else True if type(position) is q: ba = True bb = False if not resonance: test = inner < position < outer color = COLOR_TEXTO else: test = inner < position # TNOs orbit well outside of 40AUs. color = (255, 0, 0) # color provisorio else: # type(position) is PlanetOrbit ba = False bb = True test = True # saved orbits are valid by definition color = COLOR_STARORBIT if test is True: new = OrbitMarker(self, 'Orbit', star, position, is_orbit=ba, is_complete_orbit=bb, is_resonance=bc) self._markers[star].append(new) self._orbits[star].append(new) self.sort_markers() self.add_button_and_type(star, new, color) self.properties.add(new, layer=4) def add_button_and_type(self, star, marker, color): orbit_type = OrbitType(self) button = OrbitButton(self, color) self._buttons[star].append(button) # Buttons, OrbitTypes and Markers are all Intertwined. orbit_type.intertwine(m=marker, b=button) button.intertwine(m=marker, o=orbit_type) marker.intertwine(o=orbit_type, b=button) self.orbit_descriptions.add(orbit_type) if len(self.buttons): self.sort_buttons() self.properties.add(button, layer=4) self.properties.add(orbit_type, layer=4) button.enable() def sort_markers(self): self.markers.sort(key=lambda m: m.value) for i, marker in enumerate(self.markers, start=1): marker.rect.y = i * 2 * 10 + 38 + self.offset if not self.area_markers.contains(marker.rect): marker.hide() else: marker.show() def sort_buttons(self): x, y = self.curr_x, self.curr_y for bt in sorted(self.buttons, key=lambda b: b.get_value().m): bt.move(x, y) if not self.area_buttons.contains(bt.rect): bt.hide() else: bt.show() if x + bt.rect.w + 15 < self.rect.w - bt.rect.w + 15: x += bt.rect.w + 15 else: x = 3 y += 32 def delete_marker(self, marker): """ :type marker: OrbitMarker """ if not marker.locked: marker.kill() marker.linked_type.kill() marker.linked_button.kill() if marker is self.area_modify.marker: self.area_modify.unlink() idx = self.markers.index(marker) del self.markers[idx] self.buttons.remove(marker.linked_button) self.sort_markers() self.sort_buttons() def on_mousebuttondown(self, event): if self.area_scroll.collidepoint(event.pos): last_is_hidden = not self.markers[-1].is_visible if len(self.markers) > 16 and event.button in (4, 5): if event.button == 4 and self.offset < 0: self.offset += 20 elif event.button == 5 and last_is_hidden: self.offset -= 20 self.sort_markers() elif self.area_buttons.collidepoint( event.pos) and self.buttons is not None and len(self.buttons): self.buttons.sort(key=lambda b: b.get_value().m) last_is_hidden = not self.buttons[-1].is_visible first_is_hidden = not self.buttons[0].is_visible if event.button == 4 and first_is_hidden: self.curr_y += 32 elif event.button == 5 and last_is_hidden: self.curr_y -= 32 self.sort_buttons() elif event.button == 1 and self.markers is not None: for marker in self.markers: marker.deselect() marker.enable() def check_orbits(self): self.orbits.sort(key=lambda o: o.value.m) for x, p in enumerate(self.orbits[1:], start=1): a = self.orbits[x - 1].value.m if x > 0 and len( self.orbits) else self.orbits[0].value.m # el anterior assert a + 0.15 < p.value.m, 'Orbit @' + str( p.value.m) + ' is too close to Orbit @' + str(a) if x + 1 < len(self.orbits): b = self.orbits[x + 1].value.m # el posterior assert p.value.m < b - 0.15, 'Orbit @' + str( p.value.m) + ' is too close to Orbit @' + str(b) def anchor_maker(self, marker): self.area_modify.link(marker) self.selected_marker = marker def clear(self, event): if event.data['panel'] is self: for marker in self.markers: marker.kill() for orbit in self.buttons: orbit.kill() self.markers.clear() self.clear_ratios() def save_orbits(self, event): orbits = self._loaded_orbits for system in Systems.get_systems(): if system.star_system.letter == 'S': for star in system: for marker in self._orbits.get(star, []): d = self.create_save_data(marker.orbit) orbits.append(d) else: star = system.star_system for marker in self._orbits.get(star, []): d = self.create_save_data(marker.orbit) orbits.append(d) EventHandler.trigger(event.tipo + 'Data', 'Orbit', {'Stellar Orbits': orbits}) @staticmethod def create_save_data(orb): d = {} if hasattr(orb, 'semi_major_axis'): d['a'] = round(orb.semi_major_axis.m, 2) if hasattr(orb, 'inclination'): d['i'] = orb.inclination.m if hasattr(orb, 'eccentricity'): d['e'] = orb.eccentricity.m if hasattr(orb, 'astrobody'): d['astrobody'] = orb.astrobody.id d['star_id'] = orb.astrobody.orbit.star.id return d def load_orbits(self, event): for position in event.data.get('Stellar Orbits', []): if position not in self._loaded_orbits: self._loaded_orbits.append(position) def set_loaded_orbits(self): for orbit_data in self._loaded_orbits: a = q(orbit_data['a'], 'au') if 'e' not in orbit_data: self.add_orbit_marker(a) else: e = q(orbit_data['e']) i = q(orbit_data['i'], 'degree') system = Systems.get_system_by_id(orbit_data['star_id']) planet = system.get_astrobody_by(orbit_data['astrobody'], tag_type='id') star = system.star_system planet.set_orbit(star, [a, e, i]) self.add_orbit_marker(planet.orbit) self.planet_area.delete_objects(planet) # borrar las órbitas cargadas para evitar que se dupliquen. self.sort_markers() self._loaded_orbits.clear() def fill_indexes(self): assert len(Systems.get_systems()) for system in Systems.get_systems(): star = system.star_system if star not in self._markers: self._markers[star] = [] self._orbits[star] = [] self._buttons[star] = [] self.indexes.append(star) def show(self): try: self.fill_indexes() self.set_current() self.no_star_error = False except AssertionError: self.no_star_error = True for prop in self.properties.get_widgets_from_layer(2): prop.show() self.visible_markers = True if len(self._loaded_orbits): self.set_loaded_orbits() self.show_markers_button.show() super().show() def hide(self): super().hide() for item in self.properties.widgets(): item.hide() def toggle_stellar_orbits(self): if self.visible_markers: self.area_modify.color_alert() self.add_orbits_button.disable() for marker in self.markers: marker.hide() else: for marker in self.markers: marker.show() self.hide_orbit_types() self.show_markers_button.disable() self.add_orbits_button.enable() self.area_modify.color_standby() self.visible_markers = not self.visible_markers self.area_modify.visible_markers = self.visible_markers def hide_orbit_types(self): for orbit_type in self.orbit_descriptions.widgets(): orbit_type.hide() for orbit_button in self.buttons: orbit_button.unlock() def deselect_markers(self, m): for marker in self.markers: marker.deselect() marker.enable() m.select() def link_astrobody_to_stellar_orbit(self, astrobody): locked = [i for i in self.buttons if i.locked] if len(locked): orbit = PseudoOrbit(locked[0].linked_marker.orbit) locked[0].linked_marker.orbit = orbit locked[0].linked_type.show() locked[0].linked_type.link_astrobody(astrobody) self.add_orbits_button.disable() if astrobody.celestial_type == 'planet': self.recomendation.suggest(astrobody, orbit, Systems.get_current_star()) self.recomendation.show_suggestion(astrobody, orbit.temperature) def update(self): super().update() idx = Systems.get_current_idx() if idx != self.last_idx: self.set_current() self.last_idx = idx if not self.no_star_error: self.image.fill(COLOR_BOX, self.area_markers) else: f = self.crear_fuente(16) text = 'There is no star system set. Go back to the Star Panel and set a star first.' rect = Rect(50, 100, 200, 100) render = render_textrect(text, f, rect.w, (0, 0, 0), COLOR_BOX) self.image.blit(render, rect) def __repr__(self): return 'Orbit Panel' def set_current_digit(self, idx): self.curr_digit = self.ratios.index(idx) def cycle(self): has_values = False for ratio in self.ratios: ratio.deselect() has_values = ratio.value != '' valid = has_values and not self.no_star_error valid = valid and self.selected_marker is not None if valid: self.resonances_button.enable() else: ratio = next(self.cycler) ratio.select() WidgetHandler.set_origin(ratio) def ratios_to_string(self): x = int(self.digit_x.value) y = int(self.digit_y.value) assert x >= y, 'invalid ratio' self.write('{}° Order'.format(x - y), self.order_f, right=self.digit_x.rect.left - 2, y=self.digit_x.rect.y) return '{}:{}'.format(x, y) def clear_ratios(self): self.digit_x.clear() self.digit_y.clear()