diff --git a/addon.gradle b/addon.gradle
index ee3a49ec..6fd0f883 100644
--- a/addon.gradle
+++ b/addon.gradle
@@ -6,6 +6,7 @@ javadoc {
include "com/gtnewhorizon/structurelib/structure/**"
include "com/gtnewhorizon/structurelib/util/**"
include "com/gtnewhorizon/structurelib/StructureLibAPI.java"
+ include "com/gtnewhorizon/structurelib/StructureEvent.java"
options {
overview "src/main/javadoc/overview.html"
diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureEvent.java b/src/main/java/com/gtnewhorizon/structurelib/StructureEvent.java
new file mode 100644
index 00000000..03f9e797
--- /dev/null
+++ b/src/main/java/com/gtnewhorizon/structurelib/StructureEvent.java
@@ -0,0 +1,147 @@
+package com.gtnewhorizon.structurelib;
+
+import com.gtnewhorizon.structurelib.structure.IStructureElement;
+import cpw.mods.fml.common.eventhandler.Event;
+import net.minecraft.world.World;
+import net.minecraftforge.common.MinecraftForge;
+
+/**
+ * Subscribers are required to not keep reference to these events, nor to depend on values of these events after
+ * the method call returns.
+ *
+ * All of these events will be fired on the {@link net.minecraftforge.common.MinecraftForge#EVENT_BUS}, if they are
+ * enabled by {@link StructureLibAPI#enableInstrument(Object)}.
+ */
+public abstract class StructureEvent extends Event {
+ private final World world;
+ private final int x, y, z;
+ /*
+ * In case the event is passed to another thread for processing.
+ */
+ private final Object identifier = StructureLibAPI.instrument.get();
+
+ StructureEvent(World world, int x, int y, int z) {
+ this.world = world;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ /**
+ * Location of this event.
+ */
+ public World getWorld() {
+ return world;
+ }
+
+ /**
+ * Location of this event. Absolute world coordinate.
+ */
+ public int getX() {
+ return x;
+ }
+
+ /**
+ * Location of this event. Absolute world coordinate.
+ */
+ public int getY() {
+ return y;
+ }
+
+ /**
+ * Location of this event. Absolute world coordinate.
+ */
+ public int getZ() {
+ return z;
+ }
+
+ /**
+ * The instrument identifier. Subscribers should filter by these identifiers to prevent listening to a structure
+ * event for other people.
+ */
+ public Object getInstrumentIdentifier() {
+ return identifier;
+ }
+
+ /**
+ * Fired just before any structure element is visited.
+ */
+ public static final class StructureElementVisitedEvent extends StructureEvent {
+ private final int a, b, c;
+ private final IStructureElement> element;
+
+ /**
+ * Fire this event on {@link MinecraftForge#EVENT_BUS} by populating it with given arguments.
+ * Do nothing if instrumenting isn't enabled for current thread.
+ *
+ * Any {@link Exception} thrown by event listeners will be logged and ignored. {@link Error} will be propagated
+ * as is.
+ */
+ public static void fireEvent(
+ World world, int x, int y, int z, int a, int b, int c, IStructureElement> element) {
+ if (StructureLibAPI.isInstrumentEnabled()) {
+ try {
+ MinecraftForge.EVENT_BUS.post(new StructureElementVisitedEvent(world, x, y, z, a, b, c, element));
+ } catch (Exception e) {
+ // logging is done by event bus, we just ignore the consequence here
+ }
+ }
+ }
+
+ StructureElementVisitedEvent(
+ World world, int x, int y, int z, int a, int b, int c, IStructureElement> element) {
+ super(world, x, y, z);
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.element = element;
+ }
+
+ /**
+ * A position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
+ *
+ * Submit a feature request if you need the baseOffset.
+ */
+ public int getA() {
+ return a;
+ }
+
+ /**
+ * B position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
+ *
+ * Submit a feature request if you need the baseOffset.
+ */
+ public int getB() {
+ return b;
+ }
+
+ /**
+ * C position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
+ *
+ * Submit a feature request if you need the baseOffset.
+ */
+ public int getC() {
+ return c;
+ }
+
+ /**
+ * Element being visited.
+ */
+ public IStructureElement> getElement() {
+ return element;
+ }
+
+ @Override
+ public String toString() {
+ return "StructureElementVisitedEvent{" + "a="
+ + a + ", b="
+ + b + ", c="
+ + c + ", element="
+ + element + ", world="
+ + getWorld() + ", x="
+ + getX() + ", y="
+ + getY() + ", z="
+ + getZ() + '}';
+ }
+ }
+}
diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java
index 0164289e..0728af5b 100644
--- a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java
+++ b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java
@@ -7,6 +7,7 @@ import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment;
+import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
import cpw.mods.fml.common.network.NetworkRegistry;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
@@ -38,6 +39,54 @@ public class StructureLibAPI {
public static final int HINT_BLOCK_META_AIR = 13;
public static final int HINT_BLOCK_META_NOT_AIR = 14;
public static final int HINT_BLOCK_META_ERROR = 15;
+ static final ThreadLocal