class BoardViewWidget(BaseViewWidget): def __init__(self, project: 'Project') -> None: BaseViewWidget.__init__(self) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.project = project self.image_view_cache = {} self.compositor = CompositeManager() self.trace_renderer = TraceRender(self) self.via_renderer = THRenderer(self) self.hairline_renderer = HairlineRenderer(self) self.debug_renderer = DebugRender(self) self.render_commands = StackupRenderCommands() self.__cad_cache = CADCache(self.project) self.__sel_cache = SelectionHighlightCache(self.project) # TODO, currently broken self.poly_renderer = CachedPolygonRenderer(self) # Initial view is a normalized 1-1-1 area. # Shift to be 10cm max self.viewState.set_scale(1./100000) self.boardViewState = BoardViewState() self.boardViewState.changed.connect(self.update) def resizeGL(self, width: int, height: int) -> None: super(BoardViewWidget, self).resizeGL(width, height) self.compositor.resize(width, height) def text_color(self) -> List[float]: return [1, 1, 1] def current_layer_hack(self) -> 'Layer': return self.boardViewState.current_layer def color_for_pad(self, pad) -> List[float]: if pad.th_diam != 0: return [0.5, 0.5, 0.5] return self.color_for_layer(pad.layer) def color_for_trace(self, trace: Trace) -> List[float]: return self.color_for_layer(trace.layer) def color_for_layer(self, layer: Layer) -> List[float]: return list(layer.color) def sel_colormod(self, t: bool, oldcolor: List[float]) -> List[float]: if t: return [1, 1, 1, 1] return oldcolor def image_view_cache_load(self, il: 'ImageLayer') -> ImageView: key = id(il) if key not in self.image_view_cache: iv = ImageView(il) iv.initGL(self.gls) self.image_view_cache[key] = iv return self.image_view_cache[key] def reinit(self) -> None: self.trace_renderer.initializeGL(self.gls) self.via_renderer.initializeGL(self.gls) self.poly_renderer.initializeGL() self.hairline_renderer.initializeGL() self.debug_renderer.initializeGL(self.gls) self.compositor.initializeGL(self.gls, self.width(), self.height()) for i in list(self.image_view_cache.values()): i.initGL() def current_side(self) -> None: side = self.project.stackup.side_for_layer(self.boardViewState.current_layer) return side def vp_is_visible(self, via_pair: ViaPair) -> bool: if self.boardViewState.current_layer is None: return False if self.boardViewState.render_mode == MODE_CAD: return True else: # In layer mode, viapair is visible if we're on any layer layer = self.boardViewState.current_layer f, s = via_pair.layers return f.order <= layer.order <= s.order def get_visible_point_cloud(self) -> List[Vec2]: bboxes = [i.bbox for i in self.getVisible()] points = [] # Create points from the AABB corners of geometry # TODO: stop using AABB details, use convex hull points for bb in bboxes: points.append(bb.tl) points.append(bb.tr) points.append(bb.bl) points.append(bb.br) if self.boardViewState.render_mode == MODE_TRACE and self.boardViewState.show_images and len(self.boardViewState.current_layer.imagelayers) > 0: for i in self.boardViewState.current_layer.imagelayers: points.extend(i.get_corner_points()) if not points: return return points def getVisible(self) -> Sequence[Geom]: objects = [] if self.boardViewState.render_mode == MODE_CAD and not self.boardViewState.show_trace_mode_geom: return # Add visible vias vp_visible = {} for i in self.project.stackup.via_pairs: vp_visible[i] = self.vp_is_visible(i) for via in self.project.artwork.vias: if vp_visible[via.viapair]: objects.append(via) # Traces for trace in self.project.artwork.traces: if self.layer_visible(trace.layer): objects.append(trace) for polygon in self.project.artwork.polygons: if self.layer_visible(polygon): objects.append(polygon) if self.boardViewState.render_mode == MODE_CAD: objects.extend(self.project.artwork.components) for cmp in self.project.artwork.components: objects.extend(cmp.get_pads()) elif self.boardViewState.current_layer is None: pass else: cur_side = self.current_side() for cmp in self.project.artwork.components: if cmp.side == cur_side: objects.append(cmp) for pad in cmp.get_pads(): if pad.is_through(): objects.append(pad) elif pad.side == cur_side: objects.append(pad) # TODO: Airwires are always visible objects += self.project.artwork.airwires return objects def _layer_visible(self, l: Layer) -> bool: if self.boardViewState.render_mode == MODE_CAD: return True else: return l is self.boardViewState.current_layer def layer_visible(self, l: Layer) -> bool: return self.__layer_visible_lut[l.number] def layer_visible_m(self, l: Layer) -> bool: return self.boardViewState.current_layer in l def query_point(self, pt: Point2) -> Optional[Geom]: # todo: don't use this function anywhere, or introduce some picking heuristic all_aw = self.query_point_multiple(pt) if not all_aw: return None # Return an arbitrary element return all_aw.pop() def query_point_multiple(self, pt: Point2) -> Sequence[Geom]: all_aw = set(self.project.artwork.query_point_multiple(pt)) vis_aw = set(self.getVisible()) return vis_aw.intersection(all_aw) def __render_top_half(self) -> None: """ :return: """ # Forcibly init all layers for l in self.project.stackup.layers: self.render_commands.layers[l] for v in self.project.stackup.via_pairs: self.render_commands.vias[v] self.render_commands.clear() # Update CAD cache self.__cad_cache.update_if_necessary() self.__cad_cache.extendTo(self.render_commands) # Update Selection cache self.__sel_cache.update_if_necessary(self.selectionList) for k, v in self.render_commands.layers.items(): GL.glPushDebugGroup(GL.GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Layer %r" % k) with self.compositor.get(k): self.trace_renderer.render_va(v.va_traces, self.viewState.glMatrix, COL_LAYER_MAIN) GL.glPopDebugGroup() # TODO: Render Text # Draw all the viapairs for k, v in self.render_commands.vias.items(): GL.glPushDebugGroup(GL.GL_DEBUG_SOURCE_APPLICATION, 0, -1, "ViaPair %r" % k) with self.compositor.get(k): self.via_renderer.render_filled(self.viewState.glMatrix, v.va_vias) GL.glPopDebugGroup() # Draw the multilayer components GL.glPushDebugGroup(GL.GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Multi Cmp") with self.compositor.get("MULTI"): self.via_renderer.render_filled(self.viewState.glMatrix, self.render_commands.multi.va_vias) # TODO: Render text GL.glPopDebugGroup() # Draw the front and back sides for k, v in self.render_commands.sides.items(): GL.glPushDebugGroup(GL.GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Cmp %r" % k) with self.compositor.get(("LINEART", k)): self.hairline_renderer.render_va(self.viewState.glMatrix, v.va_outlines, COL_CMP_LINE) # TODO: Render text GL.glPopDebugGroup() # Just create (don't actually bind) the overlay layer GL.glPushDebugGroup(GL.GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Overlay") with self.compositor.get("OVERLAY"): self.hairline_renderer.render_va(self.viewState.glMatrix, self.__cad_cache.airwire_va, COL_SEL) self.hairline_renderer.render_va(self.viewState.glMatrix, self.__sel_cache.thinline_va, COL_SEL) self.trace_renderer.render_va(self.__sel_cache.thickline_va, self.viewState.glMatrix, COL_SEL) self.via_renderer.render_filled(self.viewState.glMatrix, self.__sel_cache.via_va, COL_SEL) GL.glPopDebugGroup() GL.glPushDebugGroup(GL.GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Tool") self.render_tool() GL.glPopDebugGroup() def render_mode_cad(self) -> None: # Composite all the layers GL.glDisable(GL.GL_DEPTH_TEST) GL.glClear(GL.GL_COLOR_BUFFER_BIT) draw_things = [] # Prepare to draw all the layers for i in self.project.stackup.layers: if not self.layer_visible(i): continue draw_things.append((i, self.color_for_layer(i))) # and the via pairs for i in self.project.stackup.via_pairs: if not self.vp_is_visible(i): continue draw_things.append((i, (255, 0, 255))) # Now, sort them based on an order in which layers are drawn from bottom-to-top # (unless the board is flipped, in which case, top-to-bottom), followed by the currently selected layer # Via pairs are draw immediately _after_ the artwork of the layer def layer_key(a): lt, _ = a if isinstance(lt, Layer): a_l = lt b = 0 elif isinstance(lt, ViaPair): a_l = lt.layers[0] b = 1 else: raise ValueError("Unknown layer key type for %r" % type(a)) # We always draw the current layer last if a_l == self.boardViewState.current_layer: a = 1 else: a = -a_l.order return (a, b) draw_things.sort(key=layer_key) draw_things.insert(0, (("LINEART", SIDE.Top), (255, 255, 255))) draw_things.append(("MULTI", (255, 255, 255))) draw_things.append((("LINEART", SIDE.Bottom), (255, 255, 255))) with self.compositor.composite_prebind() as pb: for key, color in draw_things: pb.composite(key, color) pb.composite("OVERLAY", (255, 255, 255)) return def render_mode_trace(self) -> None: # Composite all the layers GL.glDisable(GL.GL_DEPTH_TEST) GL.glClear(GL.GL_COLOR_BUFFER_BIT) layer = self.boardViewState.current_layer if layer is None: return # Draw the imagery stackup_layer = self.boardViewState.current_layer if stackup_layer is not None and self.boardViewState.show_images and (len(stackup_layer.imagelayers) > 0): images = list(stackup_layer.imagelayers) i = self.boardViewState.layer_permute % len(images) images_cycled = images[i:] + images[:i] for l in images_cycled: self.image_view_cache_load(l).render(self.viewState.glMatrix) if not self.boardViewState.show_trace_mode_geom: return with self.compositor.composite_prebind() as pb: # Render the traced geometry color = self.color_for_layer(layer) pb.composite(layer, color) # Render any viapairs on this layer for via_pair in self.project.stackup.via_pairs: if self.vp_is_visible(via_pair): pb.composite(via_pair, (255, 0, 255)) # Render the line-art side = self.project.stackup.side_for_layer(layer) pb.composite(("LINEART", side), (255, 255, 255)) # Multilayer through-holes pb.composite("MULTI", (255, 255, 255)) # And the tool overlay pb.composite("OVERLAY", (255, 255, 255)) def render(self) -> None: with Timer() as t_render: # Update the layer-visible check self.__layer_visible_lut = [self._layer_visible(i) for i in self.project.stackup.layers] # zero accuum buffers for restarts self.trace_renderer.restart() self.poly_renderer.restart() # Update the Compositor self.compositor.restart() # Fill colors self.compositor.set_color_table( [ (255, 255, 255, 255), # Color 0 is always the Layer current color (ignored) (255, 255, 255, 255), # Color of Text (255, 255, 255, 255), # Color of Selection (128, 128, 128, 255), # Color of Vias (128, 128, 0, 255), # Color of Airwires (190, 190, 0, 255), ] ) self.__render_top_half() if self.boardViewState.render_mode == MODE_CAD: # Render layer stack bottom to top self.render_mode_cad() elif self.boardViewState.render_mode == MODE_TRACE: # Render a single layer for maximum contrast self.render_mode_trace() self.debug_renderer.render()
class BoardViewWidget(BaseViewWidget): def __init__(self, project): BaseViewWidget.__init__(self) self.project = project self.image_view_cache = { } self.pad_renderer = PadRender(self) self.dip_renderer = DIPRender(self) self.smd_renderer = SMDRender(self) self.trace_renderer = TraceRender(self) self.via_renderer = THRenderer(self) self.text_batch = TextBatcher(self.gls.text) self.poly_renderer = CachedPolygonRenderer(self) self.hairline_renderer = HairlineRenderer(self) self.passive_renderer = PassiveRender(self) # Initial view is a normalized 1-1-1 area. # Shift to be 10cm max self.viewState.transform = translate(-0.9, -0.9).dot(scale(1./100000)) def text_color(self): return [1,1,1] def current_layer_hack(self): return self.viewState.current_layer def color_for_pad(self, pad): if pad.th_diam != 0: return [0.5, 0.5, 0.5] return self.color_for_layer(pad.layer) def color_for_trace(self, trace): return self.color_for_layer(trace.layer) def color_for_layer(self, layer): return list(layer.color) def sel_colormod(self, t, oldcolor): if t: return [1,1,1,1] return oldcolor def image_view_cache_load(self, il): key = id(il) if key not in self.image_view_cache: iv = ImageView(il) iv.initGL(self.gls) self.image_view_cache[key] = iv return self.image_view_cache[key] def reinit(self): self.pad_renderer.initializeGL(self, self.gls) self.dip_renderer.initializeGL(self.gls) self.smd_renderer.initializeGL(self.gls) self.trace_renderer.initializeGL(self.gls) self.via_renderer.initializeGL(self.gls) self.text_batch.initializeGL() self.poly_renderer.initializeGL() self.hairline_renderer.initializeGL() for i in list(self.image_view_cache.values()): i.initGL() def getVisibleArtwork(self): objects = [] objects += self.project.artwork.vias objects += self.project.artwork.traces objects += self.project.artwork.polygons objects += self.project.artwork.airwires return objects def render_component(self, mat, cmp, render_mode=RENDER_STANDARD, render_hint=RENDER_HINT_NORMAL): if not self.layer_visible_m(cmp.on_layers()): return if isinstance(cmp, DIPComponent): self.dip_renderer.render(mat, cmp, render_mode, render_hint) elif isinstance(cmp, SMD4Component): self.smd_renderer.render(mat, cmp, render_mode, render_hint) elif isinstance(cmp, PassiveComponent): self.passive_renderer.render(mat, cmp, render_mode, render_hint) else: pass #raise TypeError("Can't render %s" % cmp) cm = mat.dot(cmp.matrix) for pad in cmp.get_pads(): pad_render_mode = render_mode if not pad.is_through() and not self.layer_visible(pad.layer): continue if pad in self.selectionList: pad_render_mode |= RENDER_SELECTED self.pad_renderer.render(cm, pad, pad_render_mode, render_hint) def layer_visible(self, l): return l is self.viewState.current_layer or self.viewState.draw_other_layers def layer_visible_m(self, l): return self.viewState.current_layer in l or self.viewState.draw_other_layers def render(self): t_render_start = time.time() # zero accuum buffers for restarts self.trace_renderer.restart() self.via_renderer.restart() self.text_batch.restart() self.poly_renderer.restart() self.hairline_renderer.restart() stackup_layer = self.viewState.current_layer if stackup_layer is None: return # Render all images down onto the layer with Timer() as il_timer: if self.viewState.show_images: for l in stackup_layer.imagelayers: self.image_view_cache_load(l).render(self.viewState.glMatrix) # Now render features self.lt = time.time() GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) artwork = self.getVisibleArtwork() # Build rendering batches with Timer() as t_aw: for i in artwork: rs = RENDER_SELECTED if i in self.selectionList else 0 if isinstance(i, Trace): if self.layer_visible(i.layer): self.trace_renderer.deferred(i, rs, RENDER_HINT_NORMAL) elif isinstance(i, Via): if self.layer_visible_m(i.viapair.all_layers): self.via_renderer.deferred(i.pt, i.r, 0, rs, RENDER_HINT_NORMAL) elif isinstance(i, Polygon): if self.layer_visible(i.layer): self.poly_renderer.deferred(i, rs, RENDER_HINT_NORMAL) elif isinstance(i, Airwire): self.hairline_renderer.deferred(i.p0, i.p1, AIRWIRE_COLOR, None, RENDER_HINT_NORMAL) else: raise NotImplementedError() with Timer() as cmp_timer: for cmp in self.project.artwork.components: render_state = 0 if cmp in self.selectionList: render_state |= RENDER_SELECTED self.render_component(self.viewState.glMatrix, cmp, render_state) with Timer() as other_timer: super(BoardViewWidget, self).render() def ly_order_func(layer): # We always draw the current layer last if layer is self.viewState.current_layer: return 1 return -layer.order with Timer() as t: # Draw all the layers layers = sorted(self.project.stackup.layers, key=ly_order_func) for layer in layers: self.trace_renderer.render_deferred_layer(self.viewState.glMatrix, layer) self.poly_renderer.render(self.viewState.glMatrix, layer) self.text_batch.render(key=layer) self.hairline_renderer.render_group(self.viewState.glMatrix, layer) self.via_renderer.render(self.viewState.glMatrix) # Render the non-layer text self.text_batch.render() self.hairline_renderer.render_group(self.viewState.glMatrix, None) self.hairline_renderer.render_group(self.viewState.glWMatrix, "OVERLAY_VS") #self.hairline_renderer.render_group(self.viewState.glMatrix, "OVERLAY_VS") GL.glFinish() all_time = time.time() - t_render_start print("Render time all: %f ot: %f cmp: %f aw: %f gl: %f" % (all_time, other_timer.interval, cmp_timer.interval, t_aw.interval, t.interval))
class BoardViewWidget(BaseViewWidget): def __init__(self, project): BaseViewWidget.__init__(self) self.project = project self.image_view_cache = { } self.pad_renderer = PadRender(self) self.dip_renderer = DIPRender(self) self.smd_renderer = SMDRender(self) self.trace_renderer = TraceRender(self) self.via_renderer = THRenderer(self) self.text_batch = TextBatcher(self.gls.text) self.poly_renderer = CachedPolygonRenderer(self) self.hairline_renderer = HairlineRenderer(self) self.passive_renderer = PassiveRender(self) # Initial view is a normalized 1-1-1 area. # Shift to be 10cm max self.viewState.transform = translate(-0.9, -0.9).dot(scale(1./100000)) def text_color(self): return [1,1,1] def current_layer_hack(self): return self.viewState.current_layer def color_for_pad(self, pad): if pad.th_diam != 0: return [0.5, 0.5, 0.5] return self.color_for_layer(pad.layer) def color_for_trace(self, trace): return self.color_for_layer(trace.layer) def color_for_layer(self, layer): return list(layer.color) def sel_colormod(self, t, oldcolor): if t: return [1,1,1,1] return oldcolor def image_view_cache_load(self, il): key = id(il) if key not in self.image_view_cache: iv = ImageView(il) iv.initGL(self.gls) self.image_view_cache[key] = iv return self.image_view_cache[key] def reinit(self): self.pad_renderer.initializeGL(self, self.gls) self.dip_renderer.initializeGL(self.gls) self.smd_renderer.initializeGL(self.gls) self.trace_renderer.initializeGL(self.gls) self.via_renderer.initializeGL(self.gls) self.text_batch.initializeGL() self.poly_renderer.initializeGL() self.hairline_renderer.initializeGL() for i in list(self.image_view_cache.values()): i.initGL() def getVisibleArtwork(self): objects = [] objects += self.project.artwork.vias objects += self.project.artwork.traces objects += self.project.artwork.polygons objects += self.project.artwork.airwires return objects def render_component(self, mat, cmp, render_mode=RENDER_STANDARD, render_hint=RENDER_HINT_NORMAL): if not self.layer_visible_m(cmp.on_layers()): return if isinstance(cmp, DIPComponent): self.dip_renderer.render(mat, cmp, render_mode, render_hint) elif isinstance(cmp, SMD4Component): self.smd_renderer.render(mat, cmp, render_mode, render_hint) elif isinstance(cmp, PassiveComponent): self.passive_renderer.render(mat, cmp, render_mode, render_hint) else: pass #raise TypeError("Can't render %s" % cmp) cm = mat.dot(cmp.matrix) for pad in cmp.get_pads(): pad_render_mode = render_mode if not pad.is_through() and not self.layer_visible(pad.layer): continue if pad in self.selectionList: pad_render_mode |= RENDER_SELECTED self.pad_renderer.render(cm, pad, pad_render_mode, render_hint) def layer_visible(self, l): return l is self.viewState.current_layer or self.viewState.draw_other_layers def layer_visible_m(self, l): return self.viewState.current_layer in l or self.viewState.draw_other_layers def render(self): t_render_start = time.time() # zero accuum buffers for restarts self.trace_renderer.restart() self.via_renderer.restart() self.text_batch.restart() self.poly_renderer.restart() self.hairline_renderer.restart() stackup_layer = self.viewState.current_layer if stackup_layer is None: return # Render all images down onto the layer with Timer() as il_timer: if self.viewState.show_images: images = list(stackup_layer.imagelayers) i = self.viewState.layer_permute % len(images) images_cycled = images[i:] + images[:i] for l in images_cycled: self.image_view_cache_load(l).render(self.viewState.glMatrix) # Now render features self.lt = time.time() GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) artwork = self.getVisibleArtwork() # Build rendering batches with Timer() as t_aw: for i in artwork: rs = RENDER_SELECTED if i in self.selectionList else 0 if isinstance(i, Trace): if self.layer_visible(i.layer): self.trace_renderer.deferred(i, rs, RENDER_HINT_NORMAL) elif isinstance(i, Via): if self.layer_visible_m(i.viapair.all_layers): self.via_renderer.deferred(i.pt, i.r, 0, rs, RENDER_HINT_NORMAL) elif isinstance(i, Polygon): if self.layer_visible(i.layer): self.poly_renderer.deferred(i, rs, RENDER_HINT_NORMAL) elif isinstance(i, Airwire): self.hairline_renderer.deferred(i.p0, i.p1, AIRWIRE_COLOR, None, RENDER_HINT_NORMAL) else: raise NotImplementedError() with Timer() as cmp_timer: for cmp in self.project.artwork.components: render_state = 0 if cmp in self.selectionList: render_state |= RENDER_SELECTED self.render_component(self.viewState.glMatrix, cmp, render_state) with Timer() as other_timer: super(BoardViewWidget, self).render() def ly_order_func(layer): # We always draw the current layer last if layer is self.viewState.current_layer: return 1 return -layer.order with Timer() as t: # Draw all the layers layers = sorted(self.project.stackup.layers, key=ly_order_func) for layer in layers: self.trace_renderer.render_deferred_layer(self.viewState.glMatrix, layer) self.poly_renderer.render(self.viewState.glMatrix, layer) self.text_batch.render(key=layer) self.hairline_renderer.render_group(self.viewState.glMatrix, layer) self.via_renderer.render(self.viewState.glMatrix) # Render the non-layer text self.text_batch.render() self.hairline_renderer.render_group(self.viewState.glMatrix, None) self.hairline_renderer.render_group(self.viewState.glWMatrix, "OVERLAY_VS") #self.hairline_renderer.render_group(self.viewState.glMatrix, "OVERLAY_VS") GL.glFinish() all_time = time.time() - t_render_start print("Render time all: %f ot: %f cmp: %f aw: %f gl: %f" % (all_time, other_timer.interval, cmp_timer.interval, t_aw.interval, t.interval))