├── .clang-format ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── bitmap.fth ├── board.fth ├── boardforth.c ├── boardforth.fth ├── components.fth ├── geometry.fth ├── notes.org ├── test.fth ├── test_board.fth ├── test_draw.fth ├── units.fth ├── util.fth └── vector.fth /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -1 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveBitFields: false 9 | AlignConsecutiveDeclarations: false 10 | AlignEscapedNewlines: Left 11 | AlignOperands: Align 12 | AlignTrailingComments: true 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllConstructorInitializersOnNextLine: true 15 | AllowAllParametersOfDeclarationOnNextLine: true 16 | AllowShortEnumsOnASingleLine: true 17 | AllowShortBlocksOnASingleLine: Never 18 | AllowShortCaseLabelsOnASingleLine: false 19 | AllowShortFunctionsOnASingleLine: All 20 | AllowShortLambdasOnASingleLine: All 21 | AllowShortIfStatementsOnASingleLine: WithoutElse 22 | AllowShortLoopsOnASingleLine: true 23 | AlwaysBreakAfterDefinitionReturnType: None 24 | AlwaysBreakAfterReturnType: None 25 | AlwaysBreakBeforeMultilineStrings: true 26 | AlwaysBreakTemplateDeclarations: Yes 27 | BinPackArguments: true 28 | BinPackParameters: true 29 | BraceWrapping: 30 | AfterCaseLabel: false 31 | AfterClass: false 32 | AfterControlStatement: Never 33 | AfterEnum: false 34 | AfterFunction: false 35 | AfterNamespace: false 36 | AfterObjCDeclaration: false 37 | AfterStruct: false 38 | AfterUnion: false 39 | AfterExternBlock: false 40 | BeforeCatch: false 41 | BeforeElse: false 42 | BeforeLambdaBody: false 43 | BeforeWhile: false 44 | IndentBraces: false 45 | SplitEmptyFunction: true 46 | SplitEmptyRecord: true 47 | SplitEmptyNamespace: true 48 | BreakBeforeBinaryOperators: None 49 | BreakBeforeBraces: Attach 50 | BreakBeforeInheritanceComma: false 51 | BreakInheritanceList: BeforeColon 52 | BreakBeforeTernaryOperators: true 53 | BreakConstructorInitializersBeforeComma: false 54 | BreakConstructorInitializers: BeforeColon 55 | BreakAfterJavaFieldAnnotations: false 56 | BreakStringLiterals: true 57 | ColumnLimit: 80 58 | CommentPragmas: '^ IWYU pragma:' 59 | CompactNamespaces: false 60 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 61 | ConstructorInitializerIndentWidth: 4 62 | ContinuationIndentWidth: 4 63 | Cpp11BracedListStyle: true 64 | DeriveLineEnding: true 65 | DerivePointerAlignment: true 66 | DisableFormat: false 67 | ExperimentalAutoDetectBinPacking: false 68 | FixNamespaceComments: true 69 | ForEachMacros: 70 | - foreach 71 | - Q_FOREACH 72 | - BOOST_FOREACH 73 | IncludeBlocks: Regroup 74 | IncludeCategories: 75 | - Regex: '^' 76 | Priority: 2 77 | SortPriority: 0 78 | - Regex: '^<.*\.h>' 79 | Priority: 1 80 | SortPriority: 0 81 | - Regex: '^<.*' 82 | Priority: 2 83 | SortPriority: 0 84 | - Regex: '.*' 85 | Priority: 3 86 | SortPriority: 0 87 | IncludeIsMainRegex: '([-_](test|unittest))?$' 88 | IncludeIsMainSourceRegex: '' 89 | IndentCaseLabels: true 90 | IndentCaseBlocks: false 91 | IndentGotoLabels: true 92 | IndentPPDirectives: None 93 | IndentExternBlock: AfterExternBlock 94 | IndentWidth: 2 95 | IndentWrappedFunctionNames: false 96 | InsertTrailingCommas: None 97 | JavaScriptQuotes: Leave 98 | JavaScriptWrapImports: true 99 | KeepEmptyLinesAtTheStartOfBlocks: false 100 | MacroBlockBegin: '' 101 | MacroBlockEnd: '' 102 | MaxEmptyLinesToKeep: 1 103 | NamespaceIndentation: None 104 | ObjCBinPackProtocolList: Never 105 | ObjCBlockIndentWidth: 2 106 | ObjCBreakBeforeNestedBlockParam: true 107 | ObjCSpaceAfterProperty: false 108 | ObjCSpaceBeforeProtocolList: true 109 | PenaltyBreakAssignment: 2 110 | PenaltyBreakBeforeFirstCallParameter: 1 111 | PenaltyBreakComment: 300 112 | PenaltyBreakFirstLessLess: 120 113 | PenaltyBreakString: 1000 114 | PenaltyBreakTemplateDeclaration: 10 115 | PenaltyExcessCharacter: 1000000 116 | PenaltyReturnTypeOnItsOwnLine: 200 117 | PointerAlignment: Left 118 | RawStringFormats: 119 | - Language: Cpp 120 | Delimiters: 121 | - cc 122 | - CC 123 | - cpp 124 | - Cpp 125 | - CPP 126 | - 'c++' 127 | - 'C++' 128 | CanonicalDelimiter: '' 129 | BasedOnStyle: google 130 | - Language: TextProto 131 | Delimiters: 132 | - pb 133 | - PB 134 | - proto 135 | - PROTO 136 | EnclosingFunctions: 137 | - EqualsProto 138 | - EquivToProto 139 | - PARSE_PARTIAL_TEXT_PROTO 140 | - PARSE_TEST_PROTO 141 | - PARSE_TEXT_PROTO 142 | - ParseTextOrDie 143 | - ParseTextProtoOrDie 144 | - ParseTestProto 145 | - ParsePartialTestProto 146 | CanonicalDelimiter: '' 147 | BasedOnStyle: google 148 | ReflowComments: true 149 | SortIncludes: true 150 | SortUsingDeclarations: true 151 | SpaceAfterCStyleCast: false 152 | SpaceAfterLogicalNot: false 153 | SpaceAfterTemplateKeyword: true 154 | SpaceBeforeAssignmentOperators: true 155 | SpaceBeforeCpp11BracedList: false 156 | SpaceBeforeCtorInitializerColon: true 157 | SpaceBeforeInheritanceColon: true 158 | SpaceBeforeParens: ControlStatements 159 | SpaceBeforeRangeBasedForLoopColon: true 160 | SpaceInEmptyBlock: false 161 | SpaceInEmptyParentheses: false 162 | SpacesBeforeTrailingComments: 2 163 | SpacesInAngles: false 164 | SpacesInConditionalStatement: false 165 | SpacesInContainerLiterals: true 166 | SpacesInCStyleCastParentheses: false 167 | SpacesInParentheses: false 168 | SpacesInSquareBrackets: false 169 | SpaceBeforeSquareBrackets: false 170 | Standard: Auto 171 | StatementMacros: 172 | - Q_UNUSED 173 | - QT_REQUIRE_VERSION 174 | TabWidth: 8 175 | UseCRLF: false 176 | UseTab: Never 177 | WhitespaceSensitiveMacros: 178 | - STRINGIZE 179 | - PP_STRINGIZE 180 | - BOOST_PP_STRINGIZE 181 | ... 182 | 183 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.eo 3 | tags 4 | /boardforth 5 | /pforth.dic 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pforth"] 2 | path = pforth 3 | url = https://github.com/philburk/pforth.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Nick Pascucci 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: format tags clean run 2 | 3 | export PF_USER_CUSTOM = ../../boardforth.c 4 | export LDADD = -L/usr/local/lib -lSDL2 5 | 6 | TAGS_OPTS = --declarations -o TAGS 7 | 8 | build: boardforth 9 | 10 | boardforth: boardforth.c 11 | $(MAKE) -C pforth/build/unix pfdicdat 12 | mv pforth/build/unix/pforth.dic . 13 | $(MAKE) -C pforth/build/unix clean 14 | cp pforth.dic pforth/build/unix/ 15 | $(MAKE) -C pforth/build/unix pfdicapp EXTRA_CCOPTS="-DPF_NO_MAIN" 16 | mv pforth/build/unix/pforth ./boardforth 17 | 18 | tags: 19 | find . -type f -iname "*.[ch]" | etags $(TAGS_OPTS) - 20 | 21 | format: boardforth.c 22 | clang-format -i boardforth.c 23 | 24 | clean: 25 | $(MAKE) -C pforth/build/unix clean 26 | rm pforth.dic boardforth 27 | 28 | run: boardforth 29 | ./boardforth 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BoardForth 2 | 3 | BoardForth is an experimental Forth-based circuit board design tool. It 4 | integrates pForth with SDL2 to provide graphics display capabilities in a 5 | portable and lightweight Forth environment. 6 | 7 | ## Design 8 | 9 | SDL and Forth don't naturally fit together. SDL is a C library through and 10 | through, and doesn't conform exactly to Forth calling conventions. To integrate 11 | the two, BoardForth embeds the pForth interpreter within an SDL application. SDL 12 | runs on the main thread, controlling a rendering stack used to display a system 13 | window. A sub-thread executes pForth. The two threads share a common pixel 14 | buffer, whose access is managed using an SDL mutex lock. These structures are 15 | exposed by C functions linked into the Forth dictionary, allowing the two 16 | programs to communicate. 17 | 18 | The main thread is responsible for monitoring the SDL event loop and exiting if 19 | the app is quit, as well as waiting for a semaphore to be set by the Forth code 20 | and triggering a re-render of the pixel buffer to the display. If the Forth 21 | interpreter exits it sets another semaphore indicating to the main loop that it 22 | should exit. 23 | 24 | Drawing primitives are provided as Forth words, implemented using pixel-level 25 | operations. This allows easy iteration and does not require a separate 26 | compilation step. 27 | -------------------------------------------------------------------------------- /bitmap.fth: -------------------------------------------------------------------------------- 1 | \ Color space 2 | : ARGB_ALPHA ( c -- n , Get the alpha channel of a color ) 3 | 24 RSHIFT 4 | ; 5 | 6 | : ARGB_RED ( c -- n , Get the alpha channel of a color ) 7 | 16 RSHIFT 255 AND 8 | ; 9 | 10 | : ARGB_GREEN ( c -- n , Get the alpha channel of a color ) 11 | 8 RSHIFT 255 AND 12 | ; 13 | 14 | : ARGB_BLUE ( c -- n , Get the alpha channel of a color ) 15 | 255 AND 16 | ; 17 | 18 | : ARGB_JOIN ( b g r a -- c , Combine channels into a color ) 19 | 24 LSHIFT 20 | SWAP 16 LSHIFT + 21 | SWAP 8 LSHIFT + 22 | SWAP + 23 | ; 24 | 25 | : ARGB_BLEND { ctop cbot -- cmix , Blend colors together with alpha transparency } 26 | \ For each channel: 27 | \ C_out = C_top * alpha_top + C_bot * alpha_bot * (1 - alpha_top) 28 | 29 | CTOP ARGB_BLUE CTOP ARGB_ALPHA 255 */ 30 | 255 CTOP ARGB_ALPHA - 31 | CBOT ARGB_BLUE CBOT ARGB_ALPHA 255 */ 255 */ 32 | + 33 | 34 | CTOP ARGB_GREEN CTOP ARGB_ALPHA 255 */ 35 | 255 CTOP ARGB_ALPHA - 36 | CBOT ARGB_GREEN CBOT ARGB_ALPHA 255 */ 255 */ 37 | + 38 | 39 | CTOP ARGB_RED CTOP ARGB_ALPHA 255 */ 40 | 255 CTOP ARGB_ALPHA - 41 | CBOT ARGB_RED CBOT ARGB_ALPHA 255 */ 255 */ 42 | + 43 | 44 | \ alpha_out = alpha_top + (alpha_bottom * (255 - alpha_top) / 255) 45 | 255 CTOP ARGB_ALPHA - 46 | CBOT ARGB_ALPHA 255 */ 47 | CTOP ARGB_ALPHA + 48 | 49 | ARGB_JOIN 50 | ; 51 | 52 | : OPACITY ( c a -- c , Set the opacity of the color ) 53 | SWAP 54 | DUP DUP 55 | ARGB_BLUE ROT 56 | ARGB_GREEN ROT 57 | ARGB_RED 58 | 3 ROLL 59 | ARGB_JOIN 60 | ; 61 | 62 | \ Pixels 63 | 64 | HEX 65 | FFFFFFFF CONSTANT PIX_MASK 66 | DECIMAL 67 | 68 | : COLOR@ ( addr -- c , Get the color at a given address ) 69 | @ PIX_MASK AND 70 | ; 71 | 72 | : COLOR! ( c addr -- , Set the color stored at a given address ) 73 | DUP @ PIX_MASK INVERT AND 74 | ROT PIX_MASK AND OR SWAP ! 75 | ; 76 | 77 | : PIXELS ( -- n , Get the size of a pixel in bytes ) 78 | DISP.PIXSIZE 79 | ; 80 | 81 | : COLORS ( -- n , Get the size of a color in bytes ) 82 | PIXELS 83 | ; 84 | 85 | : DISP.BUF_SIZE ( -- n , Get the size of the display buffer in bytes ) 86 | PIXELS DISP.WIDTH DISP.HEIGHT * * CHARS 87 | ; 88 | 89 | \ Create a temporary drawing buffer which can be used to incrementally set up a 90 | \ frame. 91 | DISP.BUF_SIZE ALLOCATE 0= NOT ABORT" Could not allocate draw buffer!" 92 | CONSTANT DRAW_BUF 93 | 94 | \ Get the X pixel address offset. 95 | : X_OFF ( n -- n ) DISP.PIXSIZE * ; 96 | 97 | \ Get the Y pixel address offset. 98 | : Y_OFF ( n -- n ) DISP.PIXSIZE * DISP.WIDTH * ; 99 | 100 | VARIABLE DIRTY_X_MIN 101 | VARIABLE DIRTY_Y_MIN 102 | 103 | DISP.WIDTH DIRTY_X_MIN ! 104 | DISP.HEIGHT DIRTY_Y_MIN ! 105 | 106 | VARIABLE DIRTY_X_MAX 107 | VARIABLE DIRTY_Y_MAX 108 | 109 | 0 DIRTY_X_MAX ! 110 | 0 DIRTY_Y_MAX ! 111 | 112 | : BLIT ( -- , Draw the contents of the drawing buffer onto the display buffer ) 113 | DIRTY_Y_MAX @ DIRTY_Y_MIN @ > IF 114 | DIRTY_X_MAX @ DIRTY_X_MIN @ > IF 115 | DIRTY_Y_MAX @ DIRTY_Y_MIN @ DO 116 | DIRTY_X_MAX @ DIRTY_X_MIN @ DO 117 | DRAW_BUF I X_OFF J Y_OFF + + COLOR@ 118 | DISP.PIXBUF I X_OFF J Y_OFF + + COLOR@ ARGB_BLEND 119 | DISP.PIXBUF I X_OFF J Y_OFF + + COLOR! 120 | LOOP 121 | LOOP 122 | THEN 123 | THEN 124 | ; 125 | 126 | : CLEAR ( addr -- , Clear a render buffer ) 127 | DRAW_BUF DISP.BUF_SIZE ERASE 128 | 0 DIRTY_X_MAX ! 129 | 0 DIRTY_Y_MAX ! 130 | 131 | DISP.WIDTH DIRTY_X_MIN ! 132 | DISP.HEIGHT DIRTY_Y_MIN ! 133 | ; 134 | 135 | : PIX_ADDR ( x y -- a ) Y_OFF SWAP X_OFF + DRAW_BUF + ; 136 | 137 | : IN_BOUNDS? ( x y -- f , Test if an XY coordinate is on the screen ) 138 | DUP 0 >= SWAP DISP.HEIGHT < AND 139 | SWAP 140 | DUP 0 >= SWAP DISP.WIDTH < AND 141 | AND 142 | ; 143 | 144 | : SET_DIRTY ( x y -- , Set the dirty region bounds ) 145 | DUP DIRTY_Y_MIN @ < IF DUP DIRTY_Y_MIN ! THEN 146 | DUP DIRTY_Y_MAX @ > IF DIRTY_Y_MAX ! ELSE DROP THEN 147 | DUP DIRTY_X_MIN @ < IF DUP DIRTY_X_MIN ! THEN 148 | DUP DIRTY_X_MAX @ > IF DIRTY_X_MAX ! ELSE DROP THEN 149 | ; 150 | 151 | \ Set the pixel at (x,y) to n. 152 | : SET_PIX ( c x y -- ) 153 | 2DUP IN_BOUNDS? IF 154 | 2DUP SET_DIRTY 155 | PIX_ADDR \ c addr 156 | SWAP PIX_MASK AND \ addr c 157 | OVER @ PIX_MASK INVERT AND 158 | OR 159 | SWAP ! 160 | ELSE 161 | 3DROP 162 | \ TODO When clipping is implemented, ABORT if asked to render outside the 163 | \ viewport so that errors can be detected earlier. 164 | THEN 165 | ; 166 | 167 | :STRUCT ARGB 168 | BYTE ARGB.BLUE 169 | BYTE ARGB.GREEN 170 | BYTE ARGB.RED 171 | BYTE ARGB.ALPHA 172 | ;STRUCT 173 | 174 | \ Convenience words for one half/quarter of the display width. 175 | : HALF_WIDTH DISP.WIDTH 2 / ; 176 | : HALF_HEIGHT DISP.HEIGHT 2 / ; 177 | : HALF HALF_WIDTH HALF_HEIGHT ; 178 | 179 | : QUARTER_WIDTH HALF_WIDTH 2 / ; 180 | : QUARTER_HEIGHT HALF_HEIGHT 2 / ; 181 | : QUARTER QUARTER_WIDTH QUARTER_HEIGHT ; 182 | 183 | \ Colors 184 | 185 | HEX 186 | \ Basic colors 187 | : TRANSPARENT 00000000 ; 188 | : BLACK FF000000 ; 189 | : WHITE FFFFFFFF ; 190 | : RED FFFF0000 ; 191 | : GREEN FF00FF00 ; 192 | : BLUE FF0000FF ; 193 | 194 | \ Basic combinations 195 | : CYAN FF00FFFF ; 196 | : FUSCHIA FFFF00FF ; 197 | : YELLOW FFFFFF00 ; 198 | : GRAY FF808080 ; 199 | 200 | \ CSS Colors 201 | : DARK_RED FF8B0000 ; 202 | : DARK_GREEN FF006400 ; 203 | : DARK_BLUE FF00008B ; 204 | : GOLD FFFFD700 ; 205 | : COPPER FFB87333 ; 206 | 207 | \ Custom colors 208 | : FR4 FF204F35 ; 209 | DECIMAL 210 | 211 | 212 | \ Shapes 213 | 214 | \ Draw a horizontal line in a given color. 215 | : DRAW.HLINE ( c x y w -- ) 216 | DUP 0> IF 217 | 0 218 | ELSE 219 | 0 SWAP 220 | THEN 221 | 2DUP = NOT IF 222 | DO 223 | 3DUP 224 | SWAP I + SWAP 225 | SET_PIX 226 | LOOP 227 | ELSE 228 | 2DROP 229 | THEN 230 | 3DROP 231 | ; 232 | 233 | \ Draw a vertical line in a given color. 234 | : DRAW.VLINE ( c x y h -- ) 235 | 0 DO 236 | 3DUP 237 | I + 238 | SET_PIX 239 | LOOP 240 | 3DROP 241 | ; 242 | 243 | \ Draw a rectangle in a given color. 244 | : DRAW.RECT ( c x1 y1 x2 y2 -- ) 245 | \ Get coordinates in the right order: highest to lowest in x and y. 246 | ROT -2SORT 2SWAP 2SORT 2SWAP 247 | DO 248 | 3DUP 249 | 2DUP - ABS 250 | NIP I SWAP 251 | DRAW.HLINE 252 | LOOP 253 | 3DROP 254 | ; 255 | 256 | \ : DRAW.CIRCLE ( c x y d -- ) 257 | \ TODO 258 | \ ; 259 | 260 | \ Draw a test pattern. 261 | \ w r 262 | \ b g 263 | : DRAW.TEST ( -- ) 264 | CLEAR 265 | WHITE QUARTER_WIDTH QUARTER_HEIGHT 2DUP QUARTER TRANSLATE DRAW.RECT 266 | RED HALF_WIDTH QUARTER_HEIGHT 2DUP QUARTER TRANSLATE DRAW.RECT 267 | BLUE QUARTER_WIDTH HALF_HEIGHT 2DUP QUARTER TRANSLATE DRAW.RECT 268 | GREEN HALF_WIDTH HALF_HEIGHT 2DUP QUARTER TRANSLATE DRAW.RECT 269 | DISP.LOCK 270 | BLIT 271 | DISP.UNLOCK 272 | DISP.RENDER 273 | ; 274 | -------------------------------------------------------------------------------- /board.fth: -------------------------------------------------------------------------------- 1 | \ Board and component layout primitives. 2 | 3 | \ A structure representing a printed circuit board. 4 | :STRUCT BOARD 5 | RPTR BOARD.FIRST_PART \ Pointer to the first part 6 | RPTR BOARD.LAST_PART \ Pointer to the last part 7 | BYTE BOARD.NUM_LAYERS \ Number of layers in the board 8 | ;STRUCT 9 | 10 | : BOARD.INIT ( addr -- addr, Initialize a board's memory ) 11 | [ SIZEOF() BOARD ] LITERAL ERASE 12 | ; 13 | 14 | \ Most operations act on the current board. 15 | VARIABLE CURRENT_BOARD 16 | 17 | 0 CONSTANT BOARD_OUTLINE_LAYER 18 | 1 CONSTANT TOP_COPPER_LAYER 19 | 2 CONSTANT TOP_MASK_LAYER 20 | 3 CONSTANT TOP_SILK_LAYER 21 | 22 | \ TODO Add support for bottom/intermediate layers 23 | 24 | 32 CONSTANT MAX_LAYERS 25 | 26 | \ Each bit corresponds to a layer; if the layer is visible, it is one. 27 | VARIABLE LAYER_VISIBLE_BITFIELD 28 | HEX FFFF DECIMAL LAYER_VISIBLE_BITFIELD ! 29 | 30 | : LAYER_MASK ( n -- n , Create bitmask for layer ) 31 | 1 SWAP LSHIFT 32 | ; 33 | 34 | : LAYER_ON? ( n -- f , Return a truthy value if layer n is enabled ) 35 | LAYER_MASK LAYER_VISIBLE_BITFIELD @ AND 36 | ; 37 | 38 | : SHOW_LAYER ( n -- , Turn on a layer for rendering ) 39 | LAYER_MASK 40 | LAYER_VISIBLE_BITFIELD @ 41 | OR 42 | LAYER_VISIBLE_BITFIELD ! 43 | ; 44 | 45 | : HIDE_LAYER ( n -- , Turn on a layer for rendering ) 46 | LAYER_MASK 47 | LAYER_VISIBLE_BITFIELD @ 48 | XOR 49 | LAYER_VISIBLE_BITFIELD ! 50 | ; 51 | 52 | VARIABLE UNITS_PER_PIXEL 53 | 100 UM UNITS_PER_PIXEL ! 54 | 55 | : BOARD_ZOOM ( -- n , Get the zoom factor for the board ) 56 | -1 UNITS_PER_PIXEL @ * 57 | ; 58 | 59 | : ZOOM_IN ( -- , Zoom in by a factor of 2 ) 60 | UNITS_PER_PIXEL @ 2 / UNITS_PER_PIXEL ! 61 | ; 62 | 63 | : ZOOM_OUT ( -- , Zoom out by a factor of 2 ) 64 | UNITS_PER_PIXEL @ 2 * UNITS_PER_PIXEL ! 65 | ; 66 | 67 | VARIABLE LAYER_COLORS MAX_LAYERS COLORS * ALLOT 68 | 69 | : LAYER_COLOR_ADDR ( n -- addr , Get the address of the layer color cell ) 70 | COLORS * LAYER_COLORS + 71 | ; 72 | 73 | : LAYER_COLOR ( n -- c , Get the color for a given layer) 74 | LAYER_COLOR_ADDR COLOR@ 75 | ; 76 | 77 | : SET_LAYER_COLOR ( c n -- , Set the color of a layer ) 78 | LAYER_COLOR_ADDR COLOR! 79 | ; 80 | 81 | FR4 BOARD_OUTLINE_LAYER SET_LAYER_COLOR 82 | 83 | \ WHITE BOT_SILK_LAYER SET_LAYER_COLOR 84 | \ BLUE BOT_MASK_LAYER SET_LAYER_COLOR 85 | \ RED BOT_COPPER_LAYER SET_LAYER_COLOR 86 | 87 | \ TODO Use separate colors for bottom/top layers. 88 | COPPER TOP_COPPER_LAYER SET_LAYER_COLOR 89 | BLUE 127 OPACITY TOP_MASK_LAYER SET_LAYER_COLOR 90 | WHITE TOP_SILK_LAYER SET_LAYER_COLOR 91 | 92 | \ TODO Set other layer colors 93 | 94 | : LAYERS ( n -- ) 95 | DUP MAX_LAYERS > ABORT" Can't use that many layers; check MAX_LAYERS" 96 | CURRENT_BOARD @ S! BOARD.NUM_LAYERS 97 | ; 98 | 99 | \ A board "component". Components are considered broadly, and include the board 100 | \ substrate itself as well as discrete components, copper traces, silkscreen 101 | \ legends, etc. Components can be rendered into a graphics object for view or 102 | \ fabrication, and connected together in a netlist. 103 | \ 104 | \ Rendering is done by EXECUTE'ing the execution token stored in the DRAW field. 105 | \ The DRAW logic should have the stack effect ( l -- vaddr ), where l is the 106 | \ layer number and vaddr is the address of the resulting vector object. 107 | \ 108 | \ TODO How do we want to handle cleaning up vector objects after use? Right now 109 | \ they are just leaked; this is probably fine for a small project. 110 | :STRUCT COMPONENT 111 | RPTR COMPONENT.DRAW \ Execution token to draw the component as vector 112 | RPTR COMPONENT.NEXT_PART \ Next component in the list 113 | RPTR COMPONENT.PORT_COORDS \ Pointer to table with coordinates for each port 114 | BYTE COMPONENT.NUM_PORTS \ Number of ports in this component 115 | ;STRUCT 116 | 117 | : COMPONENT.INIT ( addr -- addr , Initialize a component) 118 | DUP [ SIZEOF() COMPONENT ] LITERAL ERASE \ Zero memory 119 | ; 120 | 121 | : COMPONENT.CREATE ( -- addr , Create an anonymous component ) 122 | HERE \ Avoid assigning name to this vector by creating memory directly 123 | [ SIZEOF() COMPONENT ] LITERAL ALLOT 124 | COMPONENT.INIT 125 | ; 126 | 127 | : COMPONENT.NAMED ( -- addr , Create a named component and leave its address ) 128 | CREATE COMPONENT.CREATE COMPONENT.INIT 129 | ; 130 | 131 | : BOARD.ADD ( addr -- , Add a component to the current board ) 132 | DUP \ Check if the current board has any components set. 133 | CURRENT_BOARD @ S@ BOARD.FIRST_PART 0= 134 | IF 135 | \ If not, set the first part to our component. 136 | CURRENT_BOARD @ S! BOARD.FIRST_PART 137 | ELSE 138 | \ Otherwise, we need to find the last part in the chain and extend. 139 | CURRENT_BOARD @ S@ BOARD.LAST_PART 140 | S! COMPONENT.NEXT_PART 141 | THEN 142 | \ The new part is always the last one. 143 | CURRENT_BOARD @ S! BOARD.LAST_PART 144 | ; 145 | 146 | : BOARD.DRAW_COMPONENT ( l addr -- addr , Draw a component as a vector image ) 147 | S@ COMPONENT.DRAW EXECUTE 148 | DUP BOARD_ZOOM SWAP VEC.ZOOM 149 | ; 150 | 151 | : BOARD.DRAW_LAYER_IMAGE ( addr l -- , Draw a vector image in the layer color ) 152 | LAYER_COLOR SWAP VEC.DRAW 153 | ; 154 | 155 | : BOARD.DRAW_LAYER ( l -- , Draw a board layer to the temporary draw buffer ) 156 | CR DUP ." Drawing layer " . 157 | CURRENT_BOARD @ S@ BOARD.FIRST_PART 158 | DUP 0= NOT IF 159 | BEGIN \ l addr 160 | 2DUP \ l addr l addr 161 | \ Create the vector object representing the component at this layer 162 | BOARD.DRAW_COMPONENT \ l addr vaddr 163 | 2 PICK BOARD.DRAW_LAYER_IMAGE \ l addr 164 | S@ COMPONENT.NEXT_PART \ l addr' 165 | DUP \ l addr' addr' 166 | 0= 167 | UNTIL 168 | THEN 169 | 2DROP 170 | ; 171 | 172 | : BOARD.DRAW ( -- , Draw the board to the screen ) 173 | DISP.LOCK 174 | CURRENT_BOARD @ S@ BOARD.NUM_LAYERS 0 DO 175 | I LAYER_ON? IF 176 | CLEAR 177 | I BOARD.DRAW_LAYER 178 | BLIT 179 | THEN 180 | LOOP 181 | DISP.UNLOCK 182 | DISP.RENDER 183 | ; 184 | 185 | : RECTANGULAR.DRAW_LAYER { lyr w h -- addr } 186 | FALSE 187 | lyr 188 | CASE 189 | BOARD_OUTLINE_LAYER OF 190 | DROP 191 | 0 0 w h VEC.RECT 192 | TRUE 193 | ENDOF 194 | TOP_MASK_LAYER OF 195 | DROP 196 | 0 0 w h VEC.RECT 197 | TRUE 198 | ENDOF 199 | ENDCASE 200 | NOT IF 201 | VEC.NONE 202 | THEN 203 | ; 204 | 205 | : RECTANGULAR.DRAW { w h -- } 206 | :NONAME ( l -- vaddr ) 207 | w | LITERAL 208 | h | LITERAL 209 | | RECTANGULAR.DRAW_LAYER 210 | | ; \ caddr xt 211 | ; 212 | 213 | : RECTANGULAR.CREATE ( w h -- addr , Define a rectangular circuit board ) 214 | COMPONENT.CREATE \ w h caddr 215 | -ROT \ caddr w h 216 | RECTANGULAR.DRAW \ caddr xt 217 | OVER \ caddr xt caddr 218 | S! COMPONENT.DRAW \ caddr 219 | ; 220 | 221 | : RECTANGULAR ( w h -- , Define and link a rectangular circuit board ) 222 | RECTANGULAR.CREATE BOARD.ADD 223 | ; 224 | -------------------------------------------------------------------------------- /boardforth.c: -------------------------------------------------------------------------------- 1 | /* -*- fill-column: 80; -*- */ 2 | 3 | /* 4 | Copyright (c) 2020 Nick Pascucci 5 | 6 | Permission to use, copy, modify, and distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE. 12 | */ 13 | 14 | #include 15 | 16 | #include "pforth/csrc/pf_all.h" 17 | 18 | SDL_Window *gWindow = NULL; 19 | SDL_Renderer *gRenderer = NULL; 20 | SDL_Texture *gTexture = NULL; 21 | 22 | Uint32 *pixels; 23 | SDL_mutex *pixels_mutex; 24 | 25 | SDL_atomic_t quit; 26 | SDL_sem *frame_ready_sem; 27 | 28 | #define WIDTH 800 29 | #define HEIGHT 600 30 | 31 | void clear_pixel_buffer(void); 32 | 33 | const char *ui_start() { 34 | if (gWindow != NULL) { 35 | return 0; 36 | } 37 | 38 | if (SDL_Init(SDL_INIT_VIDEO) < 0) { 39 | return SDL_GetError(); 40 | } 41 | 42 | gWindow = 43 | SDL_CreateWindow("BoardForth", SDL_WINDOWPOS_CENTERED, 44 | SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN); 45 | if (gWindow == NULL) { 46 | return SDL_GetError(); 47 | } 48 | 49 | gRenderer = SDL_CreateRenderer(gWindow, -1, 0); 50 | if (gRenderer == NULL) { 51 | return SDL_GetError(); 52 | } 53 | 54 | gTexture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_ARGB8888, 55 | SDL_TEXTUREACCESS_STATIC, WIDTH, HEIGHT); 56 | if (gTexture == NULL) { 57 | return SDL_GetError(); 58 | } 59 | 60 | pixels = (Uint32 *)malloc(WIDTH * HEIGHT * sizeof(Uint32)); 61 | if (pixels == NULL) { 62 | return 1; 63 | } 64 | clear_pixel_buffer(); 65 | 66 | pixels_mutex = SDL_CreateMutex(); 67 | if (pixels_mutex == NULL) { 68 | return 1; 69 | } 70 | 71 | SDL_SetRenderTarget(gRenderer, gTexture); 72 | SDL_SetRenderDrawColor(gRenderer, 0x00, 0x00, 0x00, 0x00); 73 | SDL_RenderClear(gRenderer); 74 | 75 | SDL_SetRenderTarget(gRenderer, NULL); 76 | SDL_RenderCopy(gRenderer, gTexture, NULL, NULL); 77 | SDL_RenderPresent(gRenderer); 78 | 79 | return 0; 80 | } 81 | 82 | void teardown(void) { 83 | if (gTexture != NULL) { 84 | SDL_DestroyTexture(gTexture); 85 | gTexture = NULL; 86 | } 87 | if (gRenderer != NULL) { 88 | SDL_DestroyRenderer(gRenderer); 89 | gRenderer = NULL; 90 | } 91 | if (gWindow != NULL) { 92 | SDL_DestroyWindow(gWindow); 93 | gWindow = NULL; 94 | } 95 | if (pixels != NULL) { 96 | free(pixels); 97 | pixels = NULL; 98 | } 99 | } 100 | 101 | void clear_pixel_buffer(void) { 102 | memset(pixels, NULL, WIDTH * HEIGHT * sizeof(Uint32)); 103 | } 104 | 105 | static cell_t pixel_addr(void) { return pixels; } 106 | 107 | static cell_t height(void) { return HEIGHT; } 108 | 109 | static cell_t width(void) { return WIDTH; } 110 | 111 | static cell_t pixel_size(void) { return sizeof(Uint32); } 112 | 113 | static void request_render(void) { 114 | SDL_SemPost(frame_ready_sem); 115 | } 116 | 117 | static void lock_pixels(void) { 118 | if (SDL_LockMutex(pixels_mutex) == 0) { 119 | return; 120 | } else { 121 | /* Something strange happened. */ 122 | exit(1); 123 | } 124 | } 125 | 126 | static void unlock_pixels(void) { 127 | SDL_UnlockMutex(pixels_mutex); 128 | } 129 | 130 | static void render(void) { 131 | lock_pixels(); 132 | 133 | printf("Rendering!\n"); 134 | 135 | SDL_UpdateTexture(gTexture, NULL, pixels, WIDTH * sizeof(Uint32)); 136 | SDL_RenderClear(gRenderer); 137 | SDL_RenderCopy(gRenderer, gTexture, NULL, NULL); 138 | SDL_RenderPresent(gRenderer); 139 | clear_pixel_buffer(); 140 | 141 | printf("Render done\n"); 142 | 143 | unlock_pixels(); 144 | } 145 | 146 | /* 147 | * pForth function table. The functions here are bound to names in the Forth 148 | * dictionary below. 149 | */ 150 | CFunc0 CustomFunctionTable[] = { 151 | (CFunc0)pixel_addr, (CFunc0)height, (CFunc0)width, 152 | (CFunc0)pixel_size, (CFunc0)request_render, (CFunc0)lock_pixels, 153 | (CFunc0)unlock_pixels, 154 | }; 155 | 156 | Err CompileCustomFunctions(void) { 157 | Err err; 158 | int i = 0; 159 | 160 | /* Compile Forth words that call custom functions. 161 | * Make sure order of functions matches that in CustomFunctionTable. 162 | * Parameters are: Name in UPPER CASE, Function Index, Mode, NumParams 163 | */ 164 | err = CreateGlueToC("DISP.PIXBUF", i++, C_RETURNS_VALUE, 0); 165 | if (err < 0) return err; 166 | 167 | err = CreateGlueToC("DISP.HEIGHT", i++, C_RETURNS_VALUE, 0); 168 | if (err < 0) return err; 169 | 170 | err = CreateGlueToC("DISP.WIDTH", i++, C_RETURNS_VALUE, 0); 171 | if (err < 0) return err; 172 | 173 | err = CreateGlueToC("DISP.PIXSIZE", i++, C_RETURNS_VALUE, 0); 174 | if (err < 0) return err; 175 | 176 | err = CreateGlueToC("DISP.RENDER", i++, C_RETURNS_VOID, 0); 177 | if (err < 0) return err; 178 | 179 | err = CreateGlueToC("DISP.LOCK", i++, C_RETURNS_VOID, 0); 180 | if (err < 0) return err; 181 | 182 | err = CreateGlueToC("DISP.UNLOCK", i++, C_RETURNS_VOID, 0); 183 | if (err < 0) return err; 184 | 185 | return 0; 186 | } 187 | 188 | static int run_forth(void *ptr) { 189 | int ret; 190 | pfSetQuiet(FALSE); 191 | ret = (int)pfDoForth("pforth.dic", NULL, FALSE); 192 | SDL_AtomicSet(&quit, 1); 193 | return ret; 194 | } 195 | 196 | #ifdef PF_NO_MAIN 197 | int main(int argc, char **argv) { 198 | SDL_Thread *forth_thread; 199 | 200 | SDL_AtomicSet(&quit, 0); 201 | frame_ready_sem = SDL_CreateSemaphore(0); 202 | 203 | if (0 != ui_start()) { 204 | return 1; 205 | } 206 | 207 | forth_thread = SDL_CreateThread(run_forth, "pForth", (void *)NULL); 208 | 209 | if (NULL == forth_thread) { 210 | printf("SDL_CreateThread failed: %s\n", SDL_GetError()); 211 | exit(1); 212 | } 213 | 214 | while (1) { 215 | SDL_Event event; 216 | 217 | SDL_Delay(50); 218 | 219 | if (SDL_SemTryWait(frame_ready_sem) == 0) { 220 | render(); 221 | } 222 | 223 | /* Respond to Forth quitting. */ 224 | if (SDL_AtomicGet(&quit)) { 225 | teardown(); 226 | exit(0); 227 | } 228 | 229 | while (SDL_PollEvent(&event) != 0) { 230 | /* Respond to window system quit event. */ 231 | if (event.type == SDL_QUIT) { 232 | teardown(); 233 | return 0; 234 | } 235 | } 236 | } 237 | return 0; 238 | } 239 | #endif 240 | -------------------------------------------------------------------------------- /boardforth.fth: -------------------------------------------------------------------------------- 1 | \ Useful for debugging! 2 | include pforth/fth/utils/dump_struct.fth 3 | 4 | include util.fth 5 | include units.fth 6 | include geometry.fth 7 | include bitmap.fth 8 | include vector.fth 9 | include board.fth 10 | include components.fth 11 | -------------------------------------------------------------------------------- /components.fth: -------------------------------------------------------------------------------- 1 | \ Component library. 2 | 3 | \ Resistors 4 | 5 | \ SMT resistors have two pads, which are applied only to a single copper 6 | \ layer. The pad dimensions are given as a set of three values specifying the 7 | \ height, width, and gap: 8 | \ v-v width (b) 9 | \ [ ] [ ] } height (a) 10 | \ ^--^ gap (c) 11 | \ 12 | \ | Imperial Code | Metric Code | a (mm) | b (mm) | c (mm) | 13 | \ | ------------- | ----------- | ------ | ------ | ------ | 14 | \ | 0201 | 0603 | 0.3 | 0.3 | 0.3 | 15 | \ | 0402 | 1005 | 0.6 | 0.5 | 0.5 | 16 | \ | 0603 | 1608 | 0.9 | 0.6 | 0.9 | 17 | \ | 0805 | 2012 | 1.3 | 0.7 | 1.2 | 18 | \ | 1206 | 3216 | 1.6 | 0.9 | 2.0 | 19 | \ | 1812 | 3246 | 4.8 | 0.9 | 2.0 | 20 | \ | 2010 | 5025 | 2.8 | 0.9 | 3.8 | 21 | \ | 2512 | 6332 | 3.5 | 1.6 | 3.8 | 22 | \ 23 | \ Commonly the imperial code is used to specify the part number, but the metric 24 | \ measurements are used. (It's confusing.) Here we will follow the convention of 25 | \ prefixing each part word with an I for imperial or M for metric; the imperial 26 | \ word is an alias for the metric one. 27 | 28 | : SMT_RESISTOR.DRAW_PADS { x y a b c -- addr } 29 | \ Left pad 30 | 0 0 b a VEC.RECT 31 | 32 | \ Right pad: x is offset from the left pad by the gap size 33 | b c + 34 | 0 35 | b c b + + 36 | a 37 | VEC.RECT 38 | 39 | 2 VEC.GROUP 40 | \ Translate pads to the right overall position 41 | DUP x y ROT VEC.TRANSLATE 42 | ; 43 | 44 | : SMT_RESISTOR.DRAW_LAYER { lyr x y a b c -- addr } 45 | FALSE 46 | lyr 47 | CASE 48 | TOP_COPPER_LAYER OF 49 | DROP 50 | x y a b c SMT_RESISTOR.DRAW_PADS 51 | TRUE 52 | ENDOF 53 | TOP_MASK_LAYER OF 54 | DROP 55 | x y a b c SMT_RESISTOR.DRAW_PADS 56 | POL.SUB OVER S! VEC.POL 57 | TRUE 58 | ENDOF 59 | ENDCASE 60 | NOT IF 61 | VEC.NONE 62 | THEN 63 | ; 64 | 65 | \ Create xt which draws an SMT resistor with the given location and 66 | \ dimensions. The pads are arranged horizontally. 67 | : SMT_RESISTOR.DRAW { x y a b c -- addr } 68 | :NONAME ( l -- vaddr ) 69 | x | LITERAL 70 | y | LITERAL 71 | a | LITERAL 72 | b | LITERAL 73 | c | LITERAL 74 | | SMT_RESISTOR.DRAW_LAYER 75 | | ; \ caddr xt 76 | ; 77 | 78 | : M0603 ( x y -- , Define a metric 0603 resistor ) 79 | COMPONENT.CREATE -ROT 80 | 300 UM 300 UM 300 UM SMT_RESISTOR.DRAW 81 | OVER S! COMPONENT.DRAW 82 | ; 83 | 84 | : M1005 85 | COMPONENT.CREATE -ROT 86 | 600 UM 500 UM 500 UM SMT_RESISTOR.DRAW 87 | OVER S! COMPONENT.DRAW 88 | ; 89 | 90 | : M1608 91 | COMPONENT.CREATE -ROT 92 | 900 UM 600 UM 900 UM SMT_RESISTOR.DRAW 93 | OVER S! COMPONENT.DRAW 94 | ; 95 | 96 | : M2012 97 | COMPONENT.CREATE -ROT 98 | 1300 UM 700 UM 1200 UM SMT_RESISTOR.DRAW 99 | OVER S! COMPONENT.DRAW 100 | ; 101 | 102 | : M3216 103 | COMPONENT.CREATE -ROT 104 | 1600 UM 900 UM 2000 UM SMT_RESISTOR.DRAW 105 | OVER S! COMPONENT.DRAW 106 | ; 107 | 108 | : M3246 109 | COMPONENT.CREATE -ROT 110 | 4800 UM 900 UM 2000 UM SMT_RESISTOR.DRAW 111 | OVER S! COMPONENT.DRAW 112 | ; 113 | 114 | : M5025 115 | COMPONENT.CREATE -ROT 116 | 2800 UM 900 UM 3800 UM SMT_RESISTOR.DRAW 117 | OVER S! COMPONENT.DRAW 118 | ; 119 | 120 | : M6332 121 | COMPONENT.CREATE -ROT 122 | 3500 UM 1600 UM 3800 UM SMT_RESISTOR.DRAW 123 | OVER S! COMPONENT.DRAW 124 | ; 125 | 126 | \ Imperial aliases 127 | : I0201 M0603 ; 128 | : I0402 M1005 ; 129 | : I0603 M1608 ; 130 | : I0805 M2012 ; 131 | : I1206 M3216 ; 132 | : I1812 M3246 ; 133 | : I2010 M5025 ; 134 | : I2512 M6332 ; 135 | -------------------------------------------------------------------------------- /geometry.fth: -------------------------------------------------------------------------------- 1 | \ Geometry manipulation routines. 2 | 3 | : REFLECT_Y ( x y -- x y' , Reflect a coordinate pair over the Y axis ) 4 | -1 * 5 | ; 6 | 7 | : REFLECT_X ( x y -- x' y , Reflect a coordinate pair over the X axis ) 8 | SWAP -1 * SWAP 9 | ; 10 | 11 | : TRANSLATE_Y ( x y n -- x y' , Translate a coordinate pair along the Y axis ) 12 | + 13 | ; 14 | 15 | : TRANSLATE_X ( x y n -- x' y , Translate a coordinate pair along the X axis ) 16 | ROT + SWAP 17 | ; 18 | 19 | : TRANSLATE ( x y nx ny -- x' y' , Translate a coordinate pair ) 20 | ROT + 21 | -ROT + 22 | SWAP 23 | ; 24 | 25 | \ Zoom focus is the origin. To zoom on another region, translate there first 26 | \ then back. 27 | : ZOOM ( x y n -- x' y' , Zoom a coordinate pair by a factor of n ) 28 | DUP SGN 29 | CASE 30 | 1 OF TUCK * -ROT * SWAP ENDOF \ Zoom in 31 | 0 OF DROP ENDOF \ 0 zoom is a no-op 32 | -1 OF ABS TUCK / -ROT / SWAP ENDOF \ Zoom out 33 | ENDCASE 34 | ; 35 | -------------------------------------------------------------------------------- /notes.org: -------------------------------------------------------------------------------- 1 | # Local Variables: 2 | # fill-column: 80 3 | # End: 4 | 5 | * BoardForth Developer Notes 6 | 7 | This file holds work-in-progress notes and reference material for BoardForth 8 | development. 9 | 10 | ** Gerber Compatibility 11 | 12 | The end result of a design is a set of Gerber files which can be sent to a PCB 13 | fabrication house. To make this process easy, we should aim to keep the Forth 14 | representation of the board close to the Gerber mental model. This primarily 15 | means representing component graphics as Gerber analogs. 16 | 17 | ** Data Structures 18 | 19 | *Board Table* 20 | 21 | - Defines the number of layers. 22 | - Contains pointers to the first and last components. 23 | 24 | - Q: How is the board shape represented in Gerber? 25 | - A: It's a separate file. 26 | 27 | *Component List* 28 | 29 | - Singly-linked list of components, first component is the board itself 30 | - Components are also pointed to by Forth words defined by their names. 31 | - Each component contains: 32 | - Pointer to a Forth word which renders it to a layer 33 | - Count of the number of ports it has 34 | - Coordinates for each port 35 | 36 | *Connections Table* 37 | 38 | - Count of connections made 39 | - List of cells: 40 | - Address of first component 41 | - Port of first component 42 | - Address of second component 43 | - Port of second component 44 | 45 | - Q: Is this the best way to handle connections? 46 | - Are all connections point to point, or are they multipoint? 47 | - Are all connections to pins, or can they be represented as pins? 48 | - I suppose the symbols on most diagrams have a natural number of connection 49 | points. 50 | - Physical location of connection endpoints 51 | 52 | *Layer Render Buffer* 53 | 54 | Pixel buffer exactly matching the window size, used for rendering a single board 55 | layer. 56 | 57 | *Board Render Buffer* 58 | 59 | Board layers in the layer render buffer are composited onto the board render buffer to produce a final image which is then drawn to the screen. 60 | 61 | ** Rendering 62 | 63 | *** Layering 64 | 65 | Components may render graphics objects on multiple layers. For example, an IC 66 | component might have a set of pads which go on the top copper layer, a mask 67 | exposing the pads in the soldermask layer, and a silkscreen marker. At the end 68 | of the process each layer will be output as a single Gerber file, so we should 69 | try to use a compatible internal representation. 70 | 71 | The rendering process might look something like: 72 | 73 | - Create a set of vector object buffers to render to, one for each layer 74 | - For each component in the board: 75 | - Draw abstract component using the vector buffers 76 | - How do we know which ones to use for each component? What if the number of 77 | layers changes? Do we need to redefine each component to increase the 78 | layer count? Might be able to just ignore this as most hobby boards are 79 | only two layers. 80 | - Create a pixel buffer to render the final result to. 81 | - Create a temporary pixel buffer to use for rendering each layer. 82 | - For each layer in order of view preference: 83 | - Initialize the temporary pixel buffer with 100% transparent pixels. 84 | - If the layer is enabled, draw it to the temporary pixel buffer using the 85 | layer color and transparency, putting color where "dark" polarity (see the 86 | Gerber format for explanation) shapes are and removing it from "clear" 87 | polarity regions. 88 | - TODO 89 | 90 | *** Drawing objects 91 | 92 | - Note that the Gerber coordinate system is right-up, rather than right-down as 93 | screen coordinates are. 94 | - Gerber files must use mm and can have 6 decimals of precision in their 95 | coordinates, which puts a lower bound on the feature size. 96 | 97 | **** TODO Rendering: add viewport and scaling 98 | Rendering a layer should proceed something like this: 99 | - Convert components to their vector representation at the layer of interest. 100 | - Translate the vector object to account for viewport pan. 101 | - Scale the vector object to account for zoom. 102 | - Is this the right order? I suppose it depends on the routines we use for 103 | translating the viewport and what units they use, which are dependent on the 104 | zoom level. 105 | - Apply a viewport clipping mask to the vector object. 106 | - Draw the transformed and clipped vector object to the pixel buffer. 107 | 108 | ** Imagined Programs 109 | 110 | Here's a little mockup of what a BoardForth program might look like when the 111 | system is up and running. 112 | 113 | #+BEGIN_SRC forth 114 | include boardforth.fth 115 | 116 | board MY_BOARD 117 | my_board current_board ! 118 | 119 | \ Sets the number of layers on the board. 120 | 2 layers 121 | 122 | \ Origin is the bottom left corner of whatever shape the user specifies here. 123 | \ mm: ( n -- n ) Converts millimeter coordinate to internal representation 124 | 30 mm 30 mm rectangular 125 | 126 | 127 | \ Set the draw location to (5mm, 5mm) 128 | \ go: ( x y -- , moves edit location ) 129 | 5 mm 5 mm go 130 | 131 | \ Add a custom drawn object, in this case a 3mm fiducial. 132 | \ mark.fiducial: ( d -- , creates mark and adds to board) 133 | 3 mm mark.fiducial 134 | 135 | 8 mm 0 mm go 136 | 137 | \ part.uc.2X3_HEADER ( -- , creates header, adds to board, defs variable) 138 | part.conn.2X3_HEADER H1 139 | 140 | 10 mm 10 mm go 141 | 142 | \ Add an ATMEGA128 to the board named "IC1" 143 | \ part.uc.ATMEGA128-16AU ( -- , creates IC component, adds to board, defs variable) 144 | part.uc.ATMEGA128-16AU IC1 145 | 146 | \ Add a connection from IC1 pin 22 to H1 pin 1. 147 | \ connect: ( addr n addr n -- addr , leaves address of trace on stack) 148 | IC1 22 pin H1 1 pin connect 149 | 150 | \ Route the connection along the board 151 | #+END_SRC 152 | 153 | ** References 154 | 155 | [[https://www.ucamco.com/files/downloads/file_en/416/the-gerber-file-format-specification-revision-2020-09-update_en.pdf?c0748ea9bf8efa9d8e145205a173e460][The Gerber Format Specification]] 156 | 157 | This spec defines how a Gerber image file is formed. These files define the 158 | shape of each board layer, including silk screens, soldermasks, and copper. 159 | 160 | [[https://www.ucamco.com/files/downloads/file_en/396/the-gerber-job-format-specification-revision-2020-08_en.pdf?c0748ea9bf8efa9d8e145205a173e460][The Gerber Job Format Specification]] 161 | 162 | This spec defines how a Gerber job file is formed. Job files are used to provide 163 | specifications not captured in the image format, and are generally not used for 164 | personal board projects. 165 | 166 | [[https://www.ucamco.com/en/gerber/downloads][Gerber Downloads]] 167 | 168 | Various resources including a file viewer and test data. 169 | -------------------------------------------------------------------------------- /test.fth: -------------------------------------------------------------------------------- 1 | board b 2 | b current_board ! 3 | 2 layers 4 | 30 40 rectangular 5 | 6 | \ Case 1: Render to board layer 7 | \ Create a vector representation of the board 8 | board_layer \ Vector layer to render to 9 | b S@ board.first_part S@ component.draw execute 10 | 11 | \ Check that the coordinates line up 12 | dup S@ vec.x1 dup . 0= not abort" X1 coordinate does not equal 0!" 13 | dup S@ vec.y1 dup . 0= not abort" Y1 coordinate does not equal 0!" 14 | dup S@ vec.x2 dup . 30 = not abort" X2 coordinate does not equal 30!" 15 | dup S@ vec.y2 dup . 40 = not abort" Y2 coordinate does not equal 40!" 16 | dup S@ vec.type dup . shape.rect = not abort" Board shape does not equal SHAPE.RECT!" 17 | dup S@ vec.polarity dup . pol.add = not abort" Board polarity does not equal POL.ADD!" 18 | 19 | drop \ Forget vector 20 | 21 | \ Case 2: Render to non-board layer 22 | \ Create a vector representation of the board 23 | top_copper_layer \ Vector layer to render to 24 | b S@ board.first_part S@ component.draw execute 25 | 26 | \ Check that the coordinates line up 27 | dup S@ vec.x1 dup . 0= not abort" X1 coordinate does not equal 0!" 28 | dup S@ vec.y1 dup . 0= not abort" Y1 coordinate does not equal 0!" 29 | dup S@ vec.x2 dup . 0= not abort" X2 coordinate does not equal 0!" 30 | dup S@ vec.y2 dup . 0= not abort" Y2 coordinate does not equal 0!" 31 | dup S@ vec.type dup . shape.none = not abort" Board shape does not equal SHAPE.NONE!" 32 | dup S@ vec.polarity dup . pol.add = not abort" Board polarity does not equal POL.ADD!" 33 | 34 | drop \ Forget vector 35 | -------------------------------------------------------------------------------- /test_board.fth: -------------------------------------------------------------------------------- 1 | board b 2 | b current_board ! 3 | 4 layers 4 | 30 mm 40 mm rectangular 5 | 6 | 10 mm 10 mm M0603 constant r1 7 | r1 board.add 8 | 9 | \ board.draw 10 | -------------------------------------------------------------------------------- /test_draw.fth: -------------------------------------------------------------------------------- 1 | clear blit 2 | 3 | 50 50 100 100 vec.rect 4 | 150 150 250 250 vec.rect 5 | 2 vec.group 6 | 7 | constant test_group 8 | 9 | blue test_group vec.draw 10 | 11 | blit 12 | 13 | 50 50 test_group vec.translate 14 | 15 | red 127 opacity test_group vec.draw 16 | 17 | blit 18 | 19 | -40 0 test_group vec.translate 20 | 21 | 2 test_group vec.zoom 22 | 23 | green 127 opacity test_group vec.draw 24 | 25 | blit disp.render 26 | -------------------------------------------------------------------------------- /units.fth: -------------------------------------------------------------------------------- 1 | \ Units of measure. 2 | 3 | \ BoardForth's internal representation nominally uses millimeters, in 4 | \ conformance with The Gerber File Format Specification. In order to represent 5 | \ the 6 decimal places required by the spec in a fixed-point format, the actual 6 | \ representation is in nanometers. 7 | \ 8 | \ Inches are not supported, here or in the Gerber format: 9 | \ "Inch is only there for historic reasons and is now a useless 10 | \ embellishment. It will be revoked at some future date." 11 | 12 | : NM ( n -- n , Convert a value in nanometers to internal representation ) 13 | \ noop 14 | ; 15 | 16 | : UM ( n -- n , Convert a value in whole micrometers to internal representation ) 17 | 1000 * 18 | ; 19 | 20 | : MM ( n -- n , Convert a value in whole millimeters to internal representation ) 21 | UM 1000 * 22 | ; 23 | 24 | : >NM ( n -- n , Convert a value in internal representation to nanometers ) 25 | \ noop 26 | ; 27 | 28 | : >UM ( n -- n , Convert a value in internal representation to whole micrometers ) 29 | 1000 / 30 | ; 31 | 32 | : >MM ( n -- n , Convert a value in internal representation to whole millimeters ) 33 | UM 1000 / 34 | ; 35 | 36 | -------------------------------------------------------------------------------- /util.fth: -------------------------------------------------------------------------------- 1 | \ Misc 2 | 3 | : 3DROP ( n n n -- ) DROP DROP DROP ; 4 | 5 | : 4DUP ( d d -- d d d d ) 2OVER 2OVER ; 6 | 7 | : 4DROP ( d d -- ) 4 0 DO DROP LOOP ; 8 | 9 | : SGN ( n -- n , Return the sign coefficient of the number ) 10 | DUP 0> IF 11 | DROP 1 12 | ELSE 13 | 0= IF 14 | 0 15 | ELSE 16 | -1 17 | THEN 18 | THEN 19 | ; 20 | 21 | \ Shorthand for postpone which makes it easier to write lambdas. 22 | : | POSTPONE POSTPONE ; IMMEDIATE 23 | 24 | : L | LITERAL ; IMMEDIATE 25 | -------------------------------------------------------------------------------- /vector.fth: -------------------------------------------------------------------------------- 1 | \ Vector drawing utilities. 2 | \ 3 | \ This file provides vector primitives which loosely mirror those defined by the 4 | \ Gerber file format, with the aim of enabling easy conversion into that format. 5 | 6 | \ "There are four types of graphics objects: 7 | \ 8 | \ 1. Draws are straight-line segments, stroked with the current aperture, which 9 | \ must be a solid circular one. 10 | \ 2. Arcs are circular segments, stroked with the current aperture, which must 11 | \ be a solid circular one. 12 | \ 3. Flashes are replications of the current aperture in the image plane. Any 13 | \ valid aperture can be flashed (see 4.7.5). An aperture is typically flashed 14 | \ many times. 15 | \ 4. Regions are defined by its contour (see 4.10.1). A contour is a closed 16 | \ sequence of connected linear or circular segments. 17 | \ 18 | \ In PCB copper layers, tracks are typically represented by draws and arcs, pads 19 | \ by flashes and copper pours by regions. Tracks is then a generic name for 20 | \ draws and arcs." 21 | \ 22 | \ Gerber File Format Specification, Section 2.3 23 | 24 | 0 CONSTANT POL.SUB \ Remove color from the image 25 | 1 CONSTANT POL.ADD \ Add color to the image 26 | 27 | \ Shape tags for structure below 28 | 0 CONSTANT SHAPE.NONE \ Blank 29 | 1 CONSTANT SHAPE.RECT \ Rectangle 30 | 2 CONSTANT SHAPE.CIRCLE \ Circle 31 | 3 CONSTANT SHAPE.LINE \ Straight line 32 | 4 CONSTANT SHAPE.ARC \ Circular arc 33 | 5 CONSTANT SHAPE.GROUP \ Composite shape 34 | 35 | \ Vector shapes are defined in terms of two XY coordinates. These coordinates 36 | \ are in a standard right-up cartesian coordinate system, and are interpreted as 37 | \ follows for each shape: 38 | \ 39 | \ RECT: Diagonal endpoints. 40 | \ CIRCLE: XY1 is center, XY2 is on the diameter. Radius is cartesian distance 41 | \ between the two. 42 | \ LINE: Straight line between the two points. 43 | \ ARC: Clockwise arc from XY1 to XY2, both on the diameter of a circle. 44 | \ GROUP: XY1 is the origin offset for the contained shapes, XY2 ignored. 45 | :STRUCT VECTOR 46 | LONG VEC.X1 \ First X coordinate for this shape 47 | LONG VEC.Y1 \ First Y coordinate for this shape 48 | LONG VEC.X2 \ Second X coordinate for this shape 49 | LONG VEC.Y2 \ Second Y coordinate for this shape 50 | RPTR VEC.DATA \ Additional data needed to fully describe the shape 51 | BYTE VEC.TYPE \ Shape tag 52 | BYTE VEC.POL \ Whether to add or subtract from the image 53 | ;STRUCT 54 | 55 | :STRUCT GROUP_INFO 56 | LONG GROUP.N \ Number of child vectors in this group 57 | RPTR GROUP.MEMBERS \ Dummy value, start of address space for members 58 | ;STRUCT 59 | 60 | : VEC.INIT ( x1 y1 x2 y2 type addr -- addr , Initialize a vector ) 61 | SWAP OVER S! VEC.TYPE 62 | SWAP OVER S! VEC.Y2 63 | SWAP OVER S! VEC.X2 64 | SWAP OVER S! VEC.Y1 65 | SWAP OVER S! VEC.X1 66 | 67 | \ Set default values 68 | 0 OVER S! VEC.DATA 69 | POL.ADD OVER S! VEC.POL 70 | ; 71 | 72 | : VEC.CREATE ( x1 y1 x2 y2 type -- addr , Create an anonymous vector ) 73 | HERE \ Avoid assigning name to this vector by creating memory directly 74 | [ SIZEOF() VECTOR ] LITERAL ALLOT 75 | VEC.INIT 76 | ; 77 | 78 | : VEC.NONE ( -- addr , Create an empty vector object ) 79 | 0 0 0 0 SHAPE.NONE VEC.CREATE 80 | ; 81 | 82 | : VEC.RECT ( x1 y1 x2 y2 -- addr , Create a rectangle ) 83 | SHAPE.RECT VEC.CREATE 84 | ; 85 | 86 | : VEC.CIRCLE_CR ( x y r -- addr , Create a circle by center and radius ) 87 | 0 2OVER 2SWAP DROP + \ Create XY2 coordinate (x, y+r) 88 | SHAPE.CIRCLE VEC.CREATE 89 | ; 90 | 91 | : GROUP_INFO.CREATE ( n -- addr , Create an anonymous group info struct ) 92 | \ Avoid assigning name to this vector by creating memory directly 93 | HERE SWAP 94 | [ SIZEOF() GROUP_INFO ] LITERAL ALLOT 95 | DUP CELLS ALLOT 96 | OVER S! GROUP.N 97 | ; 98 | 99 | : GROUP.MEMBER_COUNT ( addr -- n , Get the number of members in the group ) 100 | S@ VEC.DATA S@ GROUP.N 101 | ; 102 | 103 | : GROUP.MEMBER_ADDR ( addr n -- addr , Get the address of a group member cell ) 104 | \ Get base address of members field 105 | SWAP S@ VEC.DATA 106 | .. GROUP.MEMBERS 107 | SWAP 108 | CELLS + 109 | ; 110 | 111 | : GROUP.GET_MEMBER ( addr n -- addr , Get the actual address of a group member ) 112 | GROUP.MEMBER_ADDR @ 113 | ; 114 | 115 | : GROUP.INIT ( n -- vaddr n , Create a group object to hold n members ) 116 | 0 0 0 0 SHAPE.GROUP VEC.CREATE \ addr* n vaddr 117 | SWAP 118 | 2DUP \ a* v n v n 119 | GROUP_INFO.CREATE \ a* v n v g 120 | SWAP S! VEC.DATA \ a* v n 121 | ; 122 | 123 | : GROUP.ADD_MEMBERS ( addr* vaddr n -- vaddr , Add n members to a group ) 124 | 0 DO \ a* v 125 | DUP \ a* v v 126 | -ROT \ a*' v a' v 127 | I GROUP.MEMBER_ADDR ! \ Index to I'th member and set value 128 | LOOP 129 | ; 130 | 131 | \ A group defines a local coordinate system which can contain other vector 132 | \ objects, all of which are translated and scaled relative to the group origin. 133 | : VEC.GROUP ( addr* n -- addr , Group n vectors together ) 134 | GROUP.INIT GROUP.ADD_MEMBERS 135 | ; 136 | 137 | : VEC.WH ( addr -- w h , Get the width and height of a vector container ) 138 | DUP DUP DUP 139 | S@ VEC.X2 140 | SWAP S@ VEC.X1 - 141 | -ROT S@ VEC.Y2 142 | SWAP S@ VEC.Y1 - 143 | ; 144 | 145 | : VEC.XY1 ( addr -- x1 y1 , Get the XY1 coordinates of the vector ) 146 | DUP \ addr addr 147 | S@ VEC.X1 SWAP \ x1 addr 148 | S@ VEC.Y1 \ x1 y1 149 | ; 150 | 151 | : VEC.XY2 ( addr -- x2 y2 , Get the XY2 coordinates of the vector ) 152 | DUP 153 | S@ VEC.X2 SWAP 154 | S@ VEC.Y2 155 | ; 156 | 157 | : VEC.ZOOM_PRIMITIVE ( n addr -- , Apply a zoom transformation to a non-group ) 158 | SWAP 2DUP SWAP \ addr n n addr 159 | VEC.XY1 ROT ZOOM \ addr n x1' y1' 160 | 3 PICK TUCK \ addr n x1' addr y1' 161 | S! VEC.Y1 162 | S! VEC.X1 \ addr n 163 | OVER \ addr n addr 164 | VEC.XY2 ROT ZOOM \ addr x2' y2' 165 | ROT TUCK \ x2' addr y2' addr 166 | S! VEC.Y2 167 | S! VEC.X2 168 | ; 169 | 170 | \ Positive zoom factor zooms in. A negative zoom factor will zoom out. 171 | : VEC.ZOOM ( n addr -- , Apply a zoom transformation to the vector ) 172 | DUP S@ VEC.TYPE SHAPE.GROUP = 173 | IF 174 | DUP GROUP.MEMBER_COUNT 0 DO 175 | 2DUP I GROUP.GET_MEMBER RECURSE 176 | LOOP 177 | THEN 178 | VEC.ZOOM_PRIMITIVE 179 | ; 180 | 181 | : VEC.TRANSLATE_PRIMITIVE ( dx dy addr -- , Translate a non-group vector object ) 182 | TUCK 183 | 2OVER 2OVER 184 | DUP S@ VEC.Y1 185 | ROT + SWAP S! VEC.Y1 186 | DUP S@ VEC.X1 187 | ROT + SWAP S! VEC.X1 188 | DUP S@ VEC.Y2 189 | ROT + SWAP S! VEC.Y2 190 | DUP S@ VEC.X2 191 | ROT + SWAP S! VEC.X2 192 | ; 193 | 194 | : VEC.TRANSLATE ( dx dy addr -- , Translate a vector object ) 195 | DUP S@ VEC.TYPE SHAPE.GROUP = 196 | IF 197 | DUP GROUP.MEMBER_COUNT 0 DO 198 | 3DUP I GROUP.GET_MEMBER RECURSE 199 | LOOP 200 | THEN 201 | VEC.TRANSLATE_PRIMITIVE 202 | ; 203 | 204 | : VEC.SCREEN_COORDS ( addr -- sx1 sy1 sx2 sy2 , Get screen coords of vector ) 205 | DUP VEC.XY1 REFLECT_Y DISP.HEIGHT TRANSLATE_Y 206 | ROT VEC.XY2 REFLECT_Y DISP.HEIGHT TRANSLATE_Y 207 | ; 208 | 209 | : VEC.DRAW_RECT ( c addr -- , Draw a vector rectangle to a pixel buffer ) 210 | VEC.SCREEN_COORDS DRAW.RECT 211 | ; 212 | 213 | : VEC.DRAW ( c addr -- , Draw a vector shape to a pixel buffer ) 214 | \ Handle polarity: If the polarity is SUBTRACT, draw clear pixels instead of 215 | \ colored ones. Otherwise, use the given color. 216 | DUP S@ VEC.POL 217 | POL.SUB = IF 218 | NIP TRANSPARENT SWAP 219 | THEN 220 | 221 | DUP S@ VEC.TYPE 222 | CASE 223 | SHAPE.NONE OF DROP DROP ( Do nothing ) ENDOF 224 | SHAPE.RECT OF VEC.DRAW_RECT ENDOF 225 | SHAPE.GROUP OF 226 | DUP GROUP.MEMBER_COUNT 0 DO 227 | 2DUP I GROUP.GET_MEMBER RECURSE 228 | LOOP 229 | 2DROP 230 | ENDOF 231 | ENDCASE 232 | ; 233 | 234 | : VEC.SHOW_TYPE ( n -- , Display a human-friendly version of the vector type ) 235 | CASE 236 | 0 OF ." SHAPE.NONE " ENDOF 237 | 1 OF ." SHAPE.RECT " ENDOF 238 | 2 OF ." SHAPE.CIRCLE " ENDOF 239 | 3 OF ." SHAPE.LINE " ENDOF 240 | 4 OF ." SHAPE.ARC " ENDOF 241 | 5 OF ." SHAPE.GROUP " ENDOF 242 | ENDCASE 243 | ; 244 | 245 | : VEC.SHOW ( addr -- , Display a human-friendly printout of the vector ) 246 | CR 247 | ." => " 248 | DUP S@ VEC.X1 ." XY1: ( " . 249 | DUP S@ VEC.Y1 ." , " . ." ) " 250 | DUP S@ VEC.X2 ." XY2: ( " . 251 | DUP S@ VEC.Y2 ." , " . ." ) " 252 | DUP S@ VEC.TYPE ." TYPE: " VEC.SHOW_TYPE 253 | DUP S@ VEC.POL ." POLARITY: " . 254 | 255 | DUP S@ VEC.TYPE SHAPE.GROUP = 256 | IF 257 | CR ." CHILDREN:" 258 | DUP GROUP.MEMBER_COUNT 0 DO 259 | DUP I GROUP.GET_MEMBER RECURSE 260 | LOOP 261 | THEN 262 | DROP 263 | ; 264 | --------------------------------------------------------------------------------