Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ Possible log types:
- `[fixed]` for any bug fixes.
- `[security]` to invite users to upgrade in case of vulnerabilities.

### v0.6.0 (2025-07-10)

- [added] Added support for G04 'standard comments' where comment attributes are placed in G04 commands.
e.g. 'G04 #@! TA.AperFunction,SMDPad,CuDef*'
This means that when you're looking for attributes, you now have to look in two places:
1) `Command::ExtendedCode(ExtendedCode::[FileAttribute|ObjectAttribute|ApertureAttribute])` and
2) `Command::FunctionCode(FunctionCode::GCode(GCode::Comment(CommentContent::Standard(StandardComment::[FileAttribute|ObjectAttribute|ApertureAttribute]))))`
It also means when you're making/serializing gerber files you need to choose where to put the attributes.
Refer to Gerber spec 2024.05 - "4.1 Comment (G04)" and "5.1.1 Comment attributes".
Unfortunately, in 2025, manufacturing files containing comment attributes are still widespread.
- [changed] Removed 'Eq' from `FunctionCode`, due to use of `f64` in attributes (`ExtendedCode` wasn't `Eq` either)

### v0.5.0 (2025-07-10)

- [added] Support for legacy/deprecated gerber commands: `IP`, `MI`, `SF`, `OF`, `IR`, and `AS`.
Expand Down
15 changes: 12 additions & 3 deletions examples/polarities-apertures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn main() {
let cf = CoordinateFormat::new(2, 6);
let commands: Vec<Command> = vec![
FunctionCode::GCode(GCode::Comment("Ucamco ex. 2: Shapes".to_string())).into(),
FunctionCode::GCode(GCode::Comment(CommentContent::String(
"Ucamco ex. 2: Shapes".to_string(),
)))
.into(),
ExtendedCode::CoordinateFormat(cf).into(),
ExtendedCode::Unit(Unit::Inches).into(),
ExtendedCode::FileAttribute(FileAttribute::GenerationSoftware(GenerationSoftware::new(
Expand All @@ -25,7 +28,10 @@ fn main() {
)))
.into(),
ExtendedCode::LoadPolarity(Polarity::Dark).into(),
FunctionCode::GCode(GCode::Comment("Define Apertures".to_string())).into(),
FunctionCode::GCode(GCode::Comment(CommentContent::String(
"Define Apertures".to_string(),
)))
.into(),
ExtendedCode::ApertureMacro(ApertureMacro::new("TARGET125").add_content(MoirePrimitive {
center: (0.0.into(), 0.0.into()),
diameter: 0.125.into(),
Expand Down Expand Up @@ -119,7 +125,10 @@ fn main() {
aperture: Aperture::Macro("THERMAL80".to_string(), None),
})
.into(),
FunctionCode::GCode(GCode::Comment("Start image generation".to_string())).into(),
FunctionCode::GCode(GCode::Comment(CommentContent::String(
"Start image generation".to_string(),
)))
.into(),
FunctionCode::DCode(DCode::SelectAperture(10)).into(),
FunctionCode::DCode(DCode::Operation(Operation::Move(Some(Coordinates::new(
0,
Expand Down
9 changes: 6 additions & 3 deletions examples/two-boxes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
use std::io::stdout;

use gerber_types::{
Aperture, ApertureDefinition, Circle, Command, CoordinateFormat, Coordinates, DCode,
ExtendedCode, FileAttribute, FunctionCode, GCode, GenerationSoftware, GerberCode,
Aperture, ApertureDefinition, Circle, Command, CommentContent, CoordinateFormat, Coordinates,
DCode, ExtendedCode, FileAttribute, FunctionCode, GCode, GenerationSoftware, GerberCode,
InterpolationMode, MCode, Operation, Part, Polarity, Unit,
};

Expand All @@ -14,7 +14,10 @@ const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn main() {
let cf = CoordinateFormat::new(2, 6);
let commands: Vec<Command> = vec![
FunctionCode::GCode(GCode::Comment("Ucamco ex. 1: Two square boxes".to_string())).into(),
FunctionCode::GCode(GCode::Comment(CommentContent::String(
"Ucamco ex. 1: Two square boxes".to_string(),
)))
.into(),
ExtendedCode::Unit(Unit::Millimeters).into(),
ExtendedCode::CoordinateFormat(cf).into(),
ExtendedCode::FileAttribute(FileAttribute::GenerationSoftware(GenerationSoftware::new(
Expand Down
74 changes: 69 additions & 5 deletions src/function_codes.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Function code types.

use std::io::Write;

use crate::attributes;
use crate::coordinates::{CoordinateOffset, Coordinates};
use crate::errors::GerberResult;
use crate::traits::{GerberCode, PartialGerberCode};
use std::io::Write;

// DCode

Expand All @@ -26,12 +26,12 @@ impl<W: Write> GerberCode<W> for DCode {

// GCode

#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
pub enum GCode {
InterpolationMode(InterpolationMode),
RegionMode(bool),
QuadrantMode(QuadrantMode),
Comment(String),
Comment(CommentContent),
}

impl<W: Write> GerberCode<W> for GCode {
Expand All @@ -46,12 +46,76 @@ impl<W: Write> GerberCode<W> for GCode {
}
}
GCode::QuadrantMode(ref mode) => mode.serialize(writer)?,
GCode::Comment(ref comment) => writeln!(writer, "G04 {}*", comment)?,
GCode::Comment(ref content) => {
write!(writer, "G04 ")?;
content.serialize_partial(writer)?;
writeln!(writer, "*")?;
}
};
Ok(())
}
}

/// See Gerber spec 2024.05.
/// 1) 4.1 - Comment (G04)
/// 2) 5.1.1 - Comment attributes
#[derive(Debug, Clone, PartialEq)]
pub enum CommentContent {
String(String),
/// "Content starting with ”#@!“ is reserved for standard comments. The purpose of standard
/// comments is to add meta-information in a formally defined manner, without affecting image
/// generation. They can only be used if defined in this specification"
Standard(StandardComment),
}

impl<W: Write> PartialGerberCode<W> for CommentContent {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
CommentContent::String(ref string) => {
write!(writer, "{}", string)?;
}
CommentContent::Standard(ref standard) => {
standard.serialize_partial(writer)?;
}
}
Ok(())
}
}

/// See Gerber spec 2024.05.
/// 1) 4.1 - Comment (G04)
/// 2) 5.1.1 - Comment attributes
#[derive(Debug, Clone, PartialEq)]
pub enum StandardComment {
/// TF
FileAttribute(attributes::FileAttribute),
/// TO
ObjectAttribute(attributes::ObjectAttribute),
/// TA
ApertureAttribute(attributes::ApertureAttribute),
}

impl<W: Write> PartialGerberCode<W> for StandardComment {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
write!(writer, "#@! ")?;
match *self {
StandardComment::FileAttribute(ref fa) => {
write!(writer, "TF")?;
fa.serialize_partial(writer)?;
}
StandardComment::ObjectAttribute(ref oa) => {
write!(writer, "TO")?;
oa.serialize_partial(writer)?;
}
StandardComment::ApertureAttribute(ref aa) => {
write!(writer, "TA")?;
aa.serialize_partial(writer)?;
}
}
Ok(())
}
}

// MCode

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down
64 changes: 60 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,79 @@ mod serialization_tests {
#[test]
fn test_comment() {
//! The serialize method of the GerberCode trait should generate strings.
let comment = GCode::Comment("testcomment".to_string());
let comment = GCode::Comment(CommentContent::String("testcomment".to_string()));
assert_code!(comment, "G04 testcomment*\n");
}

/// `standard comment` is a term defined in the gerber spec. See `2024.05 4.1 Comment (G04)`
#[test]
fn test_standard_comment_with_standard_attributes() {
//! Attributes should be able to be stored in G04 comments starting with `#@!`
let comment = GCode::Comment(CommentContent::Standard(
StandardComment::ApertureAttribute(ApertureAttribute::ApertureFunction(
ApertureFunction::SmdPad(SmdPadType::CopperDefined),
)),
));
assert_code!(comment, "G04 #@! TA.AperFunction,SMDPad,CuDef*\n");

let comment = GCode::Comment(CommentContent::Standard(StandardComment::FileAttribute(
FileAttribute::FileFunction(FileFunction::Profile(Some(Profile::NonPlated))),
)));
assert_code!(comment, "G04 #@! TF.FileFunction,Profile,NP*\n");

let comment = GCode::Comment(CommentContent::Standard(StandardComment::ObjectAttribute(
ObjectAttribute::Component("R1".to_string()),
)));
assert_code!(comment, "G04 #@! TO.C,R1*\n");
}

#[test]
fn test_standard_comment_with_custom_attributes() {
// custom attributes are not prefixed with a `.`.
let comment = GCode::Comment(CommentContent::Standard(
StandardComment::ApertureAttribute(ApertureAttribute::UserDefined {
name: "Example".to_string(),
values: vec!["value1".to_string(), "value2".to_string()],
}),
));
assert_code!(comment, "G04 #@! TAExample,value1,value2*\n");

let comment = GCode::Comment(CommentContent::Standard(StandardComment::FileAttribute(
FileAttribute::UserDefined {
name: "Example".to_string(),
values: vec!["value1".to_string(), "value2".to_string()],
},
)));
assert_code!(comment, "G04 #@! TFExample,value1,value2*\n");

let comment = GCode::Comment(CommentContent::Standard(StandardComment::ObjectAttribute(
ObjectAttribute::UserDefined {
name: "Example".to_string(),
values: vec!["value1".to_string(), "value2".to_string()],
},
)));
assert_code!(comment, "G04 #@! TOExample,value1,value2*\n");
}

#[test]
fn test_vec_of_comments() {
//! A `Vec<T: GerberCode>` should also implement `GerberCode`.
let mut v = Vec::new();
v.push(GCode::Comment("comment 1".to_string()));
v.push(GCode::Comment("another one".to_string()));
v.push(GCode::Comment(CommentContent::String(
"comment 1".to_string(),
)));
v.push(GCode::Comment(CommentContent::String(
"another one".to_string(),
)));
assert_code!(v, "G04 comment 1*\nG04 another one*\n");
}

#[test]
fn test_single_command() {
//! A `Command` should implement `GerberCode`
let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment("comment".to_string())));
let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment(CommentContent::String(
"comment".to_string(),
))));
assert_code!(c, "G04 comment*\n");
}

Expand Down
18 changes: 11 additions & 7 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ macro_rules! impl_command_fromfrom {

// Main categories

#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
pub enum FunctionCode {
DCode(function_codes::DCode),
GCode(function_codes::GCode),
Expand Down Expand Up @@ -185,34 +185,38 @@ mod test {
use crate::extended_codes::Polarity;
use crate::function_codes::GCode;
use crate::traits::GerberCode;
use crate::{ApertureBlock, Mirroring, Rotation, Scaling, StepAndRepeat};
use crate::{ApertureBlock, CommentContent, Mirroring, Rotation, Scaling, StepAndRepeat};

#[test]
fn test_debug() {
//! The debug representation should work properly.
let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment("test".to_string())));
let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment(CommentContent::String(
"test".to_string(),
))));
let debug = format!("{:?}", c);
assert_eq!(debug, "FunctionCode(GCode(Comment(\"test\")))");
assert_eq!(debug, "FunctionCode(GCode(Comment(String(\"test\"))))");
}

#[test]
fn test_function_code_serialize() {
//! A `FunctionCode` should implement `GerberCode`
let c = FunctionCode::GCode(GCode::Comment("comment".to_string()));
let c = FunctionCode::GCode(GCode::Comment(CommentContent::String(
"comment".to_string(),
)));
assert_code!(c, "G04 comment*\n");
}

#[test]
fn test_function_code_from_gcode() {
let comment = GCode::Comment("hello".into());
let comment = GCode::Comment(CommentContent::String("hello".into()));
let f1: FunctionCode = FunctionCode::GCode(comment.clone());
let f2: FunctionCode = comment.into();
assert_eq!(f1, f2);
}

#[test]
fn test_command_from_function_code() {
let comment = FunctionCode::GCode(GCode::Comment("hello".into()));
let comment = FunctionCode::GCode(GCode::Comment(CommentContent::String("hello".into())));
let c1: Command = Command::FunctionCode(comment.clone());
let c2: Command = comment.into();
assert_eq!(c1, c2);
Expand Down