EzyCad coding style
Use this style when editing or adding C/C++ code in the EzyCad project (files under src/, not third_party or build output).
Headers
Use
#pragma oncefor include guards (no#ifndef/#define/#endif).Include order: Put the closest / most direct dependencies first.
In a
.cppfile: include this file’s own header first (e.g. insketch_nodes.cpp, the first line is#include "sketch_nodes.h"). Then other project headers it needs, then external/system headers (<...>). Use a blank line before the<...>block when you have both.In a
.hfile: include the project headers this file directly depends on first (e.g.sketch_nodes.hhas#include "types.h"before<gp_Pln.hxx>;shp_operation.hhas"shp.h","types.h"first). Then external/system headers. So project ("...") before external (<...>), with the most relevant project header at the very top when there is one.
Prefer forward declarations for types used only as pointers/references (e.g.
class gp_Pln;) when it reduces includes.
Naming
Classes / structs: PascalCase with underscores, e.g.
Sketch_nodes,Shp,Sketch_AIS_edge,Occt_view.Enums: PascalCase; enum values either PascalCase or descriptive with underscores (e.g.
Full,Background,Sketch_add_node,_countfor sentinel).Member variables:
m_prefix (e.g.m_nodes,m_view). Static members:s_prefix (e.g.s_snap_dist_pixels).Prefer clear domain prefixes for related member groups (e.g.
m_underlay_*) instead of mixed short forms.Constants (e.g. lookup arrays for enums):
c_prefix (e.g.c_mode_strs,c_chamfer_mode_strs).Functions / methods: snake_case (e.g.
add_new_node,get_node_exact,try_get_node_idx_snap).Private methods: snake_case with trailing underscore (e.g.
update_axis_snap_anno_).Type aliases: snake_case with suffix by role, e.g.
*_ptrfor handles (AIS_Shape_ptr,Shp_ptr),*_rsltfor result types (Shp_rslt). Typedefs likeScreenCoordsare PascalCase.Macros: UPPER_SNAKE_CASE (e.g.
EZY_ASSERT,EZY_ASSERT_MSG,DBG_MSG).
Formatting
Line endings
Use LF (
\n) for all EzyCad-owned text files:src/,tests/,scripts/,cmake/,docs/,agents/, and other project paths listed in.gitattributes.Do not mix CRLF and LF in the same file; mixed endings trigger editor warnings and noisy diffs.
third_party/is vendored upstream code and is not normalized by this rule.On Windows, configure your editor to LF for C++ (Visual Studio: File → Advanced Save Options, or normalize when prompted). In this repo, run
git config core.autocrlf falseso Git does not reintroduce CRLF on checkout. Git applies the rules in.gitattributeson commit.
clang-format
Run scripts/format-src.ps1 (or clang-format -i on individual files) before committing C++ changes. .clang-format at the repo root is the source of truth; the settings below match that file. Anything not listed inherits BasedOnStyle: LLVM (e.g. spaces not tabs, short loops not collapsed to one line).
BasedOnStyle: LLVM— Start from the LLVM preset; only the options below override it.IndentWidth: 2/TabWidth: 2— Indent with 2 spaces; do not use tab characters.ColumnLimit: 128— Prefer wrapping before column 128.BreakBeforeBraces: Allman— Opening{on the next line for classes, structs, functions, control statements, namespaces, and enums.BreakConstructorInitializers: BeforeComma— Constructor initializer lists break before each comma (comma starts the continuation line).AccessModifierOffset: -1— Access specifiers are outdented one space:` public:`,` private:`,` protected:`(one leading space).PointerAlignment: Left— Attach*/&to the type:int* p,const Shp& shp.AlignConsecutiveDeclarations: true— Align types and names across consecutive declarations in the same block when it helps readability.AlignConsecutiveAssignments: true— Align=across consecutive assignments in the same block when it helps readability.AllowShortIfStatementsOnASingleLine: false— Do not put an entireif(condition + body) on one line; condition and statement stay on separate lines. You may still omit braces for a single-statement body.AllowShortLambdasOnASingleLine: Inline— Very short inline lambdas may stay on one line; longer lambdas break across lines.IndentCaseLabels: false—case/defaultlabels align with the surroundingswitch, not extra-indented under it.SortIncludes: false— clang-format does not reorder#includelines. Follow Headers above manually (own header first in.cpp, project"..."before<...>).
Use // clang-format off / // clang-format on only where layout must be preserved (e.g. macro-like tables). Prefer formatting the file normally.
Not enforced by clang-format (project convention): brace-initialize members (bool ok {false};); declare locals close to first use; omit braces on single-statement if/for/while bodies when clear.
Documentation
User-facing Markdown (now in docs/: usage.md, usage-*.md, scripting.md, building-occt.md etc., plus Read the Docs, images, in-app help URLs) is covered in ezycad_doc_style.md. This file is for src/ C++ only.
Versioning and releases
Product version lives in
src/version.h:EZYCAD_VERSION_MAJOR,EZYCAD_VERSION_MINOR,EZYCAD_VERSION_PATCH, andEZYCAD_VERSION_STRING. Follow Semantic Versioning (MAJOR.MINOR.PATCH). The header is hand-maintained so it stays valid regardless of build system (CMake or otherwise).Release notes live in
CHANGELOG.mdat the repo root, using Keep a Changelog sections (Added,Changed,Fixed, etc.). Add notable work under[Unreleased]as you go; when you cut a release, rename that section to[x.y.z] - YYYY-MM-DD, bumpversion.hto match, and create a git tagvx.y.zconsistent withEZYCAD_VERSION_STRING.Do not confuse the product version with
k_settings_versioningui_settings.cpp(andversionin saved settings JSON): that value is the settings file format version, not the application version.
Code organization
Reader-first order (
.cpp): Arrange functions from high-level behavior to lower-level detail so the first screen shows the primary workflow before helper details.Helper placement: Prefer keeping narrow file-local helpers close to the functions that use them. Avoid large top-of-file helper blocks unless helpers are broadly reused across the file.
Templates: Prefer putting template implementations in
.inlfiles included from the header (e.g.types.inl,utl.inl).OCCT handles: Use
opencascade::handle<T>and project aliases (e.g.AIS_Shape_ptr,Shp_ptr).Result/error handling: See Fail fast below. Use
Result<T>andStatusfromutl.h,CHK_RET(...)for early return on failure.Assertions: See Fail fast below. Use
EZY_ASSERTandEZY_ASSERT_MSGfromdbg.h; useDBG_MSGfor debug logging.
Don’t Repeat Yourself (DRY)
DRY is important, but it is guidance, not a strict rule.
Reader-first pairing: Pragmatic DRY matches Reader-first order in Code organization above. Prefer a self-contained workflow or UI pane you can read top-to-bottom over distant abstractions whose only win is deleting a few repeated lines.
Avoid hasty abstractions: do not generalize too early before patterns are stable.
Too much DRY can increase coupling by forcing unrelated code through one shared abstraction.
Prefer readability over clever reuse when repetition is small and explicit code is clearer.
When duplication is acceptable: a few lines repeated across nearby UI or glue code can beat a shared helper if call sites are likely to diverge (different tooltips, widths, or disabled logic) or if extracting would scatter one screen across many symbols. Prefer locality: keep a small block self-contained so a reader does not jump to understand one pane.
Rule of thumb: extract when the behavior is the same and stable (or when a bug fix must touch N copies); wait when the pattern is still moving. If you extract, prefer a narrow file-local helper next to its users over a generic framework.
Context matters: stronger DRY is often good in monolith/shared-library code; some duplication can be healthier in fast-changing or separated systems.
Balance DRY with KISS, YAGNI, and overall cognitive load.
Fail fast
EzyCad uses fail fast: detect problems as early as possible. Do not hide programmer mistakes with silent fallbacks (e.g. substituting empty defaults when a required dependency is missing) unless that behavior is an explicit, documented product decision.
Separate who is responsible for the fault:
Programmer errors (use asserts)
Use EZY_ASSERT / EZY_ASSERT_MSG (dbg.h) for conditions that must hold if the code and call patterns are correct:
Preconditions that callers are required to satisfy (e.g. an object that is always constructed before use in normal app flow).
Internal invariants, impossible
elsebranches after exhaustive handling, index bounds that prove from prior logic.“This should never happen” after a switch or state machine where all cases were meant to be covered.
These are not expected at runtime in correct usage; they signal a bug to fix. Do not use asserts for user input, files, or the operating system.
User mistakes and external failures (return errors or status)
Use Status, Result<T>, CHK_RET, or other explicit error paths for faults that can happen in valid programs:
User errors: wrong mode, nothing selected when something must be selected, invalid input — typically
Status::user_error("...")or an appropriateResult_statuswith a clear message.OS and I/O: file missing, disk full, permission denied, read/write failures — return or propagate failure; log or surface a message as the UI already does elsewhere. Never assert on these.
Modeling / OCCT / topology: operation failed in the kernel — use
Status/Resultand existing result categories (e.g.Topo_error) as fits the call site.
Prefer CHK_RET(expr) when a callee returns Status or Result<T> and the caller should abort on failure.
Quick reference
Kind of problem |
Mechanism |
|---|---|
Bug, contract violation, impossible branch |
|
User error, I/O, OS, recoverable external failure |
|
Source encoding (ASCII only)
Comments, string literals, and documentation in
src/must be 7-bit ASCII only (code points U+0000 through U+007F). Do not use Unicode punctuation or symbols (e.g. en dash, em dash, ellipsis, arrows, multiplication sign, middle dot, math letters like theta) or curly/smart apostrophes.Use ASCII substitutes instead, for example:
Ranges:
0-255,1-9(hyphen), not en dash.Punctuation in prose:
-for dash;...for ellipsis;->for “maps to” / arrows in comments; plain'for apostrophes.Math in comments: spell out (
sqrt(2),theta) or use ASCII operators (xfor cross-product context,*for multiply).
Run
scripts/check-nonascii-src.ps1(orcheck-nonascii-src.cmd) before committing when touchingsrc/.For AI tools: the same rule is summarized in
agents/ezycad-ascii-source.md.
C++ usage
Prefer
enum classfor enumerations.Use
std::optionalwhere a value may be absent.Use
constandconst&for parameters and accessors where appropriate.Prefer glm vector types (
glm::vec2/vec3/vec4,glm::dvec*) for geometric/color-like grouped values (positions, directions, extents, RGB/RGBA colors) instead of raw C arrays when storing or passing data in project code. Use raw arrays only at API boundaries that requirefloat*/double*(e.g. some ImGui or OCCT calls), and convert at the boundary.Keep consistent with existing file style (e.g. include order, brace placement) when editing.
Comments
Use
//for line comments.Document non-obvious parameters or lifetime requirements (e.g.
// \view` must exist for the lifetime of this object`).Prefer brief inline comments for non-obvious logic; keep comments in sync with code.