Archive for category Projects
Inkbook Introduction
Posted by cheshirekow in Inkbook, Uncategorized on June 7, 2011
Inkbook is a new project I’ve started to replace Xournal for my needs. What I really want is a tightly integrated, full-features inking experience for Ubuntu.
What’s wrong with xournal?
Xournal is great. I use it all the time. However, there are a lot of really simple features I would like it to have. I took a look at the code, and it’s pretty hard to understand. The lack of good documentation means it’s not worth my time. There’s no sense in committing a ton of time trying to learn the code base, just to find out that an apparently simple feature is impossible to implement without restructuring the whole thing. So, I’m just restructuring the whole thing :).
I’ll start by going through all the things that I don’t like about Xournal.
Memory Usage
One of the biggest problems I have with Xournal is it’s memory usage. A typical 10 page Xournal document consumes around 300MB of RAM, and takes about 60 seconds to open. This is a big nuisance to me. I suspect that Xournal stores the whole document in memory, which is the cause.
Bitmaps
A lot of times I really want to paste some snippet into my notes. There is a Xournal patch for using bitmaps, and it’s not terrible, but the images render fuzzy and it’s difficult to scale and place them in the document. I usually end up exporting the whole thing to PDF for later reference. I’ve written a script which can copy parts of the screen to the clipboard (like the Adobe Reader snapshot tool), so I’d really like to be able to drop a bunch of images into a notebook and draw around them, write on them, etc.
Layers
I think that layers are a really useful tool, but it’s hard to use them in xournal. First of all, you have to select them from a drop down list at the bottom of the screen, not a list box. You can’t reorder them. And if you move to a lower layer, all the layers above it disappear.
Pen Options
Only three line widths and no fast-access colorwheel.
Shapes
Can only draw shapes by having the recognizer interpret them. Why not have shape tools that allow you to drop the shape and then resize, move around?
No lasso tool
Rectangular selection just doesn’t cut it for me. Especially when I have potato shaped drawings that I want to move around, without moving the text around it.
Inkbook
What I really want is a digital notebook. Inkbook aims to be just that. Inkbook is really a merger of features that I like from both Xournal and Inkscape, and an attempt to fix some of the problems I have with both. Here is a list of the features I’m currently focusing on.
- very large documents
- ability to organize notebooks (like folders)
- ability to link individual pages to multiple notebooks
- multiple layers per page
- multiple page sizes
- continuous range of brush sizes
- continuous color picking
- bitmap cut & paste
- grouping of paths
- objects (shapes)
- collaboration (openbook module?)
Very large documents and Organization
I want to be able to have several dozens of pages in a document, which basically means that the entire document can’t be stored in memory. Therefore, I’m attemping to store the data an a sqlite database. This also addresses the desire to have better organizational facilities. I’m implementing separate database objects for notebooks, pages, layers, objects, and paths.
A notebook is an ordered list of notebooks and an ordered list of pages (i.e. a folder). A page is an ordered list of layers. A layer is an ordered list of objects. An object is an ordered list of objects, images, or paths. A path is an ordered list of drawing primitives (most likely a one-to-one mapping to the cairo API).
Organization and View
For organizing notebooks, I plan to have a triew-view (i.e. directory tree). I’ll have a thumbnail page view which shows the current pages and those near it, and allows for scrolling through the whole notebook. This will be a custom widget which renders each of the pages via their thubmail image. I’ll have a list-view to organize layers on the page. The list view will also show list complex objects so they can be easily selected and edited (but it wont display any information about handdrawn paths, as there will be a large number of these). The main view will display a viewport of the page.
Current Progress
I’ve got a proof-of-concept running with the sqlite database file backend and working views the notebook organization and layers. I’ve got a proof-of-concept for the thumbnail view but it needs more work. It’s written in C++ and meant to be very easy to understand and extend. I’m using Gtkmm3 (unstable) because it’s GTK, but it’s C++, and it has cairo as the native API. Here’s a screenshot:
TxDuino: Configuration Builder in WxWidgets
Posted by cheshirekow in TxDuino on October 31, 2009
In relation to the TxDuino (somewhat documented here), I wrote a C++ class to ease the interfacing with the device. That code was posted here. In this post, I am sharing a program that can be used to easily discover and save the configuration of the saturation points of the different actuators on a vehicle. The program is written using the wxWidgets toolkit so *should* be compatible with any wx-supported operating system. Of course, at this point in time I still haven’t updated the TxDuino class with linux implementations yet, but hopefully that will happen soon.
The saturation points are the raw commands that correspond to the extreme edges of the actuators range. For instance, if the actuator is a servo, then the minimum saturation point is the minimum command that the servo can actually achieve. If a command lower than that is sent, then the servo will constantly jitter as it cannot actually achieve that rotation. By setting the saturation points, normalized commands (percentages) can be sent to the device using the TxDuino::setPercent()
function.
A screenshot of the program in action is below:
You can connect to the device by entering the device name in the text field at the top and clicking “Connect” (or pressing enter). Once the device is connected (success or failure will be reported in the console at the bottom), then commands can start being sent. You can start sending commands by clicking “Continue” or stop at any time by clicking “Pause”. Note that “pausing” won’t stop your vehicle from doing something retarded, the vehicle’s actuators will just be stuck in the last commanded state. If the program is not paused, then every time you change one of the values in the spin controls, then that new value is sent as the current command.
To discover the value of the minimum saturation point of one of the actuators, start by clicking on the spin control for that channel, setting it to zero, and hitting enter (this makes the control think it has changed whether it has or not so the value is sent to that channel). If the actuator is saturated (on a servo, if it’s making noise and/or jittering but not correcting itself) then raise the value by one unit. Keep raising the value until the actuator is no longer saturated. Repeat for the max saturation point and the neutral point. The neutral point is the point corresponding to 0%. For a servo this is most likely near the center of it’s range. For a speed controller, it should be the same as the minimum saturation.
A windows binary of this program can be downloaded here: Tester .
The source can be downloaded here: TxDuino Actuator Configuration Tester.
TxDuino: C++ classes (windows)
Posted by cheshirekow in TxDuino on October 31, 2009
In order to make interfacing with the TxDuino as easy as possible, I wrote a little c++ class to take care of all the heavy lifting. You connect to a TxDuino device by creating a TxDuino
object. The constructor accepts a device name. In windows that looks like “\.COM8
” (my arduino is installed on COM port 8). In linux it’ll look something like “/dev/ttyS2
” except that I haven’t gotten around to implementing the linux part of this class yet.
The class, along with the supporting classes and test program are included here: TxDuino Class Source.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | /** * file TxDuino.h * date: Oct 27, 2009 * brief: * * detail: */ #ifndef CTXDUINO_H_ #define CTXDUINO_H_ #include <vector> #include <string> #include "types.h" namespace txduino { /** * brief operating system dependendant implementation structure */ struct STxDuinoImpl; /** * brief Class encapsulating the interface / API for communicating with one * TxDuino device. The TxDuino sends a standard RC PPM signal encoding * commands for up to 8 PWM channels (servos, engine controllers). * * The magnitude of each channel is divided into 250 discrete segments. * Exactly how those segments are interpreted by the actuators is determined * by the configuration of this object. * * note: channels are zero indexed * * todo add a constructor that accepts a csv file with the saturations and * neutral points that can be generated from the test program */ class CTxDuino { private: STxDuinoImpl* m_osInfo; /// pointer to os-dependant implementation std::string m_devName; /// system device name, /// i.e. "\.COM2", "/dev/tty2" u8 m_chan [9]; /// value for each channel [0,250] u8 m_neutral [8]; /// the "center" for each actuator u8 m_minsat [8]; /// the minimum value for each channel u8 m_maxsat [8]; /// the maximum value for each channel public: /** * brief Constructs a new TxDuino object serving as an interface * into on particular TxDuino device. * param strDevice device string, i.e. "\.COM2", "/dev/tty2" */ CTxDuino( std::string strDevice ); /** * brief cleans up OS resources reserved for this serial connection */ virtual ~CTxDuino(); /** * brief sends the current channel definitions to the device */ void send(); /** * brief sets the value of an actuator as a percent of it's viable * range. * param chan the channel to set * param percent -100% < percent < 100%; value to set channel to */ void setPercent( s32 chan, f64 percent ); /** * brief returns the percent value the indicated actuator is set to, * note: if the neutral point is equal to one of the saturation * points this value may be unreliable * param chan the channel to get * return percent value of actuator on indicated channel */ f64 getPercent( s32 chan ); /** * brief sets the raw value of the actuator pulse width * param chan the channel to set * param value 0 < value < 250; value to set channel to */ void setRaw( s32 chan, u8 value ); /** * brief returns the raw value the indicated actuator is set to * param chan the channel to get * return a value between 0 and 250 indicated the pulse length for * that actuator (multiply by 4us and add 700us to get the * actual pulse length) */ u8 getRaw( s32 chan ); /** * brief sets the raw value of the actuator pulse width corresponding * to a neutral state of that actuator * param chan the channel to set * param value 0 < value < 250; value to set channel to */ void setNeutral( s32 chan, u8 value ); /** * brief returns the raw value corresponding to a neutral state of * the indicated actuator * param chan the channel to get * return a value between 0 and 250 indicated the pulse length for * that actuator (multiply by 4us and add 700us to get the * actual pulse length) */ u8 getNeutral( s32 chan ); /** * brief sets the raw value of the actuator pulse width corresponding * to the minimum state of the indicated actuator * param chan the channel to set * param value 0 < value < 250; value to set channel to */ void setMinSat( s32 chan, u8 value ); /** * brief returns the raw value corresponding to a minimum state of * the indicated actuator * param chan the channel to get * return a value between 0 and 250 indicated the pulse length for * that actuator (multiply by 4us and add 700us to get the * actual pulse length) */ u8 getMinSat( s32 chan ); /** * brief sets the raw value of the actuator pulse width corresponding * to the maximum state of the indicated actuator * param chan the channel to set * param value 0 < value < 250; value to set channel to */ void setMaxSat( s32 chan, u8 value ); /** * brief returns the raw value corresponding to a maximum state of * the indicated actuator * param chan the channel to get * return a value between 0 and 250 indicated the pulse length for * that actuator (multiply by 4us and add 700us to get the * actual pulse length) */ u8 getMaxSat( s32 chan ); /** * brief return the device name that was used to connect to this * txduino */ std::string getName(); }; } #endif /* CTXDUINO_H_ */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 | /** * file CTxDuino.cpp * date: Oct 27, 2009 * brief: * * detail: */ #include "CTxDuino.h" #include "compile.h" #include <iostream> #include <iomanip> #include <sstream> #include <cmath> #include "IllegalArgumentException.h" #include "IOException.h" #ifdef TXD_MINGW #include <windows.h> #endif namespace txduino { #ifdef TXD_MINGW struct STxDuinoImpl { HANDLE hComPort; /// handle to the opened COM device }; #endif #ifdef TXD_LINUX struct STxDuinoImpl { FILE hSerialFile; }; #endif /** * The initial state of the individual actuators is initialized as follows * * verbatim * minimum saturation: 0 * neutral: 125 * maximum saturation: 250 * value: 125 * endverbatim * * Note that this is probably not appropriate for your system. Many of the * servos that we've used have minimum saturations around 10 and maximum * saturations around 240. Use the actuator test program to determine what * these values should be. */ CTxDuino::CTxDuino( std::string strDevice ) { using std::cout; using std::endl; using std::stringstream; // intiialize all the arrays for( int i=0; i < 8; i++ ) { m_chan [i] = 125; m_minsat [i] = 0; m_maxsat [i] = 250; m_neutral [i] = 125; } // stop byte m_chan[8] = 0xFF; // initialize the OS dependant information m_osInfo = new STxDuinoImpl(); /* ---------------------------------------------------------------------------- * Windows Specific Implementation: * ---------------------------------------------------------------------------*/ #ifdef TXD_MINGW // open the file using the windows API m_osInfo->hComPort = CreateFile( strDevice.c_str(), // file name GENERIC_READ | GENERIC_WRITE, // access mode: read and write FILE_SHARE_READ|FILE_SHARE_WRITE, // (sharing) NULL, // (security) 0: none OPEN_EXISTING, // (creation) i.e. don't make it 0, // (overlapped operation) NULL); // no template file // check to make sure the file open succeeded if( m_osInfo->hComPort == INVALID_HANDLE_VALUE ) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid Device Name: " << strDevice; throw IllegalArgumentException(message.str()); } // get the current settings on the com port DCB dcb; GetCommState( m_osInfo->hComPort, &dcb ); // change the settings, the TxDuino uses a BAUD rate of 9600 dcb.fBinary = 1; dcb.BaudRate = CBR_9600; dcb.Parity = NOPARITY; dcb.ByteSize = 8; dcb.StopBits = ONESTOPBIT; // set the new settings for the port SetCommState( m_osInfo->hComPort, &dcb ); #endif } CTxDuino::~CTxDuino() { /* ---------------------------------------------------------------------------- * Windows Specific Implementation: * ---------------------------------------------------------------------------*/ #ifdef TXD_MINGW // close the device if it's open if( m_osInfo->hComPort != INVALID_HANDLE_VALUE ) CloseHandle( m_osInfo->hComPort ); #endif delete m_osInfo; } void CTxDuino::send() { using std::stringstream; // ensure that the last byte of the packet is the stop byte m_chan[8] = 0xFF; /* ---------------------------------------------------------------------------- * Windows Specific Implementation: * ---------------------------------------------------------------------------*/ #ifdef TXD_MINGW DWORD bytesWritten; BOOL retVal = WriteFile( m_osInfo->hComPort, // output handle m_chan, // buffer of bytes to send 9, // number of bytes to send from buffer &bytesWritten, // pointer to a word that receives number of // bytes written NULL); // pointer to an OVERLAPPED struct if( bytesWritten != 9 ) { stringstream message( stringstream::in | stringstream::out ); message << "Bytes written to device less than expected: " << bytesWritten << ", expecting 9"; throw IOException(message.str()); } if( retVal == 0 ) { stringstream message( stringstream::in | stringstream::out ); message << "Writing to device failed; error code: " << GetLastError(); throw IOException(message.str()); } #endif } /** * If the percent is positive, the raw value is calculated as follows * * verbatim * raw = neutral + (max - neutral) * percent * endverbatim * * if the percent is negative, the raw value is calculated as follows * * verbatim * raw = neutral - (neutral - min) * percent * endverbatim */ void CTxDuino::setPercent( s32 chan, f64 percent ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if(percent > 0) { m_chan[chan] = (u8) (m_neutral[chan] + ( m_maxsat[chan] - m_neutral[chan]) * percent ); } else { m_chan[chan] = (u8) (m_neutral[chan] - ( m_minsat[chan] - m_neutral[chan]) * percent ); } } /** * If the value is strictly less than the neutral value then the percent value * is calculated by * * verbatim * percent = -( neutral - value ) / (neutral - min); * endverbatim * * If the value is strictly greater than the neutral value then the percent * value is calculated by * * verbatim * percent = ( value - neutral ) / (max - neutral); * endverbatim * * If the value is equal to the neutral value then the percent value is zero. */ f64 CTxDuino::getPercent( s32 chan ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if( m_chan[chan] < m_neutral[chan] ) { return (double)(-(m_neutral[chan] - m_chan[chan])) / (m_neutral[chan] - m_minsat[chan]); } else if( m_chan[chan] > m_neutral[chan] ) { return (double)(m_chan[chan] - m_neutral[chan]) / (m_maxsat[chan] - m_neutral[chan]); } else { return 0.0; } } void CTxDuino::setRaw( s32 chan, u8 value ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if(value > 250) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid value: " << value << HERE << "valid channels are 0-250"; } m_chan[chan] = value; } u8 CTxDuino::getRaw( s32 chan ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } return m_chan[chan]; } void CTxDuino::setNeutral( s32 chan, u8 value ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if(value > 250) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid value: " << value << HERE << "valid channels are 0-250"; } m_neutral[chan] = value; } u8 CTxDuino::getNeutral( s32 chan ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } return m_neutral[chan]; } void CTxDuino::setMinSat( s32 chan, u8 value ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if(value > 250) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid value: " << value << HERE << "valid channels are 0-250"; } m_minsat[chan] = value; } u8 CTxDuino::getMinSat( s32 chan ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } return m_minsat[chan]; } void CTxDuino::setMaxSat( s32 chan, u8 value ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if(value > 250) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid value: " << value << HERE << "valid channels are 0-250"; } m_maxsat[chan] = value; } u8 CTxDuino::getMaxSat( s32 chan ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } return m_maxsat[chan]; } std::string CTxDuino::getName() { return m_devName; } } |
A re-write of the serialTest.exe
program that sends sinusoidal commands to the plane using this new class demonstrates its use.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | /** * file serialTest.cpp * date: Oct 27, 2009 * brief: * * detail: * This is a simple test program that demonstrates how to connect to and * write commands to the arduino transmitter interface using windows. * * the TxDuino is an interface into the futaba FP-TP-FM transmitter module, * which accepts an RC PPM input. This signal contains a maximum of 8 * servo channels. */ #include <iostream> #include <iomanip> #include <cmath> #include "CTxDuino.h" #include "constants.h" using namespace std; using namespace txduino; int main( int argc, char** argv ) { // check to ensure that the command line included a device to open if( argc < 2 ) { cout << "Usage: serialTest.exe [Device Name]n" " where [Device Name] is the name of the COM port file onn " " windows (i.e. \\.\COM8), or the name of the serialn " " device on *nix (i.e. /dev/tty8)n" << endl; return -1; } // grab a pointer to the device to open char* strDevName = argv[1]; // create the txduino device CTxDuino tx(strDevName); // send a sinusoidal input on all channels (except for channel 3, which is // usually the throttle) for 10 seconds for(int i=0; i < 1000; i++) { for(int j=0; j < 8; j++) tx.setRaw(j, (unsigned char) (125.0 + 75.0 * sin( 2.0 * PI * i / 100.0 )) ); tx.setRaw(2, 0); for(int j=0; j < 8; j++) cout << setw(3) << (int)tx.getRaw(j) << " | "; cout << endl; tx.send(); #ifdef TXD_MIGNW Sleep(1); #endif #ifdef TXD_LINUX usleep(0.001); #endif } return 0; } |