class CementedElement(): """Cemented element domain model. Manage rendering and selection/editing. A CementedElement consists of 3 or more Surfaces, 2 or more Gaps, and edge_extent information. Attributes: parent: the :class:`ElementModel` label: string identifier idxs: list of seq_model interface indices ifcs: list of :class:`~rayoptics.seq.interface.Interface` gaps: list of thickness and material :class:`~rayoptics.seq.gap.Gap` tfrm: global transform to element origin, (Rot3, trans3) medium_name: the material filling the gap flats: semi-diameter of flat if ifc is concave, or None handles: dict of graphical entities actions: dict of actions associated with the graphical handles """ clut = rgbt.RGBTable(filename='red_blue64.csv', data_range=[10.0, 100.]) label_format = 'CE{}' serial_number = 0 def __init__(self, ifc_list, label=None): if label is None: CementedElement.serial_number += 1 self.label = CementedElement.label_format.format( CementedElement.serial_number) else: self.label = label g_tfrm = ifc_list[0][4] if g_tfrm is not None: self.tfrm = g_tfrm else: self.tfrm = (np.identity(3), np.array([0., 0., 0.])) self.idxs = [] self.ifcs = [] self.gaps = [] self.medium_name = '' for interface in ifc_list: i, ifc, g, z_dir, g_tfrm = interface self.idxs.append(i) self.ifcs.append(ifc) if g is not None: self.gaps.append(g) if self.medium_name != '': self.medium_name += ', ' self.medium_name += g.medium.name() if len(self.gaps) == len(self.ifcs): self.gaps.pop() self.medium_name = self.medium_name.rpartition(',')[0] self._sd = self.update_size() self.flats = [None]*len(self.ifcs) self.handles = {} self.actions = {} @property def sd(self): """Semi-diameter """ return self._sd @sd.setter def sd(self, semidiam): self._sd = semidiam self.edge_extent = (-semidiam, semidiam) def __json_encode__(self): attrs = dict(vars(self)) del attrs['parent'] del attrs['tfrm'] del attrs['ifcs'] del attrs['gaps'] del attrs['flats'] del attrs['handles'] del attrs['actions'] return attrs def __str__(self): fmt = 'CementedElement: {}' return fmt.format(self.idxs) def sync_to_restore(self, ele_model, surfs, gaps, tfrms): # when restoring, we want to use the stored indices to look up the # new object instances self.parent = ele_model self.ifcs = [surfs[i] for i in self.idxs] self.gaps = [gaps[i] for i in self.idxs[:-1]] self.tfrm = tfrms[self.idxs[0]] self.flats = [None]*len(self.ifcs) if not hasattr(self, 'medium_name'): self.medium_name = self.gap.medium.name() def sync_to_update(self, seq_model): # when updating, we want to use the stored object instances to get the # current indices into the interface list (e.g. to handle insertion and # deletion of interfaces) self.idxs = [seq_model.ifcs.index(ifc) for ifc in self.ifcs] def element_list(self): idxs = self.idxs ifcs = self.ifcs gaps = self.gaps e_list = [] for i in range(len(gaps)): e = Element(ifcs[i], ifcs[i+1], gaps[i], sd=self.sd, tfrm=self.tfrm, idx=idxs[i], idx2=idxs[i+1]) e_list.append(e) def tree(self, **kwargs): default_tag = '#element#cemented' tag = default_tag + kwargs.get('tag', '') zdir = kwargs.get('z_dir', 1) ce = Node('CE', id=self, tag=tag) for i, sg in enumerate(itertools.zip_longest(self.ifcs, self.gaps), start=1): ifc, gap = sg pid = f'p{i}'.format(i) p = Node(pid, id=ifc.profile, tag='#profile', parent=ce) Node(f'i{self.idxs[i-1]}', id=ifc, tag='#ifc', parent=p) # Gap branch if gap is not None: t = Node(f't{i}', id=gap, tag='#thic', parent=ce) Node(f'g{self.idxs[i-1]}', id=(gap, zdir), tag='#gap', parent=t) return ce def reference_interface(self): return self.ifcs[0] def reference_idx(self): return self.idxs[0] def interface_list(self): return self.ifcs def gap_list(self): return self.gaps def update_size(self): extents = np.union1d(self.ifcs[0].get_y_aperture_extent(), self.ifcs[-1].get_y_aperture_extent()) self.edge_extent = (extents[0], extents[-1]) self.sd = max([ifc.surface_od() for ifc in self.ifcs]) return self.sd def compute_flat(self, s): ca = s.surface_od() if (1.0 - ca/self.sd) >= 0.05: flat = ca else: flat = None return flat def extent(self): if hasattr(self, 'edge_extent'): return self.edge_extent else: return (-self.sd, self.sd) def render_shape(self): if self.ifcs[0].profile_cv < 0.0: self.flats[0] = self.compute_flat(self.ifcs[0]) else: self.flats[0] = None if self.ifcs[-1].profile_cv > 0.0: self.flats[-1] = self.compute_flat(self.ifcs[-1]) else: self.flats[-1] = None # generate the profile polylines self.profiles = [] sense = 1 for ifc, flat in zip(self.ifcs, self.flats): poly = ifc.full_profile(self.extent(), flat, sense) self.profiles.append(poly) sense = -sense # offset the profiles wrt the element origin thi = 0 for i, poly_profile in enumerate(self.profiles[1:]): thi += self.gaps[i].thi for p in poly_profile: p[0] += thi # just return outline poly_shape = [] poly_shape += self.profiles[0] poly_shape += self.profiles[-1] poly_shape.append(poly_shape[0]) return poly_shape def render_handles(self, opt_model): self.handles = {} shape = self.render_shape() # self.handles['shape'] = GraphicsHandle(shape, self.tfrm, 'polygon') for i, gap in enumerate(self.gaps): poly = [] poly += self.profiles[i] poly += self.profiles[i+1] poly.append(self.profiles[i][0]) color = calc_render_color_for_material(gap.medium) self.handles['shape'+str(i+1)] = GraphicsHandle(poly, self.tfrm, 'polygon', color) return self.handles def handle_actions(self): self.actions = {} return self.actions
class Element(): clut = rgbt.RGBTable(filename='red_blue64.csv', data_range=[10.0, 100.]) label_format = 'E{}' def __init__(self, s1, s2, g, tfrm=None, idx=0, idx2=1, sd=1., label='Lens'): self.label = label if tfrm is not None: self.tfrm = tfrm else: self.tfrm = (np.identity(3), np.array([0., 0., 0.])) self.s1 = s1 self.s1_indx = idx self.s2 = s2 self.s2_indx = idx2 self.gap = g self.medium_name = self.gap.medium.name() self._sd = sd self.flat1 = None self.flat2 = None self.render_color = self.calc_render_color() self.handles = {} self.actions = {} @property def sd(self): return self._sd @sd.setter def sd(self, semidiam): self._sd = semidiam self.edge_extent = (-semidiam, semidiam) def __json_encode__(self): attrs = dict(vars(self)) del attrs['parent'] del attrs['tfrm'] del attrs['s1'] del attrs['s2'] del attrs['gap'] del attrs['handles'] del attrs['actions'] return attrs def __str__(self): fmt = 'Element: {!r}, {!r}, t={:.4f}, sd={:.4f}, glass: {}' return fmt.format(self.s1.profile, self.s2.profile, self.gap.thi, self.sd, self.gap.medium.name()) def sync_to_restore(self, ele_model, surfs, gaps, tfrms): # when restoring, we want to use the stored indices to look up the # new object instances self.parent = ele_model self.tfrm = tfrms[self.s1_indx] self.s1 = surfs[self.s1_indx] self.gap = gaps[self.s1_indx] self.s2 = surfs[self.s2_indx] if not hasattr(self, 'medium_name'): self.medium_name = self.gap.medium.name() def sync_to_update(self, seq_model): # when updating, we want to use the stored object instances to get the # current indices into the interface list (e.g. to handle insertion and # deletion of interfaces) self.s1_indx = seq_model.ifcs.index(self.s1) self.s2_indx = seq_model.ifcs.index(self.s2) self.render_color = self.calc_render_color() def reference_interface(self): return self.s1 def reference_idx(self): return self.s1_indx def interface_list(self): return [self.s1, self.s2] def gap_list(self): return [self.gap] def get_bending(self): cv1 = self.s1.profile_cv cv2 = self.s2.profile_cv delta_cv = cv1 - cv2 bending = 0. if delta_cv != 0.0: bending = (cv1 + cv2) / delta_cv return bending def set_bending(self, bending): cv1 = self.s1.profile_cv cv2 = self.s2.profile_cv delta_cv = cv1 - cv2 cv2_new = 0.5 * (bending - 1.) * delta_cv cv1_new = bending * delta_cv - cv2_new self.s1.profile_cv = cv1_new self.s2.profile_cv = cv2_new def update_size(self): extents = np.union1d(self.s1.get_y_aperture_extent(), self.s2.get_y_aperture_extent()) self.edge_extent = (extents[0], extents[-1]) self.sd = max(self.s1.surface_od(), self.s2.surface_od()) return self.sd def calc_render_color(self): try: gc = float(self.gap.medium.glass_code()) except AttributeError: return (255, 255, 255, 64) # white else: # set element color based on V-number indx, vnbr = glass_decode(gc) dsg, rgb = gp.find_glass_designation(indx, vnbr) # rgb = Element.clut.get_color(vnbr) return rgb def compute_flat(self, s): ca = s.surface_od() if (1.0 - ca / self.sd) >= 0.05: flat = ca else: flat = None return flat def extent(self): if hasattr(self, 'edge_extent'): return self.edge_extent else: return (-self.sd, self.sd) def render_shape(self): if self.s1.profile_cv < 0.0: self.flat1 = self.compute_flat(self.s1) poly = self.s1.full_profile(self.extent(), self.flat1) if self.s2.profile_cv > 0.0: self.flat2 = self.compute_flat(self.s2) poly2 = self.s2.full_profile(self.extent(), self.flat2, -1) for p in poly2: p[0] += self.gap.thi poly += poly2 poly.append(poly[0]) return poly def render_handles(self, opt_model): self.handles = {} ifcs_gbl_tfrms = opt_model.seq_model.gbl_tfrms shape = self.render_shape() self.handles['shape'] = GraphicsHandle(shape, self.tfrm, 'polygon') extent = self.extent() if self.flat1 is not None: extent_s1 = self.flat1, else: extent_s1 = extent poly_s1 = self.s1.full_profile(extent_s1, None) gh1 = GraphicsHandle(poly_s1, ifcs_gbl_tfrms[self.s1_indx], 'polyline') self.handles['s1_profile'] = gh1 if self.flat2 is not None: extent_s2 = self.flat2, else: extent_s2 = extent poly_s2 = self.s2.full_profile(extent_s2, None, -1) gh2 = GraphicsHandle(poly_s2, ifcs_gbl_tfrms[self.s2_indx], 'polyline') self.handles['s2_profile'] = gh2 poly_sd_upr = [] poly_sd_upr.append([poly_s1[-1][0], extent[1]]) poly_sd_upr.append([poly_s2[0][0] + self.gap.thi, extent[1]]) self.handles['sd_upr'] = GraphicsHandle(poly_sd_upr, self.tfrm, 'polyline') poly_sd_lwr = [] poly_sd_lwr.append([poly_s2[-1][0] + self.gap.thi, extent[0]]) poly_sd_lwr.append([poly_s1[0][0], extent[0]]) self.handles['sd_lwr'] = GraphicsHandle(poly_sd_lwr, self.tfrm, 'polyline') poly_ct = [] poly_ct.append([0., 0.]) poly_ct.append([self.gap.thi, 0.]) self.handles['ct'] = GraphicsHandle(poly_ct, self.tfrm, 'polyline') return self.handles def handle_actions(self): self.actions = {} shape_actions = {} shape_actions['pt'] = BendAction(self) shape_actions['y'] = AttrAction(self, 'sd') self.actions['shape'] = shape_actions s1_prof_actions = {} s1_prof_actions['pt'] = SagAction(self.s1) self.actions['s1_profile'] = s1_prof_actions s2_prof_actions = {} s2_prof_actions['pt'] = SagAction(self.s2) self.actions['s2_profile'] = s2_prof_actions sd_upr_action = {} sd_upr_action['y'] = AttrAction(self, 'sd') self.actions['sd_upr'] = sd_upr_action sd_lwr_action = {} sd_lwr_action['y'] = AttrAction(self, 'sd') self.actions['sd_lwr'] = sd_lwr_action ct_action = {} ct_action['x'] = AttrAction(self.gap, 'thi') self.actions['ct'] = ct_action return self.actions
class Element(): """Lens element domain model. Manage rendering and selection/editing. An Element consists of 2 Surfaces, 1 Gap, and edge_extent information. Attributes: parent: the :class:`ElementModel` label: string identifier s1: first/origin :class:`~rayoptics.seq.interface.Interface` s2: second/last :class:`~rayoptics.seq.interface.Interface` gap: element thickness and material :class:`~rayoptics.seq.gap.Gap` tfrm: global transform to element origin, (Rot3, trans3) medium_name: the material filling the gap flat1: semi-diameter of flat if s1 is concave, or None flat2: semi-diameter of flat if s2 is concave, or None handles: dict of graphical entities actions: dict of actions associated with the graphical handles """ clut = rgbt.RGBTable(filename='red_blue64.csv', data_range=[10.0, 100.]) label_format = 'E{}' serial_number = 0 def __init__(self, s1, s2, g, tfrm=None, idx=0, idx2=1, sd=1., label=None): if label is None: Element.serial_number += 1 self.label = Element.label_format.format(Element.serial_number) else: self.label = label if tfrm is not None: self.tfrm = tfrm else: self.tfrm = (np.identity(3), np.array([0., 0., 0.])) self.s1 = s1 self.s1_indx = idx self.s2 = s2 self.s2_indx = idx2 self.gap = g self.medium_name = self.gap.medium.name() self._sd = sd self.flat1 = None self.flat2 = None self.handles = {} self.actions = {} @property def sd(self): """Semi-diameter """ return self._sd @sd.setter def sd(self, semidiam): self._sd = semidiam self.edge_extent = (-semidiam, semidiam) def __json_encode__(self): attrs = dict(vars(self)) del attrs['parent'] del attrs['tfrm'] del attrs['s1'] del attrs['s2'] del attrs['gap'] del attrs['handles'] del attrs['actions'] return attrs def __str__(self): fmt = 'Element: {!r}, {!r}, t={:.4f}, sd={:.4f}, glass: {}' return fmt.format(self.s1.profile, self.s2.profile, self.gap.thi, self.sd, self.gap.medium.name()) def sync_to_restore(self, ele_model, surfs, gaps, tfrms): # when restoring, we want to use the stored indices to look up the # new object instances self.parent = ele_model self.tfrm = tfrms[self.s1_indx] self.s1 = surfs[self.s1_indx] self.gap = gaps[self.s1_indx] self.s2 = surfs[self.s2_indx] if not hasattr(self, 'medium_name'): self.medium_name = self.gap.medium.name() def sync_to_update(self, seq_model): # when updating, we want to use the stored object instances to get the # current indices into the interface list (e.g. to handle insertion and # deletion of interfaces) self.s1_indx = seq_model.ifcs.index(self.s1) self.s2_indx = seq_model.ifcs.index(self.s2) self.medium_name = self.gap.medium.name() def tree(self, **kwargs): """Build tree linking sequence to element model. """ default_tag = '#element#lens' tag = default_tag + kwargs.get('tag', '') zdir = kwargs.get('z_dir', 1) # Interface branch 1 e = Node('E', id=self, tag=tag) p1 = Node('p1', id=self.s1.profile, tag='#profile', parent=e) Node(f'i{self.s1_indx}', id=self.s1, tag='#ifc', parent=p1) # Gap branch t = Node('t', id=self.gap, tag='#thic', parent=e) Node(f'g{self.s1_indx}', id=(self.gap, zdir), tag='#gap', parent=t) # Interface branch 2 p2 = Node('p2', id=self.s2.profile, tag='#profile', parent=e) Node(f'i{self.s2_indx}', id=self.s2, tag='#ifc', parent=p2) return e def reference_interface(self): return self.s1 def reference_idx(self): return self.s1_indx def interface_list(self): return [self.s1, self.s2] def gap_list(self): return [self.gap] def get_bending(self): cv1 = self.s1.profile_cv cv2 = self.s2.profile_cv delta_cv = cv1 - cv2 bending = 0. if delta_cv != 0.0: bending = (cv1 + cv2)/delta_cv return bending def set_bending(self, bending): cv1 = self.s1.profile_cv cv2 = self.s2.profile_cv delta_cv = cv1 - cv2 cv2_new = 0.5*(bending - 1.)*delta_cv cv1_new = bending*delta_cv - cv2_new self.s1.profile_cv = cv1_new self.s2.profile_cv = cv2_new def update_size(self): extents = np.union1d(self.s1.get_y_aperture_extent(), self.s2.get_y_aperture_extent()) self.edge_extent = (extents[0], extents[-1]) self.sd = max(self.s1.surface_od(), self.s2.surface_od()) return self.sd def compute_flat(self, s): ca = s.surface_od() if (1.0 - ca/self.sd) >= 0.05: flat = ca else: flat = None return flat def extent(self): if hasattr(self, 'edge_extent'): return self.edge_extent else: return (-self.sd, self.sd) def render_shape(self): if self.s1.profile_cv < 0.0: self.flat1 = self.compute_flat(self.s1) else: self.flat1 = None poly = self.s1.full_profile(self.extent(), self.flat1) if self.s2.profile_cv > 0.0: self.flat2 = self.compute_flat(self.s2) else: self.flat2 = None poly2 = self.s2.full_profile(self.extent(), self.flat2, -1) for p in poly2: p[0] += self.gap.thi poly += poly2 poly.append(poly[0]) return poly def render_handles(self, opt_model): self.handles = {} ifcs_gbl_tfrms = opt_model.seq_model.gbl_tfrms shape = self.render_shape() color = calc_render_color_for_material(self.gap.medium) self.handles['shape'] = GraphicsHandle(shape, self.tfrm, 'polygon', color) extent = self.extent() if self.flat1 is not None: extent_s1 = self.flat1, else: extent_s1 = extent poly_s1 = self.s1.full_profile(extent_s1, None) gh1 = GraphicsHandle(poly_s1, ifcs_gbl_tfrms[self.s1_indx], 'polyline') self.handles['s1_profile'] = gh1 if self.flat2 is not None: extent_s2 = self.flat2, else: extent_s2 = extent poly_s2 = self.s2.full_profile(extent_s2, None, -1) gh2 = GraphicsHandle(poly_s2, ifcs_gbl_tfrms[self.s2_indx], 'polyline') self.handles['s2_profile'] = gh2 poly_sd_upr = [] poly_sd_upr.append([poly_s1[-1][0], extent[1]]) poly_sd_upr.append([poly_s2[0][0]+self.gap.thi, extent[1]]) self.handles['sd_upr'] = GraphicsHandle(poly_sd_upr, self.tfrm, 'polyline') poly_sd_lwr = [] poly_sd_lwr.append([poly_s2[-1][0]+self.gap.thi, extent[0]]) poly_sd_lwr.append([poly_s1[0][0], extent[0]]) self.handles['sd_lwr'] = GraphicsHandle(poly_sd_lwr, self.tfrm, 'polyline') poly_ct = [] poly_ct.append([0., 0.]) poly_ct.append([self.gap.thi, 0.]) self.handles['ct'] = GraphicsHandle(poly_ct, self.tfrm, 'polyline') return self.handles def handle_actions(self): self.actions = {} shape_actions = {} shape_actions['pt'] = BendAction(self) shape_actions['y'] = AttrAction(self, 'sd') shape_actions['glass'] = ReplaceGlassAction(self.gap) self.actions['shape'] = shape_actions s1_prof_actions = {} s1_prof_actions['pt'] = SagAction(self.s1) self.actions['s1_profile'] = s1_prof_actions s2_prof_actions = {} s2_prof_actions['pt'] = SagAction(self.s2) self.actions['s2_profile'] = s2_prof_actions sd_upr_action = {} sd_upr_action['y'] = AttrAction(self, 'sd') self.actions['sd_upr'] = sd_upr_action sd_lwr_action = {} sd_lwr_action['y'] = AttrAction(self, 'sd') self.actions['sd_lwr'] = sd_lwr_action ct_action = {} ct_action['x'] = AttrAction(self.gap, 'thi') self.actions['ct'] = ct_action return self.actions