Skip to content
Open
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
25 changes: 25 additions & 0 deletions EasyEDA-Loader/Component.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,31 @@ public class ComponentInfo

[JsonProperty("packageDetail")]
public PackageDetail PackageDetail { get; set; }

// Multi-subpart components (op-amps with multiple gates, big MCUs like STM32MP135 whose
// pins are split across several drawn "parts") carry their real pin/shape data here
// instead of in the top-level dataStr - the top-level one is empty ("shape": []) for
// these parts. See EasyEDALoader.cs for how this gets used.
[JsonProperty("subparts")]
public List<Subpart> Subparts { get; set; }
}

public class Subpart
{
[JsonProperty("uuid")]
public string Uuid { get; set; }

[JsonProperty("title")]
public string Title { get; set; }

[JsonProperty("dataStr")]
public SymbolData DataStr { get; set; }

[JsonProperty("description")]
public string Description { get; set; }

[JsonProperty("thumb")]
public string Thumb { get; set; }
}

public class Root
Expand Down
25 changes: 24 additions & 1 deletion EasyEDA-Loader/EEPCB.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using PCB;

using System;
using System.Collections.Generic;

namespace EasyEDA_Loader
{
Expand Down Expand Up @@ -132,14 +133,36 @@ public static IPCB_Text3 CreateText(IPCB_LibComponent c, TLayerConstant layer, s
return textObject;
}

public static IPCB_ComponentBody CreateComponentBody(IPCB_LibComponent c, string fileName, double rx, double ry, double rz, double x, double y, double z)
public static IPCB_Region CreateSolidRegion(IPCB_LibComponent c, TLayerConstant layer, List<EePoint> points)
{
var contour = AltiumApi.GlobalVars.PCBServer.PCBContourFactory();
if (contour == null) return null;
foreach (var p in points)
{
contour.AddPoint(AltiumApi.MmToCoord(p.X) + c.GetState_XLocation(), AltiumApi.MmToCoord(p.Y) + c.GetState_YLocation());
}

var region = AltiumApi.GlobalVars.PCBServer.PCBObjectFactory(TObjectId.eRegionObject, TDimensionKind.eNoDimension, TObjectCreationMode.eCreate_Default) as IPCB_Region;
if (region == null) return null;
region.SetState_Kind(TRegionKind.eRegionKind_Copper);
region.SetOutlineContour(contour);
region.SetState_V7Layer(new V7_Layer(layer));
return region;
}

public static IPCB_ComponentBody CreateComponentBody(IPCB_LibComponent c, string fileName, double rx, double ry, double rz, double x, double y, double z, TLayerConstant layer)
{
var stepModel = AltiumApi.GlobalVars.PCBServer.PCBObjectFactory(TObjectId.eComponentBodyObject, TDimensionKind.eNoDimension, TObjectCreationMode.eCreate_Default) as IPCB_ComponentBody;
if (stepModel == null) return null;
var model = stepModel.ModelFactory_FromFilename(fileName, false);
if (model == null) return null;
model.SetState(rx, ry, rz, AltiumApi.MmToCoord(z));
stepModel.SetModel(model);
// Every other shape in this file (pads, tracks, arcs, text) sets its own layer via
// SetState_V7Layer - this call was missing entirely for component bodies, leaving a
// freshly-created body at whatever the internal default layer is instead of the side
// (top/bottom) the SVGNODE's own layerid actually specifies.
stepModel.SetState_V7Layer(new V7_Layer(layer));
// Model is created at the bottom-left origin of the board, so we need to offset it
stepModel.MoveByXY(AltiumApi.MmToCoord(x) + c.GetState_Board().GetState_XOrigin(), AltiumApi.MmToCoord(y) + c.GetState_Board().GetState_YOrigin());
return stepModel;
Expand Down
33 changes: 33 additions & 0 deletions EasyEDA-Loader/EESCH.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using EDP;
using SCH;
using System.Collections.Generic;
using System.Windows.Forms;

namespace EasyEDA_Loader
Expand Down Expand Up @@ -161,6 +162,38 @@ public static void CreateLine(ISch_Lib schLib, ISch_Component c, double x1, doub
c.AddSchObject(line);
}

// For filled schematic graphics (EasyEDA's PL/PG/PT shapes - polylines drawn closed,
// polygons, and paths, e.g. diode arrows), which were previously parsed but never drawn.
// ISch_Polygon (shared by ISch_Polyline) exposes SetState_VerticesCount + SetState_Vertex
// for building the outline point-by-point. Filled the same way CreateRectangle fills itself.
public static void CreatePolygon(ISch_Lib schLib, ISch_Component c, List<EePoint> points)
{
if (points == null || points.Count < 3)
return;
var polygon = AltiumApi.GlobalVars.SCHServer.SchObjectFactory(SCH.TObjectId.ePolygon, SCH.TObjectCreationMode.eCreate_Default) as ISch_Polygon;
if (polygon == null)
return;

polygon.SetState_VerticesCount(points.Count);
for (int i = 0; i < points.Count; i++)
{
// SetState_Vertex is 1-indexed, not 0-indexed - without the "+ 1" the last vertex
// silently defaults to (0,0) and the rest are shifted one slot from what was set.
polygon.SetState_Vertex(i + 1, new DXP.Point
{
X = AltiumApi.MilsToCoord(points[i].X),
Y = AltiumApi.MilsToCoord(points[i].Y)
});
}
polygon.SetState_LineWidth(TSize.eSmall);
polygon.SetState_Color(0);
polygon.SetState_AreaColor(0);
polygon.SetState_IsSolid(true);
polygon.SetState_OwnerPartId(schLib.GetState_CurrentSchComponentPartId());
polygon.SetState_OwnerPartDisplayMode(schLib.GetState_CurrentSchComponentDisplayMode());
c.AddSchObject(polygon);
}

public static void AssignFootprint(ISch_Component c, string libraryPath, string modelName, string modelMapping)
{
var modelType = "PCBLIB";
Expand Down
37 changes: 35 additions & 2 deletions EasyEDA-Loader/EasyEDALoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,13 @@ private void Run(
var root = selection.Root;
var owner_id = root.Component.Owner.Uuid;
var ee_footprint = root.Component.PackageDetail.Footprint;
// The top-level Symbol (root.Component.Symbol) has an empty shape list for
// multi-subpart components, but its Head.Parameters (Name/Pre) are still the
// correct ones for the component as a whole - a subpart's own Pre has a
// per-part suffix ("U?.1") that would be wrong here. Real per-part
// pins/shapes come from Subparts instead.
var ee_symbol = root.Component.Symbol;
bool isMultiPart = root.Component.Subparts != null && root.Component.Subparts.Count > 0;
string package = ee_footprint.Head.Parameters.Package;
EeFootprint3dModel model = selection.Include3dModel ? ee_footprint.GetModel() : null;

Expand Down Expand Up @@ -154,8 +160,22 @@ private void Run(
var component = EESCH.CreateComponent(partName, description, ee_symbol.Head.Parameters.Pre);
if (schLib != null && component != null)
{
// Moved SetState_Current_SchComponent to before drawing, not after.
// SetState_CurrentSchComponentPartId (used per-subpart below to draw
// real multi-part components) needs the library's "current component"
// context to already be this component while per-part IDs are being
// set - otherwise every subpart's pins land in Part A regardless of
// the part ID set.
schLib.SetState_Current_SchComponent(component);
AltiumApi.GlobalVars.PCBServer.PreProcess();
SymbolDrawing.CreateComponent(schLib, component, pcbLibraryPath, package, ee_symbol);
if (isMultiPart)
{
SymbolDrawing.CreateMultiPartComponent(schLib, component, pcbLibraryPath, package, root.Component.Subparts);
}
else
{
SymbolDrawing.CreateComponent(schLib, component, pcbLibraryPath, package, ee_symbol);
}

if (productInfo?.Parameters != null)
{
Expand All @@ -165,8 +185,21 @@ private void Run(
}
}

// Default Comment (shown as "*" until set). Comment isn't a plain
// parameter you add - it's a dedicated, always-present object
// retrieved via GetState_SchComment(), the same pattern already used
// above for the designator (GetState_SchDesignator()). Adding a
// separate parameter named "Comment" would create an unrelated extra
// parameter and leave the real comment field at its "*" default.
if (!string.IsNullOrEmpty(ee_symbol.Head.Parameters.ManufacturerPart))
{
var comment = component.GetState_SchComment();
comment.SetState_Text(ee_symbol.Head.Parameters.ManufacturerPart);
comment.SetState_ShowName(false);
comment.SetState_IsHidden(false);
}

AltiumApi.GlobalVars.PCBServer.PostProcess();
schLib.SetState_Current_SchComponent(component);
schLib.GraphicallyInvalidate();
schDocument.DoFileSave("SchLib");
}
Expand Down
2 changes: 1 addition & 1 deletion EasyEDA-Loader/FootprintData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public override EeFootprintShape ReadJson(JsonReader reader, Type objectType, Ee
case "SVGNODE":
return EeFootprint3dModel.FromString(raw);
case "SOLIDREGION":
return null;
return EeFootprintSolidRegion.FromString(raw);
}
}

Expand Down
25 changes: 15 additions & 10 deletions EasyEDA-Loader/FootprintShapes/EeFootprint3dModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ public static EeFootprint3dModel FromString(string data)
{
var parts = data.Split(new[] { "~" }, StringSplitOptions.None);
SvgNode node = JsonConvert.DeserializeObject<SvgNode>(parts[1]);
var originParts = node.Attrs.COrigin.Split(new[] { "," }, StringSplitOptions.None);
var rotationParts = node.Attrs.CRotation.Split(new[] { "," }, StringSplitOptions.None);

double CenterX = double.Parse(originParts[0], System.Globalization.CultureInfo.InvariantCulture);
double CenterY = double.Parse(originParts[1], System.Globalization.CultureInfo.InvariantCulture);
// c_origin is wherever the 3D CAD tool's own workspace zero happened to be when the
// STEP model was authored - it has no relationship to where the model should be
// placed on the footprint, so it's not used here. The model is placed at the
// footprint's own local origin instead (the same reference point every other shape
// is anchored to via ConvertX/ConvertY).

// Center compute, shouldnt be needed, the GL engine does this for verification of somekind
/*
Expand Down Expand Up @@ -49,12 +51,13 @@ public static EeFootprint3dModel FromString(string data)
{
Name = node.Attrs.Title,
Uuid = node.Attrs.Uuid,
LayerId = node.Attrs.Layerid,
Height = ConvertToMM(double.Parse(node.Attrs.CHeight, System.Globalization.CultureInfo.InvariantCulture)),
Width = ConvertToMM(double.Parse(node.Attrs.CWidth, System.Globalization.CultureInfo.InvariantCulture)),
Translation = new Vec3
{
X = ConvertToMM(CenterX),
Y = ConvertToMM(CenterY),
X = 0,
Y = 0,
Z = ConvertToMM(double.Parse(node.Attrs.Z, System.Globalization.CultureInfo.InvariantCulture))
},
Rotation = new Vec3
Expand Down Expand Up @@ -105,13 +108,14 @@ public override bool AddToComponent(IPCB_LibComponent c, EeFootprintContext ctx)
string temp = Path.Combine(Path.GetTempPath(), $"{Uuid}.step");
File.WriteAllBytes(temp, modelTask.Result);

// The translation is not quite right, the values shown in "3D Model Manager" are available from the Search API as "3D Model Transform"
// The Y axis is slightly off and I cannot figure out the missing piece maybe combination of rotation/y-flip/re-center causing this to be wrong
// Where the mesh starts X,Y in the EE model manager seems to differ from the computed one here
// The Z is the lowest Z of the mesh plus the Z offset (hence why we download the Raw mesh and search for the lowest vert.z as this offset is not part of the info)

// Will leave this for now as it's "close enough" most of the time to only need a nudge by a few 10ths of a millimeter
var body = EEPCB.CreateComponentBody(c, temp, Rotation.X, Rotation.Y, Rotation.Z, ConvertX(Translation.X, ctx), ConvertY(Translation.Y, ctx), Translation.Z + heightTask.Result);
// Translation.X/Y are already local-origin coordinates (see FromString) - they land
// directly on the footprint's own origin without going through ConvertX/ConvertY,
// which would otherwise apply the bounding-box centering a second time.
var layer = ctx.Layers.GetLayer(LayerId);
var targetLayer = layer != null ? EEPCB.EELayerToAltium(layer.Name) : TLayerConstant.eTopLayer;
var body = EEPCB.CreateComponentBody(c, temp, Rotation.X, Rotation.Y, Rotation.Z, Translation.X, Translation.Y, Translation.Z + heightTask.Result, targetLayer);
EEPCB.AddToPCB(c, body);

File.Delete(temp);
Expand All @@ -127,6 +131,7 @@ public override bool AddToComponent(IPCB_LibComponent c, EeFootprintContext ctx)

public string Name { get; set; }
public string Uuid { get; set; }
public string LayerId { get; set; }
public double Height { get; set; }
public double Width { get; set; }
public Vec3 Translation { get; set; }
Expand Down
17 changes: 17 additions & 0 deletions EasyEDA-Loader/FootprintShapes/EeFootprintPad.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public static EeFootprintPad FromString(string data)
HolePoints = EePoint.ListFromString(parts[14]),
IsPlated = ParseBoolean(parts[15]),
IsLocked = ParseBoolean(parts[16]),
PasteMaskExpansion = ConvertToMM(double.Parse(parts[17], System.Globalization.CultureInfo.InvariantCulture)),
SolderMaskExpansion = ConvertToMM(double.Parse(parts[18], System.Globalization.CultureInfo.InvariantCulture)),
};
}
public override List<UIElement> AddToCanvas(Canvas c, EeFootprintContext ctx)
Expand Down Expand Up @@ -173,6 +175,19 @@ public override bool AddToComponent(IPCB_LibComponent c, EeFootprintContext ctx)
}
pad.SetState_HoleWidth(AltiumApi.MmToCoord(HoleLength));
}
// Same GetState_Cache/SetState_Cache pattern EeFootprintHole.cs already uses to
// zero a hole's solder mask expansion, reused here for the pad's own solder/paste
// mask expansion overrides (previously-parsed fields that were never read).
// EasyEDA only carries a single solder value (no separate top/bottom), so both
// sides are set the same, matching the convention EeFootprintHole.cs already uses.
var padCache = pad.GetState_Cache();
padCache.SolderMaskExpansionValid = TCacheState.eCacheManual;
padCache.UseSeparateExpansions = true;
padCache.SolderMaskExpansion = AltiumApi.MmToCoord(SolderMaskExpansion);
padCache.SolderMaskBottomExpansion = AltiumApi.MmToCoord(SolderMaskExpansion);
padCache.PasteMaskExpansionValid = TCacheState.eCacheManual;
padCache.PasteMaskExpansion = AltiumApi.MmToCoord(PasteMaskExpansion);
pad.SetState_Cache(padCache);
EEPCB.AddToPCB(c, pad);
}
catch (Exception ex)
Expand Down Expand Up @@ -201,6 +216,8 @@ public override bool AddToComponent(IPCB_LibComponent c, EeFootprintContext ctx)
public List<EePoint> HolePoints { get; set; }
public bool IsPlated { get; set; }
public bool IsLocked { get; set; }
public double SolderMaskExpansion { get; set; }
public double PasteMaskExpansion { get; set; }
}

}
Loading